ECC/tests
Jamkris cdbc925d89 fix(ci): flag refs/pull checkouts under pull_request_target
The `pull_request_target` rule's `expressionPattern` matches only
the canonical `github.event.pull_request.head.{ref,sha,repo.full_name}`
interpolations. It does not match the second canonical form of
the same exploit — fetching `refs/pull/<N>/{head,merge}` directly:

  - uses: actions/checkout@v4
    with:
      ref: refs/pull/${{ github.event.pull_request.number }}/merge

The merge-ref variant is what GitHub's own security guidance calls
out as the highest-severity privilege-escalation pattern under
`pull_request_target`: it materialises the PR's merge commit
(attacker code spliced with base), executes inside a workflow that
has full repo-scoped tokens, and gives the attacker the chance to
exfiltrate secrets or push to default branches. `refs/pull/N/head`
is functionally equivalent — same source, same trust boundary.

Reproduced on `main` before this commit:

  $ cat /tmp/bad.yml
  name: bad
  on: { pull_request_target: { types: [opened] } }
  permissions: { contents: read }
  jobs:
    do:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4
          with:
            ref: refs/pull/${{ github.event.pull_request.number }}/merge
            persist-credentials: false
        - run: npm ci --ignore-scripts

  $ ECC_WORKFLOWS_DIR=/tmp node scripts/ci/validate-workflow-security.js
  Validated workflow security for 1 workflow files
  $ echo $?
  0

Expected: violation flagging the refs/pull checkout under pull_request_target.
Actual: passes silently.

Fix: add a `refPattern` to the `pull_request_target` rule:

    /^\s*ref:\s*['"]?[^'"\n]*refs\/(?:remotes\/)?pull\/[^'"\n\s]+/m

and apply it per checkout step inside the existing
event-gated loop. The pattern matches the ref VALUE so it catches
all interpolation shapes — `refs/pull/123/head`,
`refs/pull/${{ github.event.pull_request.number }}/merge`,
`${{ env.FOO }}/refs/pull/N/head` — without enumerating the
possible interpolations themselves.

Scoping: the rule is already gated on the workflow containing
`pull_request_target:`, so non-privileged `pull_request` workflows
that legitimately check out a PR ref are not affected.

After this commit the reproduction above exits 1 with:

  ERROR: bad.yml:10 - pull_request_target must not checkout an untrusted pull_request head ref/repository

Three new regression tests in `tests/ci/validate-workflow-security.test.js`:
  - rejects pull_request_target + refs/pull/<N>/merge
  - rejects pull_request_target + hardcoded refs/pull/<N>/head
  - allows pull_request_target with no `with.ref:` (base-ref checkout —
    the safe pattern from GitHub's own guidance)

Test count: 17 → 20 in this file; full `yarn test` still green.

Together with the previous commit, this closes the two
independent `validate-workflow-security.js` bypasses I found.
2026-05-17 21:19:29 -04:00
..
ci fix(ci): flag refs/pull checkouts under pull_request_target 2026-05-17 21:19:29 -04:00
commands fix: accept crlf command frontmatter 2026-04-30 03:41:18 -04:00
docs Add preview pack smoke gate 2026-05-17 15:35:23 -04:00
hooks fix: add context monitor cost warning opt-out 2026-05-17 01:53:57 -04:00
integration fix: port continuous-learning observer fixes 2026-05-11 03:35:42 -04:00
lib fix(installer): harden locale docs install 2026-05-17 20:46:04 -04:00
scripts fix: skip disabled discussion queries in platform audit 2026-05-17 20:32:09 -04:00
__init__.py Small changes 2026-04-12 12:34:45 +05:30
codex-config.test.js fix(codex): broaden context7 config checks 2026-03-29 00:26:16 -04:00
conftest.py docs: salvage focused stale PR contributions 2026-05-11 05:31:12 -04:00
opencode-config.test.js fix: namespace opencode command agents 2026-04-05 14:21:57 -07:00
opencode-plugin-hooks.test.js fix: harden opencode project file probes 2026-05-11 22:40:19 -04:00
plugin-manifest.test.js docs: verify Codex marketplace readiness (#1931) 2026-05-15 12:30:26 -04:00
run-all.js fix: narrow unicode cleanup scope 2026-03-29 21:21:18 -04:00
test_astraflow_provider.py feat: add Astraflow provider support 2026-05-11 23:21:46 -04:00
test_builder.py docs: salvage focused stale PR contributions 2026-05-11 05:31:12 -04:00
test_claude_provider.py docs: salvage focused stale PR contributions 2026-05-11 05:31:12 -04:00
test_executor.py Small changes 2026-04-12 12:34:45 +05:30
test_provider_tools.py fix: port LLM provider config and tool schemas 2026-05-11 04:12:35 -04:00
test_resolver.py feat: add Astraflow provider support 2026-05-11 23:21:46 -04:00
test_templates.py docs: salvage focused stale PR contributions 2026-05-11 05:31:12 -04:00
test_types.py feat: add Astraflow provider support 2026-05-11 23:21:46 -04:00