Action registry: declare the account scopes already enforced by route guards #774

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

F4 — registry scope labels omit enforced account requirements

scripts/action_registry.py:149 infers account policy from the auth path,
AI credential operation IDs and the Apps surface path. It omits account
requirements in other route families. Examples:

  • Files Shares: crates/plugins/files/src/shares.rs:155, 214, 255.
  • Files public links: crates/plugins/files/src/public.rs:450, 508, 550,
    620.
  • Version listing: crates/plugins/files/src/lib.rs:3553.
  • Notifications: crates/plugins/notifications/src/routes.rs:186 and 193
    require both data and account authority.
  • Tab order: crates/calternal-server/src/preferences.rs:142 requires account
    authority.

All 22 operations in these groups are labelled only data in the registry.
This is a discovery and contract defect, not an authorization bypass: the
routes enforce the stronger guard. CLI lists the wrong requirement. MCP
lists these tools for data-only credentials. Generated WebMCP does not select
the account action retry path for them. A scope hint must describe the guard,
not weaken it.

Fix: declare required scopes at the route contract, generate the registry
from that declaration, and check each of these groups in the generator tests.
Preserve route denials. Until contract security is complete, use one reviewed
account operation list rather than another partial path rule.

Context: sec-mcp-scopes audit at origin/dev c4a61e8cf0. The same generator policy is in merge-round-7a. Duplicate searches across open and closed registry, scope and MCP issue titles found feature #484 and tool-list performance #515, but no issue for these omitted route requirements. Source review only; no live bypass claim.

### F4 — registry scope labels omit enforced account requirements `scripts/action_registry.py:149` infers account policy from the auth path, AI credential operation IDs and the Apps surface path. It omits account requirements in other route families. Examples: - Files Shares: `crates/plugins/files/src/shares.rs:155`, `214`, `255`. - Files public links: `crates/plugins/files/src/public.rs:450`, `508`, `550`, `620`. - Version listing: `crates/plugins/files/src/lib.rs:3553`. - Notifications: `crates/plugins/notifications/src/routes.rs:186` and `193` require both data and account authority. - Tab order: `crates/calternal-server/src/preferences.rs:142` requires account authority. All 22 operations in these groups are labelled only `data` in the registry. This is a discovery and contract defect, not an authorization bypass: the routes enforce the stronger guard. CLI lists the wrong requirement. MCP lists these tools for data-only credentials. Generated WebMCP does not select the account action retry path for them. A scope hint must describe the guard, not weaken it. Fix: declare required scopes at the route contract, generate the registry from that declaration, and check each of these groups in the generator tests. Preserve route denials. Until contract security is complete, use one reviewed account operation list rather than another partial path rule. Context: sec-mcp-scopes audit at origin/dev c4a61e8cf090170f35b1bed3350d9de20c83ecd5. The same generator policy is in merge-round-7a. Duplicate searches across open and closed registry, scope and MCP issue titles found feature #484 and tool-list performance #515, but no issue for these omitted route requirements. Source review only; no live bypass claim.
Author
Owner

Additional source evidence for #774: all four User Background Work operations (list_my_jobs, my_job_events, get_my_job, cancel_my_job) use the account guard at crates/calternal-server/src/wire.rs:3077 and :3612. They too are labelled data-only. The confirmed affected set is 26 operations, not the initial 22. Route denials remain in force.

Additional source evidence for #774: all four User Background Work operations (`list_my_jobs`, `my_job_events`, `get_my_job`, `cancel_my_job`) use the account guard at crates/calternal-server/src/wire.rs:3077 and :3612. They too are labelled data-only. The confirmed affected set is 26 operations, not the initial 22. Route denials remain in force.
Author
Owner

The independent source-guard check found one more mismatch outside the audit's 26 entries: set_user_plugin checks account at crates/calternal-server/src/main.rs:649, but the registry declares data. I added its account declaration. The source check follows local guard helpers for every contract action and ignores SQL strings, so a table name cannot be mistaken for a Rust function call.

Registry checks: Ran 14 tests / OK. Web check: svelte-check found 0 errors and 0 warnings. The full web test run is still active; it has reported a timeout in the unrelated KeyboardShortcutsCard test. The shared host load average was about 70 while dependencies compiled. I will report the complete test result and check whether any changed path failed.

The independent source-guard check found one more mismatch outside the audit's 26 entries: `set_user_plugin` checks `account` at `crates/calternal-server/src/main.rs:649`, but the registry declares `data`. I added its account declaration. The source check follows local guard helpers for every contract action and ignores SQL strings, so a table name cannot be mistaken for a Rust function call. Registry checks: `Ran 14 tests` / `OK`. Web check: `svelte-check found 0 errors and 0 warnings`. The full web test run is still active; it has reported a timeout in the unrelated `KeyboardShortcutsCard` test. The shared host load average was about 70 while dependencies compiled. I will report the complete test result and check whether any changed path failed.
Author
Owner

Registry validation now checks detected data, account and admin guards for every declared handler, including shared local principal helpers. All 14 tests pass. The reviewed supplement corrects the 26 reported entries plus set_user_plugin, empty_trash and six Calendar grant operations. HEAD: 0e4ea05c3.

The focused local-server round includes MCP data/read denial, full API data denial, CLI Trash/feed denial, unchanged Trash/feed state, other-User Note isolation, legacy Log response compatibility and the existing cross-User/admin matrices. A bench profile and production captures at 390/820/1440 px in both themes with macOS rendering are prepared. They have not run yet: the first Rust crate is still compiling dependencies on the shared host. No Rust success is claimed.

Web validation: svelte-check found 0 errors and 0 warnings; Test Files 154 passed (154); Tests 1073 passed (1073); production build completed. The first default-timeout test run had one 5000ms timeout in KeyboardShortcutsCard; rerunning with two workers and 30000ms timeouts passed, without changing its expectations.

Registry validation now checks detected data, account and admin guards for every declared handler, including shared local principal helpers. All 14 tests pass. The reviewed supplement corrects the 26 reported entries plus set_user_plugin, empty_trash and six Calendar grant operations. HEAD: 0e4ea05c3. The focused local-server round includes MCP data/read denial, full API data denial, CLI Trash/feed denial, unchanged Trash/feed state, other-User Note isolation, legacy Log response compatibility and the existing cross-User/admin matrices. A bench profile and production captures at 390/820/1440 px in both themes with macOS rendering are prepared. They have not run yet: the first Rust crate is still compiling dependencies on the shared host. No Rust success is claimed. Web validation: svelte-check found 0 errors and 0 warnings; Test Files 154 passed (154); Tests 1073 passed (1073); production build completed. The first default-timeout test run had one 5000ms timeout in KeyboardShortcutsCard; rerunning with two workers and 30000ms timeouts passed, without changing its expectations.
Author
Owner

Implemented scopefix; validation incomplete at the 3-hour limit. Not ready for merge.

Branch: job/scopefix. Base: 2f4482ded0. Head: 1f1ba91cc3.
The required origin/dev and origin/job/merge-round-7a merges both returned Already up to date. No push or deploy was performed.

Built:

  • Files checks owning account and data authority before the mutation lock, permanent Trash removal, Index changes or publication. A regression test checks unchanged Trash and Index after denial and owning account success.
  • Calendar public feed management checks account/data authority and the existing data_user resource boundary before writes. Subscriptions retain data authority.
  • Registry scope policy corrects the 26 audited mismatches plus set_user_plugin and the seven Trash/Calendar actions. The server applies the reviewed requirements before body extraction. MCP discovery filters declared scopes. Tests audit detected data/account/admin guards across declared handlers and exercise every eligible action through the registry middleware.
  • Generated and legacy MCP API results share an untrusted-data/provenance envelope; errors, server instructions, llms.txt and the public Skill carry the trust boundary. Legacy Log selection preserves the envelope after a successful write. Inert Note/Mail/search fixtures also check reserved payload fields.
  • Web Trash and Calendar grant writes use the existing passkey step-up helper.
  • A focused cross-User/admin/MCP/API/CLI probe, serial/burst bench profile and twelve production screenshot captures with macOS rendering are prepared.

UX gaps closed: stale account sessions retry through the shared passkey flow; cancelling the Trash check says Nothing changed. UX gaps left: real-server pointer/touch/keyboard walkthrough and all screenshots remain unverified.

Decisions: only reviewed declarations opt into central enforcement, to preserve existing public capability flows (including download_app_password_profile) and legacy route guards. Permanent removal and Calendar grant mutations require recent assertions; grant reads require account/data without an assertion. MCP payloads move under data with fixed trust/source fields; these identify API provenance, not content authorship, and do not guarantee prompt-injection prevention.

Verified gate output (verbatim excerpts):

registry-test.log

..............
----------------------------------------------------------------------
Ran 14 tests in 2.631s

OK

calternal-api-clippy.log

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 51s

calternal-api-test.log

    Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 12s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.31s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-plugin-files-clippy.log

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 40m 00s

web-check-final.log

svelte-check found 0 errors and 0 warnings

web-test-head.log

 Test Files  154 passed (154)
      Tests  1073 passed (1073)
   Duration  664.68s (transform 31%, environment 28%, import 21%, tests 14%, setup 6%)

web-build-final.log

✓ built in 3m 13s
✓ built in 245ms
✓ built in 9m 2s
  Wrote site to "build"
  ✔ done

Exit statuses confirmed: cargo fmt --check 0 (no output); calternal-api clippy/test 0; calternal-plugin-files clippy 0; web check/test/build 0. Registry has 340 operations and 322 generated tools; all 14 registry tests pass. Python syntax, shell syntax, screenshot-script syntax and git diff --check pass.

Known gaps: Files tests were stopped during compilation; Calendar/server clippy and tests did not run to completion. The local server/CLI binary was not built, so no real-server round, MCP probe, bench measurements or screenshots ran. No measured performance regression claim is made; the profile includes notes.list baseline reference p50 1.3ms/p95 3.1ms, with no equivalent account-denial/envelope baseline. No screenshots exist to attach.

Build evidence: the first dependency build spent over an hour through the shared sccache. Active clients waited 81–145 seconds. Restarting this job without RUSTC_WRAPPER produced API gates and Files clippy; the latter finished in 40m 00s. The first default web test run had one 5000ms KeyboardShortcutsCard timeout; full runs with two workers and 30000ms timeouts passed. No existing assertion or fixture was changed to hide a failure. Remaining Rust checks must run before merge.

Source comments were re-read. No migrations or dependency versions were changed. Build output is being cleaned with cargo clean; web build output and Python caches were removed. Review artifacts remain gitignored.

Files:

  • apps/web/src/lib/files/api.ts
  • apps/web/src/lib/webmcp/tools.ts
  • apps/web/src/routes/settings/apps/CalendarFeedsGroup.svelte
  • bench/scopefix-739.py
  • contracts/action-authority.json
  • contracts/actions.json
  • crates/calternal-api/src/actions.rs
  • crates/calternal-server/src/agent_docs.rs
  • crates/calternal-server/src/agent_docs/skill_intro.md
  • crates/calternal-server/src/authz.rs
  • crates/calternal-server/src/mcp.rs
  • crates/calternal-server/src/wire.rs
  • crates/plugins/calendar/src/feeds/publication.rs
  • crates/plugins/files/src/lib.rs
  • docs/action-registry.md
  • docs/mcp.md
  • scripts/action_registry.py
  • scripts/test_action_registry.py
  • tests/adversarial/mcp_probe.py
  • tests/adversarial/run.sh
  • tests/adversarial/scopefix_review.mjs
Implemented scopefix; validation incomplete at the 3-hour limit. Not ready for merge. Branch: job/scopefix. Base: 2f4482ded066d9c5d9c59130377907f7fd2916c9. Head: 1f1ba91cc39ac1b8abcdec8c266919c5e3691302. The required origin/dev and origin/job/merge-round-7a merges both returned Already up to date. No push or deploy was performed. Built: - Files checks owning account and data authority before the mutation lock, permanent Trash removal, Index changes or publication. A regression test checks unchanged Trash and Index after denial and owning account success. - Calendar public feed management checks account/data authority and the existing data_user resource boundary before writes. Subscriptions retain data authority. - Registry scope policy corrects the 26 audited mismatches plus set_user_plugin and the seven Trash/Calendar actions. The server applies the reviewed requirements before body extraction. MCP discovery filters declared scopes. Tests audit detected data/account/admin guards across declared handlers and exercise every eligible action through the registry middleware. - Generated and legacy MCP API results share an untrusted-data/provenance envelope; errors, server instructions, llms.txt and the public Skill carry the trust boundary. Legacy Log selection preserves the envelope after a successful write. Inert Note/Mail/search fixtures also check reserved payload fields. - Web Trash and Calendar grant writes use the existing passkey step-up helper. - A focused cross-User/admin/MCP/API/CLI probe, serial/burst bench profile and twelve production screenshot captures with macOS rendering are prepared. UX gaps closed: stale account sessions retry through the shared passkey flow; cancelling the Trash check says Nothing changed. UX gaps left: real-server pointer/touch/keyboard walkthrough and all screenshots remain unverified. Decisions: only reviewed declarations opt into central enforcement, to preserve existing public capability flows (including download_app_password_profile) and legacy route guards. Permanent removal and Calendar grant mutations require recent assertions; grant reads require account/data without an assertion. MCP payloads move under data with fixed trust/source fields; these identify API provenance, not content authorship, and do not guarantee prompt-injection prevention. Verified gate output (verbatim excerpts): registry-test.log ```text .............. ---------------------------------------------------------------------- Ran 14 tests in 2.631s OK ``` calternal-api-clippy.log ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 51s ``` calternal-api-test.log ```text Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 12s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.31s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` calternal-plugin-files-clippy.log ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 40m 00s ``` web-check-final.log ```text svelte-check found 0 errors and 0 warnings ``` web-test-head.log ```text Test Files 154 passed (154) Tests 1073 passed (1073) Duration 664.68s (transform 31%, environment 28%, import 21%, tests 14%, setup 6%) ``` web-build-final.log ```text ✓ built in 3m 13s ✓ built in 245ms ✓ built in 9m 2s Wrote site to "build" ✔ done ``` Exit statuses confirmed: cargo fmt --check 0 (no output); calternal-api clippy/test 0; calternal-plugin-files clippy 0; web check/test/build 0. Registry has 340 operations and 322 generated tools; all 14 registry tests pass. Python syntax, shell syntax, screenshot-script syntax and git diff --check pass. Known gaps: Files tests were stopped during compilation; Calendar/server clippy and tests did not run to completion. The local server/CLI binary was not built, so no real-server round, MCP probe, bench measurements or screenshots ran. No measured performance regression claim is made; the profile includes notes.list baseline reference p50 1.3ms/p95 3.1ms, with no equivalent account-denial/envelope baseline. No screenshots exist to attach. Build evidence: the first dependency build spent over an hour through the shared sccache. Active clients waited 81–145 seconds. Restarting this job without RUSTC_WRAPPER produced API gates and Files clippy; the latter finished in 40m 00s. The first default web test run had one 5000ms KeyboardShortcutsCard timeout; full runs with two workers and 30000ms timeouts passed. No existing assertion or fixture was changed to hide a failure. Remaining Rust checks must run before merge. Source comments were re-read. No migrations or dependency versions were changed. Build output is being cleaned with cargo clean; web build output and Python caches were removed. Review artifacts remain gitignored. Files: - `apps/web/src/lib/files/api.ts` - `apps/web/src/lib/webmcp/tools.ts` - `apps/web/src/routes/settings/apps/CalendarFeedsGroup.svelte` - `bench/scopefix-739.py` - `contracts/action-authority.json` - `contracts/actions.json` - `crates/calternal-api/src/actions.rs` - `crates/calternal-server/src/agent_docs.rs` - `crates/calternal-server/src/agent_docs/skill_intro.md` - `crates/calternal-server/src/authz.rs` - `crates/calternal-server/src/mcp.rs` - `crates/calternal-server/src/wire.rs` - `crates/plugins/calendar/src/feeds/publication.rs` - `crates/plugins/files/src/lib.rs` - `docs/action-registry.md` - `docs/mcp.md` - `scripts/action_registry.py` - `scripts/test_action_registry.py` - `tests/adversarial/mcp_probe.py` - `tests/adversarial/run.sh` - `tests/adversarial/scopefix_review.mjs`
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#774
No description provided.