Skip to content

fix: clear repository-wide Semgrep findings - #38

Closed
seonghobae wants to merge 2 commits into
mainfrom
fix/semgrep-baseline-security
Closed

fix: clear repository-wide Semgrep findings#38
seonghobae wants to merge 2 commits into
mainfrom
fix/semgrep-baseline-security

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • require an absolute HTTPS URL before fetching OIDC JWKS
  • document narrowly justified Semgrep suppressions for HTTP(S) log export and parameterized psycopg/SQLAlchemy execution
  • add a regression test rejecting non-HTTPS JWKS URLs

Validation

  • PYTHONPATH=src python3 -m pytest -q tests/test_api.py tests/test_graph_security.py
  • PYTHONPATH=src python3 -m pytest -q
  • CodeGraph reindexed after the change

Dependency chain

This isolates an existing main-branch Semgrep baseline blocker before dependency-only PR #37. After this merges, #37 can be rebased without carrying unrelated security changes.

Summary by CodeRabbit

  • 보안 개선
    • JWKS 키 조회 시 HTTPS URL만 허용하고 로컬 파일 및 상대 경로 접근을 차단합니다.
  • 버그 수정
    • 보안 분석 경고가 발생하던 네트워크 및 데이터베이스 호출을 검토 기준에 맞게 정리했습니다.
  • 테스트
    • 파일 기반 URL이 거부되고 HTTPS URL만 허용되는 동작을 검증하는 테스트를 추가했습니다.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

JWKS 로더가 절대 HTTPS URL만 처리하도록 변경되었습니다. 로컬 파일 URL 거부 테스트가 추가되었습니다. 기존 SQL 및 네트워크 호출에는 정적 분석 예외 주석이 추가되었습니다.

Changes

JWKS URL 검증

Layer / File(s) Summary
HTTPS JWKS URL 검증
src/sdp/authz.py, tests/test_api.py
_load_jwks_from_url이 HTTPS 스킴과 호스트를 요구합니다. file:// URL은 ValueError를 발생시킵니다. 테스트가 이 동작을 검증합니다.

정적 분석 예외 주석

Layer / File(s) Summary
기존 호출의 분석 예외 명시
src/sdp/graph_store.py, src/sdp/observability.py
기존 SQL 실행과 네트워크 전송 호출에 nosemgrep 주석을 추가했습니다. 실행 방식과 타임아웃 동작은 변경되지 않았습니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 저장소 전체의 Semgrep 발견 사항을 해결하는 변경 사항을 명확하게 요약합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/semgrep-baseline-security

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

@seonghobae
seonghobae enabled auto-merge (squash) August 3, 2026 17:04
@seonghobae

Copy link
Copy Markdown
Contributor Author

Superseded by #34: every hunk here (the three nosemgrep suppressions and the JWKS scheme guard + test) is contained in #34 in stricter form (allow-list guard with mocked-urlopen tests in tests/test_authz.py, fuller suppression justifications). Close this once #34 merges; keeping it open only as fallback until then.

@opencode-agent

opencode-agent Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 95b8424fde56c2fd58e9732a4572db827fe3632f
  • Workflow run: 31171523051
  • Workflow attempt: 1
  • Gate result: APPROVE (exit 0)
B[\"HTTPS scheme/netloc guard (new)\"] --> C[\"urlopen + nosemgrep\"]; D[\"_export_to_sink\"] --> E[\"scheme in {http,https} allowlist\"] --> F[\"urlopen + nosemgrep\"]; G[\"semantic_search / AGE cypher\"] --> H[\"closed SQL fragments + bound params\"] --> I[\"execute + nosemgrep\"]; the diagram reflects head flow where the new guard rejects non-HTTPS JWKS URLs and the suppressed calls are unreachable for unsafe inputs.\nPoC/execution: no browser/runtime-tool receipts exist in bounded evidence (non-web PR); guard behavior is established by the trusted regression test plus the source traces cited in the adversarial probes; no OPENCODE_EXECUTION_RECEIPT lines for Playwright/DevTools are present.\nDDD/domain: changes remain inside the authz / observability / graph-store domain seams; defense-in-depth hardening, no new domain concepts.\nCDD/context: the suppressions and guard are scoped to operator-configured URLs (SDP_OIDC_JWKS_URL, SDP_LOG_SINK_URL) and the parameterized graph-query path.\nSimilar issues: author conversation comment (2026-08-04) states PR #34 supersedes these hunks in stricter form and #38 is a fallback until #34 merges \u2014 historical context only; current-head evidence reports no unresolved non-outdated threads.\nClaim/concept check: PR claims (absolute-HTTPS JWKS requirement, justified suppressions, regression test) verified against the current-head diff and source traces.\nStandards search: HTTPS-only JWKS fetch aligns with OIDC Discovery/JWKS best practice; the scheme allowlist prevents urllib local file handlers (SSRF / local-file-read class).\nCompatibility/convention: no new DB tables, columns, API fields, routes, or config keys; no naming or reserved-word concerns; the added local variable parsed is a short-lived local (exempt).\nBreaking-change/backcompat: the HTTPS-only guard deliberately tightens SDP_OIDC_JWKS_URL \u2014 deployments using an http:// JWKS endpoint would now raise ValueError; no repo evidence of an http:// JWKS configuration exists; remediation is to serve JWKS over https.\nImplementation completeness: guard, suppressions, and regression test are complete executable code; no placeholders (pass/NotImplementedError/TODO) introduced.\nPerformance: the guard adds a constant-time urlsplit; no material change to SQL/cypher execution paths.\nDeveloper experience: inline nosemgrep justifications document why each finding is safe; the regression test is focused, importable (import sdp.authz as app_authz), and fast.\nUser experience: non-web API/authn surface \u2014 the change affects OIDC bootstrap and observability export, both operator-configured pipelines; no user-facing UI change.\nVisual/DOM: non-web change; interaction surface reviewed is the API security path (JWKS fetch), SQL execution paths, and log-sink export path; no Playwright evidence applicable (no OPENCODE_EXECUTION_RECEIPT present).\nAccessibility/i18n: no UI/DOM change, no accessibility or i18n surface affected.\nSupply-chain/license: no dependency changes; urllib.parse is stdlib; no new packages.\nPackaging: no manifest/workflow changes; pyproject python >=3.10 unchanged; no unpackaged source surfaces listed.\nSecurity/privacy: JWKS fetch restricted to https (blocks file:// local reads and http downgrade); cypher and SQL executions confirmed fully parameterized; sink URL scheme allowlisted before urlopen; regression test covers the guard; no secrets touched.\n\nApproval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.\nVerification posture: CodeGraph evidence was initialized and bounded current-head evidence reviewed for changed-file evidence including src/sdp/authz.py, src/sdp/graph_store.py, src/sdp/observability.py, tests/test_api.py.\nLinter/static: workflow/static review evidence is bounded by the current-head GitHub Checks gate and changed-file evidence.\nTDD/regression: coverage execution evidence and focused changed hunks were reviewed from bounded-review-evidence.md.\nCoverage: coverage execution evidence reports supported repository test suites passed.\nDocstring coverage: coverage execution evidence reports configured repository docstring gates passed or docstring coverage was advisory.\nDAG: CodeGraph/source-backed behavior map connects src/sdp/authz.py to the affected review, runtime, or workflow path and required checks.\nPoC/execution: coverage-evidence job executed on the current head and reported PASS.\nDDD/domain: workflow and repository-governance invariants were reviewed against changed files in bounded evidence.\nCDD/context: CodeGraph evidence, changed-file history, and focused hunks were reviewed from bounded-review-evidence.md.\nSimilar issues: changed-file history evidence was reviewed for comparable local precedents.\nClaim/concept check: bounded evidence, repository source, current-head workflow evidence, and, where numeric, scientific, statistical, or literature-backed claims are affected, original-paper/formula evidence and parameter-recovery expectations were used for claims.\nStandards search: standards and external-source claims require trusted bounded source evidence prepared outside the isolated model process; no evidence-backed standards blocker is present in bounded evidence.\nCompatibility/convention: changed workflow/script conventions, object naming, and reserved-word safety for schema/API/config/code surfaces were checked in bounded evidence.\nBreaking-change/backcompat: deployment evidence and changed-file history were checked for backward-compatibility risk.\nPerformance: changed surfaces were checked for performance risk in bounded evidence.\nDeveloper experience: changed automation, review, test, setup, and maintenance surfaces were checked for helpful or obstructive DX impact in bounded evidence.\nUser experience: connected user, operator, API, CLI, documentation, review-comment, status-check, rendering, and workflow-reader behavior was checked for contradictions against code, docs, and tests in bounded evidence.\nVisual/DOM: deterministic repair does not infer browser runtime execution; source-backed DOM/UI evidence and trusted workflow receipts were reviewed when present, and non-web surfaces used API/CLI/log/docs/workflow evidence instead.\nAccessibility/i18n: accessibility, localization, and human-readable text surfaces were checked where UI, CLI, API message, docs, logs, or review text changed.\nSupply-chain/license: dependency, package, model, container, and external-tool changes were checked in bounded evidence.\nPackaging: package, build, test, lint, and security contracts were checked in bounded evidence.\nSecurity/privacy: workflow-token, review-gate, and repository-automation security/privacy boundaries were checked in bounded evidence.\n","adversarial_validation":{"status":"passed","probes":[{"path":"src/sdp/authz.py","line":121,"hypothesis":"The new HTTPS-only JWKS guard can be bypassed with a non-HTTPS URL (file:///etc/passwd or http://) that urlopen would fetch from the local filesystem, enabling SSRF/local-file read.","attack_or_counterexample":"Call _load_jwks_from_url('file:///etc/passwd') with no env override, expecting a local file read instead of a rejection.","evidence":"Trusted regression test test_oidc_jwks_loader_rejects_non_https_url (tests/test_api.py, current head) observed pytest.raises(ValueError, match='HTTPS') succeed for input file:///etc/passwd \u2014 the local-file counterexample was rejected before any fetch; source trace at src/sdp/authz.py:121 shows the urlopen call is reachable only after the parsed.scheme != 'https' or not parsed.netloc guard raises; Coverage execution evidence reports supported repository test suites passed at head; source-line-sha256=bd23af07df4b1d63459a6e6afe002862f89d5f93439d1007d3807689b3401db5","outcome":"falsified"},{"path":"src/sdp/graph_store.py","line":769,"hypothesis":"The nosemgrep suppression on the semantic-search execution masks a SQL injection where attacker-controlled kind is interpolated into the statement text.","attack_or_counterexample":"Pass kind=\"dataset' OR 1=1--\" through SemanticSearchRequest.kind and observe whether the WHERE fragment becomes attacker-controlled SQL.","evidence":"Trusted source trace at src/sdp/graph_store.py:769 observed conn.execute(sql(stmt), params) receive only closed SQL fragments while every dynamic value (vec, limit, kind) is carried exclusively as a bound parameter (:vec, :limit, :kind) in params, so a hostile kind string such as dataset' OR 1=1-- cannot alter the statement text; the identical bounded pattern is documented at src/sdp/graph_store.py:478 for the AGE cypher call (pg_sql.Literal quoting + JSON bound parameter); source-line-sha256=2e1af0bec90a9e7f1ab1e5039bd352c81120ea90429949c53829cdef98cfc65e","outcome":"falsified"},{"path":"src/sdp/observability.py","line":140,"hypothesis":"The urlopen call in the observability sink is reachable with a file:// SDP_LOG_SINK_URL, re-enabling urllib local file handlers (SSRF / local file access).","attack_or_counterexample":"Set SDP_LOG_SINK_URL=file:///etc/passwd and force the http/https branch to execute the urlopen call.","evidence":"Trusted source trace at src/sdp/observability.py:140 observed the urlopen(UrlRequest(...)) call sit inside the if scheme in {'http','https'} branch; a file:// sink URL evaluates scheme 'file' and can only take the explicit file-path branch or fall through to raise ValueError('unsupported SDP_LOG_SINK_URL scheme'), so the urlopen line is unreachable for local file handlers; source-line-sha256=06364d468d925d9213071e3ece94e50935cacdd01d264ce4d5faff9d367afc65","outcome":"falsified"}],"residual_risk":"SDP_OIDC_JWKS_URL now requires an absolute https URL; any deployment pointing at an http:// JWKS endpoint would fail at runtime with ValueError (deliberate tightening; no repo evidence of such configuration). The new test covers file:// rejection but not http:// or empty-URL rejection \u2014 the guard source at src/sdp/authz.py:116-118 handles those cases identically. The nosemgrep suppressions remain valid only while new dynamic values are added as bound parameters rather than string interpolation. Behavior claims rest on the trusted regression test and source traces because no runtime-tool execution receipts exist for this non-web PR."},"findings":[]}

-->

Changed-File Evidence Map

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

opencode-agent[bot]
opencode-agent Bot previously approved these changes Aug 7, 2026

@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 reviewed the current-head bounded evidence and found no blocking issues.

Findings

No blocking findings.

Summary

Overview: PR #38 (fix: clear repository-wide Semgrep findings) makes four focused changes reviewed at head 95b8424 — (1) src/sdp/authz.py requires an absolute HTTPS URL (urlsplit scheme+netloc guard) before fetching OIDC JWKS, closing a file:// local-file-read/SSRF primitive in _load_jwks_from_url and documenting the remaining urlopen with a justified nosemgrep suppression; (2) src/sdp/graph_store.py adds justified nosemgrep suppressions on the AGE cypher execution (line 478, pg_sql.Literal quoting + JSON bound parameter) and the semantic-search SQL execution (line 769, closed fragments with all dynamic values bound); (3) src/sdp/observability.py adds a justified nosemgrep suppression on the http/https branch of _export_to_sink (lines 139-140), unreachable for file:// schemes because of the scheme allowlist; (4) tests/test_api.py adds regression test test_oidc_jwks_loader_rejects_non_https_url asserting ValueError match HTTPS for file:///etc/passwd. Changed files inspected as changed-file evidence from current-head hunks/CodeGraph: src/sdp/authz.py, src/sdp/graph_store.py, src/sdp/observability.py, tests/test_api.py.

Approval sufficiency: Evidence affirmatively supports the PR intent — every suppression is backed by a source-verified safety argument and the new guard has a regression test; no blocking findings.
Verification posture: Coverage execution evidence reports Result PASS (supported repository test suites passed; docstring gates passed/advisory); repository test command is python3 -m pytest tests; no active failed GitHub Checks at head; mergeStateStatus blocked is branch policy, not a conflict (mergeable: true).
Linter/static: three nosemgrep suppressions added with inline justification comments; targets are scheme-allowlisted urllib calls and parameterized psycopg/SQLAlchemy executions; no bandit/hadolint finding raised in evidence.
TDD/regression: new regression test test_oidc_jwks_loader_rejects_non_https_url (tests/test_api.py) rejects file:///etc/passwd; pre-existing OIDC JWKS verification tests still pass at head per Coverage execution evidence.
Coverage: cite Coverage execution evidence — Result PASS, supported repository test suites passed.
Docstring coverage: configured repository docstring gates passed or advisory per Coverage execution evidence.
DAG: flowchart of the base-to-head changed flow — A["_load_jwks_from_url"] --> B["HTTPS scheme/netloc guard (new)"] --> C["urlopen + nosemgrep"]; D["_export_to_sink"] --> E["scheme in {http,https} allowlist"] --> F["urlopen + nosemgrep"]; G["semantic_search / AGE cypher"] --> H["closed SQL fragments + bound params"] --> I["execute + nosemgrep"]; the diagram reflects head flow where the new guard rejects non-HTTPS JWKS URLs and the suppressed calls are unreachable for unsafe inputs.
PoC/execution: no browser/runtime-tool receipts exist in bounded evidence (non-web PR); guard behavior is established by the trusted regression test plus the source traces cited in the adversarial probes; no OPENCODE_EXECUTION_RECEIPT lines for Playwright/DevTools are present.
DDD/domain: changes remain inside the authz / observability / graph-store domain seams; defense-in-depth hardening, no new domain concepts.
CDD/context: the suppressions and guard are scoped to operator-configured URLs (SDP_OIDC_JWKS_URL, SDP_LOG_SINK_URL) and the parameterized graph-query path.
Similar issues: author conversation comment (2026-08-04) states PR #34 supersedes these hunks in stricter form and #38 is a fallback until #34 merges — historical context only; current-head evidence reports no unresolved non-outdated threads.
Claim/concept check: PR claims (absolute-HTTPS JWKS requirement, justified suppressions, regression test) verified against the current-head diff and source traces.
Standards search: HTTPS-only JWKS fetch aligns with OIDC Discovery/JWKS best practice; the scheme allowlist prevents urllib local file handlers (SSRF / local-file-read class).
Compatibility/convention: no new DB tables, columns, API fields, routes, or config keys; no naming or reserved-word concerns; the added local variable parsed is a short-lived local (exempt).
Breaking-change/backcompat: the HTTPS-only guard deliberately tightens SDP_OIDC_JWKS_URL — deployments using an http:// JWKS endpoint would now raise ValueError; no repo evidence of an http:// JWKS configuration exists; remediation is to serve JWKS over https.
Implementation completeness: guard, suppressions, and regression test are complete executable code; no placeholders (pass/NotImplementedError/TODO) introduced.
Performance: the guard adds a constant-time urlsplit; no material change to SQL/cypher execution paths.
Developer experience: inline nosemgrep justifications document why each finding is safe; the regression test is focused, importable (import sdp.authz as app_authz), and fast.
User experience: non-web API/authn surface — the change affects OIDC bootstrap and observability export, both operator-configured pipelines; no user-facing UI change.
Visual/DOM: non-web change; interaction surface reviewed is the API security path (JWKS fetch), SQL execution paths, and log-sink export path; no Playwright evidence applicable (no OPENCODE_EXECUTION_RECEIPT present).
Accessibility/i18n: no UI/DOM change, no accessibility or i18n surface affected.
Supply-chain/license: no dependency changes; urllib.parse is stdlib; no new packages.
Packaging: no manifest/workflow changes; pyproject python >=3.10 unchanged; no unpackaged source surfaces listed.
Security/privacy: JWKS fetch restricted to https (blocks file:// local reads and http downgrade); cypher and SQL executions confirmed fully parameterized; sink URL scheme allowlisted before urlopen; regression test covers the guard; no secrets touched.

Approval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.
Verification posture: CodeGraph evidence was initialized and bounded current-head evidence reviewed for changed-file evidence including src/sdp/authz.py, src/sdp/graph_store.py, src/sdp/observability.py, tests/test_api.py.
Linter/static: workflow/static review evidence is bounded by the current-head GitHub Checks gate and changed-file evidence.
TDD/regression: coverage execution evidence and focused changed hunks were reviewed from bounded-review-evidence.md.
Coverage: coverage execution evidence reports supported repository test suites passed.
Docstring coverage: coverage execution evidence reports configured repository docstring gates passed or docstring coverage was advisory.
DAG: CodeGraph/source-backed behavior map connects src/sdp/authz.py to the affected review, runtime, or workflow path and required checks.
PoC/execution: coverage-evidence job executed on the current head and reported PASS.
DDD/domain: workflow and repository-governance invariants were reviewed against changed files in bounded evidence.
CDD/context: CodeGraph evidence, changed-file history, and focused hunks were reviewed from bounded-review-evidence.md.
Similar issues: changed-file history evidence was reviewed for comparable local precedents.
Claim/concept check: bounded evidence, repository source, current-head workflow evidence, and, where numeric, scientific, statistical, or literature-backed claims are affected, original-paper/formula evidence and parameter-recovery expectations were used for claims.
Standards search: standards and external-source claims require trusted bounded source evidence prepared outside the isolated model process; no evidence-backed standards blocker is present in bounded evidence.
Compatibility/convention: changed workflow/script conventions, object naming, and reserved-word safety for schema/API/config/code surfaces were checked in bounded evidence.
Breaking-change/backcompat: deployment evidence and changed-file history were checked for backward-compatibility risk.
Performance: changed surfaces were checked for performance risk in bounded evidence.
Developer experience: changed automation, review, test, setup, and maintenance surfaces were checked for helpful or obstructive DX impact in bounded evidence.
User experience: connected user, operator, API, CLI, documentation, review-comment, status-check, rendering, and workflow-reader behavior was checked for contradictions against code, docs, and tests in bounded evidence.
Visual/DOM: deterministic repair does not infer browser runtime execution; source-backed DOM/UI evidence and trusted workflow receipts were reviewed when present, and non-web surfaces used API/CLI/log/docs/workflow evidence instead.
Accessibility/i18n: accessibility, localization, and human-readable text surfaces were checked where UI, CLI, API message, docs, logs, or review text changed.
Supply-chain/license: dependency, package, model, container, and external-tool changes were checked in bounded evidence.
Packaging: package, build, test, lint, and security contracts were checked in bounded evidence.
Security/privacy: workflow-token, review-gate, and repository-automation security/privacy boundaries were checked in bounded evidence.

Adversarial validation

{"status":"passed","probes":[{"path":"src/sdp/authz.py","line":121,"hypothesis":"The new HTTPS-only JWKS guard can be bypassed with a non-HTTPS URL (file:///etc/passwd or http://) that urlopen would fetch from the local filesystem, enabling SSRF/local-file read.","attack_or_counterexample":"Call _load_jwks_from_url('file:///etc/passwd') with no env override, expecting a local file read instead of a rejection.","evidence":"Trusted regression test test_oidc_jwks_loader_rejects_non_https_url (tests/test_api.py, current head) observed pytest.raises(ValueError, match='HTTPS') succeed for input file:///etc/passwd — the local-file counterexample was rejected before any fetch; source trace at src/sdp/authz.py:121 shows the urlopen call is reachable only after the parsed.scheme != 'https' or not parsed.netloc guard raises; Coverage execution evidence reports supported repository test suites passed at head; source-line-sha256=bd23af07df4b1d63459a6e6afe002862f89d5f93439d1007d3807689b3401db5","outcome":"falsified"},{"path":"src/sdp/graph_store.py","line":769,"hypothesis":"The nosemgrep suppression on the semantic-search execution masks a SQL injection where attacker-controlled kind is interpolated into the statement text.","attack_or_counterexample":"Pass kind=\"dataset' OR 1=1--\" through SemanticSearchRequest.kind and observe whether the WHERE fragment becomes attacker-controlled SQL.","evidence":"Trusted source trace at src/sdp/graph_store.py:769 observed conn.execute(sql(stmt), params) receive only closed SQL fragments while every dynamic value (vec, limit, kind) is carried exclusively as a bound parameter (:vec, :limit, :kind) in params, so a hostile kind string such as dataset' OR 1=1-- cannot alter the statement text; the identical bounded pattern is documented at src/sdp/graph_store.py:478 for the AGE cypher call (pg_sql.Literal quoting + JSON bound parameter); source-line-sha256=2e1af0bec90a9e7f1ab1e5039bd352c81120ea90429949c53829cdef98cfc65e","outcome":"falsified"},{"path":"src/sdp/observability.py","line":140,"hypothesis":"The urlopen call in the observability sink is reachable with a file:// SDP_LOG_SINK_URL, re-enabling urllib local file handlers (SSRF / local file access).","attack_or_counterexample":"Set SDP_LOG_SINK_URL=file:///etc/passwd and force the http/https branch to execute the urlopen call.","evidence":"Trusted source trace at src/sdp/observability.py:140 observed the urlopen(UrlRequest(...)) call sit inside the if scheme in {'http','https'} branch; a file:// sink URL evaluates scheme 'file' and can only take the explicit file-path branch or fall through to raise ValueError('unsupported SDP_LOG_SINK_URL scheme'), so the urlopen line is unreachable for local file handlers; source-line-sha256=06364d468d925d9213071e3ece94e50935cacdd01d264ce4d5faff9d367afc65","outcome":"falsified"}],"residual_risk":"SDP_OIDC_JWKS_URL now requires an absolute https URL; any deployment pointing at an http:// JWKS endpoint would fail at runtime with ValueError (deliberate tightening; no repo evidence of such configuration). The new test covers file:// rejection but not http:// or empty-URL rejection — the guard source at src/sdp/authz.py:116-118 handles those cases identically. The nosemgrep suppressions remain valid only while new dynamic values are added as bound parameters rather than string interpolation. Behavior claims rest on the trusted regression test and source traces because no runtime-tool execution receipts exist for this non-web PR."}
  • Result: APPROVE
  • Reason: HTTPS-only OIDC JWKS guard verified by a passing regression test; three Semgrep suppressions confirmed on parameterized/allowlisted execution paths; no active failed checks, no unresolved review threads, and Coverage execution evidence reports supported repository test suites passed at head.
  • Head SHA: 95b8424fde56c2fd58e9732a4572db827fe3632f
  • Workflow run: 31171523051
  • Workflow attempt: 1

@opencode-agent
opencode-agent Bot dismissed their stale review August 7, 2026 13:40

Superseded automated OpenCode approval whose explicit review evidence does not match exact current head cb1644a; a fresh current-head review is required.

@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_api.py (1)

448-450: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

회귀 테스트에서 실제 파일 핸들러를 사용하지 않도록 하세요.

현재 검증이 통과하면 urlopen은 호출되지 않습니다. 그러나 회귀 시 테스트가 호스트의 /etc/passwd 존재와 내용에 의존하고 로컬 파일 읽기를 시도할 수 있습니다. app_authz.urlopen을 fake로 교체하고 호출되면 즉시 실패하도록 하세요. 호스트 없는 HTTPS URL도 테스트 케이스에 추가하세요.

수정 예시
-def test_oidc_jwks_loader_rejects_non_https_url():
+def test_oidc_jwks_loader_rejects_non_https_url(monkeypatch):
+    def fail_if_called(*args, **kwargs):
+        raise AssertionError("urlopen must not be called")
+
+    monkeypatch.setattr(app_authz, "urlopen", fail_if_called)
     with pytest.raises(ValueError, match="HTTPS"):
         app_authz._load_jwks_from_url("file:///etc/passwd")
🤖 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_api.py` around lines 448 - 450, Update
test_oidc_jwks_loader_rejects_non_https_url to replace app_authz.urlopen with a
fake that fails immediately if invoked, then verify the existing file URL is
rejected without accessing the host filesystem. Add a hostless HTTPS URL case to
the same regression test and preserve the ValueError match for both inputs.
🤖 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 `@src/sdp/authz.py`:
- Around line 116-118: Update the JWKS URL validation near urlsplit in the
authorization flow to check parsed.hostname instead of parsed.netloc, while
retaining the HTTPS scheme requirement. Ensure URLs without an actual host,
including empty-host and userinfo-only forms, are rejected before the urlopen
call.

---

Nitpick comments:
In `@tests/test_api.py`:
- Around line 448-450: Update test_oidc_jwks_loader_rejects_non_https_url to
replace app_authz.urlopen with a fake that fails immediately if invoked, then
verify the existing file URL is rejected without accessing the host filesystem.
Add a hostless HTTPS URL case to the same regression test and preserve the
ValueError match for both inputs.
🪄 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: 77c36e2e-4f70-4218-b73d-8742f956e777

📥 Commits

Reviewing files that changed from the base of the PR and between 9a7088e and cb1644a.

📒 Files selected for processing (4)
  • src/sdp/authz.py
  • src/sdp/graph_store.py
  • src/sdp/observability.py
  • tests/test_api.py

Comment thread src/sdp/authz.py
Comment on lines +116 to +118
parsed = urlsplit(jwks_url)
if parsed.scheme != "https" or not parsed.netloc:
raise ValueError("OIDC JWKS URL must be absolute HTTPS")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- target file context ---'
cat -n src/sdp/authz.py | sed -n '85,135p'

printf '%s\n' '--- URL validation and fetch call sites ---'
rg -n -C 3 'jwks_url|urlsplit|urlopen|parsed\.netloc|parsed\.hostname' src tests || true

printf '%s\n' '--- Python URL parsing behavior for edge cases ---'
python3 - <<'PY'
from urllib.parse import urlsplit

urls = [
    "https://:443",
    "https://@",
    "https://user@:443",
    "https://example.com:443",
    "https:///path",
    "https://",
]
for url in urls:
    parsed = urlsplit(url)
    try:
        hostname = parsed.hostname
    except ValueError as exc:
        hostname = f"<ValueError: {exc}>"
    print(f"{url!r}: netloc={parsed.netloc!r}, hostname={hostname!r}")
PY

Repository: ContextualWisdomLab/semantic-data-portal

Length of output: 7237


parsed.hostname으로 JWKS URL의 호스트를 검증하세요.

parsed.netlochttps://:443https://@처럼 실제 호스트가 없는 URL도 통과시킵니다. Line 117에서 not parsed.hostname을 확인하여 해당 URL을 urlopen 호출 전에 거부하세요.

🤖 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 `@src/sdp/authz.py` around lines 116 - 118, Update the JWKS URL validation near
urlsplit in the authorization flow to check parsed.hostname instead of
parsed.netloc, while retaining the HTTPS scheme requirement. Ensure URLs without
an actual host, including empty-host and userinfo-only forms, are rejected
before the urlopen call.

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #51. The surviving replacement carries this PR's complete valid boundary on the current main base: OIDC JWKS is validated through a centralized HTTPS-only outbound policy, remote observability uses the same network boundary while explicit local-file transport stays separate, and the reviewed Apache AGE/observability Semgrep suppressions remain narrowly scoped. #51 also strengthens the contract with non-global-host rejection, exact-head workflow binding, cryptography remediation, differential statement/branch coverage, authoritative doctoring, and fresh exact-head Tests/fuzz/SAST/Security evidence. No checks, reviews, or approvals from #38 transfer to #51.

@seonghobae seonghobae closed this Aug 7, 2026
auto-merge was automatically disabled August 7, 2026 14:42

Pull request was closed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant