-
Notifications
You must be signed in to change notification settings - Fork 1
fix(dav): harden authorization and bounded PROPFIND semantics #1645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
seonghobae
wants to merge
93
commits into
fix/email-shared-send-rate-limit
Choose a base branch
from
fix/dav-single-decode-1344
base: fix/email-shared-send-rate-limit
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 13 commits
Commits
Show all changes
93 commits
Select commit
Hold shift + click to select a range
b39a4b8
experiment: bound send throttle scopes with expired eviction
seonghobae 1c5049c
experiment: lock send throttle window expiry with regression test
seonghobae 23fa8f7
experiment: format own expiry lines to ruff style
seonghobae 1fcd8b5
experiment: lock org-scope isolation for send throttle
seonghobae f7390a3
experiment: lock concurrent burst atomicity for send throttle
seonghobae 624ab25
chore(email): reconcile process-local throttle proposal under shared …
seonghobae 7825933
experiment: RED nested-encoding rejection for DAV auth path (#1344)
seonghobae 0bdaf4f
experiment: GREEN single-decode DAV auth path with nested rejection (…
seonghobae c919883
test(dav): RED framework decode boundary and percent-data cases (#1344)
seonghobae eafcc8d
fix(dav): honor framework decode boundary and validate raw target (#1…
seonghobae b6931bf
test(dav): RED missing-raw-path percent fallback (#1344)
seonghobae 42b5af3
fix(dav): fail closed when raw percent provenance is unavailable (#1344)
seonghobae 5506884
docs(dav): trace authorization decode boundary and evidence (#1344)
seonghobae 9a294e3
test(dav): RED split-encoded nested percent sequences (#1344)
seonghobae 1df3aa4
fix(dav): detect second-pass escapes after one wire decode (#1344)
seonghobae 8139be8
docs(dav): record split-encoding RED and causal repair (#1344)
seonghobae f5fd8a4
test(dav): RED decoded C1 control characters (#1344)
seonghobae 9d0d3af
fix(dav): reject decoded Unicode control characters (#1344)
seonghobae dcdd606
test(dav): enforce pre-log control rejection after C1 fix (#1344)
seonghobae 9edf307
docs(dav): record decoded-control review repair (#1344)
seonghobae f29706f
test(dav): cover percent-free missing-raw-path fallback (#1344)
seonghobae 8e6acc7
docs(dav): record missing-raw-path positive acceptance (#1344)
seonghobae 2b55a02
test(dav): reject unsupported capability advertisement (#1344)
seonghobae 6a565ed
fix(dav): advertise only implemented capabilities (#1344)
seonghobae 0990d0f
test(dav): align discovery assertions with operational surface (#1344)
seonghobae 25531c0
test(dav): fold capability regression into owned suite (#1344)
seonghobae 50bd004
docs(dav): record truthful capability discovery boundary (#1344)
seonghobae cc4838f
docs(dav): trace CalDAV and CardDAV capability tokens (#1344)
seonghobae 4079fc8
docs(dav): distinguish registered from advertised methods (#1344)
seonghobae 68ce68a
test(dav): preserve canonical path and request bounds (#1344)
seonghobae b2eb1fe
test(dav): isolate canonical path RED (#1344)
seonghobae cff6e61
fix(dav): propagate canonical path and bound request work (#1344)
seonghobae a000037
docs(dav): record predecessor succession and URI bounds (#1344)
seonghobae fe678f6
test(dav): reject unsupported PUT before body read
seonghobae 90ba3d9
fix(dav): reject unsupported PUT before reading body
seonghobae 4709189
docs(dav): trace unsupported PUT body boundary
seonghobae e0e1469
test(dav): expose PROPFIND depth coercion
seonghobae 377693c
fix(dav): enforce finite PROPFIND depth semantics
seonghobae 0dc0db0
test(dav): make finite PROPFIND depth explicit
seonghobae a32d65d
test(dav): preserve explicit depth in canonical route regression
seonghobae 5d1f2c8
docs(dav): record finite PROPFIND depth boundary
seonghobae 71b9d04
test(dav): cover depth-zero collection semantics
seonghobae bcdebd8
docs(dav): trace depth-zero acceptance evidence
seonghobae 268a41a
test(dav): require Depth 1 to include collection resource
seonghobae e966609
fix(dav): include target collection in Depth 1 response
seonghobae aec5151
docs(dav): trace Depth 1 target-and-member contract
seonghobae facfdcc
test(dav): reproduce unbounded depth-one member materialization
seonghobae b784ce9
fix(webdav): allow bounded project-folder reads
seonghobae 093e748
fix(dav): bound depth-one project member materialization
seonghobae e281993
test(dav): keep stubs aligned with bounded folder reads
seonghobae 18b1bd7
test(webdav): verify bounded folder query reaches SQL
seonghobae 3411565
docs(dav): trace bounded depth-one member policy
seonghobae ecbe3c1
test(dav): lock the 256-member boundary
seonghobae bad0f67
test(dav): make PROPFIND body semantics executable RED
seonghobae 56a77d1
fix(dav): validate bounded PROPFIND request bodies
seonghobae 27b909f
test(dav): cover PROPFIND XML directive boundaries
seonghobae 847e51b
docs(dav): trace PROPFIND body semantics and bounds
seonghobae 79722fe
fix(dav): keep XML parse errors on defused boundary
seonghobae 55f8f14
test(dav): make PROPFIND element-content finding RED
seonghobae 1c133f6
fix(dav): enforce element-only PROPFIND grammar
seonghobae d2e192e
docs(dav): trace PROPFIND grammar review repairs
seonghobae cb19cb3
test(dav): make invalid allprop include grammar RED
seonghobae a4429f9
fix(dav): validate allprop before include refusal
seonghobae 8f1b146
docs(dav): trace allprop include grammar repair
seonghobae d6385bd
test(dav): reject property values before unsupported PROPFIND modes
seonghobae fe275cb
fix(dav): validate property-name grammar before PROPFIND refusal
seonghobae f29325b
docs(dav): trace property-name grammar repair
seonghobae e5a19a2
test(dav): distinguish XML whitespace from Unicode spaces
seonghobae 39a3e05
fix(dav): enforce XML S in element-only PROPFIND content
seonghobae 8d504a6
docs(dav): trace XML whitespace grammar boundary
seonghobae c0b154d
test(dav): prove RFC XML extensibility semantics
seonghobae 8d6815d
fix(dav): honor RFC XML extensibility rules
seonghobae db35735
fix(dav): preserve handler while applying XML extensibility
seonghobae 12cdf29
test(dav): align EMPTY directives with RFC extensibility
seonghobae df9d057
docs(dav): trace RFC XML extensibility repair
seonghobae ba992e3
test(dav): require canonical collection hrefs
seonghobae a755e45
fix(dav): canonicalize collection hrefs
seonghobae 6b2f2be
docs(dav): trace collection URL canonicalization
seonghobae 3c1ba92
test(dav): reject false project member enumeration
seonghobae dbcad84
fix(dav): fail closed on unknown project members
seonghobae cf08479
test(dav): keep property-only folder read at depth zero
seonghobae 8c794f1
test(dav): align path regressions with property-only reads
seonghobae eb9d94d
docs(dav): trace direct project depth boundary
seonghobae f574524
test(dav): restore finite-depth helper and positive folder read
seonghobae 3a89ec5
docs(dav): record depth harness repair
seonghobae dd6ac12
test(dav): align canonical-path probe with depth zero
seonghobae aacaf2c
docs(dav): record hosted depth regression repair
seonghobae 999d741
test(dav): reject collapsed empty path segments
seonghobae ce7ddd2
test(dav): cover leading empty path segment
seonghobae d2eb3dc
fix(dav): preserve empty path segment identity
seonghobae 1a81509
docs(dav): record empty-segment route boundary
seonghobae 2b91bce
fix(dav): preserve route-root slash while rejecting empty segments
seonghobae 1e2827b
docs(dav): record empty-segment CI regression repair
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # DAV authorization-path decoding boundary | ||
|
|
||
| ## Decision | ||
|
|
||
| Naruon authorizes DAV paths against the Unicode `path` value supplied by ASGI routing and does not percent-decode that value again. The original request-target bytes, when supplied as ASGI `raw_path`, are used only to validate wire-level percent syntax and reject encodings whose first decode would leave another `%HH` sequence. Backslashes are normalized to `/` before owner/traversal checks. | ||
|
|
||
| This separates two representations that must not be conflated: | ||
|
|
||
| - `raw_path`: original request-target path bytes. It is the only representation that can distinguish a literal percent encoded as `%25` from malformed raw `%` syntax or an ambiguous nested encoding such as `%252e`. | ||
| - `path`: framework-decoded Unicode path. Authorization, owner extraction and traversal checks consume this representation without another percent-decoding pass. | ||
|
|
||
| The ASGI HTTP specification defines `path` as having percent-encoded and UTF-8 byte sequences decoded into characters. It defines `raw_path` as the original path bytes and notes that `raw_path` is optional. Therefore a second application `unquote()` over a route parameter is not a neutral normalization step: it can create a second interpretation of data that the framework has already decoded. | ||
|
|
||
| RFC 3986 §2.4 warns that implementations must not decode the same string more than once. CWE-174 describes the corresponding weakness as double decoding that can introduce dangerous input after an earlier validation step. | ||
|
|
||
| ## Failure lineage | ||
|
|
||
| Issue #1344 identified the prior recursive-unquote loop as the wrong authorization boundary. PR #1645 initially replaced the loop with one explicit `unquote()`, but its helper-only tests did not account for framework decoding. That intermediate state also rejected legitimate encoded-percent data because `%25` became `%` and was then classified as a malformed escape. | ||
|
|
||
| The current repair is intentionally narrower: | ||
|
|
||
| 1. `c91988398414db3cd0226749aaa336c268543dbc` adds route-level RED cases for framework-decoded nested traversal, literal encoded percent data, malformed raw escapes, encoded control characters, invalid UTF-8 replacement, single-decode traversal, and a large non-recursive path. | ||
| 2. `eafcc8d4720e58bb04d3029774e5c31f38a6e1e0` removes application percent-decoding from authorization normalization and validates raw request-target syntax before owner checks. | ||
| 3. ASGI specifies that `raw_path` may be absent. `b6931bf55a4a3320e103b417ee384c82f12444ac` therefore adds a second RED case: if raw provenance is unavailable and the decoded path still contains `%`, Naruon must not guess whether that percent came from valid encoded data, malformed wire syntax or a nested encoding. | ||
| 4. `42b5af38f21231aa4cad5f2e0ce0768f81fc1ee4` makes that fallback fail closed while permitting percent-free decoded paths on ASGI servers that omit `raw_path`. | ||
|
|
||
| The old `0bdaf4fedc001fe43326fa67390f05f83a718238` checks were admitted while #1645 targeted `develop`; they are historical after the PR was retargeted to canonical parent #1417 and do not certify the current `(PR, base ref/SHA, head SHA)` identity. | ||
|
|
||
| ## Invariants | ||
|
|
||
| - Authorization never recursively percent-decodes a framework route value. | ||
| - Malformed raw percent triplets fail with HTTP 400 when `raw_path` is available. | ||
| - A raw `%25` that would decode to a literal percent is allowed when it does not form a second `%HH` sequence. | ||
| - A raw encoding such as `%252e` or `%2525` that would expose another valid percent triplet is rejected as ambiguous before authorization. | ||
| - Percent-encoded C0/DEL control characters fail before owner or DAV operation handling. | ||
| - Invalid UTF-8 replacement/surrogate values in the framework-decoded path fail before authorization. | ||
| - `.` and `..` segments, including those produced by the framework's single decode and Windows-separator normalization, remain unauthorized. | ||
| - If `raw_path` is unavailable, a residual `%` in the decoded path fails closed because its wire provenance cannot be established. Percent-free decoded paths remain supported. | ||
| - The normalization path is linear in input length; there is no recursive or fixed-round decode loop. | ||
|
|
||
| ## Reproducible acceptance | ||
|
|
||
| The owned executable acceptance is `backend/tests/test_dav_api.py`. Required evidence includes raw, singly encoded and nested traversal cases; encoded-percent data; malformed triplets; slash/backslash variants; encoded controls and invalid Unicode; route-level TestClient behavior; and a large input that exercises the non-recursive path. The current exact branch must run this test plus Ruff and the repository security/CI gates after every source or document change. A predecessor-head pass is not current-head evidence. | ||
|
|
||
| Issue #1344 stays open until current-base exact-head hosted checks, current-head independent review and all valid findings are complete. This document does not claim protected integration, release, deployment, broader DAV writeback support, or a security certification. | ||
|
|
||
| ## References | ||
|
|
||
| ASGI Team. (n.d.). *HTTP & WebSocket ASGI message format*. ASGI 3.0 documentation. Retrieved September 10, 2026, from https://asgi.readthedocs.io/en/latest/specs/www.html | ||
|
|
||
| Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform Resource Identifier (URI): Generic Syntax* (RFC 3986). Internet Engineering Task Force. https://doi.org/10.17487/RFC3986 | ||
|
|
||
| The MITRE Corporation. (n.d.). *CWE-174: Double Decoding of the Same Data* (CWE List Version 4.20). Common Weakness Enumeration. Retrieved September 10, 2026, from https://cwe.mitre.org/data/definitions/174.html |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.