Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 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 Sep 10, 2026
1c5049c
experiment: lock send throttle window expiry with regression test
seonghobae Sep 10, 2026
23fa8f7
experiment: format own expiry lines to ruff style
seonghobae Sep 10, 2026
1fcd8b5
experiment: lock org-scope isolation for send throttle
seonghobae Sep 10, 2026
f7390a3
experiment: lock concurrent burst atomicity for send throttle
seonghobae Sep 10, 2026
624ab25
chore(email): reconcile process-local throttle proposal under shared …
seonghobae Sep 10, 2026
7825933
experiment: RED nested-encoding rejection for DAV auth path (#1344)
seonghobae Sep 10, 2026
0bdaf4f
experiment: GREEN single-decode DAV auth path with nested rejection (…
seonghobae Sep 10, 2026
c919883
test(dav): RED framework decode boundary and percent-data cases (#1344)
seonghobae Sep 10, 2026
eafcc8d
fix(dav): honor framework decode boundary and validate raw target (#1…
seonghobae Sep 10, 2026
b6931bf
test(dav): RED missing-raw-path percent fallback (#1344)
seonghobae Sep 10, 2026
42b5af3
fix(dav): fail closed when raw percent provenance is unavailable (#1344)
seonghobae Sep 10, 2026
5506884
docs(dav): trace authorization decode boundary and evidence (#1344)
seonghobae Sep 10, 2026
9a294e3
test(dav): RED split-encoded nested percent sequences (#1344)
seonghobae Sep 10, 2026
1df3aa4
fix(dav): detect second-pass escapes after one wire decode (#1344)
seonghobae Sep 10, 2026
8139be8
docs(dav): record split-encoding RED and causal repair (#1344)
seonghobae Sep 10, 2026
f5fd8a4
test(dav): RED decoded C1 control characters (#1344)
seonghobae Sep 10, 2026
9d0d3af
fix(dav): reject decoded Unicode control characters (#1344)
seonghobae Sep 10, 2026
dcdd606
test(dav): enforce pre-log control rejection after C1 fix (#1344)
seonghobae Sep 10, 2026
9edf307
docs(dav): record decoded-control review repair (#1344)
seonghobae Sep 10, 2026
f29706f
test(dav): cover percent-free missing-raw-path fallback (#1344)
seonghobae Sep 10, 2026
8e6acc7
docs(dav): record missing-raw-path positive acceptance (#1344)
seonghobae Sep 10, 2026
2b55a02
test(dav): reject unsupported capability advertisement (#1344)
seonghobae Sep 10, 2026
6a565ed
fix(dav): advertise only implemented capabilities (#1344)
seonghobae Sep 10, 2026
0990d0f
test(dav): align discovery assertions with operational surface (#1344)
seonghobae Sep 10, 2026
25531c0
test(dav): fold capability regression into owned suite (#1344)
seonghobae Sep 10, 2026
50bd004
docs(dav): record truthful capability discovery boundary (#1344)
seonghobae Sep 10, 2026
cc4838f
docs(dav): trace CalDAV and CardDAV capability tokens (#1344)
seonghobae Sep 10, 2026
4079fc8
docs(dav): distinguish registered from advertised methods (#1344)
seonghobae Sep 10, 2026
68ce68a
test(dav): preserve canonical path and request bounds (#1344)
seonghobae Sep 10, 2026
b2eb1fe
test(dav): isolate canonical path RED (#1344)
seonghobae Sep 10, 2026
cff6e61
fix(dav): propagate canonical path and bound request work (#1344)
seonghobae Sep 10, 2026
a000037
docs(dav): record predecessor succession and URI bounds (#1344)
seonghobae Sep 10, 2026
fe678f6
test(dav): reject unsupported PUT before body read
seonghobae Sep 10, 2026
90ba3d9
fix(dav): reject unsupported PUT before reading body
seonghobae Sep 10, 2026
4709189
docs(dav): trace unsupported PUT body boundary
seonghobae Sep 10, 2026
e0e1469
test(dav): expose PROPFIND depth coercion
seonghobae Sep 10, 2026
377693c
fix(dav): enforce finite PROPFIND depth semantics
seonghobae Sep 10, 2026
0dc0db0
test(dav): make finite PROPFIND depth explicit
seonghobae Sep 10, 2026
a32d65d
test(dav): preserve explicit depth in canonical route regression
seonghobae Sep 10, 2026
5d1f2c8
docs(dav): record finite PROPFIND depth boundary
seonghobae Sep 10, 2026
71b9d04
test(dav): cover depth-zero collection semantics
seonghobae Sep 10, 2026
bcdebd8
docs(dav): trace depth-zero acceptance evidence
seonghobae Sep 10, 2026
268a41a
test(dav): require Depth 1 to include collection resource
seonghobae Sep 10, 2026
e966609
fix(dav): include target collection in Depth 1 response
seonghobae Sep 10, 2026
aec5151
docs(dav): trace Depth 1 target-and-member contract
seonghobae Sep 10, 2026
facfdcc
test(dav): reproduce unbounded depth-one member materialization
seonghobae Sep 10, 2026
b784ce9
fix(webdav): allow bounded project-folder reads
seonghobae Sep 10, 2026
093e748
fix(dav): bound depth-one project member materialization
seonghobae Sep 10, 2026
e281993
test(dav): keep stubs aligned with bounded folder reads
seonghobae Sep 10, 2026
18b1bd7
test(webdav): verify bounded folder query reaches SQL
seonghobae Sep 10, 2026
3411565
docs(dav): trace bounded depth-one member policy
seonghobae Sep 10, 2026
ecbe3c1
test(dav): lock the 256-member boundary
seonghobae Sep 10, 2026
bad0f67
test(dav): make PROPFIND body semantics executable RED
seonghobae Sep 10, 2026
56a77d1
fix(dav): validate bounded PROPFIND request bodies
seonghobae Sep 10, 2026
27b909f
test(dav): cover PROPFIND XML directive boundaries
seonghobae Sep 10, 2026
847e51b
docs(dav): trace PROPFIND body semantics and bounds
seonghobae Sep 10, 2026
79722fe
fix(dav): keep XML parse errors on defused boundary
seonghobae Sep 10, 2026
55f8f14
test(dav): make PROPFIND element-content finding RED
seonghobae Sep 10, 2026
1c133f6
fix(dav): enforce element-only PROPFIND grammar
seonghobae Sep 10, 2026
d2e192e
docs(dav): trace PROPFIND grammar review repairs
seonghobae Sep 10, 2026
cb19cb3
test(dav): make invalid allprop include grammar RED
seonghobae Sep 10, 2026
a4429f9
fix(dav): validate allprop before include refusal
seonghobae Sep 10, 2026
8f1b146
docs(dav): trace allprop include grammar repair
seonghobae Sep 10, 2026
d6385bd
test(dav): reject property values before unsupported PROPFIND modes
seonghobae Sep 10, 2026
fe275cb
fix(dav): validate property-name grammar before PROPFIND refusal
seonghobae Sep 10, 2026
f29325b
docs(dav): trace property-name grammar repair
seonghobae Sep 10, 2026
e5a19a2
test(dav): distinguish XML whitespace from Unicode spaces
seonghobae Sep 10, 2026
39a3e05
fix(dav): enforce XML S in element-only PROPFIND content
seonghobae Sep 10, 2026
8d504a6
docs(dav): trace XML whitespace grammar boundary
seonghobae Sep 10, 2026
c0b154d
test(dav): prove RFC XML extensibility semantics
seonghobae Sep 10, 2026
8d6815d
fix(dav): honor RFC XML extensibility rules
seonghobae Sep 10, 2026
db35735
fix(dav): preserve handler while applying XML extensibility
seonghobae Sep 10, 2026
12cdf29
test(dav): align EMPTY directives with RFC extensibility
seonghobae Sep 10, 2026
df9d057
docs(dav): trace RFC XML extensibility repair
seonghobae Sep 10, 2026
ba992e3
test(dav): require canonical collection hrefs
seonghobae Sep 10, 2026
a755e45
fix(dav): canonicalize collection hrefs
seonghobae Sep 10, 2026
6b2f2be
docs(dav): trace collection URL canonicalization
seonghobae Sep 10, 2026
3c1ba92
test(dav): reject false project member enumeration
seonghobae Sep 10, 2026
dbcad84
fix(dav): fail closed on unknown project members
seonghobae Sep 10, 2026
cf08479
test(dav): keep property-only folder read at depth zero
seonghobae Sep 10, 2026
8c794f1
test(dav): align path regressions with property-only reads
seonghobae Sep 10, 2026
eb9d94d
docs(dav): trace direct project depth boundary
seonghobae Sep 10, 2026
f574524
test(dav): restore finite-depth helper and positive folder read
seonghobae Sep 10, 2026
3a89ec5
docs(dav): record depth harness repair
seonghobae Sep 10, 2026
dd6ac12
test(dav): align canonical-path probe with depth zero
seonghobae Sep 10, 2026
aacaf2c
docs(dav): record hosted depth regression repair
seonghobae Sep 10, 2026
999d741
test(dav): reject collapsed empty path segments
seonghobae Sep 11, 2026
ce7ddd2
test(dav): cover leading empty path segment
seonghobae Sep 11, 2026
d2eb3dc
fix(dav): preserve empty path segment identity
seonghobae Sep 11, 2026
1a81509
docs(dav): record empty-segment route boundary
seonghobae Sep 11, 2026
2b91bce
fix(dav): preserve route-root slash while rejecting empty segments
seonghobae Sep 11, 2026
1e2827b
docs(dav): record empty-segment CI regression repair
seonghobae Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions backend/api/dav.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import re
from html import escape as escape_xml_text
from urllib.parse import unquote

Expand All @@ -14,14 +15,30 @@
router = APIRouter(prefix="/dav", tags=["dav"])


_INVALID_PERCENT_ESCAPE = re.compile(r"%(?![0-9A-Fa-f]{2})")
_NESTED_PERCENT_ESCAPE = re.compile(r"%(?=[0-9A-Fa-f]{2})")


def _normalize_dav_authorization_path(path: str) -> str:
# RFC 3986 §2.4: never decode the same string more than once. Decode
# exactly once, then reject ambiguous nested encodings instead of
# collapsing them with a recursive-unquote loop (CWE-174).
normalized_path = path.replace("\\", "/")
for _ in range(100):
decoded_path = unquote(normalized_path).replace("\\", "/")
if decoded_path == normalized_path:
return normalized_path
normalized_path = decoded_path
raise HTTPException(status_code=400, detail="DAV path decoding limit exceeded")
try:
decoded_path = unquote(normalized_path, errors="strict").replace("\\", "/")
except UnicodeDecodeError as exc:
raise HTTPException(
status_code=400, detail="DAV path contains invalid percent encoding"
) from exc
if _INVALID_PERCENT_ESCAPE.search(decoded_path):
raise HTTPException(
status_code=400, detail="DAV path contains invalid percent encoding"
)
if decoded_path != normalized_path and _NESTED_PERCENT_ESCAPE.search(decoded_path):
raise HTTPException(
status_code=400, detail="DAV path contains nested percent encoding"
)
return decoded_path


def _dav_path_owner_user_id(path: str) -> str | None:
Expand Down
21 changes: 21 additions & 0 deletions backend/tests/test_dav_api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import defusedxml.ElementTree as ET

import pytest
from fastapi import HTTPException
from fastapi.testclient import TestClient

from api.dav import _normalize_dav_authorization_path

from main import app
from services.webdav_service import webdav_service

Expand Down Expand Up @@ -201,3 +204,21 @@ async def run_handler():
found_in_logs = True

assert found_in_logs, "DAV Request log was not found"


@pytest.mark.parametrize(
"path",
[
"/%252e%252e/bob",
"/%25252e%25252e/bob",
"/alice%255c..%255c..%255cbob",
],
)
def test_normalize_dav_authorization_path_rejects_nested_encoding(path: str) -> None:
with pytest.raises(HTTPException):
_normalize_dav_authorization_path(path)


def test_normalize_dav_authorization_path_single_decode_round_trip() -> None:
assert _normalize_dav_authorization_path("/alice/docs") == "/alice/docs"
assert _normalize_dav_authorization_path("/%2e%2e/bob") == "/../bob"
Loading