Skip to content

Close the deferred hardening notes from the review round - #76

Merged
heznpc merged 1 commit into
mainfrom
fix/review-hardening-batch
Aug 13, 2026
Merged

Close the deferred hardening notes from the review round#76
heznpc merged 1 commit into
mainfrom
fix/review-hardening-batch

Conversation

@heznpc

@heznpc heznpc commented Aug 13, 2026

Copy link
Copy Markdown
Owner

The six low/informational items deferred from #74/#75. Nothing here is reachable in a shipped build today — each is a seam a future change could silently reopen, so each gets closed plus (where testable) a guard with proven teeth.

1. privacy.shPCH_TCC_DB_PATH gated behind PCH_TEST_MODE=1

The one test-injection seam in the new modules without the gate (PCH_TEST_BREW_BIN and PCH_TEST_OSASCRIPT_BIN both have it). Ungated, any future caller forwarding the variable turns it into an arbitrary-sqlite-read redirect. The existing TCC fixture test now declares the mode.

2. login_items.sh — manifest protocol-version check at execute

Fields this version reads could mean something else under another protocol version. cleanup.sh cross-checks its whole manifest; this is the minimum equivalent. New test tampering version 1 → 2 proves blocked — and fails against the pre-fix script.

3. Bundle-completeness guard extended beyond .sh

#71's guard only globbed scripts/**/*.sh, so a future rules/i18n/data/JXA/runtime-py dependency could recreate the exact "CI passes from the checkout, signed bundle silently lacks it" incident. Now rules/*.json, data/*.json (minus the gitignored local config), data/report_i18n/*.json, scripts/*.jxa.js, and shipped .py are all required in RUNTIME_FILES, with an explicit build/reference-only exclusion list mirroring the .sh guard. Teeth proven by removing rules/network.json from the manifest — the guard trips with the exact filename.

4. RuntimeWorkspace — foreign-bundle branch requires the development opt-in

The bundled branch skips runtimePayloadMatchesCodeSignature whenever the injected resourceURL isn't the running app's own bundle — a combination constructible only through parameter injection, but nothing stopped a future refactor from decoupling the parameters and silently running unsigned code. It now demands PCH_DEVELOPMENT_MODE=1, same as the development branch. The existing revalidation tests declare the opt-in they were implicitly relying on (the three refusal tests keep it too, so they still fail for the tampering under test rather than the missing opt-in). New test: foreign bundle without opt-in → nil, failing against the pre-fix code.

5. Pinned-file raw values refuse on key collision

Module payloads merge keep-first, but raw values (the approval token) landed via unconditional overwrite — the exact asymmetry behind the approval_token/approval_token_module near-miss found in #67's development. Both call sites now fail closed with an explicit internal-error message instead of clobbering whichever payload merged first. (No dedicated test: the collision is unconstructible from outside the function today — that unreachability is the point of the guard.)

6. Observation timeout

Slack grows 20s → 60s past the window (the 4 lsof + 2 ps snapshots run exactly when the machine is busiest — the machines this feature targets), and .timedOut now reports "제한 시간을 넘겨 중단되었습니다" instead of the generic "status 124".

Test plan

  • pytest tests/ — 370 passed
  • swift test — 169 passed
  • release_smoke.py --check-only — ok
  • Items 2, 3, 4 proven against pre-fix code (stash/edit → fail → restore → pass); items 1, 5, 6 are gate/message changes verified by the existing suites

Follows #74/#75.

🤖 Generated with Claude Code

Six items the #74/#75 reviewers classified low/informational --
nothing here is reachable in a shipped build today; each is a seam a
future change could silently reopen.

privacy.sh's PCH_TCC_DB_PATH test seam now opens only under
PCH_TEST_MODE=1, matching PCH_TEST_BREW_BIN and PCH_TEST_OSASCRIPT_BIN.
It was the one injection point in the new modules without the gate,
and ungated it doubles as an arbitrary-sqlite-read redirect for any
future caller that forwards the variable.

login_items.sh now refuses a manifest whose protocol version differs
from its own -- fields this version reads could mean something else
under another one. cleanup.sh cross-checks its whole manifest; this is
the minimum equivalent.

The bundled-runtime completeness guard now also covers non-.sh runtime
dependencies (rules/*.json, data/*.json, report_i18n, *.jxa.js, and
shipped .py), with an explicit build/reference-only exclusion list
mirroring the .sh guard's. Proven the same way: removing
rules/network.json from RUNTIME_FILES trips it.

RuntimeWorkspace's bundled branch now demands the development opt-in
whenever the injected resourceURL is not the running app's own signed
bundle. That combination skips the code-signature payload comparison
and is constructible only through parameter injection; without the
gate, a future refactor decoupling the parameters would run unsigned
code with no test noticing. The revalidation tests declare the opt-in
they were implicitly relying on.

Raw pinned-file values now refuse on key collision instead of
silently clobbering whichever module payload merged first -- the
exact asymmetry behind the approval_token/approval_token_module
near-miss fixed in #67.

Observation timeout slack grows from 20s to 60s past the window (the
snapshots run exactly when the machine is busiest), and a timeout now
says it timed out instead of "status 124".
@heznpc
heznpc merged commit 40f9ba2 into main Aug 13, 2026
8 checks passed
@heznpc
heznpc deleted the fix/review-hardening-batch branch August 13, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant