security(actions): detect transport-only verdict poll bounds - #1088
security(actions): detect transport-only verdict poll bounds#1088seonghobae wants to merge 96 commits into
Conversation
📝 WalkthroughWalkthroughGitHub Actions 폴링 탐지기가 조건부 종료, 전체 시도 제한, 상태 재설정, 도달 불가능한 종료와 셸 명령 경계를 구분하도록 확장되었다. 새 규칙, 회귀 테스트, 보안 코퍼스와 추적성 문서가 추가되었다. ChangesGitHub Actions 폴링 경계 탐지
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🟡 Moderate · up to The PR adds static HIGH findings for unsafe GitHub Actions polling patterns, but the current rules can still flag safe workflows and overlook valid bounds in some cases, potentially blocking legitimate CI configurations. Required exact-head checks are also not yet complete, so merge should wait for the detector corrections or explicit owner acceptance. Sequence Diagram(s)sequenceDiagram
participant Workflow as GitHub Actions 워크플로
participant Scanner as _scan_file
participant PollRule as 폴링 경계 규칙
participant Findings as Findings
Workflow->>Scanner: 워크플로 파일 전달
Scanner->>PollRule: gh api·sleep·상태·종료 흐름 검사
PollRule->>Findings: 폴링 경계 또는 상태 변이 결과 반환
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
| - 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]*)?$)(?>(?:(?!^ -[ \t]*run|^ run)^ {4,}[^\n]*\n)){0,80}^(?: -[ \t]*run| run)[ \t]*:[ \t]*\|[+-]?[^\n]*\n(?!(?>(?:(?!^ while[ \t])^ {10,}[^\n]*\n)){0,80}^ max_poll_transport_failures\b)(?>(?:(?!^ [A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?$)^ {10,}[^\n]*\n)){0,80}(?:^ (?P<helper_counter>[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<helper_limit>[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=helper_counter)[ \t]*=[ \t]*\$\(\([ \t]*(?P=helper_counter)[ \t]*\+[ \t]*1[ \t]*\)\)|^ done\b)^ {12,}[^\n]*\n)){0,20}^ {12}(?! )(?P=helper_counter)[ \t]*=[ \t]*\$\(\([ \t]*(?P=helper_counter)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {12}(?! )if[^\n]*\$(?P=helper_counter)[^\n]*-ge[^\n]*\$(?P=helper_limit)\b[^\n]*;[ \t]*then|^ done\b)^ {12,}[^\n]*\n)){0,12}^ {12}(?! )if[^\n]*\$(?P=helper_counter)[^\n]*-ge[^\n]*\$(?P=helper_limit)\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(?>(?:(?!^ done\b)^ {12,}[^\n]*\n)){0,40}^ done\b[^\n]*\n(?>(?:(?!^ [A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?$)^ {10,}[^\n]*\n)){0,80})?^ (?P<retry_counter_alias>[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<retry_limit_alias>[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]*\$\(\([^\n]*\bdate\b[^\n]*\+%s[^\n]*\+[ \t]*[1-9][0-9]*[ \t]*\)\))^ {10,}[^\n]*\n)){0,40}^ (?P<safe_deadline>[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]*-ge[^\n]*\$(?P=safe_deadline)\b[^\n]*;[ \t]*then|^ done\b)^ {12,}[^\n]*\n)){0,20}^ {12}(?! )if[^\n]*\bdate\b[^\n]*\+%s[^\n]*-ge[^\n]*\$(?P=safe_deadline)\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,40}^ (?P<safe_total_counter>[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<safe_total_limit>[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=safe_total_counter)[ \t]*=[ \t]*\$\(\([ \t]*(?P=safe_total_counter)[ \t]*\+[ \t]*1[ \t]*\)\)|^ done\b)^ {12,}[^\n]*\n)){0,20}^ {12}(?! )(?P=safe_total_counter)[ \t]*=[ \t]*\$\(\([ \t]*(?P=safe_total_counter)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {12}(?! )if[^\n]*\$(?P=safe_total_counter)[^\n]*-ge[^\n]*\$(?P=safe_total_limit)\b[^\n]*;[ \t]*then|^ done\b)^ {12,}[^\n]*\n)){0,12}^ {12}(?! )if[^\n]*\$(?P=safe_total_counter)[^\n]*-ge[^\n]*\$(?P=safe_total_limit)\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,40}^ (?P<safe_total_limit_first>[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<safe_total_counter_second>[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=safe_total_counter_second)[ \t]*=[ \t]*\$\(\([ \t]*(?P=safe_total_counter_second)[ \t]*\+[ \t]*1[ \t]*\)\)|^ done\b)^ {12,}[^\n]*\n)){0,20}^ {12}(?! )(?P=safe_total_counter_second)[ \t]*=[ \t]*\$\(\([ \t]*(?P=safe_total_counter_second)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {12}(?! )if[^\n]*\$(?P=safe_total_counter_second)[^\n]*-ge[^\n]*\$(?P=safe_total_limit_first)\b[^\n]*;[ \t]*then|^ done\b)^ {12,}[^\n]*\n)){0,12}^ {12}(?! )if[^\n]*\$(?P=safe_total_counter_second)[^\n]*-ge[^\n]*\$(?P=safe_total_limit_first)\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,40}^ 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|(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b)[^\n]*;[ \t]*then|^ done\b)^ {12,}[^\n]*\n)){0,40}^ {12,}if[ \t]+![ \t]+(?:[A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*["'']?\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b|(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b)[^\n]*;[ \t]*then[^\n]*\n(?>(?:(?!^ {14,}(?P=retry_counter_alias)[ \t]*=[ \t]*\$\(\([ \t]*(?P=retry_counter_alias)[ \t]*\+[ \t]*1[ \t]*\)\)|^ {12}fi\b)^ {14,}[^\n]*\n)){0,12}^ {14,}(?P=retry_counter_alias)[ \t]*=[ \t]*\$\(\([ \t]*(?P=retry_counter_alias)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {14,}if[^\n]*\$(?P=retry_counter_alias)[^\n]*-ge[^\n]*\$(?P=retry_limit_alias)\b[^\n]*;[ \t]*then|^ {12}fi\b)^ {14,}[^\n]*\n)){0,12}^ {14,}if[^\n]*\$(?P=retry_counter_alias)[^\n]*-ge[^\n]*\$(?P=retry_limit_alias)\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}fi[ \t]*(?:#[^\n]*)?$)^ {12,}[^\n]*\n)){0,40}^ {12}fi[ \t]*(?:#[^\n]*)?\n(?!(?>(?:(?!^ {12}(?! )(?:if\b|fi\b|break(?:[ \t]+[1-9][0-9]*)?\b|exit(?:[ \t]+0)?\b))^ {12,}[^\n]*\n)){0,40}^ {12}(?! )(?:break(?:[ \t]+[1-9][0-9]*)?|exit(?:[ \t]+0)?)[ \t]*(?:#[^\n]*)?$\n)(?>(?:(?!^ {12}(?! )sleep(?:[ \t]+|$)|^ done\b)^ {12,}[^\n]*\n)){0,40}^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n(?!(?>(?:(?!^ {12}(?! )(?:if\b|fi\b|break(?:[ \t]+[1-9][0-9]*)?\b|exit(?:[ \t]+0)?\b)|^ done\b)^ {12,}[^\n]*\n)){0,40}^ {12}(?! )(?:break(?:[ \t]+[1-9][0-9]*)?|exit(?:[ \t]+0)?)[ \t]*(?:#[^\n]*)?$\n)(?>(?:(?!^ done\b)^ {12,}[^\n]*\n)){0,40}^ done\b' | ||
| - 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]*)?$)(?>(?:(?!^ -[ \t]*run|^ run)^ {4,}[^\n]*\n)){0,80}^(?: -[ \t]*run| run)[ \t]*:[ \t]*\|[+-]?[^\n]*\n(?!(?>(?:(?!^ while[ \t])^ {10,}[^\n]*\n)){0,80}^ max_poll_transport_failures\b)(?>(?:(?!^ [A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?$)^ {10,}[^\n]*\n)){0,80}^ (?P<retry_limit_reverse>[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<retry_counter_reverse>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\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,40}^ (?P<safe_deadline_reverse>[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]*-ge[^\n]*\$(?P=safe_deadline_reverse)\b[^\n]*;[ \t]*then|^ done\b)^ {12,}[^\n]*\n)){0,20}^ {12}(?! )if[^\n]*\bdate\b[^\n]*\+%s[^\n]*-ge[^\n]*\$(?P=safe_deadline_reverse)\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,40}^ (?P<safe_total_counter_reverse>[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<safe_total_limit_reverse>[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=safe_total_counter_reverse)[ \t]*=[ \t]*\$\(\([ \t]*(?P=safe_total_counter_reverse)[ \t]*\+[ \t]*1[ \t]*\)\)|^ done\b)^ {12,}[^\n]*\n)){0,20}^ {12}(?! )(?P=safe_total_counter_reverse)[ \t]*=[ \t]*\$\(\([ \t]*(?P=safe_total_counter_reverse)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {12}(?! )if[^\n]*\$(?P=safe_total_counter_reverse)[^\n]*-ge[^\n]*\$(?P=safe_total_limit_reverse)\b[^\n]*;[ \t]*then|^ done\b)^ {12,}[^\n]*\n)){0,12}^ {12}(?! )if[^\n]*\$(?P=safe_total_counter_reverse)[^\n]*-ge[^\n]*\$(?P=safe_total_limit_reverse)\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,40}^ (?P<safe_total_limit_reverse_first>[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<safe_total_counter_reverse_second>[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=safe_total_counter_reverse_second)[ \t]*=[ \t]*\$\(\([ \t]*(?P=safe_total_counter_reverse_second)[ \t]*\+[ \t]*1[ \t]*\)\)|^ done\b)^ {12,}[^\n]*\n)){0,20}^ {12}(?! )(?P=safe_total_counter_reverse_second)[ \t]*=[ \t]*\$\(\([ \t]*(?P=safe_total_counter_reverse_second)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {12}(?! )if[^\n]*\$(?P=safe_total_counter_reverse_second)[^\n]*-ge[^\n]*\$(?P=safe_total_limit_reverse_first)\b[^\n]*;[ \t]*then|^ done\b)^ {12,}[^\n]*\n)){0,12}^ {12}(?! )if[^\n]*\$(?P=safe_total_counter_reverse_second)[^\n]*-ge[^\n]*\$(?P=safe_total_limit_reverse_first)\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,40}^ 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|(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b)[^\n]*;[ \t]*then|^ done\b)^ {12,}[^\n]*\n)){0,40}^ {12,}if[ \t]+![ \t]+(?:[A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*["'']?\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b|(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b)[^\n]*;[ \t]*then[^\n]*\n(?>(?:(?!^ {14,}(?P=retry_counter_reverse)[ \t]*=[ \t]*\$\(\([ \t]*(?P=retry_counter_reverse)[ \t]*\+[ \t]*1[ \t]*\)\)|^ {12}fi\b)^ {14,}[^\n]*\n)){0,12}^ {14,}(?P=retry_counter_reverse)[ \t]*=[ \t]*\$\(\([ \t]*(?P=retry_counter_reverse)[ \t]*\+[ \t]*1[ \t]*\)\)[ \t]*(?:#[^\n]*)?\n(?>(?:(?!^ {14,}if[^\n]*\$(?P=retry_counter_reverse)[^\n]*-ge[^\n]*\$(?P=retry_limit_reverse)\b[^\n]*;[ \t]*then|^ {12}fi\b)^ {14,}[^\n]*\n)){0,12}^ {14,}if[^\n]*\$(?P=retry_counter_reverse)[^\n]*-ge[^\n]*\$(?P=retry_limit_reverse)\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}fi[ \t]*(?:#[^\n]*)?$)^ {12,}[^\n]*\n)){0,40}^ {12}fi[ \t]*(?:#[^\n]*)?\n(?!(?>(?:(?!^ {12}(?! )(?:if\b|fi\b|break(?:[ \t]+[1-9][0-9]*)?\b|exit(?:[ \t]+0)?\b))^ {12,}[^\n]*\n)){0,40}^ {12}(?! )(?:break(?:[ \t]+[1-9][0-9]*)?|exit(?:[ \t]+0)?)[ \t]*(?:#[^\n]*)?$\n)(?>(?:(?!^ {12}(?! )sleep(?:[ \t]+|$)|^ done\b)^ {12,}[^\n]*\n)){0,40}^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n(?!(?>(?:(?!^ {12}(?! )(?:if\b|fi\b|break(?:[ \t]+[1-9][0-9]*)?\b|exit(?:[ \t]+0)?\b)|^ done\b)^ {12,}[^\n]*\n)){0,40}^ {12}(?! )(?:break(?:[ \t]+[1-9][0-9]*)?|exit(?:[ \t]+0)?)[ \t]*(?:#[^\n]*)?$\n)(?>(?:(?!^ done\b)^ {12,}[^\n]*\n)){0,40}^ done\b' |
There was a problem hiding this comment.
🟡 Strict polling bounds trigger false alarms
A finite deadline or attempt guard using -gt bypasses the detector's -ge-only exclusions. The scanner then reports a HIGH unbounded-loop finding.
Prompt for agents
Update both transport-only polling detectors in scanner/rules/github_actions_transport_budget.yml and scanner/rules/github_actions.yml to recognize strict greater-than guards as finite total bounds alongside greater-than-or-equal guards. Cover both wall-clock deadlines and total-attempt counters, both declaration orders, and add production _scan_file regressions for historical and renamed transport-budget forms. Preserve the existing direction check so reversed or non-expiring comparisons do not suppress findings.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
RED를 먼저 고정했습니다. d50f49ccea1cbf2aecc6da268850fddfc80db3b6의 production _scan_file 회귀가 historical/renamed 두 transport detector 모두에 대해 date ... -gt $overall_deadline과 total-attempt counter -gt limit를 finite negative로 요구하고, attempt counter/limit 선언 순서 두 가지를 모두 검증합니다. 현재 production matcher는 이 계약을 아직 만족하지 않으므로 thread는 unresolved로 유지합니다. GREEN은 같은 causally initialized state의 forward -gt/-ge를 모두 안전 경계로 인정하되 reversed/non-expiring 비교는 suppress하지 않고, 기존 vulnerable positives를 그대로 통과하는 exact-head test evidence입니다.
There was a problem hiding this comment.
Addressed on current exact head e63c34279491c9c8c90764215d6ba39c8ad8d9e3. RED commit d50f49ccea1cbf2aecc6da268850fddfc80db3b6 pins forward -gt finite deadline/attempt bounds for the historical and renamed detectors and both declaration orders; production commit 99bdf459cf7939896740c61c2c3fe9c222377312 recognizes the mathematically equivalent forward -ge/-gt forms while reversed/non-expiring comparisons remain positive. Exact-head local polling regression is 126/126 GREEN and GitHub Tests run 34082329418 is success. I am leaving the thread unresolved for fresh independent review and the remaining exact-head security/code-scanning checks.
| # 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.
🟡 Constant timeout expressions trigger blockers
A positive timeout written as ${{ 20 }} does not match the literal-only timeout-minutes exclusion. Bounded jobs can receive HIGH polling findings.
Prompt for agents
Extend the owning-job timeout exclusion consistently across all four polling detector files to accept statically positive GitHub Actions constant expressions, while continuing to reject zero, negative, empty, or dynamically unprovable expressions. Add _scan_file regressions for each detector family member and ensure sibling-job timeouts still cannot suppress a finding.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
RED를 먼저 고정했습니다. d50f49ccea1cbf2aecc6da268850fddfc80db3b6에서 owning-job timeout-minutes: ${{ 20 }}를 네 polling family member 각각의 bounded negative로 실행하고, ${{ 0 }}, ${{ -1 }}, ${{ inputs.timeout }}는 unproved timeout이라 generic vulnerable loop를 suppress하지 못한다는 control도 추가했습니다. 현재 literal-only matcher는 아직 이 계약을 만족하지 않으므로 unresolved 유지합니다. GREEN은 네 detector 모두 statically positive integer constant expression만 owning-job hard bound로 인정하고 zero/negative/empty/dynamic 및 sibling-job timeout은 계속 거부하는 exact-head regression evidence입니다.
There was a problem hiding this comment.
Addressed on current exact head e63c34279491c9c8c90764215d6ba39c8ad8d9e3. RED commit d50f49ccea1cbf2aecc6da268850fddfc80db3b6 covers timeout-minutes: ${{ 20 }} across all four polling detector identities; production commit 99bdf459cf7939896740c61c2c3fe9c222377312 accepts only a statically positive integer literal or constant expression. Zero, negative, dynamic, empty, and sibling-job timeouts remain non-suppressing. Exact-head local polling regression is 126/126 GREEN and GitHub Tests run 34082329418 is success. I am not self-resolving before fresh independent review and terminal security/code-scanning evidence.
| review_poll_failures=0 | ||
| max_poll_transport_failures=3 | ||
| while :; do | ||
| if ! reviews="$(gh api repos/example/repo/pulls/1/reviews)"; then | ||
| review_poll_failures=$((review_poll_failures + 1)) | ||
| if [ "$review_poll_failures" -ge "$max_poll_transport_failures" ]; then | ||
| exit 1 | ||
| fi | ||
| continue | ||
| fi | ||
| review_poll_failures=0 | ||
| [ -n "$reviews" ] && break | ||
| sleep 30 |
There was a problem hiding this comment.
| review_poll_failures=0 | ||
| max_poll_transport_failures=3 | ||
| poll_deadline_epoch=$(( $(date -u +%s) + 60 )) | ||
| while :; do | ||
| if ! reviews="$(gh api repos/example/repo/pulls/1/reviews)"; then | ||
| review_poll_failures=$((review_poll_failures + 1)) | ||
| if [ "$review_poll_failures" -ge "$max_poll_transport_failures" ]; then | ||
| exit 1 | ||
| fi | ||
| continue | ||
| fi | ||
| review_poll_failures=0 | ||
| [ -n "$reviews" ] && break | ||
| sleep 30 |
There was a problem hiding this comment.
RCA (exact-head
|
|
Stacked Draft successor #1129 maps the transport-only poll-bound family but does not replace this PR or close #1087. #1088 remains the canonical writer for YAML rules and the current RED precision contracts. Competing production poll-loop modules, tests, and changelog fragments were removed from the successor so single-writer ownership is preserved. |
Treat causally initialized forward -gt like -ge for total deadline and
attempt guards, accept statically positive timeout-minutes ${{ N }}
expressions on the owning job, keep helper-loop safety loop-local, and
treat bare exit as finite termination. Zero, negative, dynamic, reversed,
and sibling-job bounds remain unproved.
Refs #1087.
RED production repair on exact head
|
Exact-head Tests GREENOn Also terminal-success on this head so far: Security Process / Still Draft / BLOCKED until OpenCode, Noema, Strix, remaining security/SAST admission, and current-head review finish on this unchanged SHA. The PR body still describes unimplemented RED at |
Summary: - Record 05:40 UTC exact-head evidence for #998 coverage/bootstrap SUCCESS, #972/#1006 non-force restacks onto develop@e71d37e, and #1130 Draft under canonical #1107. - Mark G-02 in progress on #972 and G-06 as a stacked successor of #1088 rather than further regex growth. Rationale: - #999 is the single writer of the product-technical gap baseline. - Restacked heads invalidate predecessor GREEN; the snapshot must not claim protected capability. Tests: - documentation-only; no scanner behavior change
|
G-06 successor exists as stacked Draft PR #1133 ( #1088 remains the regex corpus owner. The successor preserves detector IDs |
Summary: - Snapshot 06:01 UTC records Draft #1133 `529ecb0` stacked on #1088. - ADR-0009 remains Proposed; regex detector IDs stay migration oracles. Rationale: - #999 is the single writer of the product-technical gap baseline. - Analyzer-only slice is not protected scanner emission. Tests: - documentation-only; analyzer evidence lives on #1133
Summary: - Snapshot 06:16 UTC records #1133 `5baeab9` with actions-poll-analyzer-coverage and a queued exact-head job. - Hosted coverage is queued, not claimed GREEN. Rationale: - Stacked PRs targeting #1088 do not inherit develop-only coverage workflows. The PR-number-bound job is the exact-head evidence path. Tests: - documentation-only; analyzer coverage evidence lives on #1133
Goal
Close AppGuardrail issue #1087 by converting the verified
ContextualWisdomLab/.githubrequired-review runner-capacity defect into executable AppGuardrail detector evidence while preserving explicit false-positive/false-negative boundaries.Current exact candidate:
d9744331b83e9a47d4b0a84f1e81e42e382aa4c2ondevelop@e71d37e7c58118e6764c96ab7c4492fe33eed6f8.Status: not merge-ready. The current detector-precision RED obligations are repaired, but required security/code-scanning execution, independent approval, and review reconciliation are not complete.
Verified causal incident
Protected predecessor
ContextualWisdomLab/.github@5c561a65cca3b925d533e4b40c5c3ac00f16524ehad a verdict poll whose transport-failure budget did not bound healthy API/no-verdict iterations. The canonical owner wall-clock repair is protected asContextualWisdomLab/.github@e29302c05eade7da7b0bdbb453e53980bc9d577b..githubowns workflow behavior; AppGuardrail owns reusable detection evidence.Executable detector family
The #1087 family retains four packaged HIGH/CWE-400 identities:
github-actions-transport-only-poll-bound;github-actions-transport-failure-budget-poll-bound;github-actions-poll-bound-state-reset;github-actions-poll-bound-unreachable-exit.Evidence remains bounded to conventional GitHub Actions jobs and literal shell blocks. The rules correlate executable
gh api, sleep/back-edge reachability, retry state, total bounds, and owning-job timeout rather than treating isolated tokens as proof.Repaired current-head obligations
RED commit
d50f49ccea1cbf2aecc6da268850fddfc80db3b6pinned two blocker-class false-positive boundaries:-gtdeadline/attempt guards are finite like-ge, while reversed/non-expiring comparisons remain findings;timeout-minutes: ${{ 20 }}is a statically positive hard bound, while zero, negative, dynamic, empty, and sibling-job values are not proof.Production commit
99bdf459cf7939896740c61c2c3fe9c222377312repairs both contracts. Documentation commite63c34279491c9c8c90764215d6ba39c8ad8d9e3records the exact detector and Gap evidence.Exact-head evidence
Exact head
d9744331b83e9a47d4b0a84f1e81e42e382aa4c2changes only ownership of the duplicate Gap document relative toe63c34279491c9c8c90764215d6ba39c8ad8d9e3; detector and test trees are identical.e63c342..., local production_scan_filepolling corpus passed 126/126 and the full suite passed 1,126 with one unrelated pre-existingsrc/....pyPath/String contract failure that also reproduces onb34670b....d9744331...are fresh queued: Tests34082936289, Security Process34082936385, Security Scan34082936283, SAST Semgrep34082936280, CodeQL PR34082936357, Pinned HTTPS34082936293, OpenSSF Evidence34082936367, Retention Audit34082936298, and Scan path context34082936366.Single-writer repair
docs/product-technical-gap-baseline.mdis owned by canonical writer PR #999. Current #999 heade3cb3a2d57da54f31739ce92fff307c5d8d2b3d0contains the complete, more current organization baseline, including #1088 exact headd9744331b83e9a47d4b0a84f1e81e42e382aa4c2and its detector predecessore63c34279491c9c8c90764215d6ba39c8ad8d9e3, REDd50f49c..., production99bdf459..., current Checks, G-06, and the wider PR/security corpus. Normal descendantd9744331b83e9a47d4b0a84f1e81e42e382aa4c2removed the duplicate, narrower baseline copy from this detector branch after that carryover check; no valid baseline requirement or evidence was retired.Product/architecture boundary
Composite actions, generated/non-shell control flow, cross-file state, materially different loop frameworks, and general shell ownership remain the Proposed structural GitHub Actions + shell analyzer Gap. Current rule IDs and regression corpus are migration oracles, not a claim of universal shell parsing.
Acceptance
Merge only through ordinary protection on an unchanged exact head after all required Tests/security/SAST/coverage/code-scanning lanes are terminal-success, live findings are reconciled, and a qualifying independent non-author approval exists. Do not use local probes, author replies, predecessor checks, self-approval, status coercion, force-push, or protection bypass as acceptance.
Refs #1087.