Skip to content

security(scanner): detect stored SSRF webhook persistence - #910

Merged
seonghobae merged 28 commits into
developfrom
sentinel/fix-webhook-ssrf-17689544248715160075
Aug 12, 2026
Merged

security(scanner): detect stored SSRF webhook persistence#910
seonghobae merged 28 commits into
developfrom
sentinel/fix-webhook-ssrf-17689544248715160075

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Scope

Preserve the unique stored-SSRF static detection work after the runtime webhook boundary fix was integrated through #924.

This exact head intentionally contains only:

  • the packaged python-stored-ssrf-webhook-url scanner rule;
  • authoritative CWE-918 / OWASP A10 SSRF taxonomy and remediation metadata;
  • generic sink-literal prefilter support for bounded scanner performance;
  • focused regression tests for direct, subscript, attribute, one-hop, ignored-validator, non-enforcing/conditional guards, guarded-then-unguarded sinks, and fail-closed flows.

The already-integrated control-plane URL-validation changes and unrelated branch history are excluded from the PR diff.

Evidence

  • Live protected base and merge-base: 10138195f80a77d1662fe1c8c54e8188979e033e
  • Exact head: 6c6a830383d182ae2e6051944ef61fe68ca8abf0
  • Changed files: 5
  • Test-first CI blocker fix: None-aware fail-closed guard regression RED, then GREEN
  • Local focused validation: 198 passed
  • Repository self-scan: zero deploy-blocking findings
  • compileall and git diff --check: passed

Hosted exact-head CI, security, coverage, and independent review evidence must complete before merge.

- /api/v1/webhook 엔드포인트에서 사용자로부터 전달받은 webhook url을 검증 없이 데이터베이스에 저장하는 취약점이 발견되었습니다.
- appguardrail_core/controlplane.py에 존재하는 webhook 등록 로직(do_POST)에서 DB 저장(set_webhook) 전 _is_safe_url() 함수로 url 안전성을 검증하도록 수정했습니다.
- 검증에 실패할 시 400 에러를 응답합니다.
- 이에 대한 테스트 코드를 tests/test_controlplane.py에 추가했습니다.
@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@github-project-automation github-project-automation Bot moved this to Backlog in Security Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

저장형 SSRF 규칙과 SSRF 메타데이터 정규화를 추가했습니다. 직접 URL 저장과 변수 흐름을 탐지하고, 유효한 검증 경로는 제외하도록 회귀 테스트를 추가했습니다.

Changes

저장형 SSRF 탐지

Layer / File(s) Summary
SSRF 메타데이터 정규화
appguardrail_core/rules.py
SSRF의 CWE·OWASP·SAMM 참조와 수정 안내를 등록했습니다. CWE 참조를 기준으로 카테고리를 보정합니다.
웹훅 URL 저장 규칙
scanner/rules/ssrf.yml
검증 없이 웹훅 URL을 set_webhook으로 저장하는 Python 흐름을 HIGH 심각도로 탐지합니다.
저장형 SSRF 회귀 검증
tests/test_ssrf_rule_accessors.py, tests/test_ssrf_rules.py
직접 접근, 변수 흐름, 무시된 검증 결과, 비강제 가드를 탐지합니다. 양성 검증과 fail-closed 검증 경로는 탐지하지 않습니다. 스캐너 결과의 분류와 위치 메타데이터도 검증합니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant _scan_file
  participant python_stored_ssrf_webhook_url
  participant build_rule_metadata
  _scan_file->>python_stored_ssrf_webhook_url: Python 파일의 웹훅 URL 저장 흐름 전달
  python_stored_ssrf_webhook_url-->>_scan_file: 저장형 SSRF 결과 반환
  _scan_file->>build_rule_metadata: 결과 메타데이터 정규화 요청
  build_rule_metadata-->>_scan_file: SSRF 분류와 수정 정보 반환
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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 제목은 저장형 SSRF 웹훅 지속성 탐지라는 변경의 주요 목적을 정확하고 간결하게 설명합니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentinel/fix-webhook-ssrf-17689544248715160075

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.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head d157667dff37f6594f9a589850808fcbc10c1a15.

  • Head SHA: d157667dff37f6594f9a589850808fcbc10c1a15

  • Workflow run: 31300803457

  • Workflow attempt: 1

Coverage evidence

Coverage evidence job did not run or did not publish coverage evidence.

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (2 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (2 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Test: test_controlplane.py"]
  S2 --> I2["regression suite"]
  I2 --> R2["Review risk: Test: test_controlplane.py"]
  R2 --> V2["targeted test run"]
Loading

@opencode-agent

opencode-agent Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 5ba91311e673813c218922aab245adbd3c86360d
  • Workflow run: 31316926796
  • Workflow attempt: 1
  • Gate result: REQUEST_CHANGES (approval step)

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 5ba91311e673813c218922aab245adbd3c86360d.

  • Head SHA: 5ba91311e673813c218922aab245adbd3c86360d

  • Workflow run: 31316926796

  • Workflow attempt: 1

Coverage evidence

Coverage evidence job did not run or did not publish coverage evidence.

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (5 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (5 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Test (4 files)"]
  S2 --> I2["regression suite"]
  I2 --> R2["Review risk: Test (4 files)"]
  R2 --> V2["targeted test run"]
Loading

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_controlplane_url_types.py (1)

10-13: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

웹훅 엔드포인트 수준의 회귀 테스트를 추가하세요.

현재 테스트는 private helper인 _is_safe_url만 호출합니다. 따라서 /api/v1/webhook이 비문자열 URL에 HTTP 400을 반환하는지 확인하지 못합니다. 잘못된 본문이 데이터베이스의 webhook_url을 변경하지 않는지도 확인하지 못합니다.

{"url": 123}, {"url": true}, {"url": {}}, 그리고 mapping이 아닌 JSON 본문을 엔드포인트에 전달하는 테스트를 추가하세요. 각 요청이 HTTP 400을 반환하고 기존 URL을 유지하는지 확인하세요.

As per coding guidelines, 웹훅 페이로드 검증은 서버 측 API 경계에서 확인되어야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_controlplane_url_types.py` around lines 10 - 13, Extend the
webhook endpoint tests beyond the private _is_safe_url helper by sending {"url":
123}, {"url": true}, {"url": {}}, and non-mapping JSON bodies to
/api/v1/webhook. Assert each request returns HTTP 400 and verify the persisted
webhook_url remains unchanged, ensuring validation occurs at the server API
boundary.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@appguardrail_core/controlplane.py`:
- Around line 634-638: Validate the parsed body before accessing it in the
webhook update handler: accept only a mapping and return 400 for list or string
bodies. Validate webhook_url as None, an empty string, or a string accepted by
_is_safe_url; reject all other types, including numbers and booleans, with 400
before calling _is_safe_url or set_webhook.

---

Nitpick comments:
In `@tests/test_controlplane_url_types.py`:
- Around line 10-13: Extend the webhook endpoint tests beyond the private
_is_safe_url helper by sending {"url": 123}, {"url": true}, {"url": {}}, and
non-mapping JSON bodies to /api/v1/webhook. Assert each request returns HTTP 400
and verify the persisted webhook_url remains unchanged, ensuring validation
occurs at the server API boundary.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 52a3e023-f867-4a92-81d8-5c8097de8e02

📥 Commits

Reviewing files that changed from the base of the PR and between 0d07baa and 5eddd98.

📒 Files selected for processing (4)
  • .jules/sentinel.md
  • appguardrail_core/controlplane.py
  • tests/test_controlplane.py
  • tests/test_controlplane_url_types.py

Comment thread appguardrail_core/controlplane.py Outdated
seonghobae and others added 20 commits August 9, 2026 17:36
…hook URL endpoint

- /api/v1/webhook 엔드포인트에서 사용자로부터 전달받은 webhook url을 검증 없이 데이터베이스에 저장하는 취약점이 발견되어, 저장 전 _is_safe_url() 함수로 검증하도록 수정했습니다.
- _is_safe_url() 내부에서 urllib.parse.urlparse() 사용 전 문자열 타입 확인 로직(isinstance(url, str))을 추가하여 정수형이나 불리언 등 잘못된 타입이 입력될 때 서버 에러(500) 대신 안전하게 차단되도록 수정했습니다.
- 검증에 실패할 시 400 에러를 응답하며 관련 테스트 코드를 업데이트/추가했습니다.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 5ba91311e673813c218922aab245adbd3c86360d.

  • Head SHA: 5ba91311e673813c218922aab245adbd3c86360d

  • Workflow run: 31315089382

  • Workflow attempt: 1

Coverage evidence

Coverage evidence job did not run or did not publish coverage evidence.

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (5 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (5 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Test (4 files)"]
  S2 --> I2["regression suite"]
  I2 --> R2["Review risk: Test (4 files)"]
  R2 --> V2["targeted test run"]
Loading

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 5ba91311e673813c218922aab245adbd3c86360d.

  • Head SHA: 5ba91311e673813c218922aab245adbd3c86360d

  • Workflow run: 31316926796

  • Workflow attempt: 1

Coverage evidence

Coverage evidence job did not run or did not publish coverage evidence.

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (5 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (5 files)"]
  R1 --> V1["required checks"]
  Evidence --> S2["Test (4 files)"]
  S2 --> I2["regression suite"]
  I2 --> R2["Review risk: Test (4 files)"]
  R2 --> V2["targeted test run"]
Loading

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please complete a current-head security review for exact SHA 5ba91311e673813c218922aab245adbd3c86360d. All seven repository-owned test, security, SAST, evidence, retention, and HTTPS-coverage workflows are terminal-success, and the sole inline thread is resolved. Do not transfer predecessor-head evidence.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Rate Limit Exceeded

@seonghobae have exceeded the limit for the number of chat messages per hour. Please wait 25 minutes and 14 seconds before sending another message.

Restack PR #910 onto the protected develop head after #924 integrated the runtime boundary fix. Keep only the unique packaged scanner rule, CWE-918 taxonomy, and focused regression coverage; drop superseded runtime and formatting changes.
@seonghobae seonghobae changed the title 🛡️ Sentinel: [CRITICAL] Fix stored SSRF in webhook URL endpoint security(scanner): detect stored SSRF webhook persistence Aug 12, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scanner/rules/ssrf.yml`:
- Line 4: Update the SSRF pattern-regex for set_webhook so conditional
validation is not treated as fail-closed: require rejection guards to cover
every execution path, including when the disabled condition is false. Ensure a
safe-url positive guard cannot suppress a later unprotected set_webhook sink
from the same assignment, and add regression cases for both conditional
rejection and positive-guard-then-unprotected-sink flows.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b070017-736b-4ddf-bc9d-72f72bc413ea

📥 Commits

Reviewing files that changed from the base of the PR and between e9d4946 and 84a2708.

📒 Files selected for processing (4)
  • appguardrail_core/rules.py
  • scanner/rules/ssrf.yml
  • tests/test_ssrf_rule_accessors.py
  • tests/test_ssrf_rules.py

Comment thread scanner/rules/ssrf.yml Outdated
seonghobae and others added 5 commits August 12, 2026 15:09
Avoid evaluating the bounded but expensive flow regex in Python files that cannot persist a webhook. Preserve exact detection semantics while reducing a 1 MiB sink-free scan from seconds to a linear literal check.
Do not let an unrelated conditional rejection or an earlier positive-guarded sink suppress a later unprotected webhook persistence sink. Add exact regression coverage and restack the five-file scanner scope onto current protected develop.
* feat(dashboard): 개선된 파일 업로드 UX를 위한 프록시 버튼 추가

Dashboard의 네이티브 파일 업로드 인풋을 숨기고 스타일 제어가 가능한 프록시 버튼으로 교체하여 UI 일관성을 향상시켰습니다.

- 네이티브 `<input type="file">`은 `sr-only` 등을 통해 접근성을 유지한 채 시각적으로만 숨김 처리
- `.tag` 클래스를 활용한 새로운 업로드 프록시 `<button>` 추가
- 단일 페이지 애플리케이션(SPA)에서 동일 파일 연속 선택 시 브라우저가 `change` 이벤트를 무시하는 문제 해결 (JS에서 파일 캡처 후 `value` 초기화)
- 관련 UX 학습 내용을 `.jules/palette.md`에 추가
- 변경 사항에 맞게 프론트엔드 UI 컨트랙트 테스트 업데이트

* test(dashboard): lock accessible upload proxy wiring

---------

Co-authored-by: seonghobae <8172694+seonghobae@users.noreply.github.com>
Preserve the exact reviewed scanner scope while integrating protected develop after #925.
@seonghobae
seonghobae dismissed stale reviews from opencode-agent[bot], opencode-agent[bot], and opencode-agent[bot] August 12, 2026 07:00

Superseded predecessor-head coverage blocker: this review evaluated d157667. Exact current head 6c6a830 now has successful same-head coverage-evidence, opencode-review, Strix, security, unit, exact-coverage, and scanner checks; zero unresolved threads; live protected base 1013819.

@seonghobae
seonghobae merged commit 2cc865b into develop Aug 12, 2026
38 checks passed
@seonghobae
seonghobae deleted the sentinel/fix-webhook-ssrf-17689544248715160075 branch August 12, 2026 07:01
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Security Aug 12, 2026
seonghobae added a commit that referenced this pull request Aug 12, 2026
Merge protected develop into the canonical documentation branch, promote the separately verified webhook prevention and bounded scanner-detection controls, keep PR #911 labelled active, and update executable documentation contracts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant