From d0cd0fb16ef8cc9ab25710c521833733dae291ea Mon Sep 17 00:00:00 2001 From: "allyblockcast[bot]" <290875700+allyblockcast[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:29:37 +0000 Subject: [PATCH 1/6] fix(security): correct false claim that this guard mirrors claude_k8s adapter protection sensitive-env-guard.ts asserted its allowlist "mirrors the same protection in the external claude_k8s adapter (job-manifest.ts)". No such protection exists at pinned SHA 3ad3370: getSelfPodInfo() copies the paperclip server's entire pod env onto every agent-job pod with no allowlist, denylist, or filter of any kind (BLO-22514). This guard only covers manifests built by this repo's Kubernetes sandbox-provider path and provides zero coverage for the adapter that actually renders production agent pods. Refs BLO-22514, BLO-22506. --- .../kubernetes/src/sensitive-env-guard.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts index 145e41f065e9..9661321a6189 100644 --- a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts +++ b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts @@ -7,9 +7,18 @@ // // This guard is fail-closed and runs on every manifest we build, so a future // literal credential breaks the build and the test suite rather than silently -// shipping. It mirrors the same protection in the external claude_k8s adapter -// (paperclip-adapter-claude-k8s `job-manifest.ts`), which renders the -// production agent-job pods. +// shipping. +// +// It does NOT mirror any protection in the external claude_k8s adapter. As of +// pinned SHA 3ad3370, `paperclip-adapter-claude-k8s`'s `job-manifest.ts` (the +// code that actually renders production agent-job pods) has no allowlist, +// denylist, or filter of any kind: `getSelfPodInfo()` copies the paperclip +// server's own pod env wholesale — every literal `env[].value`, every +// `valueFrom.secretKeyRef`, every `envFrom` source, and every mounted secret +// volume — onto each agent pod. This guard only covers manifests built by +// this repo's Kubernetes sandbox-provider path; it provides zero coverage for +// that adapter. See BLO-22514 for the gap and BLO-22506 for the related +// k8s-ro read exposure. // // POLICY: allowlist, not denylist. // @@ -229,4 +238,4 @@ export function assertManifestHasNoLiteralSensitiveEnv( for (const podSpec of collectPodSpecs(manifest)) { assertNoLiteralSensitiveEnv(podSpec, manifestDescription); } -} +} \ No newline at end of file From 68846c6df8fc4499187fab4f3a82a2013447210a Mon Sep 17 00:00:00 2001 From: "allyblockcast[bot]" Date: Thu, 6 Aug 2026 17:39:25 +0000 Subject: [PATCH 2/6] fix: restore trailing newline and name the correct adapter source file The prior commit dropped the file's trailing newline, which every sibling .ts file in this directory has, and which contradicted this PR's own claim of a comment-only change. Also tightens the replacement comment: getSelfPodInfo() lives in k8s-client.ts, not job-manifest.ts. The original wording attributed it to job-manifest.ts by juxtaposition. Both files are on the unfiltered path -- k8s-client.ts snapshots the server pod, job-manifest.ts replays all four inherited channels -- so name each for what it does. In a change whose entire purpose is correcting an inaccurate comment, that precision matters. Re-verified against the on-disk adapter at the pinned SHA (CLAUDE_K8S_REF=3ad33702..., Dockerfile:179). Co-Authored-By: Claude --- .../kubernetes/src/sensitive-env-guard.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts index 9661321a6189..18da4c02ef54 100644 --- a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts +++ b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts @@ -9,16 +9,19 @@ // literal credential breaks the build and the test suite rather than silently // shipping. // -// It does NOT mirror any protection in the external claude_k8s adapter. As of -// pinned SHA 3ad3370, `paperclip-adapter-claude-k8s`'s `job-manifest.ts` (the -// code that actually renders production agent-job pods) has no allowlist, -// denylist, or filter of any kind: `getSelfPodInfo()` copies the paperclip -// server's own pod env wholesale — every literal `env[].value`, every -// `valueFrom.secretKeyRef`, every `envFrom` source, and every mounted secret -// volume — onto each agent pod. This guard only covers manifests built by -// this repo's Kubernetes sandbox-provider path; it provides zero coverage for -// that adapter. See BLO-22514 for the gap and BLO-22506 for the related -// k8s-ro read exposure. +// It does NOT mirror any protection in the external claude_k8s adapter, which +// is the code that actually renders production agent-job pods. As of the SHA +// pinned by `Dockerfile` (`CLAUDE_K8S_REF=3ad3370`), that adapter applies no +// allowlist, denylist, or filter of any kind on the pod-env path: +// `getSelfPodInfo()` (`k8s-client.ts`) snapshots the paperclip server's own pod +// wholesale, and `job-manifest.ts` replays every channel of it onto each agent +// pod — literal `env[].value`s, `valueFrom.secretKeyRef`s, `envFrom` sources, +// and mounted secret volumes alike. The only entries dropped are empty-string +// values and unnamed ones. +// +// So this guard covers only manifests built by this repo's Kubernetes +// sandbox-provider path, and provides zero coverage for that adapter. See +// BLO-22514 for the gap and BLO-22506 for the related k8s-ro read exposure. // // POLICY: allowlist, not denylist. // @@ -238,4 +241,4 @@ export function assertManifestHasNoLiteralSensitiveEnv( for (const podSpec of collectPodSpecs(manifest)) { assertNoLiteralSensitiveEnv(podSpec, manifestDescription); } -} \ No newline at end of file +} From e180a8b6a8906aa5c1ed521ded4215d5b52cc032 Mon Sep 17 00:00:00 2001 From: "allyblockcast[bot]" Date: Thu, 6 Aug 2026 17:51:15 +0000 Subject: [PATCH 3/6] fix: describe getSelfPodInfo's actual extraction scope, not 'wholesale' Ally review on #1091 caught a residual overstatement in the replacement comment: getSelfPodInfo() does not snapshot the server pod wholesale. At the pinned CLAUDE_K8S_REF=3ad3370 it selects one container (name 'paperclip', else the first) and extracts a bounded field set. Since this PR exists to remove load-bearing misinformation from a security comment, an imprecise replacement defeats its purpose. Reworded to state exactly what is extracted and replayed, with line anchors verified against the pinned SHA: k8s-client.ts:111-197 container select + channel extraction job-manifest.ts:491 literal env[].value spread job-manifest.ts:562 valueFrom replay job-manifest.ts:1144 envFrom replay job-manifest.ts:879 secret-volume replay The security claim is unchanged and still holds: no allowlist, denylist, or filter of any kind sits on that path. The only valueFrom skip is a name-collision precedence rule, not a security filter. Comment-only; no runtime behavior change. Co-Authored-By: Claude --- .../kubernetes/src/sensitive-env-guard.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts index 18da4c02ef54..62ab2819ad80 100644 --- a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts +++ b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts @@ -12,12 +12,14 @@ // It does NOT mirror any protection in the external claude_k8s adapter, which // is the code that actually renders production agent-job pods. As of the SHA // pinned by `Dockerfile` (`CLAUDE_K8S_REF=3ad3370`), that adapter applies no -// allowlist, denylist, or filter of any kind on the pod-env path: -// `getSelfPodInfo()` (`k8s-client.ts`) snapshots the paperclip server's own pod -// wholesale, and `job-manifest.ts` replays every channel of it onto each agent -// pod — literal `env[].value`s, `valueFrom.secretKeyRef`s, `envFrom` sources, -// and mounted secret volumes alike. The only entries dropped are empty-string -// values and unnamed ones. +// allowlist, denylist, or filter of any kind on the pod-env path. +// `getSelfPodInfo()` (`k8s-client.ts:111-197`) selects one container off the +// server pod — the one named `paperclip`, else the first — and extracts that +// container's credential-bearing channels unfiltered: literal `env[].value`s, +// `valueFrom` entries (`secretKeyRef` included), `envFrom` sources, and the +// secret volumes mounted on it. `job-manifest.ts` then replays all four onto +// every agent pod (`:491`, `:562`, `:1144`, `:879`). The only entries dropped +// are empty-string values and unnamed ones. // // So this guard covers only manifests built by this repo's Kubernetes // sandbox-provider path, and provides zero coverage for that adapter. See From bcf7d1412156d9ee21bbf269c5ca2c3694a1aea3 Mon Sep 17 00:00:00 2001 From: "allyblockcast[bot]" Date: Thu, 6 Aug 2026 18:00:25 +0000 Subject: [PATCH 4/6] fix: scope the 'only dropped entries' claim to extraction, not replay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sentence sat after the job-manifest replay sentence, so it read as covering both stages. The replay does skip one class of entry (:563, valueFrom shadowed by a same-name literal) — name precedence, not a security filter. Verified against CLAUDE_K8S_REF=3ad3370; the adapter's own comment at job-manifest.ts:559-560 states the precedence rule. Co-Authored-By: Claude --- .../sandbox-providers/kubernetes/src/sensitive-env-guard.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts index 62ab2819ad80..e4ef8c2cbdea 100644 --- a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts +++ b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts @@ -18,8 +18,9 @@ // container's credential-bearing channels unfiltered: literal `env[].value`s, // `valueFrom` entries (`secretKeyRef` included), `envFrom` sources, and the // secret volumes mounted on it. `job-manifest.ts` then replays all four onto -// every agent pod (`:491`, `:562`, `:1144`, `:879`). The only entries dropped -// are empty-string values and unnamed ones. +// every agent pod (`:491`, `:562`, `:1144`, `:879`). Extraction drops only +// unnamed and empty-string entries; the replay's one skip (`:563`) fires when a +// literal of the same name already won, which is name precedence, not a filter. // // So this guard covers only manifests built by this repo's Kubernetes // sandbox-provider path, and provides zero coverage for that adapter. See From ff535a4ea2e8171c4612b29ebc6cf2b13d0fa988 Mon Sep 17 00:00:00 2001 From: "CTO (Paperclip agent)" Date: Thu, 6 Aug 2026 18:31:23 +0000 Subject: [PATCH 5/6] fix(security): drop exhaustive-replay claim from sensitive-env-guard comment Ally review at bcf7d141 correctly flagged that the comment overstated the pinned adapter's mechanics. It read as an exhaustive account of every point an inherited entry can be dropped or replaced, naming job-manifest.ts:563 as "the replay's one skip". It is not: at 3ad3370, job-manifest.ts:490-550 merges generated Paperclip values, adapterConfig.env overrides, ANTHROPIC_CUSTOM_HEADERS, HOME, and isolation/cache paths *after* selfPod.inheritedEnv, so same-name inherited literals are also overwritten or extended before the manifest renders. Rewritten to describe the adapter as forwarding the four channels with no security policy, subject to ordinary environment precedence, and to enumerate the drop/replace points as precedence and emptiness checks rather than filters. Also names env-guard.ts explicitly: the adapter *does* ship an env guard, but it is a Claude Code PreToolUse hook blocking shell env dumps at runtime and never inspects a manifest. Without that clause a reader who greps the adapter finds env-guard.ts and reasonably concludes this comment is wrong. Verified against the exact pinned SHA (CLAUDE_K8S_REF=3ad3370), not #1092's vendored copy, whose cherry-picks shift every line number cited here. Re-confirmed unchanged: k8s-client.ts:111-197 container selection and the :167/:173 extraction skips; the :491/:562/:1144/:879 forward sites. Comment-only; no runtime, type, or behavior change. Co-Authored-By: Claude --- .../kubernetes/src/sensitive-env-guard.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts index e4ef8c2cbdea..012e8b800043 100644 --- a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts +++ b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts @@ -15,12 +15,26 @@ // allowlist, denylist, or filter of any kind on the pod-env path. // `getSelfPodInfo()` (`k8s-client.ts:111-197`) selects one container off the // server pod — the one named `paperclip`, else the first — and extracts that -// container's credential-bearing channels unfiltered: literal `env[].value`s, -// `valueFrom` entries (`secretKeyRef` included), `envFrom` sources, and the -// secret volumes mounted on it. `job-manifest.ts` then replays all four onto -// every agent pod (`:491`, `:562`, `:1144`, `:879`). Extraction drops only -// unnamed and empty-string entries; the replay's one skip (`:563`) fires when a -// literal of the same name already won, which is name precedence, not a filter. +// container's credential-bearing channels: literal `env[].value`s, `valueFrom` +// entries (`secretKeyRef` included), `envFrom` sources, and the secret volumes +// mounted on it. `job-manifest.ts` forwards all four onto every agent pod +// (`:491`, `:562`, `:1144`, `:879`), subject only to ordinary environment +// precedence and never to a security decision. +// +// Entries are dropped or replaced at several points along that path, so do not +// read any single one as the only such point: extraction skips unnamed and +// empty-string values (`k8s-client.ts:167`, `:173`); the merge layers that run +// after the inherited env overwrite or extend same-name inherited literals +// (`job-manifest.ts:490-550` — generated Paperclip values, `adapterConfig.env` +// overrides, generated `ANTHROPIC_CUSTOM_HEADERS`, `HOME`, and isolation and +// cache paths); and the `valueFrom` replay skips names a literal already set +// (`:563`). Every one of those is name precedence or an emptiness check, so +// none of them withholds a credential on the grounds that it is sensitive. +// +// The adapter's only env-related guard, `env-guard.ts`, is a Claude Code +// `PreToolUse` hook blocking shell environment dumps (`env`, `printenv`, +// `/proc/*/environ`) at agent runtime. It never inspects a manifest, so it does +// not constrain anything described above. // // So this guard covers only manifests built by this repo's Kubernetes // sandbox-provider path, and provides zero coverage for that adapter. See From a5583c27bc8df1600ca20868a6f94639262234de Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 18:47:55 +0000 Subject: [PATCH 6/6] fix: narrow 'filter of any kind' to security-filter scope Ally flagged that "no allowlist, denylist, or filter of any kind on the pod-env path" contradicts this same comment's own lines 24-32, which document real extraction/precedence drops (unnamed and empty-string skips, same-name literal overwrite, valueFrom shadowing). Those are filters, just not security ones. Narrowed to "no security allowlist, denylist, or sensitivity-based filter", which preserves the actual security finding (nothing on this path makes a decision based on a value being sensitive) without overstating it into a claim the rest of the comment disproves. Co-Authored-By: Claude --- .../sandbox-providers/kubernetes/src/sensitive-env-guard.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts index 012e8b800043..88b999957de0 100644 --- a/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts +++ b/packages/plugins/sandbox-providers/kubernetes/src/sensitive-env-guard.ts @@ -12,7 +12,8 @@ // It does NOT mirror any protection in the external claude_k8s adapter, which // is the code that actually renders production agent-job pods. As of the SHA // pinned by `Dockerfile` (`CLAUDE_K8S_REF=3ad3370`), that adapter applies no -// allowlist, denylist, or filter of any kind on the pod-env path. +// security allowlist, denylist, or sensitivity-based filter on the pod-env +// path. // `getSelfPodInfo()` (`k8s-client.ts:111-197`) selects one container off the // server pod — the one named `paperclip`, else the first — and extracts that // container's credential-bearing channels: literal `env[].value`s, `valueFrom`