-
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
Open
seonghobae
wants to merge
93
commits into
develop
Choose a base branch
from
sentinel/detect-transport-only-poll-bound-1087
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 81 commits
Commits
Show all changes
93 commits
Select commit
Hold shift + click to select a range
a0130f4
test(actions): pin vulnerable transport-only verdict poll
seonghobae 43e63be
test(actions): pin fixed verdict poll deadline
seonghobae fc9190c
test(actions): define transport-only poll detector contract
seonghobae 023782b
feat(actions): detect transport-only verdict polling bounds
seonghobae 7d60c92
docs(security): trace verdict-poll capacity defect
seonghobae 0c79c61
docs(changelog): record bounded poll detector
seonghobae 32e23a8
test(actions): keep polling bounds scoped to owning job
seonghobae f9d4195
fix(actions): scope verdict poll bounds to owning job
seonghobae 74f16eb
docs(actions): bind poll safety evidence to owning job
seonghobae 148458b
test(actions): pin polling scope review boundaries
seonghobae 87db6be
fix(actions): require one executable polling loop
seonghobae 1ca346e
docs(actions): trace executable polling-loop boundary
seonghobae e0d48a5
test(actions): cover reordered same-job timeout
seonghobae 4feb533
fix(actions): honor owning job timeout regardless of key order
seonghobae e56aae2
test(actions): cover late same-job timeout
seonghobae 119e949
test(actions): cover late owning-job timeouts
seonghobae edf2b52
fix(actions): scan full job for timeout
seonghobae 57e683a
test(actions): define renamed transport-budget contract
seonghobae 36f184d
feat(actions): detect renamed transport-only retry budgets
seonghobae 3b4ea19
docs(actions): trace renamed retry-budget detection
seonghobae df51306
docs(changelog): record alias-safe poll detection
seonghobae e2fb93c
test(actions): bound renamed polls by causal guards
seonghobae b68568a
fix(actions): recognize renamed total poll guards
seonghobae 92c4b63
test(actions): require causal total poll guards
seonghobae cb12b27
fix(actions): require initialized total poll guards
seonghobae 86eb50c
test(actions): reject late total-bound initialization
seonghobae 95bb935
refactor(actions): isolate generic transport-budget detector
seonghobae 4b0e757
fix(actions): require pre-loop total-bound state
seonghobae bad9d32
test(security): pin poll-bound review regressions
seonghobae 99a2302
feat(security): structurally analyze Actions poll bounds
seonghobae d5e5589
chore(security): drop unused analyzer draft
seonghobae 4a948df
fix(security): require causal historical poll bounds
seonghobae 9e54673
fix(security): localize generic poll-bound state
seonghobae 7196e44
docs(security): record poll-bound causal boundaries
seonghobae ccb8000
docs(security): bind polling safety to exact loop
seonghobae b7618f6
test(security): pin mixed-name poll safety
seonghobae 3ca23fa
fix(security): accept renamed historical poll bounds
seonghobae 11bcff8
docs(security): record identifier-agnostic poll safety
seonghobae c3cb796
docs(security): record renamed polling safety state
seonghobae cdd24c9
perf(security): prefilter generic poll detector
seonghobae 34a37ec
test(security): pin poll command whitespace variants
seonghobae 327b154
fix(security): preserve gh command whitespace detection
seonghobae 23db570
fix(security): preserve generic gh whitespace detection
seonghobae de84e5b
test(security): pin mutable poll bound bypasses
seonghobae 3fe5249
test(security): keep poll mutation fixtures loop-local
seonghobae 5c64e24
test(security): pin invalid break-zero poll bypass
seonghobae 71e2617
fix(security): detect mutable total poll bounds
seonghobae a3884b0
feat(security): detect ineffective mutable poll bounds
seonghobae b4a134e
test(security): bind break-zero regression to companion
seonghobae b09140b
feat(security): isolate invalid break-zero poll detector
seonghobae 077121b
chore(security): preserve canonical mutable-bound detector
seonghobae a56f2b3
docs(security): record mutable-bound and break-zero companions
seonghobae 0136677
docs(security): trace mutable poll bounds and break-zero
seonghobae f293da0
test(security): keep large polling job workflow valid
seonghobae 2529fc5
test(security): pin polling loop locality and quoted command boundaries
seonghobae d94effe
fix(security): drop unsound break-zero companion
seonghobae 6868b10
test(security): remove shell-ambiguous break-zero oracle
seonghobae 960536f
docs(security): narrow break semantics to proven shell behavior
seonghobae 95d6afc
docs(security): remove unsound break-zero coverage claim
seonghobae cdd4b90
test(security): distinguish deadline tightening from refresh
seonghobae 8496b21
fix(security): distinguish deadline extension from tightening
seonghobae f289096
test(security): pin conditional exit and retry declaration boundaries
seonghobae c86f7c9
fix(security): require executable gh api poll evidence
seonghobae c49c943
fix(security): preserve conditional poll back edges
seonghobae e4e4cf8
fix(security): distinguish conditional poll termination
seonghobae fe312dc
fix(security): accept reversed retry declarations
seonghobae 2f3fd77
test(security): pin reversed safety and deadline guard precision
seonghobae 5ce4114
fix(security): require expiration-direction deadline guard
seonghobae 89efd14
fix(security): accept reversed total-bound declarations
seonghobae ba2b030
test(security): pin unreachable poll-bound exits
seonghobae 87ec49f
security(actions): detect unreachable poll-bound exits
seonghobae 52ab761
docs(security): trace unreachable poll-bound exits
seonghobae f0ba2c5
test(actions): reject unreachable polling continues
seonghobae 959809a
fix(actions): require reachable polling continue
seonghobae 34bc96c
test(actions): pin current polling detector review boundaries
seonghobae 27fbf01
fix(actions): honor independent polling bounds
seonghobae ebc3aaa
test(actions): pin executable poll command semantics
seonghobae 125fb94
fix(actions): require executable historical poll command
seonghobae 1a70e3a
fix(actions): preserve historical reverse bound capture
seonghobae c8263ed
fix(actions): require executable unreachable poll command
seonghobae 4990691
fix(actions): tighten generic poll execution and termination
seonghobae ca9ebfa
test(actions): pin post-sleep termination ownership
seonghobae 2610305
fix(actions): preserve poll termination ownership
seonghobae cea9ddb
test(actions): treat bare exit as polling termination
seonghobae 4cce94e
fix(actions): recognize bare exit as finite poll termination
seonghobae 7c81044
fix(actions): recognize bare exit in transport poll companion
seonghobae df4ff1c
test(actions): require historical transport counter data flow
seonghobae a467678
fix(actions): require historical transport budget data flow
seonghobae 5d87c36
test(actions): keep historical scope fixtures causal
seonghobae 696009f
docs: baseline polling detector commercial gaps
seonghobae 93f953a
docs: trace historical polling budget causally
seonghobae d50f49c
test(actions): cover strict bounds and constant timeouts
seonghobae b34670b
docs: pin current polling precision RED gaps
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
| 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. | ||
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,3 +31,39 @@ 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`. 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(?>(?:(?!^ 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' | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
| 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" | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.