fix: bug-hunt round 10 — terminal capture entropy unblock, secret-route overlay supersede, fail-closed apt probe, ack-path redaction, bundle path gate, gitconfig escapes - #39
Merged
Conversation
The capture secret gate scans a view of the exported plist with <data> payloads masked: NSKeyedArchiver base64 archives (any customised profile colour or font) scored as high-entropy credentials, blocking both repo routes and leaving the domain uncapturable. <string> values stay fully scanned; an unterminated <data> element ends the masking so malformed input is scanned in full. The secret route's placeholder write removes a superseded local overlay exactly as a shared accept does — the placeholder landed behind the overlay, so apply kept importing and re-applying the stale bytes while status and capture re-offered the domain forever. A symlinked overlay is left untouched. The status-side resolver probes both overlay spellings apply probes, so an extensionless overlay no longer splits what apply imports from what status and capture compare. Assisted-by: Claude:claude-fable-5
dpkg-query reports a not-installed package with a diagnostic in its combined output; an exec-level failure returns no output at all, which the installed-set prober read as absent. A probe failure in the before snapshot recorded a pre-existing package as ferry-installed — handing a later privileged restore --packages a package the user already had — and a lookup failure hitting both snapshots silently lost the run's record with no warning. A probe error with no output marks the snapshot unreliable, suppressing the restore record and saying so. The apt fake models combined output, and the apt rail gains the failed-before-snapshot regression test the brew rail already had. Assisted-by: Claude:claude-fable-5
A finding on a secret-shaped file name renders as (name withheld), but the retry's unmatched list printed the raw item/path in the same error. The unmatched label masks the secret-shaped component via the existing redaction helper, keeping the surrounding path visible; the fail-closed abort, post-check state save, and hash-pinned acknowledgements are unchanged. Assisted-by: Claude:claude-fable-5
Export withholds entries whose path carries a secret-shaped component; import validated only the canonical path shape, so a handcrafted bundle could plant a token-named file that wedged every later sync far from the cause. The per-component predicate moves to internal/secret, export delegates to it, and import applies it first per entry, refusing with the path withheld. Assisted-by: Claude:claude-fable-5
The inline-header split and the subsection derivation each scanned for the closing bracket without git's in-quote escapes, so a legal escaped quote (or an unquoted ] inside quotes) in a subsection name swallowed a same-line assignment into the header's raw bytes — which, for sections the identity firewall does not drop wholesale, passed through to the shared repo. One shared scanner serves both call sites; parsing stays total and reassembly byte-exact (round-trip fuzz re-run). Assisted-by: Claude:claude-fable-5
The driver tags HEAD, so accepting any dated CHANGELOG version let a hand-run tag an older still-untagged version onto newer main code — the mis-pointing the automatic path refuses by tagging only the first dated heading; the CHANGELOG gate asserts the requested version is the newest. The NEXT.md carry markers are checked in the preconditions gate, so a malformed local file fails the run before the irreversible tag push instead of after it. The functions comment stops claiming test coverage that does not exist. Assisted-by: Claude:claude-fable-5
The branch-rename hint names the remote follow-up (set main as the remote's default branch so other clones follow), and the scaffold help lists NEXT.md in the runtime layout both modes create — the --private list already named it, so the omission read as a false negative. The generated scaffold reference page is regenerated. Assisted-by: Claude:claude-fable-5
The drift explanation subjects first-touch adoption to the risky-change gate (confirmed interactively, refused non-zero unattended) instead of claiming unconditional deployment. The release how-to documents the driver's post-tag NEXT.md reset and the real re-release condition (missing, still a draft, or short of its assets). The configuration reference documents the terminal secret route and its placeholder, and scopes the bundle's local-layer gate to local/** and ferry.local.toml. deps/README names the brew = true gate its npm section already mirrors. The evals file map lists the deps-overlay eval. Roadmap clauses leave the commands reference and the tutorial, and the commands reference notes init --fresh. Assisted-by: Claude:claude-fable-5
The registry-order test comment names all six FileDomains; the secret store drops a sort into a fresh map that could not affect encoding (the TOML encoder documents key sorting) and credits the real mechanism; groupRisky's fallback comment and test name describe the defensive default instead of a preference-item path that cannot reach it; the round-3 sync test comment describes the ls-files enumeration; the agents adoption walk drops an unreachable SkipDir arm that would skip remaining siblings if it ever fired; the printPlan consumer comments name diff and init's plan preview; the allowlist comment says extending means changing ferry's compiled-in source; the Overlay comment lists gitconfig among the include-sidecar names. Assisted-by: Claude:claude-fable-5
The round-7 directory-walk entry contradicted the round-9 ls-files entry that replaced it — both would have shipped verbatim in the Release body — and two stray blank lines split the Fixed list. The round's fixes gain their entries, and the decision log records the round. Assisted-by: Claude:claude-fable-5
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug-hunt round 10: fresh hunt across the four dimensions (code, docs-vs-functionality, infrastructure, internal consistency), ~30 unique candidates, each cluster adversarially reviewed before any fix. 8 substantive findings and ~19 nitpicks confirmed; ~10 candidates refuted. Baseline was fully green before the round and all gates are green on this head (build, gofmt, vet, unit + race, full eval suite with
FERRY_BIN, consistency lint).Substantive fixes
cmd/capture.go:1055-1058filtered only iTerm2; the raw export's NSKeyedArchiver base64dataelements (any customised profile) scored ≥4.0 bits/char entropy (internal/secret/scan.go:561-573), and entropy findings are High confidence →BlockedFromRepo(internal/secret/gate.go:44-56), so the domain was uncapturable except reject/secret-store — and the store route makes it non-portable. The gate now scans a data-masked view (terminalGateInputincmd/capture.go);stringvalues stay fully scanned, an unterminateddataelement ends the masking, and the accepted bytes are unchanged.captureBlockedTerminal(cmd/capture.go:1143-1166) wrote the placeholder behind a surviving overlay, soapply(cmd/apply.go:1748-1779) kept importing — and re-applying — the stale bytes while status/capture re-offered the domain forever. The overlay-removal loop is factored out ofacceptTerminalSharedand shared; guard-first semantics preserved.cmd/status.go:265-278probed only the.plistspelling where apply probes the extensionless spelling too, splitting what apply imports from what status/capture compare.internal/deps/install.go:248-270read a no-output probe failure as "absent": a before-snapshot failure recorded a pre-existing package as ferry-installed (feeding a privilegedrestore --packagesremoval), and a symmetric lookup failure silently lost the record with noSnapshotUnreliablewarning. The brew prober had exactly this guard and regression test; apt now has both, and the test fake models combined output.work pack --acknowledgeno longer echoes a withheld path.cmd/work.go:236-249printed the rawitem/pathfor aPathSecretfinding in the same message that renders it "(name withheld)" (internal/work/pack.go:66-78). The unmatched label masks the secret-shaped component via the existing redaction helper.bundle importgates secret-shaped path components.internal/bundle/validate.gochecked only canonical path shape while export withheld such entries (cmd/export.go:275-285); a handcrafted bundle could plant a token-named file that wedged every later sync (cmd/sync.go:1046-1048) far from the cause. The per-component predicate lives ininternal/secretand both sides share it; the refusal withholds the path.internal/gitconfig/parse.go:193-212andparseSectionHeadereach mis-scanned an escaped quote (and an unquoted]in quotes) in a subsection name; a same-line assignment was swallowed into the header's raw bytes, which for non-includeIf sections passed the identity firewall intoSharedContent— the non-conservative direction. One shared close-bracket scanner serves both; round-trip fuzz re-proved byte-exact reassembly (1.5M execs).[Unreleased]no longer contradicts itself. The round-7 entry described the directory walk the round-9 entry replaced (cmd/sync.go:1127-1136usesgit ls-files --others --exclude-standard); both would have shipped verbatim in the Release body (release.yml:386). Consolidated to one entry; the stale walk-parity comment incmd/sync_round3_test.gocorrected; two stray blank lines removed.Ledger correction folded into the docs: the round-9 decision-log line "the release how-to documents step 7's local NEXT.md reset" described a sentence that did not exist; the how-to now documents the reset (verified against
scripts/release.sh:193-225), making the ledger line true.Nitpick fixes
release.shrefuses a version that is not the newest dated CHANGELOG heading (the driver tags HEAD — the mis-pointingauto-release.yml:73-74refuses) and pre-checks NEXT.md's carry markers before the irreversible push; its "sourced by tests" comment matched nothing. Sync's branch-rename hint names the remote default-branch step. Scaffold help names NEXT.md. Docs: drift explanation gains the risky-gate qualifier (cmd/guided_apply.go:125-126,cmd/apply.go:705,759refuse non-interactively — the page claimed unconditional deployment); release how-to documents the post-tag NEXT.md reset and the widened heal predicate (missing/draft/short of assets,auto-release.yml:112-114); configuration reference documents the terminal secret route + placeholder and scopes the bundle local-layer gate tolocal/**+ferry.local.toml; deps/README names thebrew = truegate; evals map lists the 40th file (deps_local_overlay_ignore_test.go); roadmap clauses dropped;init --freshnoted in commands.md; allowlist.go "extend" advice disambiguated. Hygiene: registry_test six-domain enumeration,writeDomaindead sort,groupRiskyfallback comment/test rename (branch kept), unreachableSkipDirarm in agents adoption (a latent trap — firing on a non-dir would skip remaining siblings),printPlanconsumer comments, Overlay comment's missing gitconfig.Considered and rejected (refuted by adversarial review)
release.yml:128-131shows the three-gate mirror was a considered boundary; a tag names an already-scanned merged main commit.verify; round-8 non-exhaustiveness adjudication bars the rest.ScanTextis a strict superset ofHasBinarySecret; the residual divergence over-blocks (fail-closed).reDumpexistedBefore on Lstat failure: any non-NotExist failure also fails the dump; documented tolerance, summary-only.planSummaryparameter order: deliberate, documented in place, pinned by test..gitignorenon-atomic write: the "every other repo write is atomic" premise is false — init-route writers are uniformly plain; a class-wide change is an owner call.groupRiskyunreachable fallback as a defect: defensive default kept; only its comment misattributed the reason (fixed).Carried forward
A present-but-refused local terminal overlay still falls back to shared in status where apply fails closed (needs a tri-state through
reportTerminalStatus; an existing test pins the fallback);work pack's path gate scans the whole rel where export gates per component (new lead from the refutation pass, unadjudicated); the heal predicate's 5-asset hardcode; path-keyedmanagedcarry; the secret scan's Lstat-to-read TOCTOU (O_NOFOLLOW); unpruned journal runs; the duplicated deps-overlay glob; the forward-only placeholder render.