Conversation
SecTrustedApplicationValidateWithPath runs a full static code-signature validation of the invoking app bundle, reading and hashing every sealed resource. The decrypt ACL is re-read on every Keychain preflight, so the same (trusted application, executable) pair was revalidated once per provider, per Chromium browser, per Safe Storage label, every refresh. Memoize the verdict on the trusted application's serialized data plus the executable's filesystem identity, so a rewritten or replaced binary is revalidated. The ACL is still read live on every preflight and the outcome mapping is unchanged; only the redundant recomputation is removed. The memo is bounded so a long-lived process cannot accumulate entries. Fixes steipete#3837 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs real behavior proof before merge. Reviewed September 22, 2026, 3:31 PM ET / 19:31 UTC (Revision 7). ClawSweeper reviewWhat this changesThe PR stops caching completed successful Keychain signature checks while preserving concurrent sharing and rejection caching, and updates regression tests and documentation. Merge readiness⛔ Blocked before merge - 2 items remain Keep open: current main still retains successful validations for 30 seconds, and the owner explicitly preserved this follow-up. No actionable patch defect remains, but the native after-fix proof requested in the previous review is still outstanding. Priority: P2 Review scores
Verification
How this fits togetherCodexBar checks whether a Keychain item's trusted applications permit its executable before attempting background credential access. The validation memo shares signature checks, and its results feed the browser-cookie gate that allows or skips imports. flowchart TD
A[Background cookie import] --> B[Keychain ACL preflight]
B --> C[Trusted application and executable]
C --> D[Signature validation memo]
D --> E[Share pending check or validate afresh]
E --> F[Allow or skip import]
F --> G[Noninteractive credential read]
Before merge
Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Keep successful validations fresh between completed operations while retaining concurrent sharing, bounded rejection caching, and the existing noninteractive credential-read boundary. Do we have a high-confidence way to reproduce the issue? Yes, at source level: main can reuse a completed success after a resource change that leaves its metadata key unchanged. The synthetic regressions isolate this mechanism; a native current-main reproduction was not executed. Is this the best way to solve the issue? Yes. Removing retained successes is a narrow repair because the existing metadata key cannot establish complete sealed-resource freshness, and the patch preserves the existing concurrency and rejection mechanisms. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against 173afc88a417. LabelsLabel changes: No label changes. Label justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (6 earlier review cycles)
|
ClawSweeper review on this PR found two real defects in the memoization: - SecTrustedApplicationCopyData returns only the stored path string (confirmed against Apple's open-source implementation), not the embedded code-signing requirement that verifyToDisk actually checks. Two ACL entries sharing an install path but holding different requirements collided on the same cache key. Reproduced directly: constructing two SecTrustedApplication objects from the same path with different real SecRequirements showed CopyData returning byte-identical output for both, while SecTrustedApplicationCopyExternalRepresentation (which serializes the full ACL subject, requirement included) differs. Switched the cache key to the external representation; a new regression test builds this exact same-path/different-requirement scenario and fails on the old key, passes on the new one. - SecStaticCodeCheckValidity's default flags validate every sealed resource under the bundle, not just the executable file, so a resource edited without touching the executable left every field of the cache key unchanged. Cheaply fingerprinting the full resource envelope would mean re-doing the expensive walk the cache exists to avoid, so entries now expire after a 5-minute TTL (matching the retry deadline steipete#3301 already established for the adjacent rejected-ACL cooldown) — bounding that gap and an ACL-repaired-after- rejection gap to a fixed window instead of the life of the process. Executable-identity invalidation is unaffected and still fires immediately on a replaced binary. Two new tests cover both; all 6 original tests, the 3 pre-existing adversarial trust tests, and the broader Keychain/BrowserCookie/ CookieImporter suite (221 tests, 40 suites) still pass. Lint: 0 violations across 2549 files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Update: fixed both P1 findingsVerified both against Apple's actual open-source implementation ( Finding 1 — cache-key collision (confirmed and fixed)
const char *path = TrustedApplication::required(appRef)->path();
Required(dataRef) = CFDataCreate(NULL, (const UInt8 *)path, strlen(path) + 1);— only the stored path, never the embedded Reproduced the exact collision with two real Fix: the cache key now uses New regression test Finding 2 — resource-tamper staleness (bounded, not eliminated)
Added a 5-minute TTL on cache entries — matching the retry deadline #3301 already established in this file for the adjacent rejected-ACL cooldown — so this gap (and an ACL repaired after a rejection) is bounded to a fixed window instead of the life of the process. Executable-identity invalidation is unaffected and still fires immediately for the common case (a replaced binary). New regression test Verification
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
ClawSweeper's re-review (against 2350385) found two more issues: - Every OSStatus the validator returned was cached, including transient/indeterminate ones (a locked keychain, an I/O error). That silently defeated checkGenericPasswordUncached's existing three-attempt retry recovery elsewhere in this file: a single transient failure would freeze reads for the entry's lifetime instead of clearing on the next call. Only errSecSuccess and a confirmed CSSMERR_CSP_VERIFY_FAILED rejection are settled, non-retryable facts; everything else is never written to the cache and always re-validates. - The 5-minute TTL treated a stale success and a stale rejection as the same risk. They are not: a rejection surviving past its truth only delays a legitimate read (the same tradeoff steipete#3301's cooldown already accepted for this file's adjacent case), but a success surviving past its truth wrongly authorizes a preflight that should now fail. Successes now expire after 30 seconds instead — long enough to cover one refresh's fan-out across providers and browsers (bursts measured under 20 seconds in the paired issue's profiling), short enough to never reach into the next scheduled refresh, so a resource edited between refreshes is masked for at most one cycle rather than up to five minutes. Rejections keep the original 5-minute window. Three new tests cover both, plus the existing 6 and the 3 pre-existing adversarial trust tests, plus the broader Keychain/BrowserCookie/ CookieImporter suite (223 tests, 40 suites) all still pass. Lint: 0 violations across 2549 files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Update (commit bd7ab6b): fixed both new findings from Revision 2P1 — stale success no longer possible for up to 5 minutesAgreed the risk asymmetry is real and shouldn't be papered over with one shared TTL: a rejection surviving past its truth only delays a legitimate read (the exact tradeoff #3301's cooldown already accepted for this file's adjacent case), but a success surviving past its truth wrongly authorizes a preflight that should now fail. Those are not the same risk and don't belong on the same clock. Considered and rejected three other options first:
Landed on: split the TTL by outcome. I don't think this fully closes the "owner decision" you flagged — a 30-second window is still a window, and if the maintainer wants zero tolerance for stale success, the task-scoped refactor is the real answer. Flagging that explicitly rather than presenting 30s as if it settles the question. P2 — indeterminate/transient statuses are never cachedConfirmed this was a real, unambiguous bug, missed in both earlier reviews. New test VerificationFalsification checks on both new tests (temporarily reverted just the relevant logic, confirmed each fails, restored):
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
🦞👀 Re-review progress:
|
|
Addressed the stale-success concern from the previous review in commit 7881bce.
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
Share per-key in-flight validation results across synchronous preflights. Key the bounded memo by full trust identity and executable/bundle metadata, expire stable results, and leave transient failures retryable. Preserve live ACL inspection and existing no-UI secret reads. Cover serial/concurrent fan-out, update invalidation, expiry, transient failures, and capacity eviction through synthetic preflight fixtures. Fixes #3837 Closes #3838 Co-authored-by: luk <jinyuren886@gmail.com> (cherry picked from commit dfccffe)
|
Rewritten in #3857 ( |
|
Reopening this PR to preserve the newer The newer sealed-resource invalidation concern still needs to be checked against the landed cache implementation. This revision remains under review. |
Executable and app-root metadata do not identify every sealed-resource or non-version plist change. Do not retain successful validation across completed operations; preserve in-flight coalescing, the bounded confirmed-rejection cache, and existing short synchronous operation memoization. Synthetic resource and plist regressions fail four assertions before this change. Validation: 171 focused tests, make check, and independent review through P2 pass. No live Keychain reads or prompts were used. Co-authored-by: luk <jinyuren886@gmail.com>
Preserve the original PR ancestry while retaining the reviewed implementation on current main. The landed single-flight implementation supersedes the older cache code; the remaining change keeps completed successful validations fresh without widening operation scopes.
A successful trusted-application validation could be reused for 30 seconds after a sealed bundle resource or non-version
Info.plistfield changed. The cache fingerprint only observes executable and selected bundle metadata, so that later preflight could incorrectly report.allowed, defeating the extra protection against legacy Keychain prompts.This follow-up builds on the cache implementation landed in #3867 and preserves the original PR history. Completed successes now revalidate on the next operation. Concurrent requests still share an in-flight validation, confirmed
CSSMERR_CSP_VERIFY_FAILEDrejections retain their 64-entry/five-minute cache, and transient results remain uncached. The existing short synchronous generic-password operation memo remains intact; its scope is not widened across asynchronous refreshes or deferred tasks.Thanks @jeffloo886 for identifying the sealed-resource freshness issue and updating the original proposal. Human contributor credit is retained in the fix commit and changelog.
Validation
make checkpassed with zero violations in 2,570 Swift files.make testpassed: all 1,334 selections in 120 groups succeeded on the first pass, with zero retries or timeouts.d535daba16e119e0421d1cd52bf03c1e26310add, including both macOS shards and every Linux CLI target. GitGuardian also passed.The new regressions inject signature results and Keychain preflight outcomes. No real SecItem access, account credentials, browser-cookie imports, or live Keychain prompts were used. This demonstrates stale preflight/prompt risk; it does not claim an OS credential-authorization bypass or a reproduced live prompt.
Refs #3837.