source-pattern-scanner
Analyzes source code patterns to identify languages, frameworks, and conventions.
- License 许可
- Apache-2.0
- 最近更新
- 2026-11-09
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- ci-audit-triggers
- description
- Audit a GitHub Actions/GitLab CI workflow for attacker-influenceable triggers (pull_request_target, issue_comment, workflow_run) — pwn-request issues: fork head-ref checkout, ${{ }} injection, author_association gaps. Analysis-only; harden via ci-harden-workflow.
02 资源文件 2
- 参考文档agents/openai.yaml43 B
- 参考文档references/ci-audit-triggers-dispatch.md525 B
03 SKILL.md 正文
Security-Review a CI Workflow on Untrusted Triggers
Generic source→sink review (security-review-diff) and mcp-audit miss CI-specific
escalations: the sink is a runner holding the base repo's secrets, the substitution happens in the
runner's own ${{ }}/shell semantics, and the attacker controls a fork PR's code or a comment's text while a
privileged trigger runs in base-repo context. Apply this whenever a .github/workflows/*.yml (or
.gitlab-ci.yml) fires on events an outsider can influence, or exposes secrets.*. To build or lock down
such a workflow (CODEOWNERS, branch protection, environments), use ci-harden-workflow.
Step 0: Detect platform
Pasted input takes precedence. If the user pasted workflow YAML (or quoted a workflow in the
prompt), detect the platform from that content first — on: + jobs: + runs-on:/uses: → GitHub
Actions; stages:/rules:/workflow:/script:/$CI_* → GitLab CI — and apply the matching branch
below to the pasted text, even when the current checkout is a different platform. Run the script
only when no workflow was supplied.
Run ../../runtime/bin/ci_platform.sh relative to this skill directory. The shared audit method below (classify
trigger trust → enumerate attacker-controlled inputs → trace the ref/code the job
operates on → hunt injection → audit secret reach → check cheap hardening) applies on
either platform — only the vocabulary changes:
github-actions→ the trigger/variable/injection vocabulary in steps 1-9 below applies as written.gitlab-ci→ load../../runtime/references/ci/gitlab-ci-triggers.mdfor the real GitLab equivalents (pipelines for merge requests from forks,CI_MERGE_REQUEST_*variables,$[[ inputs.* ]]interpolation, protected variables/branches in place ofauthor_association) and apply the same method through that vocabulary instead.none→ report that no CI configuration was detected and stop; don't guess at a platform or invent generic advice.
If the script cannot run (read-only session, no shell), skip it and use the same content-based detection on the CI files you can read.
- Classify the trigger's trust level.
pull_request_target,issue_comment,issues,workflow_run, anddiscussion_commentrun with the base repo's secrets and a writable token, and are reachable by untrusted actors (any commenter, any fork-PR author). Plainpull_requestfrom a fork runs without secrets and with a read-only token by default (a repo can opt to send secrets/write tokens to fork PRs; same-repopull_requestdoes get them). State which untrusted inputs the chosen trigger exposes; flag every secret-using job on the privileged triggers. - Enumerate attacker-controlled inputs. Treat each as hostile:
github.event.comment.body,issue.title/issue.body,pull_request.title/body/head.ref/head.label/head.sha, and branch/tag names. - Check what the
if:gate actually authenticates.author_association ∈ {OWNER, MEMBER, COLLABORATOR}is server-set and non-spoofable, but it has two gaps: (a) it gates the commenter/actor — not the code/PR author (a trusted maintainer commenting on an outsider's fork PR still passes the gate while the job points at fork-controlled content); and (b) it is not a write-access check —COLLABORATORincludes read-/triage-only collaborators andMEMBERis any org member, so a gate on this set can admit principals with no write access. For a privileged trigger, confirm the real permission level (e.g.gh api repos/{owner}/{repo}/collaborators/{user}/permission→admin/write) rather thanauthor_associationalone. Verify the gate covers the principal whose code or data the job consumes, not just who typed the trigger phrase. - Trace the ref/code the job operates on (pwn-request). Look for a checkout or operation on
pull_request.head.ref/head.sha. If a secret-bearing step (deploy, publish, an agent CLI with an API key,ghwith a PAT,npm installrunning fork postinstall) acts on that fork-controlled tree (Makefile, postinstall, git filters, lint/CI configs) → pwn-request, high severity: attacker code executes with secrets in scope. Requirehead.repo.full_name == base.repo.full_name(or explicitly refuse fork PRs) before any checkout/operate step. - Hunt
${{ }}expression injection. Anygithub.event.*value interpolated directly into arun:block or awith:/github-scriptinput is substituted into the YAML/shell before execution; a", backtick,$(...), or newline breaks quoting and can bleed into adjacent keys or execute commands. The fix is the distinction that matters: bind the value toenv:and reference the quoted"$VAR"insiderun:— once it arrives through the environment the${{ }}expansion never touches the script text, so it is data, not code (forwith:/github-scriptinputs, read it fromprocess.env/core.getInput, don't inline${{ }}). Flag the inline interpolation, not the env-bound passing. Caveat — awith:input is only data if the receiving action doesn't shell-template it. A composite action can re-introduce the injection by inline-templating an input into its ownrun:(e.g.jq --arg x "${{ inputs.x }}"), so an attacker-controlled value you hand off viawith:(e.g. a fork PR'shead.refas astarting_branchinput) still detonates inside the action. Fetch the action's metadata at the pinned SHA and read itsruns:steps — the file isaction.yml(GitHub's preferred name) oraction.yaml, and a subdirectory action (owner/repo/path@sha) keeps it under that path, so derive the path fromuses:and try both names:gh api repos/OWNER/REPO/contents/PATH/action.yml?ref=SHA --jq .content | base64 -d(retry withaction.yamlon 404). Confirm each untrusted input it receives is env-bound, not interpolated — SHA-pinning fixes the version, not this. When in doubt, sanitize the value (strict allowlist, e.g. a ref against^[A-Za-z0-9._/-]+$) before the hand-off. - Audit
permissions:and secret reach. Demand least privilege (contents: readunless writes are needed); identify exactly which steps seesecrets.*. A secret-bearing step gated only on untrusted input is the escalation target — narrow the gate or move the secret behind a manual approval / GitHub Environment. - Check the cheap hardening. Trigger
types: [created]only (noeditedreplay of an approved-looking comment), bot exclusion (github.event.comment.user.type != 'Bot'to stop self-trigger loops), and third-party actions pinned to a full commit SHA, not a mutable tag. - Prefer the structural remedy over a smarter gate. Either refuse fork PRs before any secret is exposed
(
head.repo.fork == true→ fail), or split into two jobs: an unprivileged job that builds/tests the fork code with no secrets, and a privileged job (triggered viaworkflow_runor gated on same-repo) that never checks out fork code. Don't rely on the author gate alone. - Report only findings with a concrete attacker path. Rank pwn-request and secret-exfil as high; rank pure expression-injection without a secret/command sink as medium. For each, name the trigger, the untrusted input, and the sink it reaches.
Sub-agent dispatch
Follow the CI audit dispatch rules
and the shared dispatch contract.
When at least three workflow files need auditing, assign one
security-reviewer unit per workflow. The parent merges cited attacker-path
evidence under this skill's severity rules; below the threshold, audit inline.
04 同仓库更多 Skills
upstream-fix
Traces quality failures upstream to spec, rule, or workflow root cause. Proposes fixes at the right level.
triage
Dedup gate + batch-classify observations into five classes, enrich ARTIFACTs, batch-approve, delegate to intake/decision or queue.
sprint-planning
Sprint planning — story selection, team composition, Gherkin specs, and visual companion plan gate for developer sign-off.
sprint-manager
Sole writer of stories/index.json and sprints/index.json — validates state transitions, activates sprints, manages epic membership.
sprint-dev
Sprint execution — dependency-driven story development, post-merge AVFL, and team review.
retro
Sprint retrospective — transcript audit engine (dynamic Workflow), story verification, findings document, and sprint closure.