Skip to content
This repository was archived by the owner on Oct 13, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ jobs:
- name: Run postinstall
run: npm run postinstall || true

- name: Run public sensitive content scan
run: node scripts/security/check-public-sensitive-content.js
Comment thread
100yenadmin marked this conversation as resolved.
Outdated

- name: Install prek
run: npm install -g @j178/prek

Expand Down Expand Up @@ -725,7 +728,7 @@ jobs:

BUNDLE_ID="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP_PATH/Contents/Info.plist")"
VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP_PATH/Contents/Info.plist")"
BRIDGE_SUMMARY="$(node -e "const fs=require('fs'); const m=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); if (m.placeholder !== true) { console.error('::error::Expected thin-app-smoke to bundle the diagnostic Bridge placeholder'); process.exit(1); } console.log('diagnostic-placeholder')" "$BRIDGE_MANIFEST")"
BRIDGE_SUMMARY="$(node -e "const fs=require('fs'); const m=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); const sha=/^[0-9a-f]{40}$/i; if (m.placeholder === true) { console.log('diagnostic-placeholder'); process.exit(0); } if (!sha.test(String(m.requestedSourceRef || '')) || !sha.test(String(m.sourceCommit || '')) || m.requestedSourceRef !== m.sourceCommit) { console.error('::error::Expected thin-app-smoke to bundle a diagnostic Bridge placeholder or the pinned real Bridge source'); process.exit(1); } console.log('pinned-real-bridge@' + String(m.sourceCommit).slice(0, 12));" "$BRIDGE_MANIFEST")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare Bridge manifest to the pinned workflow ref

In the thin-app smoke workflow, the build step pins EVAOS_DESKTOP_BRIDGE_SOURCE_REF to a specific commit, but this verification only checks that the manifest's requestedSourceRef and sourceCommit agree with each other. If the packaging code stops honoring the workflow pin and packages a different Bridge commit while writing matching manifest fields, this new acceptance path still passes; compare sourceCommit against the pinned workflow ref instead of trusting the manifest's requested ref.

Useful? React with 👍 / 👎.

node scripts/evaosVerifyRendererDependencyPrune.js "$APP_PATH"

{
Expand All @@ -738,7 +741,7 @@ jobs:
echo "- Bridge manifest: \`$BRIDGE_SUMMARY\`"
echo "- Renderer dependency prune: verified \`app.asar\` and \`app.asar.unpacked/node_modules\`"
echo
echo "This PR artifact is intentionally unpacked and may use a diagnostic Bridge placeholder when private source access is unavailable. It may be ad-hoc/non-Developer-ID signed by packaging hooks. It produces no DMG, no release metadata, no notarization/stapling proof, no TCC or permission proof, and no public release proof."
echo "This PR artifact is intentionally unpacked and may use a diagnostic Bridge placeholder when private source access is unavailable, or a pinned real Bridge source when CI can fetch it. It may be ad-hoc/non-Developer-ID signed by packaging hooks. It produces no DMG, no release metadata, no notarization/stapling proof, no TCC or permission proof, and no public release proof."
echo
echo "### Size"
echo '```'
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/public-security-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Public Security Scan

on:
pull_request:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read
pull-requests: read

jobs:
public-sensitive-content:
name: Public Sensitive Content
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
fetch-depth: 0
persist-credentials: false

- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 24

- name: Run repo-owned public scan
run: node scripts/security/check-public-sensitive-content.js

gitleaks:
name: Gitleaks Full History
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
fetch-depth: 0
persist-credentials: false

Comment thread
coderabbitai[bot] marked this conversation as resolved.
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e
Comment on lines +62 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run Gitleaks with trusted PR configuration

On pull_request, this checkout is the PR merge tree and the Gitleaks action is invoked without GITLEAKS_CONFIG; the action docs say a root gitleaks.toml is automatically detected and used, so a PR can commit a broad allowlist or disabled rules alongside a real npm/Stripe/database secret and make this new security job pass. Mirror the trusted-base pattern used for the repo-owned scanner, or otherwise point Gitleaks at a base-branch config while scanning the PR tree.

Useful? React with 👍 / 👎.

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_ENABLE_UPLOAD_ARTIFACT: false
GITLEAKS_NOTIFY_USER_LIST: ''

trufflehog:
name: TruffleHog Verified
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
fetch-depth: 0
persist-credentials: false

- name: Run TruffleHog verified full-history scan
run: |
docker run --rm \
-v "$PWD:/repo:ro" \
ghcr.io/trufflesecurity/trufflehog:3.95.7 \
git file:///repo \
--only-verified \
--no-update \
--fail
34 changes: 34 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
title = "AionUi public repository Gitleaks configuration"

[extend]
useDefault = true

[[allowlists]]
description = "Fake short OpenAI protocol detector fixture"
condition = "AND"
paths = ['''^tests/unit/common/protocolDetector\.test\.ts$''']
regexes = ['''sk-abc123def456ghi789''']

[[allowlists]]
description = "Fake long OpenAI protocol detector fixture"
condition = "AND"
paths = ['''^tests/unit/common/protocolDetector\.test\.ts$''']
regexes = ['''sk-abc123def456ghi789jkl012''']

[[allowlists]]
description = "Fake Google API key protocol detector fixture"
condition = "AND"
paths = ['''^tests/unit/common/protocolDetector\.test\.ts$''']
regexes = ['''AIzaa{35}''']

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Gitleaks allowlist regex AIzaa{35} over-matches; will not match the 35-a fixture and may leak/err

In the 'Fake Google API key protocol detector fixture' allowlist, regexes = ['''AIzaa{35}'''] is interpreted by Gitleaks as a Go regexp: AIza + one literal a + the quantifier {35}, i.e. it matches AIza followed by 36 a's. The test fixture it is meant to allow (tests/unit/common/protocolDetector.test.ts) builds the key as 'AIza' + 'a'.repeat(35) = AIza + 35 a's (39 chars total). The allowlist therefore does NOT match the fixture it claims to allow. Either the Gitleaks job will flag the legitimate test fixture (CI-breaking false positive) or, worse, the allowlist silently matches nothing and the rule provides no real coverage. The in-repo custom scanner uses a correctly escaped pattern (/'''AIzaa\{35\}'''/, line 118 of check-public-sensitive-content.js), confirming intent was a literal. Fix: align .gitleaks.toml with the literal the fixture produces, e.g. regexes = ['''AIzaaaa{34}'''] won't help; use the explicit literal AIza + 35 a's, or drop the meta-quantifier.

Why this matters: A misconfigured allowlist either breaks the new required CI gate (Gitleaks Full History) with false positives on a known fixture, or silently disables coverage so a real Google key could pass review.


[[allowlists]]
description = "Fake bridge resource token fixture"
condition = "AND"
paths = ['''^tests/unit/bootstrap/prepareEvaosDesktopBridgeResource\.test\.ts$''']
regexes = ['''super-secret-token''']

[[allowlists]]
description = "Fake bridge resource credential URL fixture"
condition = "AND"
paths = ['''^tests/unit/bootstrap/prepareEvaosDesktopBridgeResource\.test\.ts$''']
regexes = ['''https://x-access-token:super-secret-token@github\.com/electricsheephq/evaos-desktop-bridge\.git''']
12 changes: 8 additions & 4 deletions mobile/eas.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@
"submit": {
"production": {
"ios": {
"appleId": "liangzhewei@gmail.com",
"appleTeamId": "M4AG47ZV62"
"appName": "AionUi Mobile",
"bundleIdentifier": "ai.resopod.aionui",
"language": "en-US",
"sku": "aionui-mobile"
}
},
"preview": {
"ios": {
"appleId": "liangzhewei@gmail.com",
"appleTeamId": "M4AG47ZV62"
"appName": "AionUi Mobile",
"bundleIdentifier": "ai.resopod.aionui",
"language": "en-US",
"sku": "aionui-mobile-preview"
}
}
}
Expand Down
61 changes: 48 additions & 13 deletions mobile/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const projectRoot = path.join(__dirname, '..');

// Parse command line arguments
const args = process.argv.slice(2);
const profileIndex = args.indexOf('--profile');
Expand All @@ -28,6 +30,15 @@ if (!profile) {
process.exit(1);
}

function requireEnv(name, purpose) {
const value = process.env[name];
if (!value) {
console.error(`Error: ${name} is required for ${purpose}.`);
process.exit(1);
}
return value;
}

// Read current version
const versionPath = path.join(__dirname, '..', 'versions', 'version.json');
let versionData;
Expand All @@ -53,9 +64,18 @@ try {

// Build eas command args
const outputExt = platform === 'ios' ? '.ipa' : '.apk';
const localOutputPath = path.join(__dirname, '..', `build-${Date.now()}${outputExt}`);
const localOutputPath = path.join(projectRoot, `build-${Date.now()}${outputExt}`);
let buildArgs = args.filter((a) => a !== '--auto-submit' && a !== '--direct-submit');

if (!isLocal && directSubmit) {
console.error('Error: --direct-submit is only supported with --local builds.');
process.exit(1);
}

if (!isLocal && autoSubmit && !buildArgs.includes('--auto-submit-with-profile')) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Non-local auto-submit rewrites --auto-submit to --auto-submit-with-profile but eas submit profiles no longer carry Apple credentials

For cloud (!isLocal) builds with --auto-submit, the script strips --auto-submit and pushes --auto-submit-with-profile <profile>, then relies on EXPO_APPLE_ID/EXPO_APPLE_TEAM_ID being set in appleEnv (validated above). That part is consistent. However, the EAS submit profiles in mobile/eas.json (production/preview) were stripped of appleId/appleTeamId in this same PR, so EAS submit now depends entirely on these env vars being present at submit time. The validation at lines 50-56 covers iOS, but if a caller runs --auto-submit with --platform android (or no platform defaulting to ios) the env checks are skipped for android — which is correct, but there is no assertion that the submit profile actually exists for the platform. This is low risk (EAS will error clearly) but the coupling between the removed eas.json fields and the new env-var contract is undocumented; a maintainer running cloud iOS auto-submit without the env vars set will get a failure inside EAS rather than the script's clear requireEnv message, because requireEnv only runs for autoSubmit when !expoAppleTeamId — and a partial set (id present, team id missing) is handled, but the reverse ordering is fine. Net: behavior is correct, but consider adding a one-line comment in eas.json or build.js documenting that Apple credentials are now env-only.

Why this matters: The implicit contract between eas.json (credentials removed) and build.js (env vars now mandatory) is easy to break in a future edit; a comment prevents regression.

buildArgs.push('--auto-submit-with-profile', profile);
}

// For local builds with submit, capture output path for later submission
if (isLocal && (autoSubmit || directSubmit)) {
if (!buildArgs.includes('--output')) {
Expand Down Expand Up @@ -84,20 +104,34 @@ if (isLocal && platform === 'ios') {
const easCommand = `eas build ${buildArgs.join(' ')}`;
console.log(`\nRunning: ${easCommand}\n`);

// Apple-specific env vars (only needed for iOS builds)
const appleEnv =
platform === 'ios'
? {
EXPO_APPLE_TEAM_ID: process.env.EXPO_APPLE_TEAM_ID || 'M4AG47ZV62',
EXPO_APPLE_ID: process.env.EXPO_APPLE_ID || 'liangzhewei@gmail.com',
...(applePassword ? { EXPO_APPLE_PASSWORD: applePassword } : {}),
}
: {};
const expoAppleId = process.env.EXPO_APPLE_ID || process.env.APPLE_ID || process.env.appleId;
const expoAppleTeamId =
process.env.EXPO_APPLE_TEAM_ID || process.env.TEAM_ID || process.env.teamId;
const appleEnv = {};
if (platform === 'ios') {
if (expoAppleTeamId) {
appleEnv.EXPO_APPLE_TEAM_ID = expoAppleTeamId;
}
if (expoAppleId) {
appleEnv.EXPO_APPLE_ID = expoAppleId;
}
if (applePassword) {
appleEnv.EXPO_APPLE_PASSWORD = applePassword;
}
if (autoSubmit || directSubmit) {
appleEnv.EXPO_APPLE_ID = expoAppleId || requireEnv('EXPO_APPLE_ID', 'iOS submission');
}
if (autoSubmit) {
appleEnv.EXPO_APPLE_TEAM_ID =
expoAppleTeamId || requireEnv('EXPO_APPLE_TEAM_ID', 'EAS iOS submission');
Comment thread
100yenadmin marked this conversation as resolved.
}
}

// Execute eas build
try {
execSync(easCommand, {
stdio: 'inherit',
cwd: projectRoot,
env: {
...process.env,
PATH: `/usr/bin:${process.env.PATH}`,
Expand All @@ -123,7 +157,7 @@ if (platform === 'ios' && isLocal && (autoSubmit || directSubmit)) {

if (directSubmit) {
// Upload directly to App Store Connect via xcrun altool (bypasses EAS)
const appleId = process.env.APPLE_ID || 'liangzhewei@gmail.com';
const appleId = expoAppleId || requireEnv('EXPO_APPLE_ID', 'direct iOS submission');
const submitCommand = `xcrun altool --upload-app -f "${outputFile}" -t ${platform} -u "${appleId}" -p "@keychain:AC_PASSWORD"`;
console.log(`\nUploading directly to TestFlight: xcrun altool --upload-app\n`);
try {
Expand All @@ -135,17 +169,18 @@ if (platform === 'ios' && isLocal && (autoSubmit || directSubmit)) {
' Make sure your App-Specific Password is saved in Keychain as "AC_PASSWORD".',
);
console.error(
' To save it: security add-generic-password -a "liangzhewei@gmail.com" -s "AC_PASSWORD" -w "<your-app-specific-password>" -U',
' To save it: security add-generic-password -a "$EXPO_APPLE_ID" -s "AC_PASSWORD" -w "<your-app-specific-password>" -U',
);
process.exit(1);
}
} else {
// Upload via EAS submit
const submitCommand = `eas submit --platform ${platform} --path ${outputFile} --non-interactive`;
const submitCommand = `eas submit --platform ${platform} --profile ${profile} --path ${outputFile} --non-interactive`;
console.log(`\nSubmitting to TestFlight: ${submitCommand}\n`);
try {
execSync(submitCommand, {
stdio: 'inherit',
cwd: projectRoot,
env: {
...process.env,
...appleEnv,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"lint:fix": "oxlint --fix",
"format": "oxfmt",
"format:check": "oxfmt --check",
"security:public-scan": "node scripts/security/check-public-sensitive-content.js",
"i18n:types": "node scripts/generate-i18n-types.js",
"test": "vitest run",
"test:watch": "vitest",
Expand Down
Loading
Loading