Close detector bypasses: bracket env, unqualified getenv, custom-shell PR head#20
Merged
Conversation
Conalh
added a commit
that referenced
this pull request
May 22, 2026
…l and merge ref Extends #20's bypass closures with the next wave of evasions surfaced by external inspection. - JS destructuring (`const { API_TOKEN } = process.env`) and renamed destructuring (`const { API_TOKEN: t } = process.env`) now track the secret variable. addSecretVariable handles both direct and destructured forms. - Python aliased env imports: `from os import getenv as g` / `environ as e` (and the unaliased `from os import getenv` form already supported in #20) build a per-file alias map, then the secret-variable regex is generated dynamically from the alias union. Closes `token = g("API_TOKEN")` bypass. - Workflow referencesPullRequestHead now covers github.event.pull_request.head.repo.clone_url and standalone refs/pull/<n>/merge references — the two custom-shell patterns agents use when actions/checkout would attract review attention. - README: new "Detection limits" section documenting same-line URL requirement, no cross-file taint, no Python dep manifests yet, with a pointer to test/fixtures/bypasses/. - test/fixtures/bypasses/ established as the bypass-corpus pattern, with one fixture per closure and a CLI integration test per fixture. Adds 10 tests (80 total on this branch). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4 tasks
…l PR head)
- JS: addSecretVariable and referencesEnvSecret now match bracket
notation (process.env['KEY'] / ["KEY"]) alongside dot notation. An
inline `Authorization: Bearer ${process.env['API_TOKEN']}` or a stored
`const x = process.env["API_TOKEN"]` referenced later now flag the
exfil pattern.
- Python: addSecretVariable and referencesPyEnvSecret make the `os.`
prefix optional, so `from os import getenv; k = getenv("API_TOKEN")`
and `from os import environ; k = environ.get("API_KEY")` are tracked
as secret variables and trigger exfil on later external requests.
- Workflows: detectPullRequestHeadCheckoutOnTarget no longer requires a
structured `ref:`/`repository:` key — any reference to
github.event.pull_request.head.{sha,ref,repo.full_name} under a
pull_request_target workflow is flagged. Closes a bypass where a
custom `run:` step with `git checkout ${{ ... head.sha }}` skipped
detection. Subject/message/recommendation updated from "checkout" to
"reference" to reflect the broadened semantic; kind preserved for
back-compat.
Adds 6 tests (70 total, all passing on this branch's baseline).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
afa1262 to
da04278
Compare
Conalh
added a commit
that referenced
this pull request
May 22, 2026
…l and merge ref Extends #20's bypass closures with the next wave of evasions surfaced by external inspection. - JS destructuring (`const { API_TOKEN } = process.env`) and renamed destructuring (`const { API_TOKEN: t } = process.env`) now track the secret variable. addSecretVariable handles both direct and destructured forms. - Python aliased env imports: `from os import getenv as g` / `environ as e` (and the unaliased `from os import getenv` form already supported in #20) build a per-file alias map, then the secret-variable regex is generated dynamically from the alias union. Closes `token = g("API_TOKEN")` bypass. - Workflow referencesPullRequestHead now covers github.event.pull_request.head.repo.clone_url and standalone refs/pull/<n>/merge references — the two custom-shell patterns agents use when actions/checkout would attract review attention. - README: new "Detection limits" section documenting same-line URL requirement, no cross-file taint, no Python dep manifests yet, with a pointer to test/fixtures/bypasses/. - test/fixtures/bypasses/ established as the bypass-corpus pattern, with one fixture per closure and a CLI integration test per fixture. Adds 10 tests (80 total on this branch). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
3 tasks
Conalh
added a commit
that referenced
this pull request
May 22, 2026
…l and merge ref Extends #20's bypass closures with the next wave of evasions surfaced by external inspection. - JS destructuring (`const { API_TOKEN } = process.env`) and renamed destructuring (`const { API_TOKEN: t } = process.env`) now track the secret variable. addSecretVariable handles both direct and destructured forms. - Python aliased env imports: `from os import getenv as g` / `environ as e` (and the unaliased `from os import getenv` form already supported in #20) build a per-file alias map, then the secret-variable regex is generated dynamically from the alias union. Closes `token = g("API_TOKEN")` bypass. - Workflow referencesPullRequestHead now covers github.event.pull_request.head.repo.clone_url and standalone refs/pull/<n>/merge references — the two custom-shell patterns agents use when actions/checkout would attract review attention. - README: new "Detection limits" section documenting same-line URL requirement, no cross-file taint, no Python dep manifests yet, with a pointer to test/fixtures/bypasses/. - test/fixtures/bypasses/ established as the bypass-corpus pattern, with one fixture per closure and a CLI integration test per fixture. Adds 10 tests (80 total on this branch). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes three real detection bypasses surfaced by a follow-up inspection of CapabilityEcho's regex-based detectors. All three are small, mechanical broadenings — same shape as agent-gov-core's "match what the agent will actually write" philosophy. No schema changes; one
kindreused with broader semantic.process.env— js-capability.ts:addSecretVariableandreferencesEnvSecretonly matched dot notation.process.env['API_TOKEN']/process.env[\"API_TOKEN\"]slipped both, so an inline exfil expression OR a stored secret variable referenced later was missed. Now both notations match.getenv/environ.get— py-capability.ts: both helpers required theos.prefix, sofrom os import getenv; k = getenv('API_TOKEN')(and theenvironvariant) bypassed secret-variable tracking.os.is now optional in both detection points. The secret-name suffix pattern (TOKEN|SECRET|KEY|PASSWORD|CREDENTIAL|AUTH) keeps false positives bounded.pull_request_targetvia custom shell — workflow-permissions.ts:isPullRequestHeadCheckoutLineonly matched structuredref:/repository:keys. A workflow could trivially bypass by using arun:step:github.event.pull_request.head.{sha,ref,repo.full_name}inside apull_request_targetworkflow is flagged. Function renamedreferencesPullRequestHead; subject/message/recommendation reworded from "checkout" to "reference" to reflect the broadened semantic.kind(workflow_pr_head_checkout_on_target) preserved for downstream policy stability.Why batched
All three are "broaden a regex to catch an obvious bypass" — same theme, low surface, no API changes. Total source delta is ~30 lines; tests add 6 cases.
Deferred (acknowledged from the same inspection)
yaml.load(...)across lines, multi-line YAML env secret values) — needs file-content lookback infrastructure; separate PR with shared helpers.git diff --no-indexreturns empty stdout. Real but narrow (git is always present on GHA runners); defensive fix in its own PR.Declined: CLI parser refactor to
mri/yargs-parser— adds a runtime dep to a 6-flag CLI on a security-adjacent tool.Stacking note
This PR is independent of #19 ("Tighten CLI parity, monorepo annotations, and workflow noise"). The two touch different functions in
workflow-permissions.tsand different parts ofreport.ts; either order of merge is fine. Whichever lands second will need a one-line bundle rebuild rebase, no source conflicts expected.Test plan
npm test— 70 passing (64 baseline + 6 new):getenvinline exfil flagged;getenvandenviron.gettracked across lines (with file context)git clone/git checkoutreferencinghead.{sha,repo.full_name}both flag underpull_request_targetnpm run build— clean tsc + ncc bundle\${{ github.event.pull_request.head.repo.owner.login }}is not matched — onlysha,ref,repo.full_name)🤖 Generated with Claude Code