Repository navigation
ci(ci-deep): validate fuzz_seconds before writing to GITHUB_ENV - #18
Merged
Merged
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughChangesCI workflow hardening
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 85f5034e-8ff1-4f20-bb68-f49916a998b4
📒 Files selected for processing (2)
.github/workflows/ci-deep.ymlREADME.md
The dispatch input reached $GITHUB_ENV unvalidated. Routing it through an
env var stops template injection into the run: script, but $GITHUB_ENV is
parsed line by line, so a value containing a newline writes a second,
attacker-chosen variable into every later step of the job:
fuzz_seconds = "3600\nPATH=/tmp/evil"
-> FUZZ_SECS=3600
PATH=/tmp/evil
Validate the whole buffer before the write. `grep -qzE '^[0-9]+$'` anchors
against the entire NUL-delimited input; a plain `grep -qE '^[0-9]+$'` treats
the embedded newline as a line boundary, matches the benign first line and
lets the payload through. Range-check follows, and a rejected value fails the
job loudly rather than falling back to the default, which would make an
attempted injection look like an ordinary run.
workflow_dispatch on this workflow requires write access, so this is
defence in depth rather than an open door.
eilandert
force-pushed
the
ci/validate-fuzz-seconds-dispatch-input
branch
from
August 4, 2026 10:28
a21b308 to
70f26ef
Compare
`^[0-9]+$` accepted a value too large for the shell's integer type, and
`[ "$v" -lt 1 ]` does not return false on one -- it errors. Inside `if !` and
`||` that error is consumed, so both range comparisons failed rather than
compared and the value reached $GITHUB_ENV unbounded:
fuzz_seconds = 99999999999999999999999999
-> passes ^[0-9]+$
-> range check errors, not false
-> FUZZ_SECS=99999999999999999999999999
`^[0-9]{1,5}$` covers the 86400 ceiling and keeps every accepted value inside
the range the arithmetic can actually evaluate. Boundaries 1 and 86400 still
pass; 0, 99999, both overflow shapes and the newline payload all reject.
Caught by CodeRabbit on PR #18.
eilandert
added a commit
that referenced
this pull request
Aug 6, 2026
The env var kept the dispatch input out of shell syntax but not out of $GITHUB_ENV, which is parsed line by line: an input containing a newline wrote a second, attacker-chosen variable into every later step in the job. Validates whole-buffer with grep -qzE so an embedded newline cannot match as a benign first line, bounds the input to 1-5 digits, then range-checks 1..86400 and fails the job loudly rather than falling back to the default. The length bound is load-bearing: [ "$v" -lt 1 ] errors rather than returning false on a value too large for the shell integer type, and that failure is consumed by the surrounding if!/||, so a bare ^[0-9]+$ let a 26-digit input through unbounded. (Caught by CodeRabbit on PR #18.)
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.
TL;DR
ci-deep.ymllets you pass a fuzz duration when you trigger the workflow by hand. That value was written straight into$GITHUB_ENV, which GitHub reads one line at a time. Put a newline in the value and you get to define a second environment variable of your choosing, which every later step in the job then runs with. This validates the input first.Why the existing guard doesn't cover it
The input already goes through an env var rather than
${{ }}insiderun:, and the comment above it explains why: template expansion of event data into a shell script is an injection primitive. That part is correct and stays.It solves a different problem, though. Keeping the value out of the shell's syntax does nothing about what the value means to
$GITHUB_ENV, which is a line-orientedKEY=valuefile:The fix
Validate the whole value, range-check it, and fail loudly on rejection:
grep -qzis the load-bearing part. It anchors^...$against the entire NUL-delimited buffer. A plaingrep -qE '^[0-9]+$'treats the embedded newline as a line boundary, matches the benign3600on line one, and passes the payload through unharmed:A rejected value exits non-zero instead of falling back to 14400. Silently substituting the default would make an attempted injection indistinguishable from a normal scheduled run, which is the wrong thing to learn about six months later.
Scope and severity
workflow_dispatchrequires write access to the repo, so this is defence in depth, not an open door. It matters here because this file exists to be copied: the fix costs eight lines in one repo and removes the footgun from every clone.Unchanged: the schedule path, the 14400 default, and the existing env-var indirection.
Testing
Behaviour of the new block against the payload and the boundaries, run locally:
Negative control: the pre-change one-liner run against the same payload writes
PATH=/tmp/evilinto the file, so the test distinguishes the two versions rather than passing against both.ci/linter/run-all.shclean, including actionlint and zizmor (pedantic) on the edited workflow.