Skip to content

fix(security): mirror credential shapes into the PostToolUse hook, scoped to checkout content - #7009

Open
dhshah13 wants to merge 3 commits into
fullsend-ai:mainfrom
dhshah13:fix/posttool-hook-credential-shapes
Open

fix(security): mirror credential shapes into the PostToolUse hook, scoped to checkout content#7009
dhshah13 wants to merge 3 commits into
fullsend-ai:mainfrom
dhshah13:fix/posttool-hook-credential-shapes

Conversation

@dhshah13

@dhshah13 dhshah13 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Split out of #6603 at review request (waynesun09): this changes what the agent's PostToolUse hook does on every file-content tool call, which is a sandbox-side hardening change rather than a telemetry one, and it deserves its own review of the parity argument.

What this does

Part Change
Hook patterns (internal/security/hooks/secret_redact_posttool.py) Mirrors the two credential shapes the Go redactor gained in #6603 into the PostToolUse hook — google_oauth_token (ya29., with the literal c. alternative for service-account tokens) and a bare three-segment jwt — plus github_server_token (ghs_… with dots, ahead of the combined gh*_ pattern, so a JWT-wrapped installation token masks whole instead of stopping at the first dot). Both boundaries — span content and the model's context window — carry the same shapes.
Checkout-scoped skip (content_skips) A bare JWT has no fixture-shaped escape: a jwt.io-style example in a test file is byte-for-byte a valid token, and rewriting it in a Read corrupts what the agent edits against (the failure mode 63a0a7d scoped the structural patterns to avoid). So the jwt pattern is skipped when a file-content tool (Read, Grep, Edit, MultiEdit, Write, NotebookEdit, NotebookRead) is called with a path inside the checkout — the nearest .git ancestor of the hook's cwd, searched only strictly below the sandbox workspace so a .git an agent plants at or above it can never widen the root to the runner's files; none meaning no skip; the path is normalized, then resolved; .. segments, ~ paths, @-prefixed paths and URL forms (pi strips the @, expands ~ and converts file:// before opening while its adapter forwards the raw argument; any scheme is refused), malformed input and no cwd all mean mask. Claude Code sends its working directory; the pi adapter sends its process working directory — the checkout, which child shells cannot move. The runner's own OIDC token file sits beside the checkout, not in it, and still masks on Read or Grep. Bash, WebFetch and MCP output are unaffected.
Boundary source (internal/security/hooks/posttool_chain.py, tests) The boundary is the runner's constant /sandbox/workspace. The hook takes an override only from its own command line (--sandbox-workspace=PATH, the seam the subprocess tests use), which the runner-owned hooks.json and the pi adapter build without it. It is not read from environment variables: under fullsend's plain launch, Claude Code (observed locally on 2.1.235; the sandbox image pins 2.1.260) applies a checkout's .claude/settings.json env block to hook processes over the launch environment, so an env-carried boundary could be moved by a committed settings file. Not covered, and pre-existing: under claude and pi the hook interpreter is a bare python3 resolved through a PATH with the agent-writable workspace bin first, with PYTHONPATH and the user site directory honoured (codex isolates its adapter); drafted as a follow-up.
Contract docs docs/contributing/runtime-implementation.md: the wire protocol gains cwd (Claude Code's working directory; pi's process working directory), the sanitizer-scope paragraph states the root is searched strictly below /sandbox/workspace and how the boundary is pinned, and the adapter-contract line says pi sends cwd; the pi adapter's header comment says the same.

Known residual: the boundary is the workspace, not the checkout, so a .git planted in another runner-owned subdirectory (.env.d, which holds host_files credentials) can still become a root through a plain cd into it or an in-checkout symlink; only a bare JWT that no structural shape covers would skip there. Pinning the checkout path the runner already knows would remove the .git walk entirely.

Measured

  • Redactor false positives (forge comments): 2.1 MB of this repo's recent review and issue comment bodies, zero hits for both patterns.
  • Hook, measured the way 63a0a7d did (files through redact_text): golang-jwt/jwt as Read goes from 15 rewritten files (48 bare-JWT hits in 9) to 8, all structural or private-key — the class that sweep accepted; this repo and fullsend-ai/agents have no tracked file matching the jwt pattern (0/1154, 0/343). Structural masking of token-named assignments is unchanged; the skip covers the context-free pattern only.

Tests

TDD: 26 new hook tests (chain-level Read-untouched / Bash-masked / failure-path flagged; checkout vs outside — token file, .., symlink, symlink then .., Grep root, cd below the root through a symlink, a .git-less directory; a .git planted at the workspace reached through a symlinked cd or directly, a .git planted above the workspace, a checkout outside the workspace; the rewritten path forms @… and file://… for Read and Grep; a symlinked workspace path; malformed input; the skip is jwt-only), a node test that every pi PostToolUse payload carries cwd, across events on one instance, and the boundary ignoring the environment (standalone and through the chain) and honouring only an absolute command-line value (standalone). Every policy branch killed by a named mutant. Full hook suite green.

Raised in review of #6603 (rounds 4 and 6 by waynesun09); #6603 keeps the Go redactor patterns, which its span content needs.

The Go redactor and the sandbox hook guard different boundaries — span
content and the model's context window — but had drifted: the hook's
ya29 pattern missed service-account tokens (one-char c segment defeats
the length floor) and it had no bare-JWT shape at all, while its
combined gh*_ pattern stopped at the first dot of the 2026 JWT-wrapped
installation-token format, leaving payload and signature in the clear.
Mirror the three patterns from the Go side (google_oauth_token with the
c. alternative, jwt, github_server_token with dots in the class) so
both inventories carry the same shapes. Raised by waynesun09's review;
the ghs_ gap surfaced during pre-push verification of the mirror.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
The PostToolUse chain runs on every tool, and the bare-JWT prefix pattern
a3fd5c0 mirrored into it has no fixture-shaped escape: a jwt.io example
in a test file is byte-for-byte a valid token, so a Read of one came back
as `eyJh...` and the agent edited against text that is not on disk — the
failure mode 63a0a7d scoped the structural patterns to avoid.

content_skips(hook_input) returns the pattern names redact_text skips:
`jwt` when a file-content tool (Read, Grep, Edit, MultiEdit, Write,
NotebookEdit, NotebookRead) is called with a path inside the checkout —
the nearest `.git` ancestor of the hook input's `cwd`, which follows the
agent's persisted `cd`; none means no skip. The path is normalized, then
resolved. `..` segments, `~` paths, a non-string tool_name, a missing
tool_input or no `cwd` (pi's adapter sends none) mean mask, not skip, so
the runner's OIDC token file beside the checkout still masks on Read and
Grep, and Bash, WebFetch and MCP output are unchanged. The chain passes
the set on the success and failure paths; the standalone script does the
same.

Same sweep as 63a0a7d, here through redact_text: golang-jwt/jwt as Read
goes from 15 rewritten files (48 bare-JWT hits in 9) to 8, all structural
or private-key; this repo and fullsend-ai/agents have no tracked file
matching the pattern (0/1154, 0/343).

Raised by waynesun09's review.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
@dhshah13
dhshah13 requested a review from a team as a code owner September 4, 2026 14:33
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Harden PostToolUse redaction with checkout-aware JWT handling

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Adds Google OAuth, bare JWT, and complete GitHub installation-token redaction.
• Preserves checkout JWT fixtures while masking tokens from external files and non-file tools.
• Documents cwd scoping and verifies path, symlink, malformed-input, and chain behavior.
Diagram

graph TD
  A["Hook Input"] --> B["PostTool Chain"] --> C["Scope Resolver"] --> D{"Checkout content?"}
  D -- "Yes" --> E["Skip bare JWT"] --> G["Sanitized Output"]
  D -- "No" --> F["Mask all shapes"] --> G
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Pass an explicit checkout root
  • ➕ Avoids discovering repository boundaries through filesystem traversal
  • ➕ Makes checkout provenance an explicit runtime contract
  • ➖ Requires coordinated changes across Claude Code and pi adapters
  • ➖ Expands the trusted protocol surface beyond this focused fix
2. Disable bare-JWT matching for all file tools
  • ➕ Simpler policy with fewer path-resolution edge cases
  • ➕ Always preserves JWT fixtures returned by file tools
  • ➖ Could expose live runner tokens read from files outside the checkout
  • ➖ Loses the fail-safe masking behavior for malformed or missing metadata

Recommendation: Keep the checkout-aware approach: it preserves repository fixtures without weakening masking for runner token files, shell output, or ambiguous inputs. Explicit checkout metadata would be cleaner long-term, but only after every runtime adapter can provide a trusted, consistent value.

Files changed (6) +483 / -16

Bug fix (2) +124 / -13
posttool_chain.pyApply content-specific skips across chain redaction paths +6/-4

Apply content-specific skips across chain redaction paths

• Computes redaction skips from each hook input and applies them to normal, normalized, and failed-call scans. This keeps success and failure behavior consistent for checkout file content.

internal/security/hooks/posttool_chain.py

secret_redact_posttool.pyAdd credential shapes and checkout-aware JWT scoping +118/-9

Add credential shapes and checkout-aware JWT scoping

• Adds service-account Google OAuth, bare JWT, and complete GitHub server-token patterns. Introduces fail-safe checkout detection using tool-specific paths, cwd, normalization, realpath resolution, and nearest '.git' ancestry, then allows named patterns to be skipped during redaction.

internal/security/hooks/secret_redact_posttool.py

Tests (2) +353 / -1
posttool_chain_test.pyVerify checkout JWT behavior through the complete hook chain +68/-1

Verify checkout JWT behavior through the complete hook chain

• Adds chain-level coverage proving checkout JWT fixtures remain unchanged while external token files and Bash failures remain protected. Extends helpers to provide 'cwd' and tolerate the redactor's new keyword argument.

internal/security/hooks/posttool_chain_test.py

secret_redact_posttool_test.pyCover credential parity and JWT scope policy +285/-0

Cover credential parity and JWT scope policy

• Tests the new credential shapes and the complete checkout-scoping policy across tools, relative paths, traversal, symlinks, submodules, missing repositories, malformed input, and JSON-encoded tool input. Confirms only context-free JWT matching is skipped while structural and other credential patterns remain active.

internal/security/hooks/secret_redact_posttool_test.py

Documentation (2) +6 / -2
runtime-implementation.mdDocument cwd-aware PostToolUse sanitizer scope +4/-2

Document cwd-aware PostToolUse sanitizer scope

• Documents the PostToolUse 'cwd' field and the checkout-scoped bare-JWT exception. Clarifies that pi omits 'cwd', so its hook continues masking JWTs without the exception.

docs/contributing/runtime-implementation.md

fullsend-hooks.jsClarify pi adapter JWT-scoping behavior +2/-0

Clarify pi adapter JWT-scoping behavior

• Adds contract commentary explaining that pi does not send 'cwd', leaving checkout-scoped JWT skipping inactive.

internal/runtime/pi_extension/fullsend-hooks.js

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

Site preview

Preview: https://95db0718-site.fullsend-ai.workers.dev

Commit: d5718882b9c835ae02e4b958050f8e6d14380a8b

@qodo-code-review

qodo-code-review Bot commented Sep 4, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Forged checkout bypasses JWT masking ✓ Resolved 🐞 Bug ⛨ Security
Description
_checkout_root treats any lexisting .git entry as a trusted checkout boundary, so an agent can
create /sandbox/workspace/.git, persistently cd to that directory, then Read the sibling runner
OIDC token. The token path is then considered inside the forged root and the new JWT skip exposes
the unmasked credential to the model.
Code

internal/security/hooks/secret_redact_posttool.py[R115-116]

+        if os.path.lexists(os.path.join(probe, ".git")):
+            return probe
Relevance

●●● Strong

Security fail-open findings receive acceptance; forged trust boundaries closely match prior accepted
bypass hardening.

PR-#239
PR-#6536

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new implementation returns the JWT skip whenever the target is within the nearest ancestor
containing any .git entry. The PR documentation explicitly establishes both that cwd tracks
persisted agent directory changes and that the runner token is a sibling of the real checkout, which
makes a forged parent boundary include that token.

internal/security/hooks/secret_redact_posttool.py[80-96]
internal/security/hooks/secret_redact_posttool.py[110-161]
docs/contributing/runtime-implementation.md[244-244]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Do not derive the JWT-redaction exception from an agent-writable `.git` sentinel. A created `.git` directory above the actual checkout expands the trusted root to include the runner token file, so a subsequent file-content Read bypasses the bare-JWT mask.

## Issue Context
The hook documentation states that `cwd` follows the agent's persisted `cd`, and that the runner OIDC token is beside the checkout. The current implementation accepts any `.git` filesystem entry as the checkout root; an agent can create one in the workspace parent and change into it before reading the token.

## Fix Focus Areas
- internal/security/hooks/secret_redact_posttool.py[110-161]
- internal/security/hooks/posttool_chain.py[397-431]
- internal/security/hooks/secret_redact_posttool_test.py[413-640]

Pass or derive a runtime-owned, immutable checkout root rather than trusting a `.git` entry discoverable from mutable `cwd`. Keep the skip limited to targets contained by that trusted root, and add a regression test that creates a parent `.git`, changes cwd there, and verifies the sibling token still redacts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Pi corrupts JWT fixtures ✓ Resolved 🐞 Bug ≡ Correctness
Description
content_skips requires an absolute cwd, but pi's PostToolUse payload never sends one, so every
pi Read receives no skip and the newly added JWT pattern rewrites checkout fixtures. This returns
content different from disk—the exact edit-corruption path the checkout exemption is intended to
prevent.
Code

internal/security/hooks/secret_redact_posttool.py[R132-133]

+    if not isinstance(cwd, str) or not os.path.isabs(cwd):
+        return frozenset()
Relevance

●● Moderate

Pi omission is explicitly documented as intentional, but fixture corruption remains a concrete
correctness concern.

PR-#1178

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new JWT prefix pattern is active unless content_skips returns jwt, but that function
immediately declines the exemption when cwd is absent. The pi adapter constructs PostToolUse
payloads without cwd, and the runtime contract explicitly confirms this omission, so pi checkout
reads are always passed through the new JWT redaction instead of the intended fixture-preserving
path.

internal/security/hooks/secret_redact_posttool.py[53-56]
internal/security/hooks/secret_redact_posttool.py[128-133]
internal/runtime/pi_extension/fullsend-hooks.js[226-234]
docs/contributing/runtime-implementation.md[581-584]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The checkout-scoped JWT exemption requires an absolute `cwd`, but the pi PostToolUse adapter omits it. Consequently, JWT-shaped fixtures read from checkout files are masked and the agent receives content different from disk.

## Issue Context
The new bare-JWT pattern did not exist previously, so omitting `cwd` does not preserve prior pi behavior. Supply a trustworthy checkout/current working directory in pi's hook payload and add adapter-level coverage proving a checkout `Read` preserves a JWT fixture while an outside-checkout read remains masked.

## Fix Focus Areas
- internal/runtime/pi_extension/fullsend-hooks.js[226-234]
- internal/security/hooks/secret_redact_posttool.py[128-133]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 67 rules
Review mode: 🧠 Deep: This is a security-sensitive hook change with substantial new credential-detection and path-resolution logic across multiple independent runtime paths, creating a dense set of easy-to-miss masking and bypass defects.

Grey Divider

Tip of the day
💡 Did you know, you can route each action level your way: inline, summary, both, or drop

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@codecov

codecov Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread internal/security/hooks/secret_redact_posttool.py
Comment thread internal/security/hooks/secret_redact_posttool.py
dhshah13 added a commit to dhshah13/fullsend that referenced this pull request Sep 4, 2026
…end cwd from pi

Two review findings on the checkout-scoped bare-JWT skip.

The checkout root was the nearest .git ancestor of the resolved cwd,
wherever that walk led: a .git an agent plants at /sandbox/workspace,
reached through a symlink inside the checkout, made the runner's sibling
OIDC token file 'inside' and skipped the mask. The root is now searched
only strictly below SANDBOX_WORKSPACE (/sandbox/workspace, read once at
process start from FULLSEND_SANDBOX_WORKSPACE; tests set it), so a .git
at or above the workspace, or a cwd outside it, yields no root and no
skip. The claude and pi launch commands re-export the runtime's value
after sourcing the agent-writable .env, and the key is reserved from
harness env.sandbox, so an assignment in .env does not move the boundary
either; a .env that redefines the claude command is the pre-existing
launch shim seam. Residual, stated in the hook: a .git planted in another
runner-owned workspace subdirectory (.env.d) can still be a root through
the same symlink, for a bare JWT no structural shape covers.

pi's adapter sent no cwd, so under pi every file-content Read got no skip
and the newly mirrored JWT pattern rewrote checkout fixtures — the
edit-corruption the skip exists to prevent, and not prior behaviour, since
the pattern is new. The adapter now sends its process working directory:
pi is started in the checkout and its tools run in child shells, so the
agent cannot move it. codex still sends no cwd; it cannot apply the
rewrite at all, so nothing is corrupted there, only warned about.

Tests pin a planted .git at and above the workspace (direct and through a
symlinked cwd), a checkout outside the workspace, the re-exported boundary
after .env on both launch commands, and cwd on every pi PostToolUse
payload; the contract doc and the adapter header say the same.

Raised by Qodo's review of fullsend-ai#7009.

Signed-off-by: Dharit Shah <dhshah@redhat.com>
…end cwd from pi

Two review findings on the checkout-scoped bare-JWT skip, and a third
gap the second fix exposed.

The checkout root was the nearest .git ancestor of the resolved cwd,
wherever that walk led: a .git an agent plants at /sandbox/workspace,
reached through a symlink inside the checkout, made the runner's sibling
OIDC token file 'inside' and skipped the mask. The root is now searched
only strictly below SANDBOX_WORKSPACE, so a .git at or above the
workspace, or a cwd outside it, yields no root and no skip. The boundary
is the runner's constant; the hook takes an override only from its own
command line, the seam the subprocess tests use, which the runner-owned
hooks.json and the pi adapter build without it. It is not read from
environment variables: under fullsend's plain launch, Claude Code
(observed locally on 2.1.235; the sandbox image pins 2.1.260) applies a
checkout's .claude/settings.json env block to hook processes over the
launch environment, so an env-carried boundary could be moved by a
committed settings file, and the sandbox .env every launch command
sources lives in the sandbox user's workspace. Not covered, and
pre-existing: under claude and pi the hook interpreter is a bare python3
resolved through a PATH with the agent-writable workspace bin first,
with PYTHONPATH and the user site directory honoured (codex isolates its
adapter); left for a follow-up. Residual, stated in the hook: a .git
planted in another runner-owned workspace subdirectory (.env.d) can
still be a root through a plain cd or a symlink, for a bare JWT no
structural shape covers, and only for files under that subdirectory.

pi's adapter sent no cwd, so under pi every file-content Read got no skip
and the newly mirrored JWT pattern rewrote checkout fixtures — the
edit-corruption the skip exists to prevent, and not prior behaviour, since
the pattern is new. The adapter now sends its process working directory:
pi is started in the checkout and its tools run in child shells, so the
agent cannot move it. codex still sends no cwd; it cannot apply the
rewrite at all, so nothing is corrupted there, only warned about.

Sending cwd exposed a gap of its own under pi: pi strips a leading '@',
expands '~' and turns a file:// URL into a path before opening, while the
adapter forwards the raw argument, so a read of '@../.gcp-oidc-token'
resolved under the checkout in the hook and the token came back unmasked.
Those forms now never skip, for every file-content tool; any URL scheme
is refused, broader than pi's rewrite on purpose.

Tests pin a planted .git at and above the workspace (the at-workspace
case direct and through a symlinked cwd), a checkout outside the
workspace, the rewritten path forms for Read and Grep, a symlinked
workspace path, the boundary ignoring the environment (standalone and
through the chain) and honouring only an absolute command-line value
(standalone), and cwd on every pi PostToolUse payload across events; the
contract doc and the adapter header say the same.

Raised by Qodo's review of fullsend-ai#7009.

Signed-off-by: Dharit Shah <dhshah@redhat.com>

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review sweep — 1 finding could not be attached inline because the file it concerns is outside this PR's diff.

MEDIUM — Codex adapter never forwards cwd, so Codex never gets the checkout-scoped JWT skip this PR just fixed for pi
internal/runtime/codex_hook/fullsend-codex-hook.py:404 (run_post_tool_use, and run_pre_tool_use similarly)

run_post_tool_use() builds the payload sent to hook scripts from only tool_name/tool_input/tool_response/tool_resultcwd is never forwarded, even though Codex's own hook input carries it (see fixtures in internal/runtime/codex_hook_test.go:94). Codex's apply_patch tool maps to Claude's Edit (CLAUDE_TOOL_FOR_CODEX), and Edit is one of the tools content_skips keys on (_TOOL_PATH_KEY). Because cwd is always missing, content_skips always returns no skip for Codex, so a Codex edit of a checkout file containing a jwt.io-style fixture is masked and rewritten to something different from what's on disk — the exact edit-corruption failure mode this PR's checkout-scoped skip exists to prevent, and that d5718882 just fixed for pi (see the "Pi corrupts jwt fixtures" thread on this PR). The same gap is unfixed on Codex.

This file isn't touched by this PR's diff, so it's a pre-existing parity gap rather than a regression — but the PR's new docs/contributing/runtime-implementation.md contract line generically covers it ("an adapter that omits it gets no bare-JWT skip"), so it's now a documented, unaddressed hole.

Suggestion: forward hook_input['cwd'] into the payload built in run_post_tool_use (mirroring what Claude Code and the pi adapter now send), so Codex Edit/Read/Grep calls get the same checkout-scoped skip and don't corrupt JWT-shaped fixtures the agent then edits against.

# host_files credentials) can still be a root through a plain cd into it or an
# in-checkout symlink; only a bare JWT no structural shape covers would skip
# there, and only for files under that subdirectory.
SANDBOX_WORKSPACE: str = "/sandbox/workspace"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — No cross-check ties the Python hook's hardcoded SANDBOX_WORKSPACE to internal/sandbox.SandboxWorkspace

secret_redact_posttool.py hardcodes SANDBOX_WORKSPACE: str = "/sandbox/workspace" as a Python literal, documented as mirroring internal/sandbox.SandboxWorkspace (verified equal today, internal/sandbox/sandbox.go:29, pinned by internal/sandbox/sandbox_test.go:33). The checkout-scoped bare-JWT skip's entire security boundary (introduced by this PR's d5718882 fix, replacing the walk that let a forged .git widen the trusted root to the sibling OIDC token file) now depends on this literal matching the Go constant.

Verified no test enforces that: internal/security/hooks_test.go:175 only asserts SecretRedactPostToolHook is non-empty, and hooks.go (which already imports the sandbox package for SandboxHooksDir/SandboxHooksSettings) has no assertion against the embedded Python source. If SandboxWorkspace is ever renamed or changed in Go, this file's literal silently goes stale with no CI failure, and the checkout-boundary control mis-scopes.

Suggestion: add one line to hooks_test.go: assert.Contains(t, string(SecretRedactPostToolHook), \SANDBOX_WORKSPACE: str = "`+sandbox.SandboxWorkspace+`"`)` so a future rename of the Go constant fails CI instead of drifting silently.

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.

2 participants