BLOCKER: DAV XML has no element-depth bound #785

Open
opened 2026-10-02 13:10:40 +00:00 by kayg · 13 comments
Owner

Protocol audit assigned under #663; source base c4a61e8cf0. Source review only; no hostile payload, live exploit or crash-threshold measurement. The owner rule blocks authorization holes and crash/DoS risks. No product code is changed by this audit.

Evidence on the base:

  • crates/calternal-dav/src/protocol.rs:298, :391, :1132 parse request
    bytes with xmltree::Element::parse. REPORT accepts up to 512 KiB
    (:269, :1128). PROPFIND and PROPPATCH have smaller byte caps.
  • crates/calternal-dav/src/files.rs:419, :430 buffer PROPFIND and LOCK
    up to 64 KiB, then pass them to dav-server. Its 0.11.0 handle_props.rs:300
    and handle_lock.rs:117 also use xmltree without a depth check.
  • Locked xmltree 0.12.0 src/lib.rs:199–232 calls build recursively for
    each child. Locked xml 1.4.0 reader/config.rs:109–124 bounds entities,
    names, attributes and text, but has no element-depth field. A body byte cap
    does not provide a safe call-stack bound.

Impact: an authenticated DAV request can reach input-dependent recursion on
the server worker stack. Stack exhaustion can terminate the process. This is
a crash/DoS risk under the owner merge rule. It persists in round-7a. This is
not an external-entity disclosure finding: xml's external entity declarations
are unsupported, and entity expansion has separate default bounds.

Repair: use one shared streaming XML validation boundary before tree parsing
and before dav-server. Bound element depth, node count, text and attributes.
Reject DTDs for these DAV request grammars. Set a small decoded-data budget.
Validate using parser events, not a byte scan, so encodings cannot bypass it.

Regression coverage: all XML-consuming DAV methods; valid Apple requests;
element-depth and node-budget boundaries; alternate encodings; entity/DTD
rejection; malformed documents; and a normal follow-up request after rejection.
An HTTP Depth header test does not test XML element depth.

Dependency evidence: xmltree 0.12.0 source.
Versions also match Cargo.lock and the local registry source. No dependency
was added or upgraded.

Duplicate check: searched all issue states for XML depth, connection cap, SSE revocation and MCP session. Read related #457, #328, #329 and #668. No matching repair issue identified. Track the repair with source-level tests and a safe local validation of the repaired boundary. Do not close this issue from the audit job.

Protocol audit assigned under #663; source base c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Source review only; no hostile payload, live exploit or crash-threshold measurement. The owner rule blocks authorization holes and crash/DoS risks. No product code is changed by this audit. Evidence on the base: - `crates/calternal-dav/src/protocol.rs:298`, `:391`, `:1132` parse request bytes with `xmltree::Element::parse`. REPORT accepts up to 512 KiB (`:269`, `:1128`). PROPFIND and PROPPATCH have smaller byte caps. - `crates/calternal-dav/src/files.rs:419`, `:430` buffer PROPFIND and LOCK up to 64 KiB, then pass them to dav-server. Its 0.11.0 `handle_props.rs:300` and `handle_lock.rs:117` also use xmltree without a depth check. - Locked xmltree 0.12.0 `src/lib.rs:199–232` calls `build` recursively for each child. Locked xml 1.4.0 `reader/config.rs:109–124` bounds entities, names, attributes and text, but has no element-depth field. A body byte cap does not provide a safe call-stack bound. Impact: an authenticated DAV request can reach input-dependent recursion on the server worker stack. Stack exhaustion can terminate the process. This is a crash/DoS risk under the owner merge rule. It persists in round-7a. This is not an external-entity disclosure finding: xml's external entity declarations are unsupported, and entity expansion has separate default bounds. Repair: use one shared streaming XML validation boundary before tree parsing and before dav-server. Bound element depth, node count, text and attributes. Reject DTDs for these DAV request grammars. Set a small decoded-data budget. Validate using parser events, not a byte scan, so encodings cannot bypass it. Regression coverage: all XML-consuming DAV methods; valid Apple requests; element-depth and node-budget boundaries; alternate encodings; entity/DTD rejection; malformed documents; and a normal follow-up request after rejection. An HTTP Depth header test does not test XML element depth. Dependency evidence: [xmltree 0.12.0 source](https://docs.rs/xmltree/0.12.0/src/xmltree/lib.rs.html). Versions also match Cargo.lock and the local registry source. No dependency was added or upgraded. Duplicate check: searched all issue states for XML depth, connection cap, SSE revocation and MCP session. Read related #457, #328, #329 and #668. No matching repair issue identified. Track the repair with source-level tests and a safe local validation of the repaired boundary. Do not close this issue from the audit job.
Author
Owner

Started protofix on job/protofix. Base and HEAD: 2f4482ded0 (job/merge-round-7a). Read #785–#789 and sec-protocols/audit-findings.md. Scope: shared DAV XML validation, shared listener caps, live credential validity, SSE authorization lifetime, legacy MCP owner binding. No push or deployment. Validation uses bounded defensive tests; no crash or exploit reproduction.

Started protofix on job/protofix. Base and HEAD: 2f4482ded066d9c5d9c59130377907f7fd2916c9 (job/merge-round-7a). Read #785–#789 and sec-protocols/audit-findings.md. Scope: shared DAV XML validation, shared listener caps, live credential validity, SSE authorization lifetime, legacy MCP owner binding. No push or deployment. Validation uses bounded defensive tests; no crash or exploit reproduction.
Author
Owner

DAV has five direct xmltree request parses on this base plus WebDAV PROPFIND and LOCK forwarding. They now share an iterative decoded-event validation boundary before recursive parsing. Limits: depth 32, 8192 nodes, 64 attributes per element, 512 KiB total decoded strings; DTDs denied. xml 1.4.0 was already locked and cargo info verified its version and MIT license. Counter boundary tests avoid invoking excessive trees on the old parser.
Validation is in progress. No completion or live exploit claim.

DAV has five direct xmltree request parses on this base plus WebDAV PROPFIND and LOCK forwarding. They now share an iterative decoded-event validation boundary before recursive parsing. Limits: depth 32, 8192 nodes, 64 attributes per element, 512 KiB total decoded strings; DTDs denied. xml 1.4.0 was already locked and cargo info verified its version and MIT license. Counter boundary tests avoid invoking excessive trees on the old parser. Validation is in progress. No completion or live exploit claim.
Author
Owner

Progress: committed the SSE performance profile as 8020fede1. A perf-VM reference run under /root/perf.lock and the existing HDD emulator opened 50/50 streams: setup p50 23.3 ms, p95 33.3 ms, mean RSS 139014483 bytes, peak CPU 67.88%. Shared pre-fix release: /root/perf-stream-668/calternal-server. This is a reference run, not a fixed-build result. Rust guard/cap/credential and session changes are being checked; the first fresh auth test build is still compiling dependencies on the shared host (load 115). Web locked dependencies installed; web check is running. Read-only code review is running.

Progress: committed the SSE performance profile as 8020fede1. A perf-VM reference run under /root/perf.lock and the existing HDD emulator opened 50/50 streams: setup p50 23.3 ms, p95 33.3 ms, mean RSS 139014483 bytes, peak CPU 67.88%. Shared pre-fix release: /root/perf-stream-668/calternal-server. This is a reference run, not a fixed-build result. Rust guard/cap/credential and session changes are being checked; the first fresh auth test build is still compiling dependencies on the shared host (load 115). Web locked dependencies installed; web check is running. Read-only code review is running.
Author
Owner

Read-only review found two MCP lifetime problems in the first implementation: response-time owner insertion could miss cancelled initialization, and inbound-only idle tracking could close an active SDK session. Replaced them with OwnedSessions/OwnedTransport. Ownership is inserted synchronously at SDK allocation and admission is retained through worker/session cleanup; SDK inactivity remains unchanged. Added cancellation, close/DELETE, and private-context tests. Review confirmed the revised rmcp 3.5.0 API shape. Added unconditional rmcp formatting-layer filter because SDK tracing emits session IDs even at info; RUST_LOG cannot re-enable it.
Final sync: fetched origin once; origin/dev was already included. Re-merged origin/job/merge-round-7a; only adversarial route classification entries arrived. No changes to test expectations. Web check: svelte-check found 0 errors and 0 warnings. Initial web test: 5 deadline failures (TimeGrid clock cycle, Agenda dense rows, ThemePicker Auto variant, collaborationUndo, StatRow mode); 1068 passed. A run with two workers and 30-second deadlines is checking load effects; assertions are unchanged. Rust dependency build remains active; server clippy is queued on its target lock.

Read-only review found two MCP lifetime problems in the first implementation: response-time owner insertion could miss cancelled initialization, and inbound-only idle tracking could close an active SDK session. Replaced them with OwnedSessions/OwnedTransport. Ownership is inserted synchronously at SDK allocation and admission is retained through worker/session cleanup; SDK inactivity remains unchanged. Added cancellation, close/DELETE, and private-context tests. Review confirmed the revised rmcp 3.5.0 API shape. Added unconditional rmcp formatting-layer filter because SDK tracing emits session IDs even at info; RUST_LOG cannot re-enable it. Final sync: fetched origin once; origin/dev was already included. Re-merged origin/job/merge-round-7a; only adversarial route classification entries arrived. No changes to test expectations. Web check: svelte-check found 0 errors and 0 warnings. Initial web test: 5 deadline failures (TimeGrid clock cycle, Agenda dense rows, ThemePicker Auto variant, collaborationUndo, StatRow mode); 1068 passed. A run with two workers and 30-second deadlines is checking load effects; assertions are unchanged. Rust dependency build remains active; server clippy is queued on its target lock.
Author
Owner

Progress: committed bounded loopback lifecycle probe f5f8bb8d9; no excessive XML or connection load is sent. Web check passed with 0 errors and 0 warnings. Initial web test run had five 5-second timeouts on the shared loaded host; bounded retry with two workers and a 30-second timeout passed all 154 files and 1073 tests without assertion or fixture changes. Production web build passed. Rust fmt check passed; first auth regression compile remains in dependencies and server clippy is queued on that target lock. Host load recorded 135.33/149.15/135.60 at 16:44 CEST.

The new protocol authority bench profile ran on the locked perf VM with HDD emulation and the shared #668 reference release binary (not the fixed build): one stream p50/p95 2.7/2.7 ms, mean CPU 3.4%, mean RSS 129793779 bytes; 50 streams p50/p95 51.4/64.2 ms, mean CPU 2.87%, mean RSS 137289929 bytes. Each ran 15 seconds, all streams HTTP 200. Loads inside the lock were 0.08/0.44/0.48 and 0.58/0.54/0.51. Fixed-build validation and comparative measurement remain pending; these numbers are not claimed as after results.

Progress: committed bounded loopback lifecycle probe f5f8bb8d9; no excessive XML or connection load is sent. Web check passed with 0 errors and 0 warnings. Initial web test run had five 5-second timeouts on the shared loaded host; bounded retry with two workers and a 30-second timeout passed all 154 files and 1073 tests without assertion or fixture changes. Production web build passed. Rust fmt check passed; first auth regression compile remains in dependencies and server clippy is queued on that target lock. Host load recorded 135.33/149.15/135.60 at 16:44 CEST. The new protocol authority bench profile ran on the locked perf VM with HDD emulation and the shared #668 reference release binary (not the fixed build): one stream p50/p95 2.7/2.7 ms, mean CPU 3.4%, mean RSS 129793779 bytes; 50 streams p50/p95 51.4/64.2 ms, mean CPU 2.87%, mean RSS 137289929 bytes. Each ran 15 seconds, all streams HTTP 200. Loads inside the lock were 0.08/0.44/0.48 and 0.58/0.54/0.51. Fixed-build validation and comparative measurement remain pending; these numbers are not claimed as after results.
Author
Owner

Validation progress at 17:27 CEST: HEAD fde2b73d4, bounded probes and bench changes committed. Rust fixes are implemented and fmt passes, but the first calternal-auth regression build has spent almost two hours compiling fresh dependencies on the shared host (load 139.88/127.97/127.14 at 17:23). No Rust diagnostic yet. The queued server clippy was cancelled only to reorder gates: auth clippy/full test, DAV gates, then server gates. Rust fix commits await the touched-crate gates. Web check, bounded full test retry (154 files / 1073 tests) and production build passed. The optional reference lifecycle run on the perf VM could not obtain its lock, so it did not run; no result is claimed. Fixed-build lifecycle and comparative measurements remain pending. The four-hour job limit will be respected, with any remaining checks stated explicitly.

Validation progress at 17:27 CEST: HEAD fde2b73d4, bounded probes and bench changes committed. Rust fixes are implemented and fmt passes, but the first calternal-auth regression build has spent almost two hours compiling fresh dependencies on the shared host (load 139.88/127.97/127.14 at 17:23). No Rust diagnostic yet. The queued server clippy was cancelled only to reorder gates: auth clippy/full test, DAV gates, then server gates. Rust fix commits await the touched-crate gates. Web check, bounded full test retry (154 files / 1073 tests) and production build passed. The optional reference lifecycle run on the perf VM could not obtain its lock, so it did not run; no result is claimed. Fixed-build lifecycle and comparative measurements remain pending. The four-hour job limit will be respected, with any remaining checks stated explicitly.
Author
Owner

protofix #785–#789

Built:

  • One iterative DAV XML boundary before all recursive request parsers and dav-server PROPFIND/LOCK entry points; budgets cover depth, retained nodes, decoded data, parser allocation and DTD exclusion.
  • Shared non-waiting per-IP/per-User permits across implicit IMAP, STARTTLS and Notes submission; canonical IP identity and cancellation/disconnection release.
  • Shared current App Password lookup for Notes IMAP/submission, Events delivery and protocol leases; expiry, revocation, User disablement/deletion and grant scope changes end retained authority.
  • Shared authenticated SSE body guard with per-chunk checks and a quiet one-second lease. Includes current admin Role and Plugin/surface switches; existing body permits and #665–#668 primitives remain in place.
  • Owner-bound legacy MCP SDK session allocation, guard checks on continuation/replay/DELETE, transport-owned admission through cancellation and cleanup, native SDK inactivity semantics and unconditional suppression of SDK routing-handle logs.
  • Counter/authority/cap/transport regressions, a bounded owned-credential loopback lifecycle probe, and one/fifty-stream authority bench profile.

Files: crates/calternal-auth/src/store.rs; crates/calternal-dav/{Cargo.toml,src/{lib,files,protocol,request_xml}.rs}; crates/calternal-server/src/{main,wire,notes_imap,notes_submission,mcp,mcp_events,mcp_sessions,protocol_authority}.rs; Cargo.lock; tests/adversarial/protocol_lifecycle.py; bench/sse_storm.py.

Decisions not fixed by DESIGN: DAV budgets depth 32, nodes 8192 and decoded bytes 512 KiB; DTD is not part of accepted request grammar. Quiet SSE authority lease one second. Legacy MCP transport admission capped at 1024 active sessions; cleanup holds slots until SDK worker termination. Security timers skip missed ticks instead of repeating outdated reads. Native SDK inactivity timer remains authoritative.

UX gaps closed / left: not applicable; no UI source or behavior changes. No screenshots were required.

HEAD: b3e7c14adb (job/protofix). The worktree is clean. origin/dev and origin/job/merge-round-7a were merged once before final gates; no push or deploy.

Validation is incomplete. The first focused auth build took 188 minutes on the shared host. The focused grant regression passed before the later read-pool refinement and added closed-writer assertion. Full auth clippy remained in dependency checks; DAV and server clippy waited on cache/build locks. These commands were stopped for the four-hour limit. Full crate tests, the fixed-server lifecycle round, and fixed-build before/after performance results remain undone. These commits are not merge-ready. No existing test assertions or fixtures were changed to get a pass.

Gate output, verbatim:

cargo fmt --check: no output; exit 0 (final HEAD).

Focused auth regression (earlier revision):

test store::tests::active_grant_checks_identity_expiry_revocation_and_user_state ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 89 filtered out; finished in 1.44s

Web check:

svelte-check found 0 errors and 0 warnings

Web test bounded retry (two workers, 30-second deadline):

 Test Files  154 passed (154)
      Tests  1073 passed (1073)
   Start at  16:18:42
   Duration  827.75s (transform 33%, import 27%, environment 22%, tests 13%, setup 5%)

The initial web run had five 5-second timeouts; the retry changed runner settings only. Production web build passed. Python probe/profile syntax checks passed.

Incomplete Rust gates ended with dependency checks (auth) or:

    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on build directory

Performance: docs/perf/runs/2026-10-02-protocol-authority-788-reference.json records the locked HDD reference runs, not the fixed build. One stream: p50/p95 2.7/2.7 ms, mean CPU 3.4%, mean RSS 129793779 bytes. Fifty streams: p50/p95 51.4/64.2 ms, mean CPU 2.87%, mean RSS 137289929 bytes. Both ran 15 seconds with all HTTP 200; loads were 0.08/0.44/0.48 and 0.58/0.54/0.51. The existing fifty-stream baseline has p95 31.4 ms, peak CPU 51.91%, peak RSS 136183808 bytes. Routes and sample windows differ, so this is reference data only. The optional reference lifecycle check did not acquire the VM lock and did not run.

Final doc comments reviewed. The query uses the existing read pool; stream checks share immutable identity/route data. SDK inactivity remains native. Cleanup output:

     Removed 3520 files, 1.5GiB total

Web build output and generated Python caches were removed. Remaining validation is handed back explicitly; no issue is closed.

protofix #785–#789 Built: - One iterative DAV XML boundary before all recursive request parsers and dav-server PROPFIND/LOCK entry points; budgets cover depth, retained nodes, decoded data, parser allocation and DTD exclusion. - Shared non-waiting per-IP/per-User permits across implicit IMAP, STARTTLS and Notes submission; canonical IP identity and cancellation/disconnection release. - Shared current App Password lookup for Notes IMAP/submission, Events delivery and protocol leases; expiry, revocation, User disablement/deletion and grant scope changes end retained authority. - Shared authenticated SSE body guard with per-chunk checks and a quiet one-second lease. Includes current admin Role and Plugin/surface switches; existing body permits and #665–#668 primitives remain in place. - Owner-bound legacy MCP SDK session allocation, guard checks on continuation/replay/DELETE, transport-owned admission through cancellation and cleanup, native SDK inactivity semantics and unconditional suppression of SDK routing-handle logs. - Counter/authority/cap/transport regressions, a bounded owned-credential loopback lifecycle probe, and one/fifty-stream authority bench profile. Files: crates/calternal-auth/src/store.rs; crates/calternal-dav/{Cargo.toml,src/{lib,files,protocol,request_xml}.rs}; crates/calternal-server/src/{main,wire,notes_imap,notes_submission,mcp,mcp_events,mcp_sessions,protocol_authority}.rs; Cargo.lock; tests/adversarial/protocol_lifecycle.py; bench/sse_storm.py. Decisions not fixed by DESIGN: DAV budgets depth 32, nodes 8192 and decoded bytes 512 KiB; DTD is not part of accepted request grammar. Quiet SSE authority lease one second. Legacy MCP transport admission capped at 1024 active sessions; cleanup holds slots until SDK worker termination. Security timers skip missed ticks instead of repeating outdated reads. Native SDK inactivity timer remains authoritative. UX gaps closed / left: not applicable; no UI source or behavior changes. No screenshots were required. HEAD: b3e7c14adbf4a6824f9ca5a6a8224ffcf77e214f (job/protofix). The worktree is clean. origin/dev and origin/job/merge-round-7a were merged once before final gates; no push or deploy. Validation is incomplete. The first focused auth build took 188 minutes on the shared host. The focused grant regression passed before the later read-pool refinement and added closed-writer assertion. Full auth clippy remained in dependency checks; DAV and server clippy waited on cache/build locks. These commands were stopped for the four-hour limit. Full crate tests, the fixed-server lifecycle round, and fixed-build before/after performance results remain undone. These commits are not merge-ready. No existing test assertions or fixtures were changed to get a pass. Gate output, verbatim: cargo fmt --check: no output; exit 0 (final HEAD). Focused auth regression (earlier revision): ``` test store::tests::active_grant_checks_identity_expiry_revocation_and_user_state ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 89 filtered out; finished in 1.44s ``` Web check: ``` svelte-check found 0 errors and 0 warnings ``` Web test bounded retry (two workers, 30-second deadline): ``` Test Files 154 passed (154) Tests 1073 passed (1073) Start at 16:18:42 Duration 827.75s (transform 33%, import 27%, environment 22%, tests 13%, setup 5%) ``` The initial web run had five 5-second timeouts; the retry changed runner settings only. Production web build passed. Python probe/profile syntax checks passed. Incomplete Rust gates ended with dependency checks (auth) or: ``` Blocking waiting for file lock on package cache Blocking waiting for file lock on build directory ``` Performance: docs/perf/runs/2026-10-02-protocol-authority-788-reference.json records the locked HDD reference runs, not the fixed build. One stream: p50/p95 2.7/2.7 ms, mean CPU 3.4%, mean RSS 129793779 bytes. Fifty streams: p50/p95 51.4/64.2 ms, mean CPU 2.87%, mean RSS 137289929 bytes. Both ran 15 seconds with all HTTP 200; loads were 0.08/0.44/0.48 and 0.58/0.54/0.51. The existing fifty-stream baseline has p95 31.4 ms, peak CPU 51.91%, peak RSS 136183808 bytes. Routes and sample windows differ, so this is reference data only. The optional reference lifecycle check did not acquire the VM lock and did not run. Final doc comments reviewed. The query uses the existing read pool; stream checks share immutable identity/route data. SDK inactivity remains native. Cleanup output: ``` Removed 3520 files, 1.5GiB total ``` Web build output and generated Python caches were removed. Remaining validation is handed back explicitly; no issue is closed.
Author
Owner

Independent read-only review started on job/rev2-protofix. Review branch base: 440e19dce2. Target: b3e7c14ad. I will inspect source and tests without builds, servers or probes, and commit review-protofix.md and audit-findings.md. The author report is excluded from the review inputs.

Independent read-only review started on job/rev2-protofix. Review branch base: 440e19dce23040ac8ebaae88f0469b6535b1afcb. Target: b3e7c14ad. I will inspect source and tests without builds, servers or probes, and commit review-protofix.md and audit-findings.md. The author report is excluded from the review inputs.
Author
Owner

Independent read-only review complete. Target: b3e7c14ad. The author report
was not used. Branch: job/rev2-protofix. Review head:
4e739be474.

Built: review-protofix.md and audit-findings.md only, in two atomic
commits. No product code changed. No P1 defect was confirmed. Findings:

  1. P2 — SSE session checks use the single writer.
    crates/calternal-server/src/protocol_authority.rs:125 calls the SELECT
    at crates/calternal-auth/src/store.rs:2548 for each chunk and quiet
    lease. That SELECT uses the one-connection writer pool. Use the existing
    read pool and test with a separate, unavailable writer. Evidence was
    added to #788. This is source-level contention, not a measured regression.
  2. P2 — XML tests do not protect the parser boundary.
    crates/calternal-dav/src/request_xml.rs:115 tests counters directly;
    :134 never crosses a budget boundary with a document.
    tests/adversarial/protocol_lifecycle.py:130 covers only root PROPFIND.
    Removing event accounting or Files validation can leave these tests
    passing. Add bounded depth/node/decoded-data tests through the parser
    and each XML-consuming route, alternate encodings and a normal follow-up.
    This evidence stays on #785, which owns the XML repair.
  3. P2 — MCP owner denial lacks an HTTP regression.
    tests/adversarial/protocol_lifecycle.py:43 uses only one credential.
    Removing crates/calternal-server/src/mcp.rs:1399 can leave the added
    equality and lifecycle tests passing. Test another User and a second
    credential of the same User on POST, GET/resume and DELETE; the original
    owner must still succeed. Evidence was added to #789.

Issue list: #785, #788 and #789. Searches found these existing repair owners;
no new issue was created and no issue was closed.

Source checks: DAV parses share the iterative validator; all Notes listeners
share caps; retained Notes authority checks expiry; SSE bodies check current
authority; MCP handles bind to User and credential identity before SDK use.
No confirmed new authorization bypass, data loss or corruption was found in
the assigned paths. No weakened existing test expectation was found in the
protocol-fix commits. Module comments state the repair invariants.

Known gaps: runtime behavior is not verified. The added tests do not cover
real Notes expiry during commands/IDLE/SMTP, all live-listener failure paths,
or the full SSE Plugin/scope/event matrix. The target's DESIGN §58 contains
Agent discovery; the performance citation in #788 does not match that
section. No latency target was invented. Exact-version docs.rs lookups
failed; locked versions and matching local registry source were inspected.

Verification output, verbatim:

Already up to date.

This is the output of the one required origin update. git diff --check
exited 0 with no output. Rust/web gates were not run under the explicit
LIGHT prohibition; there is no build/test gate output to quote. No server,
browser, probe, performance measurement or cleanup command was run.

For the merge round: run cargo fmt --check, and per-crate clippy/test for
calternal-auth, calternal-dav and calternal-server. Run
python3 tests/adversarial/protocol_lifecycle.py --server "$CALTERNAL_SERVER_BIN"
to prove stream termination and DAV follow-up. Add the missing focused
regressions in the build job; the current lifecycle script cannot prove them.
The committed review lists each exact command.

Decisions: reuse existing repair issues; rank missing security regressions
as P2 coverage defects, without claiming a current bypass; treat unmeasured
contention separately from merge-blocking security defects. No product design
decision was made. UX gaps closed: none. UX gaps left: not assessed; no UI
was changed.

Independent read-only review complete. Target: b3e7c14ad. The author report was not used. Branch: job/rev2-protofix. Review head: 4e739be4743d2fbc63d91ae2af9dd5e61a5ec652. Built: `review-protofix.md` and `audit-findings.md` only, in two atomic commits. No product code changed. No P1 defect was confirmed. Findings: 1. **P2 — SSE session checks use the single writer.** `crates/calternal-server/src/protocol_authority.rs:125` calls the SELECT at `crates/calternal-auth/src/store.rs:2548` for each chunk and quiet lease. That SELECT uses the one-connection writer pool. Use the existing read pool and test with a separate, unavailable writer. Evidence was added to #788. This is source-level contention, not a measured regression. 2. **P2 — XML tests do not protect the parser boundary.** `crates/calternal-dav/src/request_xml.rs:115` tests counters directly; `:134` never crosses a budget boundary with a document. `tests/adversarial/protocol_lifecycle.py:130` covers only root PROPFIND. Removing event accounting or Files validation can leave these tests passing. Add bounded depth/node/decoded-data tests through the parser and each XML-consuming route, alternate encodings and a normal follow-up. This evidence stays on #785, which owns the XML repair. 3. **P2 — MCP owner denial lacks an HTTP regression.** `tests/adversarial/protocol_lifecycle.py:43` uses only one credential. Removing `crates/calternal-server/src/mcp.rs:1399` can leave the added equality and lifecycle tests passing. Test another User and a second credential of the same User on POST, GET/resume and DELETE; the original owner must still succeed. Evidence was added to #789. Issue list: #785, #788 and #789. Searches found these existing repair owners; no new issue was created and no issue was closed. Source checks: DAV parses share the iterative validator; all Notes listeners share caps; retained Notes authority checks expiry; SSE bodies check current authority; MCP handles bind to User and credential identity before SDK use. No confirmed new authorization bypass, data loss or corruption was found in the assigned paths. No weakened existing test expectation was found in the protocol-fix commits. Module comments state the repair invariants. Known gaps: runtime behavior is not verified. The added tests do not cover real Notes expiry during commands/IDLE/SMTP, all live-listener failure paths, or the full SSE Plugin/scope/event matrix. The target's DESIGN §58 contains Agent discovery; the performance citation in #788 does not match that section. No latency target was invented. Exact-version docs.rs lookups failed; locked versions and matching local registry source were inspected. Verification output, verbatim: ```text Already up to date. ``` This is the output of the one required origin update. `git diff --check` exited 0 with no output. Rust/web gates were **not run** under the explicit LIGHT prohibition; there is no build/test gate output to quote. No server, browser, probe, performance measurement or cleanup command was run. For the merge round: run `cargo fmt --check`, and per-crate clippy/test for calternal-auth, calternal-dav and calternal-server. Run `python3 tests/adversarial/protocol_lifecycle.py --server "$CALTERNAL_SERVER_BIN"` to prove stream termination and DAV follow-up. Add the missing focused regressions in the build job; the current lifecycle script cannot prove them. The committed review lists each exact command. Decisions: reuse existing repair issues; rank missing security regressions as P2 coverage defects, without claiming a current bypass; treat unmeasured contention separately from merge-blocking security defects. No product design decision was made. UX gaps closed: none. UX gaps left: not assessed; no UI was changed.
Author
Owner

Started the review-fix round on branch job/protofix. Initial job head: b3e7c14adbf4a6824f9ca5a6a8224ffcf77e214f. Fetched and merged origin/dev at base c4faf184df726a9375ae0c13bdfb6018ac2cf57e before editing. I am addressing the three P2 findings in review-protofix.md, including the related #788 and #789 test gaps.

Started the review-fix round on branch `job/protofix`. Initial job head: `b3e7c14adbf4a6824f9ca5a6a8224ffcf77e214f`. Fetched and merged `origin/dev` at base `c4faf184df726a9375ae0c13bdfb6018ac2cf57e` before editing. I am addressing the three P2 findings in `review-protofix.md`, including the related #788 and #789 test gaps.
Author
Owner

Finding #785: parser unit tests alone did not prove that each DAV XML route invokes the shared boundary checks. I added exact and over-limit parser cases for depth, node count, and decoded payload size, including UTF-16LE/BE; route regressions cover PROPFIND, PROPPATCH, REPORT, and the Files adapter's PROPFIND/LOCK with ignored extension content beyond the depth limit, followed by valid-request recovery. DAV gates are pending.

Finding #785: parser unit tests alone did not prove that each DAV XML route invokes the shared boundary checks. I added exact and over-limit parser cases for depth, node count, and decoded payload size, including UTF-16LE/BE; route regressions cover PROPFIND, PROPPATCH, REPORT, and the Files adapter's PROPFIND/LOCK with ignored extension content beyond the depth limit, followed by valid-request recovery. DAV gates are pending.
Author
Owner

DAV parser and route regressions are committed as c283850bb.

cargo clippy --offline -p calternal-dav --all-targets -- -D warnings:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 38.28s

cargo test --offline -p calternal-dav:

test result: ok. 56 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.66s

test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s

The route cases cover PROPFIND, PROPPATCH and active REPORT handlers plus Files PROPFIND and LOCK. Exact parser limits use namespace-aware decoded accounting and comments for the node boundary. Legacy Journal REPORT returns 410 before parsing, so the test covers the active untagged collection handler instead.

DAV parser and route regressions are committed as `c283850bb`. `cargo clippy --offline -p calternal-dav --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 38.28s ``` `cargo test --offline -p calternal-dav`: ``` test result: ok. 56 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.66s test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s ``` The route cases cover PROPFIND, PROPPATCH and active REPORT handlers plus Files PROPFIND and LOCK. Exact parser limits use namespace-aware decoded accounting and comments for the node boundary. Legacy Journal REPORT returns 410 before parsing, so the test covers the active untagged collection handler instead.
Author
Owner

Forgejo #785 report

Built

  • #788: require_live_session now reads from the read pool. Its regression test holds the writer pool's only connection and verifies session checks still complete.
  • #785: parser tests cover exact and over-limit depth, node count, decoded data, UTF-8/UTF-16LE/UTF-16BE. Route tests cover active PROPFIND, PROPPATCH, REPORT, and Files PROPFIND/LOCK handlers, then verify normal requests recover.
  • #789: live-app HTTP test covers a second credential for the owner and another User across POST, GET/resume, and DELETE; owner continuation; revocation, expiry, MCP surface disable, and Plugin disable/restore.

Files and commits

  • crates/calternal-auth/src/store.rs — 185c475e5 (Use read pool for live session checks)
  • crates/calternal-dav/src/{request_xml.rs,protocol.rs,files.rs} — c283850bb (Test DAV XML budgets at parser and route boundaries)
  • crates/calternal-server/src/notes_submission.rs — 661bcad35 (remove stale unused import reported by clippy)
  • crates/calternal-server/src/wire.rs — 4d2e86385, then f1259332a (MCP boundary test; correct Plugin denial assertion)

Head: f1259332aa8540409fc864d80918ae57970eabb9.

Gates

cargo fmt --all --check: exit 0, no output.

cargo clippy --offline -p calternal-auth --all-targets -- -D warnings:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 55s

cargo test --offline -p calternal-auth:

test result: FAILED. 72 passed; 17 failed; 1 ignored; 0 measured; 0 filtered out; finished in 79.12s

The new session_checks_answer_while_the_writer_pool_is_busy test passed. Most failures were transient Unavailable errors while creating in-memory SQLite fixtures; the host's I/O pressure reported 98.97% average over 10 seconds. One existing Argon2 scheduling test did not reach its verifier in time. I did not repeat this gate.

cargo clippy --offline -p calternal-dav --all-targets -- -D warnings:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 38.28s

cargo test --offline -p calternal-dav:

test result: ok. 56 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.66s

test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy --offline -p calternal-server --all-targets -- -D warnings:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 20s

This pass was before the final test-only assertion adjustment in f1259332a.

cargo test --offline -p calternal-server -- --test-threads=2:

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 176 filtered out; finished in 20.56s

test result: FAILED. 170 passed; 1 failed; 6 ignored; 0 measured; 0 filtered out; finished in 109.20s

The failure was the test expecting a non-2xx API denial at result.isError. MCP returns it as a JSON-RPC error with the API envelope at error.data.error.code. Commit f1259332a now asserts not_found there and asserts an object result after re-enabling Analytics. The corrected assertion was not rerun before the four-hour job limit.

cargo clean:

Removed 16174 files, 7.2GiB total

Decisions

  • The old Journal REPORT path returns 410 before parsing. Route coverage uses the active untagged collection REPORT handler.
  • Analytics is the user-toggleable Plugin used for the disable/restore check; core Notes and Files Plugins cannot be disabled.
  • The Plugin denial assertion follows the existing MCP JSON-RPC error envelope for non-2xx API responses.

Known gaps and merge-round work

  • Rerun CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 cargo test -p calternal-auth -- --test-threads=2 to prove the complete auth suite with bounded test concurrency.
  • Rerun CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 cargo test -p calternal-server -- --test-threads=2 to verify the corrected MCP Plugin denial assertion and the full server suite.
  • Run python3 tests/adversarial/protocol_lifecycle.py --server "$CALTERNAL_SERVER_BIN" to exercise the bounded local DAV/MCP credential lifecycle for #785–#789.
  • Run bash tests/adversarial/run.sh in the merge round to prove the full XUser, authorization, robustness, and cross-Plugin adversarial matrix.

UX gaps closed/left: not applicable; no UI changed.

# Forgejo #785 report ## Built - #788: `require_live_session` now reads from the read pool. Its regression test holds the writer pool's only connection and verifies session checks still complete. - #785: parser tests cover exact and over-limit depth, node count, decoded data, UTF-8/UTF-16LE/UTF-16BE. Route tests cover active PROPFIND, PROPPATCH, REPORT, and Files PROPFIND/LOCK handlers, then verify normal requests recover. - #789: live-app HTTP test covers a second credential for the owner and another User across POST, GET/resume, and DELETE; owner continuation; revocation, expiry, MCP surface disable, and Plugin disable/restore. ## Files and commits - `crates/calternal-auth/src/store.rs` — `185c475e5` (`Use read pool for live session checks`) - `crates/calternal-dav/src/{request_xml.rs,protocol.rs,files.rs}` — `c283850bb` (`Test DAV XML budgets at parser and route boundaries`) - `crates/calternal-server/src/notes_submission.rs` — `661bcad35` (remove stale unused import reported by clippy) - `crates/calternal-server/src/wire.rs` — `4d2e86385`, then `f1259332a` (MCP boundary test; correct Plugin denial assertion) Head: `f1259332aa8540409fc864d80918ae57970eabb9`. ## Gates `cargo fmt --all --check`: exit 0, no output. `cargo clippy --offline -p calternal-auth --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 55s ``` `cargo test --offline -p calternal-auth`: ``` test result: FAILED. 72 passed; 17 failed; 1 ignored; 0 measured; 0 filtered out; finished in 79.12s ``` The new `session_checks_answer_while_the_writer_pool_is_busy` test passed. Most failures were transient `Unavailable` errors while creating in-memory SQLite fixtures; the host's I/O pressure reported 98.97% average over 10 seconds. One existing Argon2 scheduling test did not reach its verifier in time. I did not repeat this gate. `cargo clippy --offline -p calternal-dav --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 38.28s ``` `cargo test --offline -p calternal-dav`: ``` test result: ok. 56 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.66s test result: ok. 37 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy --offline -p calternal-server --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 15m 20s ``` This pass was before the final test-only assertion adjustment in `f1259332a`. `cargo test --offline -p calternal-server -- --test-threads=2`: ``` test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 176 filtered out; finished in 20.56s test result: FAILED. 170 passed; 1 failed; 6 ignored; 0 measured; 0 filtered out; finished in 109.20s ``` The failure was the test expecting a non-2xx API denial at `result.isError`. MCP returns it as a JSON-RPC `error` with the API envelope at `error.data.error.code`. Commit `f1259332a` now asserts `not_found` there and asserts an object result after re-enabling Analytics. The corrected assertion was not rerun before the four-hour job limit. `cargo clean`: ``` Removed 16174 files, 7.2GiB total ``` ## Decisions - The old Journal REPORT path returns 410 before parsing. Route coverage uses the active untagged collection REPORT handler. - Analytics is the user-toggleable Plugin used for the disable/restore check; core Notes and Files Plugins cannot be disabled. - The Plugin denial assertion follows the existing MCP JSON-RPC error envelope for non-2xx API responses. ## Known gaps and merge-round work - Rerun `CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 cargo test -p calternal-auth -- --test-threads=2` to prove the complete auth suite with bounded test concurrency. - Rerun `CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 cargo test -p calternal-server -- --test-threads=2` to verify the corrected MCP Plugin denial assertion and the full server suite. - Run `python3 tests/adversarial/protocol_lifecycle.py --server "$CALTERNAL_SERVER_BIN"` to exercise the bounded local DAV/MCP credential lifecycle for #785–#789. - Run `bash tests/adversarial/run.sh` in the merge round to prove the full XUser, authorization, robustness, and cross-Plugin adversarial matrix. UX gaps closed/left: not applicable; no UI changed.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#785
No description provided.