Skip to content

fix(dav): reject ambiguous nested authorization encodings - #1345

Open
seonghobae wants to merge 31 commits into
developfrom
fix/dav-single-decode-authorization
Open

fix(dav): reject ambiguous nested authorization encodings#1345
seonghobae wants to merge 31 commits into
developfrom
fix/dav-single-decode-authorization

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

This current-base security/interoperability lane contains five bounded fixes on one develop-base owner:

  1. DAV single-decode authorization — authorization consumes the framework-decoded application path exactly once, preserves harmless residual percent data, rejects residual encodings that could become structural on another decode, rejects controls/NUL, normalizes backslashes consistently, and bounds authorization-path processing.
  2. DAV capability truthfulness — authenticated OPTIONS now advertises only WebDAV class 1 plus the actually implemented OPTIONS/PROPFIND methods. Unsupported writeback/richer DAV verbs are not registered and therefore fail with framework 405 Method Not Allowed rather than being falsely advertised and routed to 501 handlers.
  3. LLM provider local-network SSRF boundary — local-provider opt-in is hostname-scoped. Explicit localhost-class identities may resolve to loopback only when ALLOW_LOCAL_LLM_PROVIDERS is enabled; exact allowlisted single-label provider names may resolve only to RFC 1918 IPv4 or RFC 4193 IPv6 unique-local space. Link-local, metadata, multicast, unspecified, reserved, broadcast, and other non-global classes remain denied.
  4. DNS-rebinding loopback closure — an allowlisted non-local hostname cannot gain loopback reachability merely because local-provider mode is enabled. DNS-derived 127.0.0.1/::1 is accepted only when the original validated hostname is itself an explicit local-development identity.
  5. Workspace-document tenant isolation — workspace document lookup and the data-quality repository surface require organization scope in addition to workspace scope. Organization sessions can read only documents owned by that organization; personal sessions can read only organization_id IS NULL documents.

This PR does not include #1206 (CardDAV TXT path single-decode / opaque prompt IDs). Those remain on #1206.

Reconciliation onto current protected develop

  • Previous exact PR head: bde6998fe53c652a4fa1b8f05485aa0f679454d6
  • New exact PR head: ff6e47554a76992f8166d28aca5ffc12bd5e2088
  • Live protected develop: dd8d15191338b841f9e6f3a06507c6a5643b95d0 (includes feat(calendar): prevent status-weighted double booking #1367)
  • Merge: normal merge commit of origin/develop into fix/dav-single-decode-authorization (no squash, no force-push, no dummy commit)
  • Product-file overlap with incoming develop history: none. No DAV/SSRF/tenant-isolation semantic change was required to resolve the merge.
  • Local focused tests on this exact head (PYTHONWARNINGS=error DISABLE_BACKGROUND_WORKERS=1): 82 passed / 0 failed in 0.40s, with no Timeout/Fatal/Warn/Denied output.
    • DAV/API: tests/test_dav_api.py 33 passed + tests/test_dav_normalized_route.py 1 passed = 34
    • Local-provider/SSRF: tests/test_llm_provider_urls.py 43 passed
    • Workspace-document isolation: tests/test_data_document_authorization.py 5 passed

Test-first lineage

DAV authorization

The original DAV RED head 3dc8420d2829b8b2aa772ad969fde3967a3f4d0b changed only backend/tests/test_dav_api.py. The predecessor implementation repeatedly called urllib.parse.unquote() and violated the single-decode authorization contract. Subsequent commits implemented the bounded residual-encoding validator, propagated the normalized path through owner checks/logging/route handling, corrected route-level expectations, and added the backslash route regression.

DAV capability truthfulness

Stale PR #1296 contained a useful capability-truthfulness slice mixed with already-shipped and unrelated work. It was reconstructed on this current develop-base owner rather than merging the stale branch wholesale. RED commit 76b5d16dc681c95422e98dec2cf211c2f1afb5a0 changes the route tests to require only implemented protocol methods and 405 for unregistered verbs. Production commit a094523deb2f83f8020ccfc8badefc48789de613 narrows the router and OPTIONS response while preserving the single-decode/tenant boundary. Temporary Draft #1372 was closed after its head and this head resolved to identical tree 587d9abdc050c4dede21bafd19f9530cba327957, so one owner remains.

Local-provider address classes

Strix on predecessor head f7abb8282333b452fb9b9f67b8f12d9b953cb977 identified the hostname-scoped local-provider SSRF boundary. RED head b66af0d4aa1a9695dd24116d5301cf4759b1484a added regressions proving that allowlisted local providers must reject metadata/link-local, multicast, unspecified, and reserved/broadcast classes. Production restricted the hostname-scoped exception to RFC 1918 IPv4 and RFC 4193 IPv6 unique-local space.

DNS-rebinding loopback closure

A later exact-evidence Strix failure exposed a second-order defect: _validate_global_address() treated loopback as allowed whenever ALLOW_LOCAL_LLM_PROVIDERS was true, regardless of the original hostname. RED head 95f23c25ced6e850aba0de9b4ad1c6f4ff6c24ee added realistic DNS-resolution regressions for an allowlisted remote hostname and an allowlisted container hostname rebinding to 127.0.0.1; the focused backend run failed exactly those two tests. Production head d73e9e4e1d117ee005ace91dfe289afe31ae7132 bound loopback admission to explicit local-development hostname identity while preserving private container-network access. Follow-up test-contract head 7052b66b14f54a3c3115a11c429963708501e8a7 makes the helper-level identity requirement explicit and preserves the localhost success path.

Workspace-document tenant isolation

Strix on predecessor head df2bf10b2f724fa83bb10f3742c5c73796e19161 reported a CWE-639 cross-organization IDOR: _get_workspace_document filtered document_id and workspace_id but not organization_id. The same root cause existed in the document slice of the data-quality repository surface. RED head c05855f90250f2f7271af86888a4579b39375c53 added focused authorization regressions; production adds the organization predicate to both query sites.

Current exact candidate

  • Protected base: develop@dd8d15191338b841f9e6f3a06507c6a5643b95d0.
  • Current head: ff6e47554a76992f8166d28aca5ffc12bd5e2088.
  • GitHub now reports this PR Ready (not Draft) and mergeable against live develop. mergeStateStatus remains BLOCKED until independent non-author current-head APPROVE and exact-head product CI succeed.
  • The current diff remains bounded to the DAV/data/LLM-provider production/tests plus associated doctoring. No fix(security,api): opaque prompt IDs and CardDAV single-decode #1206 CardDAV TXT / opaque prompt-ID files are in this candidate.
  • Predecessor-evidence-does-not-transfer: historical/dismissed predecessor-head reviews, stale required-check rollups, and prior mergeability on bde6998f / a094523d do not transfer to ff6e47554a76992f8166d28aca5ffc12bd5e2088.
  • There is no qualifying independent non-author approval on this current head.

Standards and claim boundary

The DAV authorization design follows RFC 3986 §2.4 single-decoding semantics and treats CWE-174-style double decoding as an authorization hazard. Capability discovery now limits protocol claims to behavior actually implemented by this endpoint; provider-backed DAV mutation remains explicitly out of scope until a separately reviewed writeback implementation provides the required source/capability/concurrency controls. The network boundary distinguishes explicit local-development identity, intentionally private container networking, and special-purpose/non-unicast address classes rather than treating all non-global addresses as interchangeable. Workspace organization ownership remains the tenant boundary already carried by AuthContext and Document.organization_id.

Merge boundary

Do not merge until this unchanged exact head has every live required workflow/check terminal-success, zero actionable review threads, a qualifying independent non-author current-head APPROVE, and normal protected-branch/ruleset acceptance without bypass. Pending, queued, skipped-required, neutral-required, failed, stale, predecessor-head, rate-limited, status-only, author-only, or model-only evidence is non-passing. Exact-head product CI must pass independently on ff6e47554a76992f8166d28aca5ffc12bd5e2088.

This update does not APPROVE, merge, squash, force-push develop, dummy-commit, empty-requeue, or force-cancel.

Customer next action

Keep this PR Ready. Wait for exact-head product CI and required security/governance checks to finish on ff6e47554a76992f8166d28aca5ffc12bd5e2088. An independent non-author reviewer must APPROVE that exact head. Do not treat predecessor reviews or the previous behind-base mergeability as transferable. Do not merge until those current-head gates pass.

Refs #1344.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes (focused 82 passed / 0 failed on this head)
  • Any dependent changes have been merged and published in downstream modules

Open in Devin Review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

Next included review available in 58 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 32e6a4f7-ae1b-4785-969b-885b206e5bc1

📥 Commits

Reviewing files that changed from the base of the PR and between 7cfa1bf and 8c6a51e.

📒 Files selected for processing (5)
  • backend/api/dav.py
  • backend/services/llm_provider_urls.py
  • backend/tests/test_data_document_authorization.py
  • backend/tests/test_dav_api.py
  • backend/tests/test_llm_provider_urls.py
📝 Walkthrough

Walkthrough

The PR adds bounded DAV authorization-path normalization, restricts allowlisted local provider hostnames to approved private networks, and adds organization scoping to document queries. Tests and boundary documentation cover the updated behavior.

Changes

DAV authorization path validation

Layer / File(s) Summary
Bounded authorization path normalization
backend/api/dav.py
The API validates path length and control characters, normalizes backslashes, rejects ambiguous nested encodings, and removes iterative percent-decoding.
Normalized path authorization and routing
backend/api/dav.py
Authorization, logging, PROPFIND, and PUT handling use the normalized path.
Normalization and route validation tests
backend/tests/test_dav_api.py, backend/tests/test_dav_normalized_route.py
Tests cover valid percent data, nested structural encodings, control characters, backslashes, length limits, encoded traversal, logging rejection, route handling, and PROPFIND output.

Local provider network boundaries

Layer / File(s) Summary
Private network resolution validation
backend/services/llm_provider_urls.py, backend/tests/test_llm_provider_urls.py
Allowlisted local provider hostnames are limited to RFC 1918 IPv4 and RFC 4193 IPv6 networks. Tests reject disallowed special-purpose addresses and accept IPv6 unique-local addresses.
Boundary decision documentation
docs/doctoring/dav-and-local-provider-network-boundaries.md
The decision document defines DAV path rules, local-provider network rules, verification requirements, limitations, rollback procedures, and RFC references.

Document organization authorization

Layer / File(s) Summary
Organization-scoped document access
backend/api/data.py, backend/tests/test_data_document_authorization.py
Workspace and quality-surface document queries apply organization ownership filters. Tests cover cross-organization rejection, same-organization access, and personal-scope rejection.

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

Merge Risk: 🟡 Moderate · up to 7cfa1

This PR tightens DAV authorization decoding, local-provider network restrictions, and organization-scoped document access. It is not merge-ready until qualifying fresh independent review and every live required check succeed on the unchanged current head; the remaining branch-coverage request is a non-blocking follow-up.

Sequence Diagram(s)

sequenceDiagram
  participant DAVClient
  participant DAVHandler
  participant PathNormalizer
  participant DAVAuthorization
  participant PROPFINDHandler
  DAVClient->>DAVHandler: send DAV request with authorization path
  DAVHandler->>PathNormalizer: validate and normalize path
  PathNormalizer-->>DAVHandler: normalized path or HTTP error
  DAVHandler->>DAVAuthorization: authorize normalized path
  DAVAuthorization-->>DAVHandler: authorization result
  DAVHandler->>PROPFINDHandler: process normalized path
  PROPFINDHandler-->>DAVClient: WebDAV response
Loading
🚥 Pre-merge checks | ✅ 4
✅ 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 The title clearly and concisely describes the DAV authorization fix for ambiguous nested encodings.
✨ 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 fix/dav-single-decode-authorization

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.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

PR governance metadata gate is not ready for 8c6a51e6777203ff3d0db84ebfa837f0c399cf6e:

  • 2 unresolved current review thread(s) remain.
  • Required check strix is FAILURE on the current head.

Base automatically changed from fix/text-safety-malformed-comment to develop August 14, 2026 19:08
@seonghobae
seonghobae marked this pull request as ready for review August 14, 2026 19:11
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Current-head review disposition for the remaining DAV test comment: not a valid code/test defect on 0a897dbac65a903901fada99c55051c3b6a41a61. The exact-head Application CI run 31837061501, backend Python 3.14 job 94885594338, checked out this head and ran the unfiltered python -m pytest -q; it completed successfully with the route regression expecting 403. At the route boundary the ASGI/TestClient stack has already decoded the nested request target to a literal traversal segment. The production path therefore rejects the literal .. through owner/traversal validation and returns the observed fail-closed 403; _normalize_dav_authorization_path() does not receive the residual %2e%2e string assumed by the review. Changing the assertion to 400 would contradict the exercised production route. No source change is warranted for that finding. A fresh current-head automated review is still required before merge; this disposition is not an approval or a protection bypass.

@coderabbitai review

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 14, 2026
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

@seonghobae: The disposition is valid. The route receives a literal .. segment after ASGI/TestClient decoding. The owner/traversal validation then rejects the request with HTTP 403. The prior expectation of HTTP 400 does not apply to this route-level case.

I have withdrawn that finding. I will run a fresh automated review of the current head.

🐇


✏️ Learnings added
Learnt from: seonghobae
URL: https://github.com/ContextualWisdomLab/naruon/pull/1345

Timestamp: 2026-08-14T20:27:16.019Z
Learning: In the DAV route tests, the ASGI/TestClient stack can decode a nested encoded request target before application authorization-path processing. For an encoded traversal sequence that becomes a literal `..` segment at the route boundary, the DAV owner/traversal validation rejects the request fail-closed with HTTP 403. `_normalize_dav_authorization_path()` does not receive the residual percent-encoded traversal text in that case.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

No files to review.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

No files to review.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]

This comment was marked as resolved.

@seonghobae
seonghobae dismissed stale reviews from coderabbitai[bot], coderabbitai[bot], and coderabbitai[bot] August 15, 2026 04:03

Superseded by later exact-head fixes. The DAV normalized-path finding is resolved and its review thread records it as addressed. This dismissal clears stale predecessor-head review state only; a fresh qualifying current-head independent approval remains required.

@cursor

cursor Bot commented Aug 17, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

cursor Bot pushed a commit that referenced this pull request Aug 17, 2026
Bring Ready PR #1206 onto protected develop@dd8d15191338b841f9e6f3a06507c6a5643b95d0
with a normal merge commit. Preserve opaque prompt_uid, CardDAV single-pass
decode, and OIDC-only admin roles. Do not pull #1345 workspace-document isolation.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
Bring protected develop@dd8d15191338b841f9e6f3a06507c6a5643b95d0
(#1367 calendar conflicts and later develop history) into
fix/dav-single-decode-authorization without changing DAV single-decode
authorization, capability advertisement, local-provider SSRF, or
workspace-document organization isolation semantics.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
@seonghobae

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review the unchanged exact current head ff6e47554a76992f8166d28aca5ffc12bd5e2088 (normal merge of live develop@dd8d1519). Predecessor bde6998f evidence does not transfer. Local DAV/SSRF/tenant tests: 82 passed. Independent non-author APPROVE is still required; this comment is not an approval.

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

@seonghobae I will review pull request #1345 at exact head ff6e47554a76992f8166d28aca5ffc12bd5e2088. I will treat predecessor-head evidence as non-current. I will not approve the pull request from this request.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

  • Head SHA: ff6e47554a76992f8166d28aca5ffc12bd5e2088

  • Workflow run: 32116669104

  • 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["Backend (7 files)"]
  S1 --> I1["API and service runtime"]
  I1 --> R1["Review risk: Backend (7 files)"]
  R1 --> V1["backend tests"]
  Evidence --> S2["Docs: dav-and-local-provider-network-boundaries.md"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs: dav-and-local-provider-network-boundaries.md"]
  R2 --> V2["docs review"]
Loading

@opencode-agent

opencode-agent Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 89d885084fec5510c53c8cab992bf1f41c6abd55
  • Workflow run: 32420608708
  • 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 89d885084fec5510c53c8cab992bf1f41c6abd55.

  • Head SHA: 89d885084fec5510c53c8cab992bf1f41c6abd55

  • Workflow run: 32420608708

  • 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["Backend (7 files)"]
  S1 --> I1["API and service runtime"]
  I1 --> R1["Review risk: Backend (7 files)"]
  R1 --> V1["backend tests"]
  Evidence --> S2["Docs: dav-and-local-provider-network-boundaries.md"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs: dav-and-local-provider-network-boundaries.md"]
  R2 --> V2["docs review"]
Loading

@seonghobae
seonghobae enabled auto-merge (squash) August 20, 2026 20:09

@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 89d885084fec5510c53c8cab992bf1f41c6abd55.

  • Head SHA: 89d885084fec5510c53c8cab992bf1f41c6abd55

  • Workflow run: 32420608708

  • 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["Backend (7 files)"]
  S1 --> I1["API and service runtime"]
  I1 --> R1["Review risk: Backend (7 files)"]
  R1 --> V1["backend tests"]
  Evidence --> S2["Docs: dav-and-local-provider-network-boundaries.md"]
  S2 --> I2["operator or user guidance"]
  I2 --> R2["Review risk: Docs: dav-and-local-provider-network-boundaries.md"]
  R2 --> V2["docs review"]
Loading

@opencode-agent
opencode-agent Bot disabled auto-merge August 21, 2026 08:16
@seonghobae
seonghobae enabled auto-merge (squash) August 21, 2026 08:42
@opencode-agent
opencode-agent Bot disabled auto-merge August 21, 2026 11:46
@seonghobae
seonghobae dismissed stale reviews from opencode-agent[bot] and opencode-agent[bot] August 22, 2026 07:50

Verified: coverage-evidence and other non-metadata-gate required checks on the current head now report pass (gh pr checks). The coverage-evidence failure this review cited has since cleared on rerun. Dismissing as superseded.

@opencode-agent opencode-agent Bot added area: auth Authentication, authorization, identity, or tenant isolation priority: medium Normal-priority or P2 work status: blocked Blocked by conflict, dependency, or required prerequisite type: bug Defect or incorrect behavior labels Aug 22, 2026

@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 potential issues.

Open in Devin Review

Comment thread backend/api/dav.py
Comment on lines +38 to +47
def _has_ambiguous_percent_encoding(path: str) -> bool:
"""Detect residual encodings that another decode would make structural."""

for index, character in enumerate(path):
if character != "%":
continue
octet = _residual_percent_octet(path, index)
if octet in _DAV_STRUCTURAL_OCTETS:
return True
return False

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: Double-encoded harmless data now kept literal

_residual_percent_octet follows %25 chains and preserves double-encoded non-structural values (e.g. %2541) as literal text instead of decoding them to A, unlike the previous recursive unquote loop. Downstream folder_uid/segment matching sees the literal percent text. This is the stated single-decode design, noted for callers that relied on full decoding.

Open in 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.

Confirmed as intended — this is the stated single-decode contract of this PR. _residual_percent_octet (backend/api/dav.py:23-35) exists only to find octets that another decode would turn structural (_DAV_STRUCTURAL_OCTETS checked at backend/api/dav.py:44-46); harmless double-encoded text such as %2541 deliberately stays literal so downstream folder_uid/segment matching compares the exact client-sent bytes rather than a second-decoded value an attacker could smuggle. The contract is documented in docs/architecture/dav-and-local-provider-network-boundaries.md and covered by tests/test_dav_normalized_route.py (77 passed: uv run pytest tests/test_dav_api.py tests/test_dav_normalized_route.py tests/test_llm_provider_urls.py -q).

Comment on lines +83 to +89
def _is_local_provider_network_address(
address: ipaddress.IPv4Address | ipaddress.IPv6Address,
) -> bool:
return any(
address.version == network.version and address in network
for network in _LOCAL_PROVIDER_NETWORKS
)

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: Allowlisted-host private range narrowed

_is_local_provider_network_address admits only 10/8, 172.16/12, 192.168/16, and fc00::/7 for allowlisted local providers, where the prior code accepted any is_private result. Container networks outside these ranges that previously worked will now be rejected.

Open in 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.

Confirmed as intended hardening. _is_local_provider_network_address (backend/services/llm_provider_urls.py:83-90) now admits only the explicit local ranges in _LOCAL_PROVIDER_NETWORKS (10/8, 172.16/12, 192.168/16, fc00::/7) for already-allowlisted local provider hosts. The previous acceptance of any is_private address also admitted non-global surprises (e.g. 100.64/10 CGNAT, 198.18/15 benchmark, ::1/unique-local variants outside fc00::/7); narrowing to enumerated CIDRs makes the SSRF boundary deterministic and is covered by the expanded tests/test_llm_provider_urls.py cases in this PR.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Strix finding disposition (check-run 97109339574, model nvidia_nim/nvidia/nemotron-3-super-120b-a12b):

MEDIUM "Authentication Bypass via Session Verifier Override in AuthContext" — REBUTTED (false positive on unchanged trusted context).

The report itself is conditional ("If there exists a code path where..."); no such path exists:

  1. Sole production constructor: _auth_context_from_session_payload (backend/api/auth.py:507-527) always passes session_verifier explicitly (session_verifier=cast(SessionVerifier, session_verifier), line 526) from verified-token material.
  2. That value originates only from _verify_signed_session_token (backend/api/auth.py:370-407), which returns exactly "oidc" (line 379, RS256 JWKS path) or "hmac" (line 407, HS256 path). No JWT claim feeds it: claims are extracted through fixed allowlist helpers (sub, role, org, groups, workspace), and crit headers are rejected.
  3. Therefore every real HMAC/OIDC session fails the tenant-admin gate at backend/api/auth.py:515 (hmac/oidc ∉ {"server", "override"}) — deny-by-default, matching repo policy that signed sessions cannot carry tenant-admin roles.
  4. Direct AuthContext(...) constructions exist only under backend/tests/** (conftest.py:70, test_auth_real.py, test_dav_api.py, test_security_api.py, test_observability_api.py, test_data_document_authorization.py) as documented FastAPI dependency-override fixtures (see docstring backend/api/auth.py:181-189). No dataclasses.replace/kwargs construction paths exist.
  5. This file is not part of PR fix(dav): reject ambiguous nested authorization encodings #1345's diff (dav/data/llm_provider_urls only); the flag came from whole-context scanning of pre-existing code. Removing the documented test-fixture default would churn 8+ test files without closing any reachable path.

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

Labels

area: auth Authentication, authorization, identity, or tenant isolation priority: medium Normal-priority or P2 work status: blocked Blocked by conflict, dependency, or required prerequisite type: bug Defect or incorrect behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants