Skip to content

fix: close secret-scanner bypasses, harden state durability and the apply lock#20

Merged
REPPL merged 1 commit into
mainfrom
fix/secret-scanner-durability-lock-hardening
Jul 15, 2026
Merged

fix: close secret-scanner bypasses, harden state durability and the apply lock#20
REPPL merged 1 commit into
mainfrom
fix/secret-scanner-durability-lock-hardening

Conversation

@REPPL

@REPPL REPPL commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Summary

Resolves 16 of 22 verified findings from a multi-agent security + correctness review of ferry (6 parallel finders → each finding adversarially verified by an independent skeptic; 3 finder findings were pruned as false positives). The remaining 6 are deferred with documented rationale.

Headline: ferry's path-containment, ~/.ssh guard, and archive-extraction code are genuinely hardened — the review found no RCE, no zip-slip, no out-of-$HOME write. The real security surface is the secret scanner, ferry's single automated control keeping plaintext credentials out of the shared, pushed repo. This PR closes a cluster of scanner bypasses plus a set of durability, concurrency, and correctness bugs.

Security — secret-scanner bypasses

Each let a real credential reach the committed repo:

  • Quoted multi-word values (password = "correct horse battery") were scanned only up to the first space (was HIGH). The whole quoted value is now scanned.
  • Multi-line opaque values (ScanValue, e.g. a defaults export plist blob): the ^-anchored detectors were dead past line 1. Now every line is scanned.
  • Over-broad placeholder exemptions: a literal secret starting with $ or containing an interior < / ... was exempted. Only genuine ${VAR} / $(cmd) / whole-value $VAR references and <...> templates are now exempt.
  • Missing credential key names (pass, passphrase, pwd, credentials, *_key) are now recognised, with a path-value guard so *_file = /path keys don't false-positive.
  • Authorization: Bearer / Basic header tokens in generic dotfiles are now detected.
  • Binary bundle members are now scanned for provider tokens (GitHub/AWS/…) in addition to private-key headers, symmetric on export and import.

Security — concurrency & transport

  • Apply lock published atomically (temp + fsync + hardlink): a concurrent acquirer can no longer reclaim a live owner's half-written lockfile and run a second apply in parallel.
  • Init-time GitHub push hardened (protocol.ext.allow=never, hooks/fsmonitor/ssh neutralised) like every other ferry git call, so an ambient pushInsteadOf rewrite can't redirect it.

Fixed — durability & correctness

  • Journal/snapshot backup blobs and the dotfile/agents/statefile writers now fsync before the referencing metadata; a zero-length state file degrades to first-touch instead of wedging every command.
  • Capture no longer wedges a file that lacks a trailing newline into a permanent conflict.
  • ferry sync rolls back on an ahead/behind computation failure instead of silently skipping remote integration.
  • Custom bundle import --out targets are symlink-hardened like the default.

Added

  • ferry restore --undo <snapshot-id> makes the pre-restore snapshot the restore output has always advertised actually reachable; the post-restore message now prints the exact command.

Deferred (6, with rationale)

F03 overlay-shadowed agents-capture revert (needs a routing rework + dedicated tests; the file is backed up and ferry restore-able), F10 whole-file blocked-capture (design scope; recoverable pre-commit), F15/F16 residual parent-swap TOCTOU (same-user racer outside the threat model; documented; needs an openat(O_NOFOLLOW) rearchitecture), F17 bounded in-memory member buffer (by design), F22 $PATH helper resolution (reviewed sound — only the root apt rail executes privileged, and it is already resolved through a sanitized fixed dir list).

Testing

make build, gofmt -l ., go vet ./..., the full go test ./... suite including evals (FERRY_BIN set), and -race on the changed packages are all green. Regression tests were added for every scanner fix and the capture-newline fix; one eval git stub was made robust to -c global options to match the new push hardening. Docs regenerated and CHANGELOG updated.

Assisted-by: Claude:claude-fable-5

@REPPL REPPL force-pushed the fix/secret-scanner-durability-lock-hardening branch 2 times, most recently from e505a72 to 485f1a9 Compare July 15, 2026 15:54
…pply lock

Resolves 16 verified findings from a multi-agent security + correctness review
(each adversarially verified). The remaining 6 are deferred with rationale
(same-user-racer TOCTOU, a design-scope capture change, and informational items).

Secret scanner (ferry's gate against committing credentials to the shared repo):
- Capture the WHOLE quoted value, not just its first whitespace-free run, so a
  quoted multi-word passphrase no longer slips through (was HIGH).
- Scan every line of a multi-line opaque value (ScanValue), not only the first —
  the ^-anchored detectors were dead past line 1.
- Narrow the placeholder exemptions: only genuine ${VAR}/$(cmd)/whole-value $VAR
  references and <...> templates are exempt, not any $-prefixed or <-containing
  literal.
- Expand credKeyword (pass/passphrase/pwd/credentials/*_key) with a path-value
  guard so *_file=/path keys don't false-positive.
- Detect Authorization: Bearer/Basic header tokens in generic dotfiles.
- Scan binary bundle members for provider tokens too (secret.HasBinarySecret),
  symmetric on export and import.

Durability / crash-safety:
- Route journal/snapshot backup blobs through the fsync'd AtomicWrite so a crash
  can't roll a truncated backup over an intact file.
- fsync the dotfile/agents state writers and the statefile migration backup;
  treat a zero-length state file as empty on read instead of hard-failing.

Concurrency:
- Publish the apply lock atomically (temp + fsync + hardlink) so a concurrent
  acquirer can't reclaim a live owner's half-written lock and run a second apply.

Correctness / hardening:
- Preserve the live file's trailing-newline shape when composing a capture, so a
  file lacking a final newline no longer wedges in a permanent conflict.
- sync: propagate an ahead/behind computation failure and roll back instead of
  silently skipping remote integration.
- Harden the init-time GitHub push (protocol.ext.allow=never, hooks/fsmonitor/ssh
  neutralised) like every other ferry git call.
- Symlink-harden custom `bundle import --out` targets like the default.

New: `ferry restore --undo <snapshot-id>` makes the pre-restore snapshot the
output has always advertised actually reachable.

Also updates the AGENTS.md attribution policy: AI-assisted commits now carry an
Assisted-by trailer (kernel format; human remains author of record).

Docs regenerated; CHANGELOG updated; regression tests added for each scanner fix
and the capture-newline fix.

Assisted-by: Claude:claude-fable-5
@REPPL REPPL force-pushed the fix/secret-scanner-durability-lock-hardening branch from 485f1a9 to c3185aa Compare July 15, 2026 15:59
@REPPL REPPL merged commit 1e0f179 into main Jul 15, 2026
15 of 16 checks passed
@REPPL REPPL deleted the fix/secret-scanner-durability-lock-hardening branch July 15, 2026 17:16
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