Skip to content

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

Draft
seonghobae wants to merge 33 commits into
developfrom
fix/dav-single-decode-authorization
Draft

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

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Current authority — frozen predecessor

This Draft is not an active DAV source writer. Fresh succession audit on 2026-09-10 found that its valid concerns split into two later owner paths:

  • workspace-document organization isolation is represented more completely in the ancestry inherited by #1417@489bcbeaf131da123551a187228862b0e47ad549, including _document_organization_filter, current data action/quality-surface regressions, and real PostgreSQL coverage for legacy organization_id IS NULL rows;
  • DAV single-decode, wire ambiguity, control/Unicode validation, capability truth, canonical-path propagation, and bounded request work are now owned by successor fix(dav): harden authorization and bounded PROPFIND semantics #1645, current exact a000037b8fb6433992bfe8c616d0932b3855ec58, stacked on fix(email): enforce shared send throttling #1417.

The earlier exact head 9a019892f7d53f72415fa5633a0facd73815b8f4 remains intact as predecessor evidence. Do not add source, tests, fixtures, docs, or restacks here while #1645 is the active DAV successor.

Succession audit

Two #1345 invariants were found missing from pre-repair #1645 and therefore were not silently discarded:

  1. a framework-decoded backslash representation must be normalized once and the same canonical path must reach both authorization and DAV project routing;
  2. DAV authorization/request-target work must have an explicit resource ceiling.

#1645 captured those as RED 68ce68a952a9cbc027eeda8ff1117322e5209f4b / isolated RED b2eb1fe85b714a49481bd26c06fe62573069e4b5, fixed them in cff6e61d4c96ce077b21517e1c126baaaa607203, and recorded standards/succession evidence in a000037.... The successor now uses one canonical path for owner checks, logging, and PROPFIND routing, caps raw_path at 8192 octets, and bounds the decoded fallback when ASGI omits raw_path.

Other older #1345 choices are intentionally superseded rather than copied blindly. DAV: 1 is not emitted because the partial gateway does not yet satisfy RFC 4918 class-1 MUST requirements. Selected unsupported verbs may remain registered solely for explicit 501 responses; they are not advertised in Allow, which remains OPTIONS, PROPFIND.

Close boundary

Do not close this PR merely because a successor exists. Close is permitted only after #1645's current/final exact head has terminal required execution, all valid findings resolved, qualifying independent review, and a verified succession audit showing that every still-valid #1345 delta/test/fixture/contract/evidence is either preserved by #1645 or represented by its inherited #1417 ancestry. Until then this PR remains open/Draft/frozen as provenance.

Refs #1344.

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 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

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

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.

🚥 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 describes the DAV authorization security fix. It is concise, specific, and accurately reflects a substantive change in the pull request, although it does not mention the separate LLM…
✨ 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.

@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 thread backend/services/llm_provider_urls.py Outdated
@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.

@seonghobae
seonghobae marked this pull request as draft September 5, 2026 01:07

Copy link
Copy Markdown
Contributor Author

Fresh authority correction: the live protected base is develop@042b0c70531b229af3acbd0421a2f23098d848b3 and the actual PR head is now 8146c56587acea5c4aa859ba9366eef0f39540d7, not the older ff6e475... / dd8d151... pair still described in the long-form body. A fresh compare against protected develop is ahead 32 / behind 0 and remains bounded to the eight DAV/data/LLM-provider production/test/doctoring files, so the intervening lineage is preserved rather than destructively rewritten. Exact-head Application CI 33934455424, Bandit 33934455446, Semgrep 33934455467, Security Scan 33934455482, CodeQL PR 33934455461, and Docker 33934455595 are all queued. The current review list has no qualifying independent current-head APPROVED review, while all currently returned inline review threads are resolved. I restored the PR to Draft. Treat the older body wording that calls ff6e475... current and says to keep Ready as historical until the long-form body is reconciled; predecessor checks/reviews do not transfer.

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 bug Something isn't working 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