Skip to content

Tier 0: verdict integrity (HIGH tier, --exit-code, scanner fixes)#6

Merged
ethanzhoucool merged 5 commits into
mainfrom
fix/tier0-verdict-integrity
Jun 27, 2026
Merged

Tier 0: verdict integrity (HIGH tier, --exit-code, scanner fixes)#6
ethanzhoucool merged 5 commits into
mainfrom
fix/tier0-verdict-integrity

Conversation

@ethanzhoucool

Copy link
Copy Markdown
Contributor

First of three PRs from a review of the scanner's correctness and CI story. This is Tier 0: the bugs that let a rejectable app read as GREENLIT, plus the CI gate.

Severity + verdict

  • New HIGH tier between WARN and CRITICAL. Four flow-dependent guidelines move WARN to HIGH: missing ATT (5.1.2), Sign in with Apple (4.8), Restore Purchases (3.1.1), account deletion (5.1.1). As WARN they never affected the headline, so an app Apple would reject could still print GREENLIT.
  • Headline is now three-state: NOT READY (critical) / NEEDS REVIEW (high, no critical) / GREENLIT (neither), in preflight and codescan.
  • passed is unchanged (still "no criticals"), so CI that reads .summary.critical is unaffected.

CI gating

  • New opt-in --exit-code on preflight and verify. preflight exits non-zero on any CRITICAL/HIGH (or, with --verify, a failed flow). verify exits non-zero on any failed/errored flow. verify previously always exited 0, which defeated its purpose as a gate.

Scanner correctness

  • privacy: Required Reason stat()/statfs() now match real call sites with args. The old patterns only matched the empty-paren form, so two categories never fired. Removed a broken \.standard\. clause.
  • codescan: hardcoded-ipv4 validates 0-255 octets so version strings like 2020.10.5.1 aren't flagged as IPs. platform-reference no longer matches bare unquoted tokens, which fired on every React Native app via Platform.OS.
  • preflight: a crashed sub-scanner was silently swallowed (clean "no issues"); it now surfaces as a WARN.
  • verify: temp YAML that can inline secret --var values is written 0600, not 0644.

Tests added for each change. build, vet, test green. Reviewed by a separate pass: no blocking issues.

Two calls for you

  • HIGH gating is intentionally conservative: it shows NEEDS REVIEW but does not flip passed/GREENLIT or fail CI unless --exit-code is passed. Say the word and I'll make HIGH block GREENLIT outright.
  • Breaking output change: codescan --format json now serializes severity as a name ("HIGH") instead of an integer. Required so inserting a tier doesn't shift the wire value, and it matches preflight.

Known follow-ups (not here)

  • platform-reference suppresses a line that has both an RN construct and real competitor copy. Match literals before ignores to recover it.
  • Consider whether a crashed scanner should fail --exit-code, not just WARN.
ethanzhoucool and others added 5 commits June 26, 2026 11:14
Tier 0 (verdict integrity), scanner-correctness batch:

- privacy: Required Reason stat()/statfs()/fstat() patterns now match real call
  sites with arguments (stat(path, &st)), not only the argument-less stat()
  form that almost never appears in source. Drop the broken \\.standard\\.
  UserDefaults clause (matched a literal backslash; UserDefaults already covers
  the real case).
- codescan: hardcoded-ipv4 validates 0-255 octets so version/build strings like
  "2020.10.5.1" / "8.4.1.2" aren't misread as IPv4; widen the ignore to
  version/build/sdk/revision lines.
- codescan: platform-reference no longer matches bare unquoted tokens (fired on
  virtually every React Native app via Platform.OS); only string/JSX literals,
  with ignores for Platform.*, imports, package names, and build config.
- preflight: drain the previously-swallowed scanner error channel so a crashed
  sub-scanner surfaces as a WARN instead of a clean "no issues found".
- verify: write the credential-bearing temp YAML at 0600, not 0644.
- Tests covering each regex change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier 0 (verdict integrity), severity + CI-gating batch:

- codescan: new SeverityHigh tier between WARN and CRITICAL. The four
  flow-dependent guidelines static analysis can only weakly assert -- missing
  ATT (5.1.2), Sign in with Apple (4.8), Restore Purchases (3.1.1), account
  deletion (5.1.1) -- move from WARN to HIGH. They are likely rejections, not
  best-practice nits; labeling them WARN let rejectable apps read as GREENLIT.
- Severity now marshals to its NAME ("HIGH") instead of an integer ordinal, so
  inserting a tier never shifts the JSON wire value for consumers.
- preflight + codescan: three-state headline -- NOT READY (critical) /
  NEEDS REVIEW (high, no critical) / GREENLIT (neither). Summary gains a `high`
  count. `passed` deliberately stays "no criticals" for backward compatibility
  (README CI reads .summary.critical), so no existing behavior silently flips.
- preflight + verify: opt-in --exit-code flag for CI gating. preflight exits
  non-zero on any CRITICAL/HIGH (or, with --verify, a failed flow); verify exits
  non-zero on any failed/errored flow (it previously always exited 0, defeating
  its purpose as a gate). Implemented via an ErrThreshold sentinel that main()
  maps to a quiet exit 1; cobra error/usage printing moved to main().
- Tests: hard-rejection rules pinned at HIGH, summary High counting, name-based
  JSON marshaling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the unit-test gap flagged in review (preflight had none):
- computeSummary counts HIGH separately; Passed stays "no criticals".
- dedup keeps the highest severity when scanners share a title.
- preflightExit trips only with --exit-code, on critical/high or a failed flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- hardcoded-ipv4: drop build|sdk|revision from the ignore (unbounded substrings
  that swallowed real violations like `sdkEndpoint = "172.16.0.4"`). Octet
  validation already rejects version-shaped numbers; "version" still covers
  sdkVersion-style identifiers.
- platform-reference: require the import `from '...'` ignore to match a
  module-path token (no spaces), so competitor copy like
  "Download from 'Google Play'" is no longer suppressed.
- Tests pin both regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A crashed sub-scanner is surfaced as a WARN ("Scanner did not complete"), but
preflightExit only failed on CRITICAL/HIGH, so `--exit-code` could pass in CI
even though a requested scanner (e.g. a malformed --ipa) never ran. Track it as
Result.Incomplete and fail the gate on it. Default behavior (no --exit-code) is
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ethanzhoucool ethanzhoucool merged commit 6f914f2 into main Jun 27, 2026
1 check passed
@ethanzhoucool ethanzhoucool deleted the fix/tier0-verdict-integrity branch June 27, 2026 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant