feat(sast): detect bearer DNS validation TOCTOU - #1080
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughPython ChangesBearer DNS TOCTOU 탐지
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🟡 Moderate · up to The detector can currently misclassify vulnerable Python urllib flows by either connecting unrelated control-flow branches or missing Requests whose Bearer header follows other supported arguments. That could reduce security finding accuracy, so the PR is not merge-ready until these matching boundaries are corrected and covered by regression tests. Sequence Diagram(s)sequenceDiagram
participant push_scan
participant URLValidator
participant urllibRequest
participant urllibOpener
participant DNSResolver
push_scan->>URLValidator: URL 사전 검증
URLValidator-->>push_scan: 검증 결과
push_scan->>urllibRequest: Bearer Authorization 요청 생성
push_scan->>urllibOpener: 요청 전송
urllibOpener->>DNSResolver: 호스트명 재조회
DNSResolver-->>urllibOpener: 연결 주소 반환
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 38.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 99 functions across 12 files. (3 skipped: 3 unsupported.) ✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| def test_inverse_boolean_guard_cannot_borrow_one_line_bearer_state(tmp_path): | ||
| source = """\ | ||
| def deliver(url, api_key, enabled): | ||
| if not _is_safe_url(url): | ||
| return None | ||
| endpoint = url.rstrip("/") + "/api/v1/scans" | ||
| req = urllib.request.Request(endpoint) | ||
| if enabled: | ||
| req.add_header("Authorization", f"Bearer {api_key}") | ||
| if not enabled: | ||
| return urllib.request.urlopen(req, timeout=5) | ||
| return None | ||
| """ | ||
| assert _family_findings(tmp_path, source) == [] |
Security objective
Preserve the DNS-rebinding flaw fixed by security issue #892 / PR #898 as an executable AppGuardrail detector obligation instead of treating the merged runtime repair as the end of the incident.
Source authority
The source-backed vulnerable shape is protected-history predecessor
develop@42a1ae16a8352c727b2a4a34f8a74eef32cff49c, where_push_findingsfirst called_is_safe_url(url), then derived an endpoint, created anurllib.request.RequestcarryingAuthorization: Bearer ..., and finally dispatched that request throughurllib. The validation and connection therefore made separate DNS decisions. PR #898 (e8e4631f363da5a30b691f42261624db945088e0, protected mergec395459ed20cf47207cf5f8842f3c78a3a4c1298) replaced that preflight-only boundary withpost_json_pinned_https, which connects to the validated address set while preserving TLS hostname verification.Detector
Adds a HIGH CWE-367/CWE-918 detector family with unique packaged rule identities:
python-bearer-preflight-dns-toctoucovers Bearer authorization supplied directly by the trackedurllib.request.Request.python-bearer-preflight-dns-toctou-header-mutationcovers the same validated-destination race when Bearer authorization is added or restored on that live request after construction.python-bearer-preflight-dns-toctou-multiline-constructorcovers the corresponding reviewed line-wrapped constructor credential syntax.python-bearer-preflight-dns-toctou-multiline-header-mutationcovers reviewed line-wrapped post-construction credential mutations, including flows where the trackedRequestconstructor itself is also line-wrapped.python-bearer-preflight-dns-toctou-dynamic-bearer-replacementcovers a bounded post-construction case where a local replacement value is itself provably derived from a Bearer expression before being applied to the live Request.python-bearer-preflight-dns-toctou-unredirected-header-persistencecovers the urllib-specific case whereadd_unredirected_headerstores Bearer Authorization inunredirected_hdrs, so ordinary mutations ofreq.headersdo not actually remove the live credential.None of these rules match issue text. Together they bind, inside one Python function, the reusable failure path:
_is_safe_url(url)preflight;urllib.request.Requestwhose actual destination remains that endpoint;headers=argument or a later supported request-header mutation; andurllib/reviewed-opener dispatch of that live request, which can resolve the hostname again after the check.The credential-source subrules are mutually exclusive for the reviewed overlap. If the Request is already directly Bearer-authenticated, a simple later Bearer update remains a single primary-rule defect; the mutation subrule becomes authoritative for that Request only after a supported explicit Authorization removal followed by Bearer restoration. Opaque Authorization replacement, whole-header-map replacement/clearing, and unrelated
Request.full_urlmutation terminate stale credential or destination provenance rather than inheriting constructor state forever when those operations actually affect the active credential store. If a replacement variable is locally provable asBearer ..., the bounded dynamic-replacement companion preserves detection. An initially unauthenticated Request that later receives Bearer authorization remains mutation-rule positive. Foradd_unredirected_header, ordinaryreq.headers.clear(),req.headers = {},req.headers.pop("Authorization", ...), or regular-map Authorization overwrite do not remove the separate unredirected credential store;remove_header("Authorization")remains the supported universal removal boundary. The multiline companions preserve the same destination, request-identity, credential-state, and reachability barriers instead of treating wrapped formatting as a separate defect class. This preserves one family finding for one path without suppressing remove→restore or mutation-only flaws.Current-head review also exposed two bounded exhaustive-control-flow false positives in the direct-Bearer primary rule. A direct two-arm
if/elseimmediately before the outerurlopen(req)is now treated as sanitized only when both arms explicitly callreq.remove_header("Authorization"); one-sided removal remains positive because a Bearer-bearing path still reaches the sink. Likewise, a direct two-armif/elsewhere both arms terminate withreturn/raisemakes the following outer sink unreachable; if either arm can fall through, the finding remains. These are deliberately narrow proofs, not claims of general Python path analysis.The family is deliberately bounded to the reviewed Python
urllibshapes. Other HTTP libraries, cross-function request construction, non-_is_safe_urlvalidators, custom transports that independently enforce pinning, and substantially different helper-mediated flow remain separate obligations rather than speculative HIGH findings.Regression corpus
tests/fixtures/security_corpus/appguardrail_bearer_dns_toctou_vulnerable.pypreserves the historical POST preflight-then-second-resolution credential path.tests/fixtures/security_corpus/appguardrail_bearer_dns_toctou_fixed.pypreserves the reviewed pinned-HTTPS repair.tests/test_bearer_dns_toctou_rule.pyexecutes the production_scan_filepath and fixes false-positive boundaries for unauthenticated urllib delivery, validation without dispatch, sibling-function evidence donation, formatting variants, reassignment, and reviewed dispatch forms.tests/test_bearer_dns_toctou_review_boundaries.pypreserves destination/header provenance boundaries: request-body Authorization text is not header evidence; the tracked endpoint must be the actual Request URL; unrelated validated-URL replacement breaks provenance; and same-branch credential removal does not sanitize an opposite branch.tests/test_bearer_dns_toctou_restored_credentials.pycovers direct post-construction Bearer mutations, remove/restore flows, non-Bearer negatives, and replacement/reachability barriers.tests/test_bearer_dns_toctou_latest_review_regressions.pyrequires unique packaged IDs, keepsdata=/method=POST arguments visible before directheaders=, rejects nested fixed-destination Bearer request replacement, and preserves a nested replacement that still targets the validated endpoint.tests/test_bearer_dns_toctou_family_dedup.pyexecutes the production scanner and requires exactly one family finding for initially Bearer-authenticated Requests followed byadd_header,add_unredirected_header, or direct Authorization replacement, while preserving mutation-only and remove→restore positives.tests/test_bearer_dns_toctou_multiline_regressions.pyexecutes the multiline companion rules through production_scan_file, with paired vulnerable/sanitized cases for endpoint and request replacement, credential removal/non-Bearer replacement, unreachable dispatch, self-derived provenance, remove→restore, and fully multiline Request-constructor plusadd_header/add_unredirected_header/direct-header-assignment flows.tests/test_bearer_dns_toctou_request_state_mutations.pyexecutes production_scan_filefor opaque credential replacement, whole-header replacement/clearing, fixed versus self-derivedfull_url, provable Bearer-valued replacement variables, and packaged dynamic-rule identity.tests/test_bearer_dns_toctou_unredirected_persistence.pyexecutes production_scan_filefor Bearer credentials installed inunredirected_hdrs, proving ordinary-header clear/replacement/pop/overwrite do not sanitize that store whileremove_header("Authorization")terminates it.tests/test_bearer_dns_toctou_exhaustive_branch_regressions.pyexecutes production_scan_filefor paired exhaustive-vs-partial credential-removal and branch-termination cases, requiring no family finding only when every direct two-arm route sanitizes or terminates before the outer sink.docs/TRACEABILITY.mdrecords prevention versus detector maturity plus the paired false-positive/false-negative boundaries, including that endpoint replacement breaks destination provenance only before the Request is bound and that urllib's normal and unredirected header stores require distinct credential-state handling.CHANGELOG.d/892-bearer-dns-toctou-detector.mdrecords the detector, post-construction request-state provenance, dynamic Bearer replacement, and unredirected-header persistence boundaries as security capabilities.Exact candidate
678c77d6cfb7e8f40ce2b53f1de5b07b839bd95c.865b0e1a236e04aabfb51b8c0551fb22c5561deb.678c77d6cfb7e8f40ce2b53f1de5b07b839bd95c.Merge boundary
Do not merge from predecessor checks. Only the exact current head is authoritative. The repository
Security Processworkflow installs the pinned CodeGraph CLI and runspython3 scanner/cli/appguardrail.py scan --codegraph .; its exact-head result is therefore the CodeGraph conditional-gate evidence for this PR. Merge or auto-merge only after required checks are terminal-success, current-head review findings are reconciled, qualifying independent approval exists, and normal protected-branch policy accepts the PR. No force-push, protection bypass, self-approval, warning suppression, or detector waiver is authorized.Summary by CodeRabbit
보안
urllibBearer 인증 요청에서 URL 사전 검증과 DNS 확인 사이의 TOCTOU 취약점을 탐지합니다.테스트