From 2416d3da7c88dabc6bcd817bb344e6f3f3ff0215 Mon Sep 17 00:00:00 2001 From: REPPL <77722411+REPPL@users.noreply.github.com> Date: Sat, 10 Oct 2026 09:31:46 +0100 Subject: [PATCH] chore: restore the advisory references in the security-fix records The records resolved by the security drain went out in v0.13.4 with their advisory ids and reproductions withheld until the advisories published. They are published now, so each record gets its full text back. One record keeps the resolution line it gained on main after the redaction. Assisted-by: Claude:claude-opus-5-5 --- ...821313095-guard-function-keyword-body-only-warns.md | 8 +++++--- ...610090821476887-guard-allows-trap-command-string.md | 8 +++++--- ...0090821484829-guard-allows-bash-env-sourced-file.md | 8 +++++--- ...2610090821489740-guard-steps-over-bash-init-file.md | 8 +++++--- ...948-scanner-misses-stacked-json-percent-encoding.md | 8 +++++--- ...9579-scanner-png-chunk-tail-after-zlib-unscanned.md | 8 +++++--- ...-scanner-text-sniff-8192-bytes-counts-as-scanned.md | 8 +++++--- ...06490-scanner-dot-skip-fragment-passes-addresses.md | 8 +++++--- ...0097-implement-sync-merge-runs-repo-merge-driver.md | 8 +++++--- ...90821512707-scanner-tar-long-name-body-unscanned.md | 8 +++++--- ...843-pick-record-commit-runs-repo-signing-program.md | 10 ++++++---- ...issing-promisor-object-triggers-configured-fetch.md | 8 +++++--- ...21531394-read-only-git-log-runs-repo-gpg-program.md | 8 +++++--- ...70-record-lint-agent-diff-runs-repo-diff-drivers.md | 8 +++++--- ...543020-core-worktree-ancestor-becomes-store-root.md | 8 +++++--- ...548169-launch-dirty-check-runs-repo-clean-filter.md | 8 +++++--- ...mplement-land-stage-trusts-planted-worktree-path.md | 8 +++++--- ...25390900-guard-allows-shellopts-ps4-substitution.md | 2 +- ...25399967-guard-allows-exported-bash-function-env.md | 2 +- ...2257904-guard-allows-shell-stdin-redirect-script.md | 2 +- 20 files changed, 89 insertions(+), 55 deletions(-) diff --git a/.abcd/work/issues/resolved/iss-2610090821313095-guard-function-keyword-body-only-warns.md b/.abcd/work/issues/resolved/iss-2610090821313095-guard-function-keyword-body-only-warns.md index cf4dcb2ac..89fe95e91 100644 --- a/.abcd/work/issues/resolved/iss-2610090821313095-guard-function-keyword-body-only-warns.md +++ b/.abcd/work/issues/resolved/iss-2610090821313095-guard-function-keyword-body-only-warns.md @@ -5,7 +5,7 @@ slug: "guard-function-keyword-body-only-warns" severity: "minor" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-6h89-gjcg-3h3h, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/core/guard/match.go" @@ -16,6 +16,8 @@ impact: fix `abcd guard` only warns on a bash `function f { ...; }; f` whose body is a blocker, and the PreToolUse hook lets a warning run, so the function executes. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-6h89-gjcg-3h3h (draft, severity medium). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `function` is not in the reserved-word set the walk steps over before command position (internal/core/guard/match.go:158, `reserved`), so the keyword form falls to unrecognised-launcher while the POSIX form `f() { ...; }` has its body judged. The hook maps a warn to exit 1, which surfaces the message and lets the tool run; only a block (exit 2) stops it (internal/surface/cli/guard.go:455-466). \ No newline at end of file +Evidence (lines at main 7549ca2d5): `function` is not in the reserved-word set the walk steps over before command position (internal/core/guard/match.go:158, `reserved`), so the keyword form falls to unrecognised-launcher while the POSIX form `f() { ...; }` has its body judged. The hook maps a warn to exit 1, which surfaces the message and lets the tool run; only a block (exit 2) stops it (internal/surface/cli/guard.go:455-466). + +Reproduction: `Defaults().Check("function f { git push --force origin main; }; f")` is warn / unrecognised-launcher; `Defaults().Check("f() { git push --force origin main; }; f")` is block / git-push-force. On /bin/bash 3.2.57, `/bin/bash -c "function f { touch $MARK; }; f"` creates the mark. diff --git a/.abcd/work/issues/resolved/iss-2610090821476887-guard-allows-trap-command-string.md b/.abcd/work/issues/resolved/iss-2610090821476887-guard-allows-trap-command-string.md index 2a0a97a92..a37bc4d7b 100644 --- a/.abcd/work/issues/resolved/iss-2610090821476887-guard-allows-trap-command-string.md +++ b/.abcd/work/issues/resolved/iss-2610090821476887-guard-allows-trap-command-string.md @@ -5,7 +5,7 @@ slug: "guard-allows-trap-command-string" severity: "major" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-f789-v342-57jr, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/core/guard/payload.go" @@ -16,6 +16,8 @@ impact: fix `abcd guard` allows a blocker written as the command string of `trap`, and bash runs it, an EXIT trap needing no further command. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-f789-v342-57jr (draft, severity high). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `evalPayload` joins `eval`'s operands and the guard re-reads that string, which is why `eval -- 'git push --force origin main'` is a block (internal/core/guard/payload.go:1578). Nothing walks the command operand of `trap`. The hook maps an allow to exit 0 and only a block to exit 2 (internal/surface/cli/guard.go:455-470). \ No newline at end of file +Evidence (lines at main 7549ca2d5): `evalPayload` joins `eval`'s operands and the guard re-reads that string, which is why `eval -- 'git push --force origin main'` is a block (internal/core/guard/payload.go:1578). Nothing walks the command operand of `trap`. The hook maps an allow to exit 0 and only a block to exit 2 (internal/surface/cli/guard.go:455-470). + +Reproduction: `Defaults().Check` returns allow for `trap 'git push --force origin main' EXIT`, the same with `; true` appended, `trap -- '...' EXIT; true`, `trap '...' 0; true` and `trap '...' DEBUG; true`. On /bin/bash 3.2.57, `/bin/bash -c "trap 'touch $MARK' EXIT"` creates the mark; the DEBUG form runs when a later command is present. The same text under `eval` is block / git-push-force. diff --git a/.abcd/work/issues/resolved/iss-2610090821484829-guard-allows-bash-env-sourced-file.md b/.abcd/work/issues/resolved/iss-2610090821484829-guard-allows-bash-env-sourced-file.md index 15c4795b0..ced37252f 100644 --- a/.abcd/work/issues/resolved/iss-2610090821484829-guard-allows-bash-env-sourced-file.md +++ b/.abcd/work/issues/resolved/iss-2610090821484829-guard-allows-bash-env-sourced-file.md @@ -5,7 +5,7 @@ slug: "guard-allows-bash-env-sourced-file" severity: "major" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-r2w5-wf2r-jmf8, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/core/guard/payload.go" @@ -16,6 +16,8 @@ impact: fix `abcd guard` allows `BASH_ENV= bash -c true`, and non-interactive bash sources that file before `-c`, so a blocker written to the file in the same command runs. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-r2w5-wf2r-jmf8 (draft, severity high). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `shellReadsStream` treats a `-c` string as the payload and does not look at an assignment prefix for `BASH_ENV` (internal/core/guard/payload.go:1860). The stream block's own successor tells the caller "to run a script, save it and run it as a file after reading it" (internal/core/guard/payload.go:1971), and the file form it recommends is also an allow that bash runs, so closing `BASH_ENV` alone leaves the file channel open. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `shellReadsStream` treats a `-c` string as the payload and does not look at an assignment prefix for `BASH_ENV` (internal/core/guard/payload.go:1860). The stream block's own successor tells the caller "to run a script, save it and run it as a file after reading it" (internal/core/guard/payload.go:1971), and the file form it recommends is also an allow that bash runs, so closing `BASH_ENV` alone leaves the file channel open. + +Reproduction: `printf '%s\n' 'git push --force origin main' > /tmp/e; BASH_ENV=/tmp/e bash -c true` is allow, and /bin/bash 3.2.57 runs the file (a `touch` stand-in creates the mark). The same allow and execution for `bash /tmp/s.sh` and `source /tmp/s.sh` after the same printf. diff --git a/.abcd/work/issues/resolved/iss-2610090821489740-guard-steps-over-bash-init-file.md b/.abcd/work/issues/resolved/iss-2610090821489740-guard-steps-over-bash-init-file.md index 2d5145849..a17f9fc0f 100644 --- a/.abcd/work/issues/resolved/iss-2610090821489740-guard-steps-over-bash-init-file.md +++ b/.abcd/work/issues/resolved/iss-2610090821489740-guard-steps-over-bash-init-file.md @@ -5,7 +5,7 @@ slug: "guard-steps-over-bash-init-file" severity: "major" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-xr66-hjpp-xwgc, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/core/guard/payload.go" @@ -17,6 +17,8 @@ impact: fix `abcd guard` steps over the value of `bash --init-file` and `--rcfile`, and interactive bash runs that file before `-c`, so a blocker in it runs while the checked command reads `bash -i -c true`. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-xr66-hjpp-xwgc (draft, severity high). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `shellReadsStream` knows the two options only to skip their value (internal/core/guard/payload.go:1914, the list `shellStreamValueOptions` at :1943). The same process substitution in script position is a block through `readsScriptStream` (internal/core/guard/payload.go:1816). \ No newline at end of file +Evidence (lines at main 7549ca2d5): `shellReadsStream` knows the two options only to skip their value (internal/core/guard/payload.go:1914, the list `shellStreamValueOptions` at :1943). The same process substitution in script position is a block through `readsScriptStream` (internal/core/guard/payload.go:1816). + +Reproduction: `Defaults().Check` returns allow for `bash --init-file <(printf '%s\n' 'git push --force origin main') -i -c true`, the same with `--rcfile`, and `bash --init-file /tmp/init.sh -i -c true` after printf writes the file. /bin/bash 3.2.57 runs each (a `touch` stand-in creates the mark). `bash <(printf '%s\n' 'git push --force origin main')` is block / interpreter-reads-stream. Related: the file-form decision in the BASH_ENV advisory GHSA-r2w5-wf2r-jmf8. diff --git a/.abcd/work/issues/resolved/iss-2610090821491948-scanner-misses-stacked-json-percent-encoding.md b/.abcd/work/issues/resolved/iss-2610090821491948-scanner-misses-stacked-json-percent-encoding.md index 3b5698866..a5624bc63 100644 --- a/.abcd/work/issues/resolved/iss-2610090821491948-scanner-misses-stacked-json-percent-encoding.md +++ b/.abcd/work/issues/resolved/iss-2610090821491948-scanner-misses-stacked-json-percent-encoding.md @@ -5,7 +5,7 @@ slug: "scanner-misses-stacked-json-percent-encoding" severity: "major" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-74v2-f6pg-p4fq, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/adapter/scanner/percent.go" @@ -16,6 +16,8 @@ impact: fix The secret scanner misses a token or home path written as a JSON escape stacked on a percent encoding (or the reverse), because it never runs one decoder on the other's output, so `ScanText` reports nothing, `Redact` leaves it, and the launch gate ships it. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-74v2-f6pg-p4fq (draft, severity high). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `lineViews` builds one percent view of the raw line and then JSON layers of that same raw line (internal/adapter/scanner/percent.go:61). The comment at internal/adapter/scanner/jsonescape.go:45 claims the two do not compose, yet `jsonUnescapeOnce` emits `%` as `%` (internal/adapter/scanner/jsonescape.go:100). `ScanBundle` counts the text file as fully scanned (internal/adapter/scanner/scanner.go:1225), and `scanRefusals` (internal/core/launch/dryrun.go:269) refuses only an unavailable scanner, a kept hard-fail or an Unscanned path. History and memory call the same `ScanText`. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `lineViews` builds one percent view of the raw line and then JSON layers of that same raw line (internal/adapter/scanner/percent.go:61). The comment at internal/adapter/scanner/jsonescape.go:45 claims the two do not compose, yet `jsonUnescapeOnce` emits `%` as `%` (internal/adapter/scanner/jsonescape.go:100). `ScanBundle` counts the text file as fully scanned (internal/adapter/scanner/scanner.go:1225), and `scanRefusals` (internal/core/launch/dryrun.go:269) refuses only an unavailable scanner, a kept hard-fail or an Unscanned path. History and memory call the same `ScanText`. + +Reproduction: with T a `ghp_` prefix followed by 40 `q` characters, none of these hard-fails: `token=%67` followed by T without its first character (JSON-unescape then percent-decode yields T); `token=%5Cu0067` with the same tail (the reverse order); `see %2F in the log` for the probed home path (no home_path_self). The plaintext token, a single `%67` and a single `g` of the first byte still match. diff --git a/.abcd/work/issues/resolved/iss-2610090821499579-scanner-png-chunk-tail-after-zlib-unscanned.md b/.abcd/work/issues/resolved/iss-2610090821499579-scanner-png-chunk-tail-after-zlib-unscanned.md index 9e28e15bf..7eec43777 100644 --- a/.abcd/work/issues/resolved/iss-2610090821499579-scanner-png-chunk-tail-after-zlib-unscanned.md +++ b/.abcd/work/issues/resolved/iss-2610090821499579-scanner-png-chunk-tail-after-zlib-unscanned.md @@ -5,7 +5,7 @@ slug: "scanner-png-chunk-tail-after-zlib-unscanned" severity: "major" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-83x2-mf5v-j796, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/adapter/scanner/container.go" @@ -16,6 +16,8 @@ impact: fix The secret scanner reports a PNG as content-decoded while bytes inside a compressed chunk after the zlib checksum are never inflated or scanned, so a gzip member there ships through the launch gate. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-83x2-mf5v-j796 (draft, severity high). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `decodeBudget.inflate` uses `zlib.NewReader`, which stops at the Adler-32, and returns only the inflated bytes (internal/adapter/scanner/container.go:236). The `zTXt` arm covers only that output (internal/adapter/scanner/container.go:970) and `decodePNG` returns decoded after IEND (internal/adapter/scanner/container.go:951). `decodeStream`, the top-level zlib path, does cover the unread tail (internal/adapter/scanner/container.go:329). The same unread tail exists for compressed `iTXt`, `iCCP` and `IDAT`. Distinct from a private security report (the skip that never opened the PNG). A PNG-only bundle is refused by the zero-coverage sentinel; the bypass needs one other full-text file, which the include list already has. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `decodeBudget.inflate` uses `zlib.NewReader`, which stops at the Adler-32, and returns only the inflated bytes (internal/adapter/scanner/container.go:236). The `zTXt` arm covers only that output (internal/adapter/scanner/container.go:970) and `decodePNG` returns decoded after IEND (internal/adapter/scanner/container.go:951). `decodeStream`, the top-level zlib path, does cover the unread tail (internal/adapter/scanner/container.go:329). The same unread tail exists for compressed `iTXt`, `iCCP` and `IDAT`. Distinct from GHSA-9wv7-88w3-f77m (the skip that never opened the PNG). A PNG-only bundle is refused by the zero-coverage sentinel; the bypass needs one other full-text file, which the include list already has. + +Reproduction: build a small valid PNG and insert a CRC'd `zTXt` chunk before IEND: keyword `Comment`, NUL, compression method 0, a zlib member of `harmless` plus a newline, then a Huffman-coded gzip member of a `ghp_` token. Scan it beside README.md: `ContentDecoded` lists the PNG and `HardFails` is 0; gunzip of the tail returns the token. diff --git a/.abcd/work/issues/resolved/iss-2610090821502084-scanner-text-sniff-8192-bytes-counts-as-scanned.md b/.abcd/work/issues/resolved/iss-2610090821502084-scanner-text-sniff-8192-bytes-counts-as-scanned.md index def7a0e3e..0ea673b60 100644 --- a/.abcd/work/issues/resolved/iss-2610090821502084-scanner-text-sniff-8192-bytes-counts-as-scanned.md +++ b/.abcd/work/issues/resolved/iss-2610090821502084-scanner-text-sniff-8192-bytes-counts-as-scanned.md @@ -5,7 +5,7 @@ slug: "scanner-text-sniff-8192-bytes-counts-as-scanned" severity: "major" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-jwgh-838h-jrw9, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/adapter/scanner/scanner.go" @@ -16,6 +16,8 @@ impact: fix The secret scanner counts a file as fully scanned when only its first 8192 bytes are valid UTF-8 text, so a gzip member after a page of prose in an included markdown file ships with no finding. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-jwgh-838h-jrw9 (draft, severity high). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `isText` sniffs 8192 bytes (internal/adapter/scanner/scanner.go:1519). The text branch then runs `ScanText` on the raw bytes and increments FilesScanned without calling `decodeContent` (internal/adapter/scanner/scanner.go:1221-1225). `cover` already refuses an 8192-byte sniff as coverage of a decoded region. `docs/` is an include, and the bundler does not inspect content before inclusion. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `isText` sniffs 8192 bytes (internal/adapter/scanner/scanner.go:1519). The text branch then runs `ScanText` on the raw bytes and increments FilesScanned without calling `decodeContent` (internal/adapter/scanner/scanner.go:1221-1225). `cover` already refuses an 8192-byte sniff as coverage of a decoded region. `docs/` is an include, and the bundler does not inspect content before inclusion. + +Reproduction: write about 10500 bytes of ASCII prose, then append a Huffman-coded gzip of a `ghp_` token (CPython `gzip.compress` or `gzip -nc`; Go's flate writer often leaves a short token as literals, which is caught). `ScanBundle` reports FilesScanned=1, HardFails=0, empty Unscanned and empty ContentUnverified; `gzip -dc` of the tail returns the token. The same bytes named `.gz` hard-fail. diff --git a/.abcd/work/issues/resolved/iss-2610090821506490-scanner-dot-skip-fragment-passes-addresses.md b/.abcd/work/issues/resolved/iss-2610090821506490-scanner-dot-skip-fragment-passes-addresses.md index 4e1be21ba..bb83b7406 100644 --- a/.abcd/work/issues/resolved/iss-2610090821506490-scanner-dot-skip-fragment-passes-addresses.md +++ b/.abcd/work/issues/resolved/iss-2610090821506490-scanner-dot-skip-fragment-passes-addresses.md @@ -5,7 +5,7 @@ slug: "scanner-dot-skip-fragment-passes-addresses" severity: "minor" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-8fqr-pv94-5jwp, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/adapter/scanner/scanner.go" @@ -16,6 +16,8 @@ impact: fix A committed `.abcd/config/pii.json` with `skip_path_fragments: ["."]` sends every dotted path to the byte-only branch, so a non-reserved IPv4, IPv6 or MAC address in an included file ships through the launch scan. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-8fqr-pv94-5jwp (draft, severity medium). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `mergeConfig` drops a fragment only when trimming slashes and whitespace leaves it empty, so `.` is stored (internal/adapter/scanner/scanner.go:338). `skipByFragment` is `strings.Contains` (internal/adapter/scanner/scanner.go:1495). `secretPatterns` drops the identity kinds, which include the network kinds (internal/adapter/scanner/scanner.go:1474). The zero-coverage sentinel trips only when FilesScanned is zero, and an undotted `LICENSE` on the include list keeps it above zero. `scanRefusals` does not refuse ContentUnverified files (internal/core/launch/dryrun.go:269). A token, the caller's home path, a real email and a long real name on that path still block. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `mergeConfig` drops a fragment only when trimming slashes and whitespace leaves it empty, so `.` is stored (internal/adapter/scanner/scanner.go:338). `skipByFragment` is `strings.Contains` (internal/adapter/scanner/scanner.go:1495). `secretPatterns` drops the identity kinds, which include the network kinds (internal/adapter/scanner/scanner.go:1474). The zero-coverage sentinel trips only when FilesScanned is zero, and an undotted `LICENSE` on the include list keeps it above zero. `scanRefusals` does not refuse ContentUnverified files (internal/core/launch/dryrun.go:269). A token, the caller's home path, a real email and a long real name on that path still block. + +Reproduction: commit `{"skip_path_fragments":["."]}` as `.abcd/config/pii.json`, put a line of `dns ` followed by a public, non-reserved IPv4 address (the well-known public DNS resolver of four eights; capture redacted the literal) in `commands/a.md`, leave LICENSE clean. `ScanBundle` is available, FilesScanned is at least 1, findings are empty and the markdown file is ContentUnverified. `ScanText` of the same line is net:ipv4 at hard_fail. diff --git a/.abcd/work/issues/resolved/iss-2610090821510097-implement-sync-merge-runs-repo-merge-driver.md b/.abcd/work/issues/resolved/iss-2610090821510097-implement-sync-merge-runs-repo-merge-driver.md index 6fe652fbc..a00053770 100644 --- a/.abcd/work/issues/resolved/iss-2610090821510097-implement-sync-merge-runs-repo-merge-driver.md +++ b/.abcd/work/issues/resolved/iss-2610090821510097-implement-sync-merge-runs-repo-merge-driver.md @@ -5,7 +5,7 @@ slug: "implement-sync-merge-runs-repo-merge-driver" severity: "minor" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-486x-8h9p-pq5x, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/core/implement/loop/sync.go" @@ -16,6 +16,8 @@ impact: fix The implement loop's lane sync merge (`git merge --no-ff` through `pickGit`) runs a repository-configured merge driver, which executes as the operator and writes the merge result git keeps, although the loop presents this merge as running no hook code. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-486x-8h9p-pq5x (draft, severity medium). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `syncLane` (internal/core/implement/loop/sync.go:66) merges with `pickGit(lane.Worktree, "merge", "--no-ff", "--no-edit", "-m", msg, merged)` (internal/core/implement/loop/sync.go:95). `pickGit` pins hooks, fsmonitor and quotePath and runs under ScrubbedEnv, so repo and global config stay (internal/core/implement/loop/pickcommit.go:71). `landStage` calls `syncLane` only while `lane.Landing` is nil (internal/core/implement/loop/land.go:131, :139). The other side can come from a planted `refs/remotes/origin/`; no network remote is needed. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `syncLane` (internal/core/implement/loop/sync.go:66) merges with `pickGit(lane.Worktree, "merge", "--no-ff", "--no-edit", "-m", msg, merged)` (internal/core/implement/loop/sync.go:95). `pickGit` pins hooks, fsmonitor and quotePath and runs under ScrubbedEnv, so repo and global config stay (internal/core/implement/loop/pickcommit.go:71). `landStage` calls `syncLane` only while `lane.Landing` is nil (internal/core/implement/loop/land.go:131, :139). The other side can come from a planted `refs/remotes/origin/`; no network remote is needed. + +Reproduction: on git 2.39.5, set `.git/info/attributes` to `* merge=evil` (or a committed `.gitattributes`) and `merge.evil.driver` to a script; point `refs/remotes/origin/` at a commit editing a file the lane also edits; run `abcd implement step` until the lane reaches `syncLane` with no landing. The script runs, git reports success and the worktree file holds the script's output; the post-merge hook does not run. With no overlapping change the driver does not start. diff --git a/.abcd/work/issues/resolved/iss-2610090821512707-scanner-tar-long-name-body-unscanned.md b/.abcd/work/issues/resolved/iss-2610090821512707-scanner-tar-long-name-body-unscanned.md index a9e397778..32d638492 100644 --- a/.abcd/work/issues/resolved/iss-2610090821512707-scanner-tar-long-name-body-unscanned.md +++ b/.abcd/work/issues/resolved/iss-2610090821512707-scanner-tar-long-name-body-unscanned.md @@ -5,7 +5,7 @@ slug: "scanner-tar-long-name-body-unscanned" severity: "major" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-jx54-228j-mf98, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/adapter/scanner/container.go" @@ -16,6 +16,8 @@ impact: fix The secret scanner reports a tar archive as content-decoded while the bytes after the first NUL of a GNU long-name (`././@LongLink`, type L) body are consumed by the tar reader and never scanned, so a gzip member there ships. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-jx54-228j-mf98 (draft, severity high). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `decodeTar` scans only what `archive/tar.Reader.Next` returns (internal/adapter/scanner/container.go:787); `Next` consumes the type-L body, keeps the C string before the first NUL as the next name and discards the rest. `coverTarHeader` scans `h.Name` only (internal/adapter/scanner/container.go:848), and the counting reader has already consumed the hidden bytes, so the trailer check misses them. `scanRefusals` treats ContentDecoded as covered and does not refuse ContentUnverified (internal/core/launch/dryrun.go:269), so returning not-decoded is not a fix. Distinct from a private security report. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `decodeTar` scans only what `archive/tar.Reader.Next` returns (internal/adapter/scanner/container.go:787); `Next` consumes the type-L body, keeps the C string before the first NUL as the next name and discards the rest. `coverTarHeader` scans `h.Name` only (internal/adapter/scanner/container.go:848), and the counting reader has already consumed the hidden bytes, so the trailer check misses them. `scanRefusals` treats ContentDecoded as covered and does not refuse ContentUnverified (internal/core/launch/dryrun.go:269), so returning not-decoded is not a fix. Distinct from GHSA-9wv7-88w3-f77m. + +Reproduction: hand-build a ustar archive (Go's tar.Writer refuses a manual type-L header). First header `././@LongLink`, typeflag L, body `readme.txt`, NUL, then a gzip of long filler plus a `ghp_` token (not a literal in the raw archive). Second header a regular `readme.txt` of `harmless`. Two zero blocks. Scan beside README.md: ContentDecoded lists the tar and HardFails is 0; gunzip of the bytes after the NUL returns the token. diff --git a/.abcd/work/issues/resolved/iss-2610090821520843-pick-record-commit-runs-repo-signing-program.md b/.abcd/work/issues/resolved/iss-2610090821520843-pick-record-commit-runs-repo-signing-program.md index c07f56e67..897648bf7 100644 --- a/.abcd/work/issues/resolved/iss-2610090821520843-pick-record-commit-runs-repo-signing-program.md +++ b/.abcd/work/issues/resolved/iss-2610090821520843-pick-record-commit-runs-repo-signing-program.md @@ -5,17 +5,19 @@ slug: "pick-record-commit-runs-repo-signing-program" severity: "minor" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-4j38-5p8w-gf5r, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/core/implement/loop/pickcommit.go" -remedy: "Pass `-c commit.gpgsign=false` as a `pickGit` argument before the `commit` subcommand; prove it with a loop test (watched fail first) that the picked record commit leaves the gpg.program, gpg.ssh.program and defaultKeyCommand marks empty and still succeeds, the pre-commit hook still does not run, and a clean filter for a real LFS path still runs; sweep siblings (every commit, merge commit or tag abcd composes, where tag.gpgSign and the merge in a private security report can sign too)." +remedy: "Pass `-c commit.gpgsign=false` as a `pickGit` argument before the `commit` subcommand; prove it with a loop test (watched fail first) that the picked record commit leaves the gpg.program, gpg.ssh.program and defaultKeyCommand marks empty and still succeeds, the pre-commit hook still does not run, and a clean filter for a real LFS path still runs; sweep siblings (every commit, merge commit or tag abcd composes, where tag.gpgSign and the merge in GHSA-486x-8h9p-pq5x can sign too)." resolution: "The shared pin list gitutil.ExecPins now carries commit.gpgsign=false, and pickGit prepends that list instead of its own copy, so the pick's record commit and a sync's merge commit no longer start gpg.program, gpg.ssh.program or gpg.ssh.defaultKeyCommand; the commit is still made and content filters still run. TestPickGitStartsNoRepoSigningProgram proves it." impact: fix --- The picked implement run's hooks-off record commit runs the repository's signing program (`gpg.program`, `gpg.ssh.program` or `gpg.ssh.defaultKeyCommand`) as the operator when the checkout sets `commit.gpgsign=true`. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-4j38-5p8w-gf5r (draft, severity medium). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `pickCommit` (internal/core/implement/loop/pickcommit.go:92) calls `pickGit(wt, "commit", "-q", "-m", ..., "--", rel)` (internal/core/implement/loop/pickcommit.go:149). `pickGit` prepends only the hooksPath, fsmonitor and quotePath pins and runs under ScrubbedEnv (internal/core/implement/loop/pickcommit.go:71), while the comment at internal/core/implement/loop/pickcommit.go:20 says a hook dispatcher is code the loop does not run. Repo-local `commit.gpgsign=true` overrides a global false. The commit runs only for a picked run whose `Pick.Lane` is this lane. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `pickCommit` (internal/core/implement/loop/pickcommit.go:92) calls `pickGit(wt, "commit", "-q", "-m", ..., "--", rel)` (internal/core/implement/loop/pickcommit.go:149). `pickGit` prepends only the hooksPath, fsmonitor and quotePath pins and runs under ScrubbedEnv (internal/core/implement/loop/pickcommit.go:71), while the comment at internal/core/implement/loop/pickcommit.go:20 says a hook dispatcher is code the loop does not run. Repo-local `commit.gpgsign=true` overrides a global false. The commit runs only for a picked run whose `Pick.Lane` is this lane. + +Reproduction: on git 2.39.5, set `commit.gpgsign=true` and `gpg.program` to a mode-0755 script in the checkout; start a picked run and let `abcd implement step` reach the record commit. The script runs with gpg's `--status-fd=2 -bsau` arguments, the pre-commit hook does not, and git exits 128 when the script writes no signature. `gpg.format=ssh` with `gpg.ssh.program`, and `gpg.ssh.defaultKeyCommand` with no signing key, each start too. `-c commit.gpgsign=false` before `commit` exits 0 and starts nothing. diff --git a/.abcd/work/issues/resolved/iss-2610090821527948-missing-promisor-object-triggers-configured-fetch.md b/.abcd/work/issues/resolved/iss-2610090821527948-missing-promisor-object-triggers-configured-fetch.md index 8cd0468bf..8c382584d 100644 --- a/.abcd/work/issues/resolved/iss-2610090821527948-missing-promisor-object-triggers-configured-fetch.md +++ b/.abcd/work/issues/resolved/iss-2610090821527948-missing-promisor-object-triggers-configured-fetch.md @@ -5,7 +5,7 @@ slug: "missing-promisor-object-triggers-configured-fetch" severity: "minor" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-4jpc-8vpr-5x2j, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/gitutil/repo.go" @@ -16,6 +16,8 @@ impact: fix abcd's git environment does not set `GIT_NO_LAZY_FETCH`, so an object read (`show`, `cat-file`, `ls-tree`, or `tag --list` with an object-reading `tag.sort`) of a missing object in a promisor checkout lazy-fetches and runs the repository's transport program (`remote.origin.uploadpack` on a local or file:// URL, `core.sshCommand` on ssh://). -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-4jpc-8vpr-5x2j (draft, severity medium). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `gitEnv` (internal/gitutil/repo.go:74) ends its config keys at core.fsmonitor (internal/gitutil/repo.go:107) and sets no GIT_NO_LAZY_FETCH; `Run`, `RunLimited` and `IsolatedEnv` (internal/gitutil/repo.go:122) share it. Sinks: `lifeboat.Plan` (internal/core/lifeboat/plan.go:187) calls `buildArchaeology` (:305), where `gvRemovedDependencies` shows a historical manifest and ignores the error (internal/core/lifeboat/graveyard_archaeology.go:242), so `abcd disembark plan` and `pack` succeed after the program ran; `committedRegistry` cat-files `HEAD:.abcd/guard.json` and falls back to defaults (internal/core/guard/config.go:99); `GitExistingTags` (internal/core/launch/retention.go:108) is reached by `abcd launch --dry-run` twice (internal/surface/cli/launch_deep.go:77 via `changelog.LatestReleaseTag`, internal/core/changelog/anchor.go:58, and internal/core/launch/dryrun.go:229), by changelog derive and guard, capture's deferral path, `reflect` `previousTag` (internal/core/reflect/seed.go:212) and the receipt gate's `releaseImpact` (internal/core/lint/lint.go:1437). `disembark probe` does not take this path. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `gitEnv` (internal/gitutil/repo.go:74) ends its config keys at core.fsmonitor (internal/gitutil/repo.go:107) and sets no GIT_NO_LAZY_FETCH; `Run`, `RunLimited` and `IsolatedEnv` (internal/gitutil/repo.go:122) share it. Sinks: `lifeboat.Plan` (internal/core/lifeboat/plan.go:187) calls `buildArchaeology` (:305), where `gvRemovedDependencies` shows a historical manifest and ignores the error (internal/core/lifeboat/graveyard_archaeology.go:242), so `abcd disembark plan` and `pack` succeed after the program ran; `committedRegistry` cat-files `HEAD:.abcd/guard.json` and falls back to defaults (internal/core/guard/config.go:99); `GitExistingTags` (internal/core/launch/retention.go:108) is reached by `abcd launch --dry-run` twice (internal/surface/cli/launch_deep.go:77 via `changelog.LatestReleaseTag`, internal/core/changelog/anchor.go:58, and internal/core/launch/dryrun.go:229), by changelog derive and guard, capture's deferral path, `reflect` `previousTag` (internal/core/reflect/seed.go:212) and the receipt gate's `releaseImpact` (internal/core/lint/lint.go:1437). `disembark probe` does not take this path. + +Reproduction: on git 2.39.5, delete the loose blob of a historical manifest the plan shows; set `extensions.partialClone=origin`, `remote.origin.promisor=true`, a partial-clone filter, `remote.origin.url` a local path and `remote.origin.uploadpack` a mode-0755 script; run `abcd disembark plan`. The script runs and Plan returns a lifeboat with a nil error. For the tag sink, point `refs/tags/v1.1.0` at a deleted commit, set `tag.sort=taggerdate` and call `GitExistingTags`: the script runs and the call returns exit 128. `--sort=refname` and `-c tag.sort=refname` do not stop it; `GIT_NO_LAZY_FETCH=1` does. diff --git a/.abcd/work/issues/resolved/iss-2610090821531394-read-only-git-log-runs-repo-gpg-program.md b/.abcd/work/issues/resolved/iss-2610090821531394-read-only-git-log-runs-repo-gpg-program.md index 4e06922e3..1be67451f 100644 --- a/.abcd/work/issues/resolved/iss-2610090821531394-read-only-git-log-runs-repo-gpg-program.md +++ b/.abcd/work/issues/resolved/iss-2610090821531394-read-only-git-log-runs-repo-gpg-program.md @@ -5,7 +5,7 @@ slug: "read-only-git-log-runs-repo-gpg-program" severity: "major" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-9fp7-gmjr-v28j, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/gitutil/repo.go" @@ -16,6 +16,8 @@ impact: fix abcd's read-only `git log` and `git show` calls do not pin `log.showSignature=false`, so a checkout with `log.showSignature=true`, a repo `gpg.program` and one `gpgsig` commit runs that program as the operator during probes such as `abcd disembark probe`. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-9fp7-gmjr-v28j (draft, severity high). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): the comment at internal/gitutil/repo.go:22 names log as a command the hooks and fsmonitor pins make safe; `isolatedArgs` (internal/gitutil/repo.go:52) does not set log.showSignature. Sinks: `gitReverts` asks for `log --format=%s` (internal/core/lifeboat/sources_git.go:35) under `abcd disembark probe`; `LoadHistory` runs `git log --reverse --name-status` (internal/core/site/dates.go:63); `walkMentionCommits` (internal/core/capture/mentions.go:360); the decisions-append subject read (internal/core/lint/decisionsappend.go:308). A script that exits 0 (and in a replay one that exits 1) leaves the call returning the subject and a nil error. The directory must be a copy or zip including `.git`; a clone does not carry it. \ No newline at end of file +Evidence (lines at main 7549ca2d5): the comment at internal/gitutil/repo.go:22 names log as a command the hooks and fsmonitor pins make safe; `isolatedArgs` (internal/gitutil/repo.go:52) does not set log.showSignature. Sinks: `gitReverts` asks for `log --format=%s` (internal/core/lifeboat/sources_git.go:35) under `abcd disembark probe`; `LoadHistory` runs `git log --reverse --name-status` (internal/core/site/dates.go:63); `walkMentionCommits` (internal/core/capture/mentions.go:360); the decisions-append subject read (internal/core/lint/decisionsappend.go:308). A script that exits 0 (and in a replay one that exits 1) leaves the call returning the subject and a nil error. The directory must be a copy or zip including `.git`; a clone does not carry it. + +Reproduction: on git 2.39.5, put a mode-0755 script in the copied checkout, set `gpg.program` to it and `log.showSignature=true`, and point HEAD at a commit object carrying a `gpgsig` header. From another cwd run `abcd disembark probe` on it: the script runs and probe returns a report and nil. With unsigned history the script does not run. `rev-list`, `status`, `diff --name-only` and `cat-file -p` do not run it; `show --format=%s --no-patch` of the signed commit does. diff --git a/.abcd/work/issues/resolved/iss-2610090821531570-record-lint-agent-diff-runs-repo-diff-drivers.md b/.abcd/work/issues/resolved/iss-2610090821531570-record-lint-agent-diff-runs-repo-diff-drivers.md index 64aa0e179..964ed896c 100644 --- a/.abcd/work/issues/resolved/iss-2610090821531570-record-lint-agent-diff-runs-repo-diff-drivers.md +++ b/.abcd/work/issues/resolved/iss-2610090821531570-record-lint-agent-diff-runs-repo-diff-drivers.md @@ -5,7 +5,7 @@ slug: "record-lint-agent-diff-runs-repo-diff-drivers" severity: "minor" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-q333-3p8r-xc2f, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/core/lint/agentcontract.go" @@ -16,6 +16,8 @@ impact: fix record-lint's armed agent-diff check runs `git diff --unified=0` without `--no-ext-diff --no-textconv`, so a repository `diff.external`, `diff..textconv` or `diff..command` runs as the operator and its stdout can forge the `prompt_version` bump the check looks for. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-q333-3p8r-xc2f (draft, severity medium). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `promptVersionChanged` runs `gitutil.Run(repoRoot, "diff", "--unified=0", rangeSpec, "--", rel)` (internal/core/lint/agentcontract.go:388-389) and returns true on any added line prefixed `+prompt_version:`. The range is armed only by `record-lint -agent-diff` (cmd/record-lint/main.go:124, `lint.ArmAgentDiff`) or the Makefile record-lint target (`origin/main...HEAD`); default `abcd lint` does not arm it. The neighbouring decisions-append diff already passes `--no-ext-diff` and `--no-textconv` (internal/core/lint/decisionsappend.go:396, rationale at :378). A CI checkout of a pull request does not install the author's `.git/config`; a local run does. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `promptVersionChanged` runs `gitutil.Run(repoRoot, "diff", "--unified=0", rangeSpec, "--", rel)` (internal/core/lint/agentcontract.go:388-389) and returns true on any added line prefixed `+prompt_version:`. The range is armed only by `record-lint -agent-diff` (cmd/record-lint/main.go:124, `lint.ArmAgentDiff`) or the Makefile record-lint target (`origin/main...HEAD`); default `abcd lint` does not arm it. The neighbouring decisions-append diff already passes `--no-ext-diff` and `--no-textconv` (internal/core/lint/decisionsappend.go:396, rationale at :378). A CI checkout of a pull request does not install the author's `.git/config`; a local run does. + +Reproduction: on git 2.39.5, in a local checkout, plant `refs/remotes/origin/main`, change a prompt path, set `diff.external` to a mode-0755 script, and run `go run ./cmd/record-lint -agent-diff origin/main...HEAD`. The script runs; if it prints `+prompt_version: 9.9.9` the unbumped-prompt finding is absent and `lint.Lint` returns nil. `* diff=evil` with `diff.evil.textconv` or `diff.evil.command` runs too. diff --git a/.abcd/work/issues/resolved/iss-2610090821543020-core-worktree-ancestor-becomes-store-root.md b/.abcd/work/issues/resolved/iss-2610090821543020-core-worktree-ancestor-becomes-store-root.md index fa1ad3b80..3e8d9e208 100644 --- a/.abcd/work/issues/resolved/iss-2610090821543020-core-worktree-ancestor-becomes-store-root.md +++ b/.abcd/work/issues/resolved/iss-2610090821543020-core-worktree-ancestor-becomes-store-root.md @@ -5,7 +5,7 @@ slug: "core-worktree-ancestor-becomes-store-root" severity: "minor" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-6wfm-pfhv-w267, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/gitutil/repo.go" @@ -16,6 +16,8 @@ impact: fix A copied checkout whose `.git/config` sets `core.worktree=../..` makes abcd take an ancestor directory as the repository root, so `abcd decide`, intent, spec, capture and memory writes land under that ancestor instead of the checkout. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-6wfm-pfhv-w267 (draft, severity medium). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `ToplevelContext` runs `rev-parse --show-toplevel` (internal/gitutil/repo.go:565) and `ToplevelShaped` accepts any absolute answer that `PathWithin` says contains the asked directory (internal/gitutil/repo.go:599, internal/fsutil/paths.go:125). `CheckoutRoot` (internal/gitutil/repo.go:369) feeds `decideStoreRoot` (internal/surface/cli/decide.go:91) and the intent, spec, memory and capture-ledger roots. internal/core/site/outdir.go:173 names `core.worktree` as a decoy; `site.Build` and `launch.PrecheckPayload` refuse the parent. The written bodies are abcd's own records, and no program runs. `git clone` drops the key; the config has to arrive in a copied `.git`. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `ToplevelContext` runs `rev-parse --show-toplevel` (internal/gitutil/repo.go:565) and `ToplevelShaped` accepts any absolute answer that `PathWithin` says contains the asked directory (internal/gitutil/repo.go:599, internal/fsutil/paths.go:125). `CheckoutRoot` (internal/gitutil/repo.go:369) feeds `decideStoreRoot` (internal/surface/cli/decide.go:91) and the intent, spec, memory and capture-ledger roots. internal/core/site/outdir.go:173 names `core.worktree` as a decoy; `site.Build` and `launch.PrecheckPayload` refuse the parent. The written bodies are abcd's own records, and no program runs. `git clone` drops the key; the config has to arrive in a copied `.git`. + +Reproduction: on git 2.39.5, set `core.worktree=../..` in `/evil/.git/config`; from `evil` run `abcd decide ""`. The ADR is created under `/.abcd/development/decisions/adrs/`, not under `evil`; the same parent receives a spec, an intent, a capture issue and the memory pages. A sibling worktree that does not contain the checkout is refused with ErrToplevelShape. diff --git a/.abcd/work/issues/resolved/iss-2610090821548169-launch-dirty-check-runs-repo-clean-filter.md b/.abcd/work/issues/resolved/iss-2610090821548169-launch-dirty-check-runs-repo-clean-filter.md index 4b71c1f0e..192d0e0ad 100644 --- a/.abcd/work/issues/resolved/iss-2610090821548169-launch-dirty-check-runs-repo-clean-filter.md +++ b/.abcd/work/issues/resolved/iss-2610090821548169-launch-dirty-check-runs-repo-clean-filter.md @@ -5,7 +5,7 @@ slug: "launch-dirty-check-runs-repo-clean-filter" severity: "major" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-qv3j-fq4f-g5xr, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/core/launch/gates.go" @@ -16,6 +16,8 @@ impact: fix The launch dirty-tree check (`git diff ... HEAD` in `DirtyTreeFiles`) runs a repository `filter..clean` program as the operator on a copied checkout, and the gate can still report the tree clean. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-qv3j-fq4f-g5xr (draft, severity high). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `DirtyTreeFiles` (internal/core/launch/gates.go:920) runs `diff --no-renames --name-only -z HEAD` through `gitutil.Run` (internal/core/launch/gates.go:929), whose `isolatedArgs` pins only hooksPath, fsmonitor and quotePath (internal/gitutil/repo.go:52). A copied checkout loses the index stat, so git re-hashes the worktree through the clean filter. `dirtyTreeGate` reports `clean` on an empty list (internal/core/launch/gates.go:950); `abcd launch --dry-run` reaches it once `.abcd/config/artefact.json` loads (`{"kind":"binary"}` suffices), and the ship ingest reaches it when it stages a payload, `--allow-dirty` still running the diff. Siblings of the same command class: `DirtyPayloadFiles` (internal/core/launch/archive.go:490) and `dirtyCorpusPaths` (internal/core/intent/consistency.go:250). `--no-ext-diff` and `--no-textconv` do not stop a clean filter, and blanking only `.clean` leaves `.process`. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `DirtyTreeFiles` (internal/core/launch/gates.go:920) runs `diff --no-renames --name-only -z HEAD` through `gitutil.Run` (internal/core/launch/gates.go:929), whose `isolatedArgs` pins only hooksPath, fsmonitor and quotePath (internal/gitutil/repo.go:52). A copied checkout loses the index stat, so git re-hashes the worktree through the clean filter. `dirtyTreeGate` reports `clean` on an empty list (internal/core/launch/gates.go:950); `abcd launch --dry-run` reaches it once `.abcd/config/artefact.json` loads (`{"kind":"binary"}` suffices), and the ship ingest reaches it when it stages a payload, `--allow-dirty` still running the diff. Siblings of the same command class: `DirtyPayloadFiles` (internal/core/launch/archive.go:490) and `dirtyCorpusPaths` (internal/core/intent/consistency.go:250). `--no-ext-diff` and `--no-textconv` do not stop a clean filter, and blanking only `.clean` leaves `.process`. + +Reproduction: on git 2.39.5, in a repo with `.abcd/config/artefact.json` = `{"kind":"binary"}`, point `filter.evil.clean` at a mode-0755 script that appends a mark and cats stdin, set `.git/info/attributes` to `* filter=evil`, copy the directory, and run `abcd launch --dry-run` in the copy from another cwd. The mark is written and the dirty-tree row says clean. Do not switch to `git status`: on a copied tree `git status --porcelain` also runs the filter. diff --git a/.abcd/work/issues/resolved/iss-2610090821552801-implement-land-stage-trusts-planted-worktree-path.md b/.abcd/work/issues/resolved/iss-2610090821552801-implement-land-stage-trusts-planted-worktree-path.md index ba9df1389..96e572ad7 100644 --- a/.abcd/work/issues/resolved/iss-2610090821552801-implement-land-stage-trusts-planted-worktree-path.md +++ b/.abcd/work/issues/resolved/iss-2610090821552801-implement-land-stage-trusts-planted-worktree-path.md @@ -5,7 +5,7 @@ slug: "implement-land-stage-trusts-planted-worktree-path" severity: "minor" category: "security" source: "agent-finding" -found_during: "private security report, filed 2026-10-05" +found_during: "private security advisory GHSA-qfpj-v8c5-c4fw, filed 2026-10-05" origin: researcher-authored production_mode: hand-written found_at: "internal/core/implement/loop/land.go" @@ -16,6 +16,8 @@ impact: fix The implement loop's land stage accepts any non-empty worktree path from the run state file, so a planted state makes `abcd implement step` create `/.abcd/.work.local` (mode 0700) in a directory the state names. -A private security report, fixed in this release; its advisory, with the full text and reproduction, is published with the release. +Private security advisory GHSA-qfpj-v8c5-c4fw (draft, severity low). Full text, evidence and reproduction: the security-drain-2026-10-09 run directory in the main checkout's local tier. This record stays uncommitted until its fix lands; the fix commit adds it directly to resolved/. -Evidence (lines at main 7549ca2d5): `briefStage` refuses a worktree that is not the path `laneWorktree` derives (internal/core/implement/loop/brief.go:104-105). `landStage` only checks the string is non-empty and skips `syncLane` once a landing is recorded (internal/core/implement/loop/land.go:131, :139). `landRecords` uses that worktree when `landing.closes` is true (internal/core/implement/loop/land.go:237) and calls `fsutil.EnsureRealDirAll` there (internal/core/implement/loop/land.go:269). The state file is gitignored, so it arrives in a zip, not a clone. The result is an empty 0700 directory tree; no file body is written and no hook in the other repository runs. \ No newline at end of file +Evidence (lines at main 7549ca2d5): `briefStage` refuses a worktree that is not the path `laneWorktree` derives (internal/core/implement/loop/brief.go:104-105). `landStage` only checks the string is non-empty and skips `syncLane` once a landing is recorded (internal/core/implement/loop/land.go:131, :139). `landRecords` uses that worktree when `landing.closes` is true (internal/core/implement/loop/land.go:237) and calls `fsutil.EnsureRealDirAll` there (internal/core/implement/loop/land.go:269). The state file is gitignored, so it arrives in a zip, not a clone. The result is an empty 0700 directory tree; no file body is written and no hook in the other repository runs. + +Reproduction: on git 2.39.5, hand-write the state under `.abcd/.work.local/run/` for a run whose one lane is at stage land, with full base and head SHAs, a real branch at that tip, `landing.closes` true and `worktree` a relative path to another existing git repository; run `abcd implement step`. `/.abcd/.work.local` exists mode 0700, then `intent.Reconcile` fails on the absent spec. A missing branch or a missing landing record returns before the mkdir. diff --git a/.abcd/work/issues/resolved/iss-2610090925390900-guard-allows-shellopts-ps4-substitution.md b/.abcd/work/issues/resolved/iss-2610090925390900-guard-allows-shellopts-ps4-substitution.md index 7ea15a497..13a2b60c9 100644 --- a/.abcd/work/issues/resolved/iss-2610090925390900-guard-allows-shellopts-ps4-substitution.md +++ b/.abcd/work/issues/resolved/iss-2610090925390900-guard-allows-shellopts-ps4-substitution.md @@ -14,4 +14,4 @@ resolution: "the guard judges the text a line hands bash through a prompt variab impact: fix --- -abcd guard allows a bash -c whose environment prefix sets SHELLOPTS=xtrace and a PS4 holding a command substitution: bash expands PS4 before tracing the first command, so a blocker in PS4 runs although the -c payload is harmless. Sibling of the BASH_ENV finding iss-2610090821484829, found in the security-drain-2026-10-09 sweep; kept uncommitted until its fix lands. +abcd guard allows a bash -c whose environment prefix sets SHELLOPTS=xtrace and a PS4 holding a command substitution: bash expands PS4 before tracing the first command, so a blocker in PS4 runs although the -c payload is harmless. Reproduced at main 016c1510a: `SHELLOPTS=xtrace PS4='$(pkill node)' bash -c true` (and the `env` form) judges allow while `pkill node` blocks. Sibling of the BASH_ENV finding iss-2610090821484829, found in the security-drain-2026-10-09 sweep; kept uncommitted until its fix lands. diff --git a/.abcd/work/issues/resolved/iss-2610090925399967-guard-allows-exported-bash-function-env.md b/.abcd/work/issues/resolved/iss-2610090925399967-guard-allows-exported-bash-function-env.md index dfc455dfd..c309d38db 100644 --- a/.abcd/work/issues/resolved/iss-2610090925399967-guard-allows-exported-bash-function-env.md +++ b/.abcd/work/issues/resolved/iss-2610090925399967-guard-allows-exported-bash-function-env.md @@ -15,4 +15,4 @@ resolution: "env and sudo now step every '=' operand before the command as an as impact: fix --- -abcd guard allows a bash -c whose environment carries an exported function (a BASH_FUNC_%% variable, through env): bash imports it at startup, so a function named like the -c command runs a blocker in its body although the -c payload is harmless. Sibling of the BASH_ENV finding iss-2610090821484829, found in the security-drain-2026-10-09 sweep; kept uncommitted until its fix lands. +abcd guard allows a bash -c whose environment carries an exported function (a BASH_FUNC_%% variable, through env): bash imports it at startup, so a function named like the -c command runs a blocker in its body although the -c payload is harmless. Reproduced at main 016c1510a: `env 'BASH_FUNC_true%%=() { pkill node; }' bash -c true` judges allow while `pkill node` blocks. Sibling of the BASH_ENV finding iss-2610090821484829, found in the security-drain-2026-10-09 sweep; kept uncommitted until its fix lands. diff --git a/.abcd/work/issues/resolved/iss-2610090932257904-guard-allows-shell-stdin-redirect-script.md b/.abcd/work/issues/resolved/iss-2610090932257904-guard-allows-shell-stdin-redirect-script.md index 4fbf5e65c..d8108866f 100644 --- a/.abcd/work/issues/resolved/iss-2610090932257904-guard-allows-shell-stdin-redirect-script.md +++ b/.abcd/work/issues/resolved/iss-2610090932257904-guard-allows-shell-stdin-redirect-script.md @@ -14,4 +14,4 @@ resolution: "A shell with no -c string and no script operand that a redirection impact: fix --- -abcd guard allows a shell that reads its script from a file through a stdin redirect (`bash < f`, `bash -s < f`), while the pipe form `cat f | bash` blocks under interpreter-reads-stream: stdinStream (internal/core/guard/tokenize.go:49-54) is set for a pipe, a here-document, a here-string and a pipe into a group, never for a < redirect, so shellReadsStream (payload.go:1871-1874) never sees the file as the script. Sibling of iss-2610090821484829, found in the security-drain-2026-10-09 sweep; kept uncommitted until its fix lands. +abcd guard allows a shell that reads its script from a file through a stdin redirect (`bash < f`, `bash -s < f`), while the pipe form `cat f | bash` blocks under interpreter-reads-stream: stdinStream (internal/core/guard/tokenize.go:49-54) is set for a pipe, a here-document, a here-string and a pipe into a group, never for a < redirect, so shellReadsStream (payload.go:1871-1874) never sees the file as the script. Reproduced at main 016c1510a. Sibling of iss-2610090821484829, found in the security-drain-2026-10-09 sweep; kept uncommitted until its fix lands.