[quality] No guard against untrusted-context script injection in workflow run blocks
Maintainers usually reply within 1 day
A pull request for this has already been merged.
- #1541 by @hivecommons-hive — merged
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 68/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- github-actions, javascript, yaml
Research direction
Start with the preamble of tests/ci-supply-chain-helpers.test.mjs and the existing guards in tests/ci-supply-chain.test.mjs, then add the scanner to tests/helpers-ci-supply-chain.mjs. Check how .github/workflows/architecture-submission.yml passes ISSUE_NUMBER through env:, since that is the pattern a failure should point to. Done means the scanner flags an offending fixture in the helper tests, and the contract assertion passes on the current workflow tree.
Written by the indexing model from the issue text.
Description
Finding
tests/ci-supply-chain.test.mjs asserts a thorough supply-chain contract over
the real .github/workflows/** tree: every third-party action is SHA-pinned
and version-commented, every job constrains GITHUB_TOKEN, no workflow grants
write-all, no workflow uses pull_request_target, every checkout sets
persist-credentials: false, every job declares timeout-minutes and a
versioned runner image.
It does not assert anything about script injection — a ${{ }} expression
carrying webhook-payload text interpolated into a run: block or an
actions/github-script body. The runner substitutes those expressions before
the interpreter sees the string, so attacker-authored text does not arrive as
an argument; it becomes part of the script.
That is not theoretical here. .github/workflows/architecture-submission.yml:
- triggers on
issues: [labeled], so the payload it receives was authored by
whoever opened the issue, who needs no write access to the repository; - holds
permissions: contents: write,pull-requests: write,issues: write; - runs eight
run:steps and pushes a branch.
A single ${{ github.event.issue.body }} added to one of those run: blocks
would hand an arbitrary shell command that write token, with no review step
between the issue text and the run. Nothing in the suite would fail.
The tree is clean today. Verified: the only ${{ }} expression interpolated
into any script body across all ten workflows is
${{ steps.coverage_tests.outcome }} in ci.yml, whose value is one of
Playwright's enumerated step outcomes. architecture-submission.yml already
does the right thing in its failure-comment step, passing the issue number via
env: ISSUE_NUMBER and reading it as "$ISSUE_NUMBER".
So this is a regression guard, in the same shape as the pull_request_target
and persist-credentials guards already in that file: the contract holds now,
and the test is what keeps it holding.
Recommendation
Add a scanner to tests/helpers-ci-supply-chain.mjs and a contract assertion
to tests/ci-supply-chain.test.mjs, with the scanner driven over offending
documents in tests/ci-supply-chain-helpers.test.mjs — the split that file's
preamble already requires, so a scanner that stopped detecting cannot leave the
contract green over a tree it is no longer checking.
-
scriptBodies(doc)collectsrun:blocks andactions/github-script
with.scriptbodies -
injectableExpression(expression)treats thegithub.eventcontext and
github.head_refas untrusted, exempting only*.number -
scriptInjectionRisks(workflows)reportsworkflow (job) step N kind: expression - contract assertion over the real workflow tree
- scanner unit tests covering every offence-recording and clearing arm
The rule should be deliberately wider than the field list in GitHub's
Understanding the risk of script injections
guidance. That list enumerates known-bad fields and has grown with the webhook
payloads, so matching it exactly leaves each newly added string field unguarded
until the list is updated. Treating everything reachable from github.event as
untrusted, and exempting only shapes that cannot carry a shell metacharacter,
inverts that.
The remedy a failure should point at is never quoting the expression: pass the
value through env: and read it as "$VAR", which is what
architecture-submission.yml already does with ISSUE_NUMBER.
Priority
- Impact: high
- Effort: low
🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: bf18311
— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88
- Dominant language
- JavaScript
- Stars
- 0
- Forks
- 3
- Avg merge
- 7h 35m
- Merged PRs (30d)
- 579
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from cncf/endusers
-
agent/sec-check security stage/010-actionable
Difficulty 3/5 1-2 days Newbie friendliness 35/100
Maintainers usually reply within 1 day
-
agent/guide documentation stage/045-needs-decision
Difficulty 3/5 1-2 days Newbie friendliness 30/100
cncf/endusers#1924 · 3 comments ·
Maintainers usually reply within 1 day
-
agent/guide documentation stage/045-needs-decision
Difficulty 3/5 1-2 days Newbie friendliness 38/100
cncf/endusers#1923 · 1 comment ·
Maintainers usually reply within 1 day
-
agent/quality stage/010-actionable testing
Difficulty 3/5 1-2 days Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
a11y: search button accessible name does not contain its visible label (WCAG 2.5.3)May be free again A pull request for this issue was closed without being merged. Openblocked bug hive/claimed-by-scanner needs-human
Difficulty 4/5 3-5 days Newbie friendliness 39/100
cncf/endusers#1618 · 8 comments ·
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
type/bug
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
flarum/framework#5301 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
AOSSIE-Org/BabyNest#284 ·
Maintainers usually reply within 1 day
-
status:untriaged
Difficulty 1/5 Under an hour Newbie friendliness 86/100
midnightntwrk/servicedesk#275 ·
Maintainers usually reply within 1 day