Skip to content

security(actions): detect transport-only verdict poll bounds - #1088

Open
seonghobae wants to merge 93 commits into
developfrom
sentinel/detect-transport-only-poll-bound-1087
Open

security(actions): detect transport-only verdict poll bounds#1088
seonghobae wants to merge 93 commits into
developfrom
sentinel/detect-transport-only-poll-bound-1087

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Goal

Close AppGuardrail issue #1087 by converting a verified ContextualWisdomLab/.github required-review runner-capacity defect into executable AppGuardrail detector evidence, while preserving the protected canonical-owner repair and explicit false-positive/false-negative boundaries.

Current exact candidate: b34670b8130f1857a8e53e6baf7cd5933826da29 on develop@e71d37e7c58118e6764c96ab7c4492fe33eed6f8.

Status: not merge-ready. Two current detector-precision obligations are pinned RED, and current-head required execution is not terminal GREEN.

Verified causal incident

Protected predecessor ContextualWisdomLab/.github@5c561a65cca3b925d533e4b40c5c3ac00f16524e had a while : verdict poll whose max_poll_transport_failures budget counted failed gh api transports only. Healthy API/no-current-head-verdict iterations could therefore retain a runner indefinitely relative to repository control flow.

The canonical owner wall-clock repair is protected as ContextualWisdomLab/.github@e29302c05eade7da7b0bdbb453e53980bc9d577b. .github owns runner/review workflow behavior; AppGuardrail owns reusable detection evidence and does not copy owner source.

Executable detector family

The #1087 family retains four packaged HIGH/CWE-400 identities:

  • github-actions-transport-only-poll-bound: historical transport-budget incident shape;
  • github-actions-transport-failure-budget-poll-bound: renamed/identifier-agnostic transport-budget companion;
  • github-actions-poll-bound-state-reset: apparent total bound neutralized by reviewed non-convergent state mutation;
  • github-actions-poll-bound-unreachable-exit: apparent total bound whose fail-closed exit is unreachable because a directly reachable valid continue transfers control first.

The historical detector now requires causal transport-budget data flow, not a setting-name hit: zeroed failure counter → failed executable gh api branch → counter increment → threshold comparison against the positive transport budget → nonzero exit. Test-first df4ff1c3f724764b1d047b2cb95ef491c096356c, production a467678c7e3b4bedca8092eec28072fdd0aae90a, and fixture repair 5d87c36251391e5f0254eb915ca7f3068a22b21c establish that contract.

Current RED obligations

Review exposed two additional blocker-class false-positive boundaries. d50f49ccea1cbf2aecc6da268850fddfc80db3b6 pins both as production _scan_file regressions before production repair:

  1. A causally initialized total deadline/attempt guard using forward shell -gt is finite just like -ge. Regressions cover historical and renamed transport forms and both total-attempt declaration orders. Reversed/non-expiring comparisons must remain positive findings.
  2. Owning-job timeout-minutes: ${{ 20 }} is a statically positive hard bound. Regressions cover all four polling detector identities. ${{ 0 }}, ${{ -1 }}, ${{ inputs.timeout }}, empty/unproved values, and sibling-job timeouts must not suppress findings.

Production regex semantics have not yet been changed for these two RED contracts, so their review threads remain unresolved. docs/product-technical-gap-baseline.md was created and updated through b34670b8130f1857a8e53e6baf7cd5933826da29; docs/TRACEABILITY.md was updated through 93f953a692bc5f93d8036d2f9f17aa1f196aafd9 to keep the bounded detector grammar and owner evidence explicit.

Product/architecture boundary

The regex family remains intentionally bounded to conventional GitHub Actions job syntax and literal shell blocks. Composite actions, generated/non-shell control flow, cross-file state, materially different loop frameworks, and general shell block ownership remain an explicit structural GitHub Actions + shell analyzer Gap. Current rule IDs and regression corpus are migration oracles; additional regex must not be presented as universal shell semantics.

Exact-head evidence state

Every source/doc commit invalidates predecessor evidence. On exact head b34670b8130f1857a8e53e6baf7cd5933826da29 at the last fresh read:

  • CodeQL PR run 33682765699 completed startup_failure; its jobs endpoint returned jobs=[], so no source CodeQL job materialized;
  • Tests 33682763772, Security Process 33682763817, Security Scan 33682763902, SAST Semgrep 33682763880, Pinned HTTPS 33682763847, OpenSSF Evidence 33682763869, Retention Audit 33682763996, Scan path context 33682763877, OSV 33682764647, and Scorecard 33682763970 were queued;
  • fresh zero-job admission evidence was delivered to canonical control-plane RCA .github#712 rather than patched around in this leaf repository.

No queued/startup-failure run is source GREEN, and no predecessor success transfers.

Acceptance

Repair the two current RED precision contracts, then merge only through ordinary protection on an unchanged exact head after production regressions pass, all live current-head review findings are reconciled, required Tests/security/SAST/coverage/code-scanning lanes are terminal-success, a qualifying independent non-author review satisfies protection, and the protected base still accepts the candidate. Do not use local regex probes, author replies, startup failures, predecessor checks, self-approval, status coercion, force-push, or protection bypass as acceptance.

Refs #1087.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

GitHub Actions 폴링 탐지기가 조건부 종료, 전체 시도 제한, 상태 재설정, 도달 불가능한 종료와 셸 명령 경계를 구분하도록 확장되었다. 새 규칙, 회귀 테스트, 보안 코퍼스와 추적성 문서가 추가되었다.

Changes

GitHub Actions 폴링 경계 탐지

Layer / File(s) Summary
폴링 경계 규칙 구현
scanner/rules/github_actions.yml, scanner/rules/github_actions_transport_budget.yml, scanner/rules/github_actions_poll_bound_state_reset.yml, scanner/rules/github_actions_poll_unreachable_exit.yml
기존 매처가 조건부 breakexit 0을 안전 경계로 인정하지 않도록 수정했다. 식별자와 선언 순서가 다른 전송 예산을 지원했다. 상태 재설정과 도달 불가능한 fail-closed 종료를 HIGH 규칙으로 탐지한다.
기본 폴링 경계 검증
tests/fixtures/security_corpus/*, tests/test_github_actions_poll_bounds.py, tests/test_github_actions_poll_bounds_large_job.py
취약·수정 픽스처를 검증했다. 유한 시도 제한, 작업 시간 제한, 동일 작업 범위, 실행 가능한 gh apisleep, 워크플로 경로 범위를 테스트했다.
식별자 비종속 전송 예산 검증
tests/test_github_actions_poll_bound_aliases.py, tests/test_github_actions_poll_bound_command_whitespace.py, tests/test_github_actions_poll_bound_late_initialization.py, tests/test_github_actions_poll_bound_mixed_safety.py
재명명된 변수, 선언 순서, 공백과 탭, 늦은 초기화, 인과적 초기화, 헬퍼 루프 분리를 검증했다. 기존 규칙과 일반 규칙의 중복 보고도 검증했다.
상태 변이와 제어 흐름 검증
tests/test_github_actions_poll_bound_state_reset.py, tests/test_github_actions_poll_bound_review_precision.py, tests/test_github_actions_poll_deadline_tightening.py, tests/test_github_actions_poll_control_flow_review.py, tests/test_github_actions_poll_bound_control_flow_regression.py, tests/test_github_actions_poll_bound_current_head_regressions.py, tests/test_github_actions_poll_bound_unreachable_exit.py
비수렴 상태 변이와 단조 증가 상태를 구분했다. 조건부 성공 종료, 직접 도달 가능한 종료, continue 뒤의 도달 불가능한 종료, 실행되지 않는 명령 문자열의 탐지 경계를 검증했다.
추적성 및 변경 기록
CHANGELOG.d/1087-actions-transport-only-poll-bound.md, docs/TRACEABILITY.md
규칙 조건, 상태 재설정 동작, 도달 불가능한 종료, 음성 경계, 회귀 근거와 소유권 정보를 기록했다. 검토 날짜를 갱신했다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🟡 Moderate · up to 34bc9

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: 폴링 경계 또는 상태 변이 결과 반환
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 115 functions across 15 files. (2 skipped:…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 GitHub Actions의 transport-only verdict polling bound 탐지라는 핵심 변경을 정확하고 간결하게 설명합니다. 추가된 관련 규칙 전체를 열거하지 않아도 주요 변경을 충분히 나타냅니다.
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 115 functions across 15 files. (2 skipped: 2 unsupported.)

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentinel/detect-transport-only-poll-bound-1087

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae seonghobae added area: ci-cd CI, GitHub Actions, checks, release, or supply chain enhancement New feature or request priority: high security Security vulnerability or security-governance work status: needs-review Open pull request requiring current-head review or checks type: feature New or expanded product capability labels Sep 2, 2026 — with ChatGPT Codex Connector
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 1 new potential issue.

Devin Review

Comment thread scanner/rules/github_actions_transport_budget.yml Outdated

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 2 new potential issues.

Devin Review

Comment on lines +40 to +52
return ''' if ! response="$(%s)"; then
api_error_streak=$((api_error_streak + 1))
if [ "$api_error_streak" -ge "$transport_error_budget" ]; then
exit 1
fi
continue
fi
api_error_streak=0''' % command


def _historical_transport_branch(command: str = "gh api repos/example/repo/pulls/7/reviews") -> str:
"""Return the source-incident transport-failure branch."""
return ''' if ! response="$(%s)"; then

@devin-ai-integration devin-ai-integration Bot Sep 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Helper interpolation remains unchanged

Both helpers still replace %s through % command. Removing the unused f-string prefix does not change their generated shell.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +163 to +196
def test_unreachable_exit_requires_executable_poll_command(tmp_path: Path) -> None:
"""Quoted nested command text cannot witness an unreachable-bound polling defect."""
shell = """
overall_deadline=$(($(date +%s) + 600))
while :; do
if [ "$(date +%s)" -ge "$overall_deadline" ]; then
continue
exit 1
fi
if ! response="$(printf '%s\\n' '$(gh api repos/example/repo/pulls/7/reviews)')"; then
continue
fi
sleep 30
done
"""
assert _UNREACHABLE not in _scan(tmp_path, shell)


def test_unreachable_exit_keeps_direct_poll_command_positive(tmp_path: Path) -> None:
"""A directly executed gh api poll keeps the unreachable-exit finding positive."""
shell = """
overall_deadline=$(($(date +%s) + 600))
while :; do
if [ "$(date +%s)" -ge "$overall_deadline" ]; then
continue
exit 1
fi
if ! response="$(gh api repos/example/repo/pulls/7/reviews)"; then
continue
fi
sleep 30
done
"""
assert _UNREACHABLE in _scan(tmp_path, shell)

@devin-ai-integration devin-ai-integration Bot Sep 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Positive control isolates execution semantics

The paired cases distinguish quoted gh api data from direct execution. The positive assertion prevents a disabled detector from satisfying the negative regression.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 2 new potential issues.

Devin Review

Comment thread scanner/rules/github_actions.yml Outdated
Comment on lines +6 to +10
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(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,120}?^ (?P<dline>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*\$\(\([^\n]*\bdate\b[^\n]*\+%s[^\n]*\+[ \t]*[1-9][0-9]*[ \t]*\)\)[^\n]*\n(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<ind_d>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*\$\(\([^\n]*\bdate\b[^\n]*\+%s[^\n]*\+[ \t]*[1-9][0-9]*[ \t]*\)\)[^\n]*\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )if[^\n]*\bdate\b[^\n]*\+%s[^\n]*-ge[^\n]*\$(?P=ind_d)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?!(?=(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<ind_c>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n)(?=(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<ind_l>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n)(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,120}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )(?P=ind_c)[ \t]*=[ \t]*\$\(\([ \t]*(?P=ind_c)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,40}?^ {12}(?! )if[^\n]*\$(?P=ind_c)\b[^\n]*-ge[^\n]*\$(?P=ind_l)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,120}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )(?P=dline)[ \t]*=[ \t]*(?:\$\(\([^\n]*\bdate\b[^\n]*\+%s[^\n]*\+[ \t]*[1-9][0-9]*[ \t]*\)\)|\$\(\([ \t]*(?P=dline)[ \t]*\+[ \t]*[1-9][0-9]*[ \t]*\)\))[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )if[^\n]*\bdate\b[^\n]*\+%s[^\n]*-ge[^\n]*\$(?P=dline)\b[^\n]*;[ \t]*then[^\n]*\n(?:^ {14,}(?!continue\b|break\b|return\b|exit\b)[^\n]*\n){0,8}^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?:(?:if[ \t]+![ \t]+)?[A-Za-z_][A-Za-z0-9_]*[ \t]*=[^#\n]*\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b|(?:if[ \t]+![ \t]+)?(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b)[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n(?:(?!^ 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,180}?^ 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]*)?$)(?>(?:(?!^ timeout-minutes[ \t]*:|^ -[ \t]*run|^ run)^ {4,}[^\n]*\n)){0,80}^(?: -[ \t]*run| run)[ \t]*:[ \t]*\|[+-]?[^\n]*\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,120}?^ (?P<creset>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<ind_d2>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*\$\(\([^\n]*\bdate\b[^\n]*\+%s[^\n]*\+[ \t]*[1-9][0-9]*[ \t]*\)\)[^\n]*\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )if[^\n]*\bdate\b[^\n]*\+%s[^\n]*-ge[^\n]*\$(?P=ind_d2)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?!(?P=creset)\b)(?P<sclc>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,40}?^ (?P<scll>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=sclc)[ \t]*=[ \t]*0\b)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=scll)[ \t]*=)(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )(?P=sclc)[ \t]*=[ \t]*\$\(\([ \t]*(?P=sclc)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,40}?^ {12}(?! )if[^\n]*\$(?P=sclc)\b[^\n]*-ge[^\n]*\$(?P=scll)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<slcl>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,40}?^ (?!(?P=creset)\b)(?P<slcc>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=slcc)[ \t]*=[ \t]*0\b)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=slcl)[ \t]*=)(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )(?P=slcc)[ \t]*=[ \t]*\$\(\([ \t]*(?P=slcc)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,40}?^ {12}(?! )if[^\n]*\$(?P=slcc)\b[^\n]*-ge[^\n]*\$(?P=slcl)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,140}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )(?P=creset)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )(?P=creset)[ \t]*=[ \t]*\$\(\([ \t]*(?P=creset)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )if[^\n]*\$(?P=creset)\b[^\n]*-ge[^\n]*\$[A-Za-z_][A-Za-z0-9_]*\b[^\n]*;[ \t]*then[^\n]*\n(?:^ {14,}(?!continue\b|break\b|return\b|exit\b)[^\n]*\n){0,8}^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?:(?:if[ \t]+![ \t]+)?[A-Za-z_][A-Za-z0-9_]*[ \t]*=[^#\n]*\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b|(?:if[ \t]+![ \t]+)?(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b)[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n(?:(?!^ 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,180}?^ 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]*)?$)(?>(?:(?!^ timeout-minutes[ \t]*:|^ -[ \t]*run|^ run)^ {4,}[^\n]*\n)){0,80}^(?: -[ \t]*run| run)[ \t]*:[ \t]*\|[+-]?[^\n]*\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,120}?^ (?P<gcounter>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<glimit>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<gindd_cl>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*\$\(\([^\n]*\bdate\b[^\n]*\+%s[^\n]*\+[ \t]*[1-9][0-9]*[ \t]*\)\)[^\n]*\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )if[^\n]*\bdate\b[^\n]*\+%s[^\n]*-ge[^\n]*\$(?P=gindd_cl)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?!(?P=gcounter)\b)(?P<gicl_clc>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,40}?^ (?P<gicl_cll>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=gicl_clc)[ \t]*=[ \t]*0\b)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=gicl_cll)[ \t]*=)(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )(?P=gicl_clc)[ \t]*=[ \t]*\$\(\([ \t]*(?P=gicl_clc)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,40}?^ {12}(?! )if[^\n]*\$(?P=gicl_clc)\b[^\n]*-ge[^\n]*\$(?P=gicl_cll)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<gilc_cll>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,40}?^ (?!(?P=gcounter)\b)(?P<gilc_clc>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=gilc_clc)[ \t]*=[ \t]*0\b)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=gilc_cll)[ \t]*=)(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )(?P=gilc_clc)[ \t]*=[ \t]*\$\(\([ \t]*(?P=gilc_clc)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,40}?^ {12}(?! )if[^\n]*\$(?P=gilc_clc)\b[^\n]*-ge[^\n]*\$(?P=gilc_cll)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,120}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )(?P=gcounter)[ \t]*=[ \t]*\$\(\([ \t]*(?P=gcounter)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )(?P=glimit)[ \t]*=[ \t]*\$\(\([ \t]*(?P=glimit)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )if[^\n]*\$(?P=gcounter)\b[^\n]*-ge[^\n]*\$(?P=glimit)\b[^\n]*;[ \t]*then[^\n]*\n(?:^ {14,}(?!continue\b|break\b|return\b|exit\b)[^\n]*\n){0,8}^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?:(?:if[ \t]+![ \t]+)?[A-Za-z_][A-Za-z0-9_]*[ \t]*=[^#\n]*\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b|(?:if[ \t]+![ \t]+)?(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b)[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n(?:(?!^ 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,180}?^ 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]*)?$)(?>(?:(?!^ timeout-minutes[ \t]*:|^ -[ \t]*run|^ run)^ {4,}[^\n]*\n)){0,80}^(?: -[ \t]*run| run)[ \t]*:[ \t]*\|[+-]?[^\n]*\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,120}?^ (?P<glimit>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<gcounter>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<gindd_lc>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*\$\(\([^\n]*\bdate\b[^\n]*\+%s[^\n]*\+[ \t]*[1-9][0-9]*[ \t]*\)\)[^\n]*\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )if[^\n]*\bdate\b[^\n]*\+%s[^\n]*-ge[^\n]*\$(?P=gindd_lc)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?!(?P=gcounter)\b)(?P<gicl_lcc>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,40}?^ (?P<gicl_lcl>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=gicl_lcc)[ \t]*=[ \t]*0\b)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=gicl_lcl)[ \t]*=)(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )(?P=gicl_lcc)[ \t]*=[ \t]*\$\(\([ \t]*(?P=gicl_lcc)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,40}?^ {12}(?! )if[^\n]*\$(?P=gicl_lcc)\b[^\n]*-ge[^\n]*\$(?P=gicl_lcl)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?!(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ (?P<gilc_lcl>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*[1-9][0-9]*[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,40}?^ (?!(?P=gcounter)\b)(?P<gilc_lcc>[A-Za-z_][A-Za-z0-9_]*)[ \t]*=[ \t]*0[ \t]*(?:#[^\n]*)?\n(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,80}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=gilc_lcc)[ \t]*=[ \t]*0\b)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?P=gilc_lcl)[ \t]*=)(?:(?!^ done\b)^ {12,}[^\n]*\n){0,80}?^ {12}(?! )(?P=gilc_lcc)[ \t]*=[ \t]*\$\(\([ \t]*(?P=gilc_lcc)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n(?:(?!^ done\b)^ {12,}[^\n]*\n){0,40}?^ {12}(?! )if[^\n]*\$(?P=gilc_lcc)\b[^\n]*-ge[^\n]*\$(?P=gilc_lcl)\b[^\n]*;[ \t]*then[^\n]*\n^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?:(?!^ while[ \t])^ {10,}[^\n]*\n){0,120}?^ while[ \t]+(?::|true)[ \t]*;[ \t]*do[^\n]*\n(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )(?P=gcounter)[ \t]*=[ \t]*\$\(\([ \t]*(?P=gcounter)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )(?P=glimit)[ \t]*=[ \t]*\$\(\([ \t]*(?P=glimit)[ \t]*\+[ \t]*1[ \t]*\)\)[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )if[^\n]*\$(?P=gcounter)\b[^\n]*-ge[^\n]*\$(?P=glimit)\b[^\n]*;[ \t]*then[^\n]*\n(?:^ {14,}(?!continue\b|break\b|return\b|exit\b)[^\n]*\n){0,8}^ {14}(?! )exit[ \t]+[1-9][0-9]*\b[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12,}(?:(?:if[ \t]+![ \t]+)?[A-Za-z_][A-Za-z0-9_]*[ \t]*=[^#\n]*\$\([ \t]*(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b|(?:if[ \t]+![ \t]+)?(?:timeout[ \t]+\S+[ \t]+)?gh[ \t]+api\b)[^\n]*\n)(?=(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n)(?!(?:(?!^ done\b)^ {12,}[^\n]*\n){0,120}?^ {12}(?! )sleep(?:[ \t]+|$)[^\n]*\n(?:(?!^ 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,180}?^ done\b'

@devin-ai-integration devin-ai-integration Bot Sep 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Multiple entries are intentional alternatives

Each pattern-regex becomes one runtime rule entry. Distinct alternatives explain repeated IDs; tested overlaps still produce one finding.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 3 new potential issues.

Devin Review

Comment on lines +10 to +11
- 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'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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입니다.

# 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'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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입니다.

Comment thread docs/TRACEABILITY.md Outdated

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Devin Review

Comment on lines +194 to +206
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Successful polling path remains unbounded

review_poll_failures exits after transport failures but resets after successful requests. Successful empty responses still sleep indefinitely, preserving this positive regression.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +316 to +329
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Post-loop deadline remains ineffective

The causal transport budget enables detection without creating a total bound. The deadline comparison follows done, so successful empty responses remain unbounded.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ci-cd CI, GitHub Actions, checks, release, or supply chain enhancement New feature or request priority: high security Security vulnerability or security-governance work status: needs-review Open pull request requiring current-head review or checks type: feature New or expanded product capability

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant