-
Notifications
You must be signed in to change notification settings - Fork 0
security(actions): detect transport-only verdict poll bounds #1088
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 88 commits
a0130f4
43e63be
fc9190c
023782b
7d60c92
0c79c61
32e23a8
f9d4195
74f16eb
148458b
87db6be
1ca346e
e0d48a5
4feb533
e56aae2
119e949
edf2b52
57e683a
36f184d
3b4ea19
df51306
e2fb93c
b68568a
92c4b63
cb12b27
86eb50c
95bb935
4b0e757
bad9d32
99a2302
d5e5589
4a948df
9e54673
7196e44
ccb8000
b7618f6
3ca23fa
11bcff8
c3cb796
cdd24c9
34a37ec
327b154
23db570
de84e5b
3fe5249
5c64e24
71e2617
a3884b0
b4a134e
b09140b
077121b
a56f2b3
0136677
f293da0
2529fc5
d94effe
6868b10
960536f
95d6afc
cdd4b90
8496b21
f289096
c86f7c9
c49c943
e4e4cf8
fe312dc
2f3fd77
5ce4114
89efd14
ba2b030
87ec49f
52ab761
f0ba2c5
959809a
34bc96c
27fbf01
ebc3aaa
125fb94
1a70e3a
c8263ed
4990691
ca9ebfa
2610305
cea9ddb
4cce94e
7c81044
df4ff1c
a467678
5d87c36
696009f
93f953a
d50f49c
b34670b
99bdf45
e63c342
d974433
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Security | ||
|
|
||
| - Add the packaged `github-actions-transport-only-poll-bound` detector for GitHub Actions review/control-plane polling loops whose retry budget covers only transport failures while the successful-no-result path can wait indefinitely. The regression corpus pins the verified `ContextualWisdomLab/.github` vulnerable predecessor `5c561a65cca3b925d533e4b40c5c3ac00f16524e` and protected wall-clock repair `e29302c05eade7da7b0bdbb453e53980bc9d577b`. Historical-name safety suppression requires the compared deadline/attempt state to be causally initialized before the same loop, but the safety variable names themselves are identifier-agnostic; unset or late state remains vulnerable, while an unconditional successful-path termination before the sleep/back edge is finite within the reviewed shell grammar. The companion `github-actions-transport-failure-budget-poll-bound` detects the same causal failure when shell retry identifiers are renamed by requiring the failure counter, retry limit, failing `gh api` branch, increment, threshold exit, and repeatable healthy-transport sleep path to be linked in one reviewed loop. `github-actions-poll-bound-state-reset` retains a HIGH finding when an apparent deadline/total-attempt bound is neutralized by a reviewed state mutation that prevents convergence. Safety evidence is loop-local: a bounded helper loop cannot donate a deadline/attempt bound to a later poll. Negative boundaries also cover explicit same-job timeouts, stable monotonic total bounds, comments/quoted commands, split steps, sibling jobs, and non-workflow paths. Shell-error termination depends on the selected GitHub Actions shell and fail-fast flags; the withdrawn `break 0` companion is not retained because a default Linux Actions shell uses fail-fast Bash semantics, so a shell-agnostic HIGH rule would create blocker-class false positives. Explicit non-errexit invalid-break behavior remains an unclaimed boundary until shell selection/state is modeled. See issue #1087. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,3 +31,42 @@ rules: | |
| include: | ||
| - ".github/workflows/*.yml" | ||
| - ".github/workflows/*.yaml" | ||
|
|
||
| # A transport-failure counter is not a total polling bound. The verified | ||
| # ContextualWisdomLab/.github incident kept every API request successful while | ||
| # no review verdict arrived, so the loop never incremented its transport | ||
| # failure counter and retained a shared runner for hours. Keep the lightweight | ||
| # detector intentionally bounded to conventional two-space Actions job syntax: | ||
| # all positive evidence must execute inside one literal-shell run block and | ||
| # gh/sleep must occur before that same unbounded loop's `done`. The historical | ||
| # rule also requires the configured transport budget to be wired to a zeroed | ||
| # failure counter, failure-path increment, threshold comparison, and nonzero | ||
| # exit; a merely declared retry setting is not executable safety evidence. | ||
| # Same-job timeout and causally initialized fail-closed deadline/attempt guards | ||
| # are safe boundaries regardless of safety-variable spelling. Uninitialized | ||
| # or post-guard state is not safety evidence, and a healthy path that | ||
| # unconditionally breaks/exits before the back edge is finite. | ||
| - id: github-actions-transport-only-poll-bound | ||
| patterns: | ||
| - pattern-regex: '^ [A-Za-z0-9_.-]+[^\n]*\n(?!(?>(?:(?!^(?:[A-Za-z0-9_.-]+| [A-Za-z0-9_.-]+)[ \t]*:)[^\n]*\n))*^ timeout-minutes[ \t]*:[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?$)(?>(?:(?!^ timeout-minutes[ \t]*:|^ -[ \t]*run|^ run)^ {4,}[^\n]*\n)){0,80}^(?: -[ \t]*run| run)[ \t]*:[ \t]*\|[+-]?[^\n]*\n(?=(?>(?:(?!^ [A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?$)^ {10,}[^\n]*\n)){0,80}^ (?P<hist_transport_counter>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ max_poll_transport_failures)^ {10,}[^\n]*\n)){0,40}^ max_poll_transport_failures[ \t]*=[ \t]*[1-9][0-9]*[^\n]*\n(?>(?:(?!^ while[ \t])^ {10,}[^\n]*\n)){0,40}^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?>(?:(?!^ {12}(?! )if[ \t]+![^\n]*\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b|^ done\b)^ {12,}[^\n]*\n)){0,80}^ {12}(?! )if[ \t]+![^\n]*\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b[^\n]*\n(?>(?:(?!^ {14}(?! )(?P=hist_transport_counter)[ \t]*=[ \t]*\$\(\([ \t]*(?P=hist_transport_counter)[ \t]*\+[ \t]*1[ \t]*\)\)|^ done\b)^ {14,}[^\n]*\n)){0,20}^ {14}(?! )(?P=hist_transport_counter)[ \t]*=[ \t]*\$\(\([ \t]*(?P=hist_transport_counter)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {14}(?! )if[^\n]*\$(?P=hist_transport_counter)[^\n]*-ge[^\n]*\$max_poll_transport_failures\b[^\n]*;[ \t]*then|^ done\b)^ {14,}[^\n]*\n)){0,12}^ {14}(?! )if[^\n]*\$(?P=hist_transport_counter)[^\n]*-ge[^\n]*\$max_poll_transport_failures\b[^\n]*;[ \t]*then[^\n]*\n(?>(?:(?!^ {16}(?! )exit[ \t]+[1-9][0-9]*\b)^ {16,}[^\n]*\n)){0,8}^ {16}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n(?>(?:(?!^ {12}(?! )sleep(?:[ \t]+|$)|^ done\b)^ {12,}[^\n]*\n)){0,80}^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n(?>(?:(?!^ done\b)^ {12,}[^\n]*\n)){0,80}^ done\b)(?>(?:(?!^ max_poll_transport_failures)^ {10,}[^\n]*\n)){0,80}^ max_poll_transport_failures[ \t]*=[ \t]*[1-9][0-9]*[^\n]*\n(?!(?>(?:(?!^ [A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*\$\(\([^\n]*\bdate\b[^\n]*\+%s[^\n]*\+[ \t]*[1-9][0-9]*[ \t]*\)\))^ {10,}[^\n]*\n)){0,80}^ (?P<hist_deadline_any>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*\$\(\([^\n]*\bdate\b[^\n]*\+%s[^\n]*\+[ \t]*[1-9][0-9]*[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ while[ \t])^ {10,}[^\n]*\n)){0,40}^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?>(?:(?!^ {12}(?! )if[^\n]*\bdate\b[^\n]*\+%s[^\n]*-[gl]e[^\n]*\$(?P=hist_deadline_any)\b[^\n]*;[ \t]*then)^ {12,}[^\n]*\n)){0,20}^ {12}(?! )if[^\n]*\bdate\b[^\n]*\+%s[^\n]*-[gl]e[^\n]*\$(?P=hist_deadline_any)\b[^\n]*;[ \t]*then[^\n]*\n(?>(?:(?!^ {14}(?! )exit[ \t]+[1-9][0-9]*\b)^ {14,}[^\n]*\n)){0,8}^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?!(?>(?:(?!^ [A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?$)^ {10,}[^\n]*\n)){0,80}^ (?P<hist_counter_a>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ [A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?$)^ {10,}[^\n]*\n)){0,40}^ (?P<hist_limit_a>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ while[ \t])^ {10,}[^\n]*\n)){0,40}^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?>(?:(?!^ {12}(?! )(?P=hist_counter_a)[ \t]*=[ \t]*\$\(\([ \t]*(?P=hist_counter_a)[ \t]*\+[ \t]*1[ \t]*\)\))^ {12,}[^\n]*\n)){0,20}^ {12}(?! )(?P=hist_counter_a)[ \t]*=[ \t]*\$\(\([ \t]*(?P=hist_counter_a)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {12}(?! )if[^\n]*\$(?P=hist_counter_a)[^\n]*-ge[^\n]*\$(?P=hist_limit_a)\b[^\n]*;[ \t]*then)^ {12,}[^\n]*\n)){0,12}^ {12}(?! )if[^\n]*\$(?P=hist_counter_a)[^\n]*-ge[^\n]*\$(?P=hist_limit_a)\b[^\n]*;[ \t]*then[^\n]*\n(?>(?:(?!^ {14}(?! )exit[ \t]+[1-9][0-9]*\b)^ {14,}[^\n]*\n)){0,8}^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?!(?>(?:(?!^ [A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?$)^ {10,}[^\n]*\n)){0,80}^ (?P<hist_limit_b>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ [A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?$)^ {10,}[^\n]*\n)){0,40}^ (?P<hist_counter_b>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ while[ \t])^ {10,}[^\n]*\n)){0,40}^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?>(?:(?!^ {12}(?! )(?P=hist_counter_b)[ \t]*=[ \t]*\$\(\([ \t]*(?P=hist_counter_b)[ \t]*\+[ \t]*1[ \t]*\)\))^ {12,}[^\n]*\n)){0,20}^ {12}(?! )(?P=hist_counter_b)[ \t]*=[ \t]*\$\(\([ \t]*(?P=hist_counter_b)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {12}(?! )if[^\n]*\$(?P=hist_counter_b)[^\n]*-ge[^\n]*\$(?P=hist_limit_b)\b[^\n]*;[ \t]*then)^ {12,}[^\n]*\n)){0,12}^ {12}(?! )if[^\n]*\$(?P=hist_counter_b)[^\n]*-ge[^\n]*\$(?P=hist_limit_b)\b[^\n]*;[ \t]*then[^\n]*\n(?>(?:(?!^ {14}(?! )exit[ \t]+[1-9][0-9]*\b)^ {14,}[^\n]*\n)){0,8}^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?>(?:(?!^ while[ \t])^ {10,}[^\n]*\n)){0,80}^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?>(?:(?!^ {12,}(?:(?:if[ \t]+![ \t]+)?(?:[A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*)?["'']?\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b|(?:if[ \t]+![ \t]+)?(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b))^ {12,}[^\n]*\n)){0,80}^ {12,}(?:(?:if[ \t]+![ \t]+)?(?:[A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*)?["'']?\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b|(?:if[ \t]+![ \t]+)?(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b)[^\n]*\n(?!(?>^ {12}[^\n]*\n){0,80}^ {12}(?:break(?:[ \t]+[1-9][0-9]*)?|exit(?:[ \t]+0)?)[ \t]*(?:#[^\n]*)?$\n)(?>(?:(?!^ {12}(?! )sleep(?:[ \t]+|$))^ {12,}[^\n]*\n)){0,80}^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n(?>(?:(?!^ done\b)^ {12,}[^\n]*\n)){0,80}^ done\b' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Constant timeout expressions trigger blockers A positive timeout written as Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. RED를 먼저 고정했습니다.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed on current exact head |
||
| prefilter: [while, sleep] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| message: | | ||
| This GitHub Actions job has a remote polling loop whose explicit retry | ||
| limit covers transport failures but not the successful-no-result path. | ||
| If the API stays reachable while the awaited verdict/state never appears, | ||
| the job can retain a runner until an external platform limit, degrading | ||
| availability of required review/security controls. Add a total wall-clock | ||
| deadline or finite total-attempt guard checked on every polling path and | ||
| fail closed. An explicit timeout anywhere on this same job can also bound | ||
| the runner. A timeout/counter in a sibling job, command-like text in | ||
| another run step, an echoed command string, or a shell comment is not | ||
| evidence for this loop. Keep per-request transport timeouts as defense in | ||
| depth; they do not replace the total polling bound. | ||
| severity: HIGH | ||
| languages: [generic] | ||
| cwe: [CWE-400] | ||
| owasp: [A04:2021] | ||
| paths: | ||
| include: | ||
| - ".github/workflows/*.yml" | ||
| - ".github/workflows/*.yaml" | ||
Uh oh!
There was an error while loading. Please reload this page.