fix: reject retargeted synchronous secret reads - #118
Conversation
|
Codex review: needs maintainer review before merge. Reviewed August 3, 2026, 2:50 AM ET / 06:50 UTC. ClawSweeper reviewWhat this changesThe branch makes synchronous secret-file reads reject a symlink path retargeted after preview validation, matching the asynchronous reader’s Merge readinessKeep this active PR open for normal merge review. Current Priority: P1 Review scores
Verification
How this fits togetherThe secret-file API reads credential material from a caller-provided path after validating file identity and permissions. It previews metadata, opens a pinned descriptor, validates the descriptor, and returns either bounded secret bytes or a typed failure that callers can handle safely. flowchart TD
A[Caller secret path] --> B[Preview metadata]
B --> C[Resolve and open pinned descriptor]
C --> D[Compare preview and descriptor identities]
D --> E[Bounded descriptor read]
E --> F[Secret value or typed error]
Before merge
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Merge the narrow preview-versus-descriptor identity fence with its regression test so both secret readers fail closed when a path is swapped during validation. Do we have a high-confidence way to reproduce the issue? Yes. Current source shows the sync path previews one identity and later reads another descriptor without comparing them, while the added deterministic symlink-retarget test directly exercises that seam. Is this the best way to solve the issue? Yes. Reusing the existing AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against b8738eb59b29. LabelsLabel 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
|
Summary
This audits every explicit platform branch, Windows path conditional, native/fallback selection, and hand-maintained sync/async pair in
src/against the documented and exported contracts.One deterministic security divergence had a clear contract and is fixed:
readSecretFileSync()previewed one file, then trusted the identity selected by a laterrealpathSync()call. If the path was retargeted between those steps, the sync reader returned replacement bytes whilereadSecretFile()rejected the same race withpath-mismatch. The sync reader now compares the preview identity with the pinned descriptor before reading.The other confirmed divergences below need public error-code or portability decisions, so this PR deliberately leaves them unchanged.
Bug and repro
src/secret-file.ts:127: retarget a secret symlink from file A to file B inside therealpathSyncseam; before the fix,readSecretFileSync(link)returned B whilereadSecretFile(link)threwpath-mismatch. The new regression failed on unmodifiedorigin/mainwith “expected function to throw an error, but it didn't.”Inventory
path.ts,root-path.ts,root-path-existing.ts,root-path-symlink.ts;isPathInside,resolveRootPath,resolveRootPathSyncpath.ts,root-path.ts,deny-mutations.ts; Root containment and deny policiesdevice-path.ts,local-file-access.ts,filename.ts,safe-path-segment.ts; all read/open entry pointsarchive-entry.ts;validateArchiveEntryPath, extraction/read pathsNUL,dir/CON.txt,payload.txt:stream, trailing-dot/space names, and Windows-invalid characters are accepted on POSIX but alias, fail, or address ADS/device semantics on Windows. Recommend a portable segment policy, but rejecting currently accepted POSIX archives is a compatibility decision.archive.ts,archive-native.ts,archive-read.ts,archive-input.ts,archive-staging.ts,archive-kind.tsroot-impl.ts,root-context.ts,opened-realpath.ts;open,read*,stat,list,exists,walknot-filebehavior align. Windows opened-handle path discovery remains best-effort as documented.root-impl.ts,root-errors.ts;write,create,copyIn,append,openWritable,mkdir,remove,movepinned-write.ts,native-pinned-write.ts,native-operations.tsnative/src/windows.rs,native/src/unix.rs,native-operations.tsEPERM,ENOENT,EEXIST,ENAMETOOLONG, directory targets) are aligned. Unmapped Windows/NT and POSIX errors collapse toEIO; a live cross-OS fault matrix is needed to prove less common codes.native.ts,native-config.tsdirectory-durability.ts,fsync.ts; async/sync strict and best-effort APIsreplace-file.ts,replace-file-descriptor.ts,replace-file-copy-fallback.ts; async/sync twinsmove-path.ts;movePathWithCopyFallbackEXDEVand WindowsEPERMenter copy fallback; source identity, hardlinks, modes, and cleanup are fenced consistently.secure-file.ts,permissions.ts,permissions-windows.ts,windows-owner.ts,owner-dacl.ts,private-directory.tssecret-file.ts,secret-read-async.ts; strict/optional sync and async readerspath-mismatchbehavior.secret-file.ts:147,secret-read-async.ts:82EIObecomesinvalid-pathin sync andpath-mismatchin async. Recommend preserving the underlying I/O error or defining one common operational code; neither current code accurately describes it, so this PR does not choose.secret-file.ts;writeSecretFileAtomic,createSecretFileAtomicregular-file.ts; stat/read/append pairsjson.ts; strict/lenient reads and writesfile-store.ts:132,file-store.ts:339,file-store.ts:444not-file; sync returnspath-mismatch. Stable hardlinks similarly becomehardlinkasync versuspath-mismatchsync. Recommend semantic codes in sync, but that is a public code change.private-temp-workspace.ts:256read. Async returnsnot-file; sync fabricates rawENOENT. Recommend matching the async Root codes, pending compatibility approval.directory-guard.ts,guarded-mutation.ts,symlink-parents.tsallowMissing, an existing file ancestor plus/childreturns success async becauseENOTDIRis treated as missing, while sync throws rawENOTDIR. Recommend defining whether this helper is a pure symlink predicate or also validates usable ancestry before changing either side.walk.ts,root-walk.tssidecar-lock*.ts,file-lock.ts,file-lock-sync.tsEPERMretry is intentionally async-only and documented as such.file-hash.ts,native/src/fast_file.rs;sha256Filepublish-file.ts,publish-file-failure.tslocal-roots.ts,home-dir.ts,secure-temp-dir.ts,temp-target.tsjson-durable-queue.tsoutput-sibling.ts,sidecar-lock-reclaim.ts,file-lock-sync.ts,file-store-boundary.ts,archive-read.ts,archive-input.ts,archive-staging.ts,sidecar-lock.tsVerification
pnpm install --frozen-lockfilepnpm native:buildpnpm native:test: 16 passedpnpm test:coverage: 1,004 passed, 59 platform skips; 90.05% line coveragepnpm check: 1,004 passed, 59 platform skipspnpm test:security: 70 passedgit diff --checkgpt-5.6-sol, high): clean, no accepted/actionable findingsGenerated
coverage/,dist/, native binary, and Cargo target artifacts were removed before commit.