BLOCKER: bound public link password work outside async request workers #756

Open
opened 2026-10-02 13:09:20 +00:00 by kayg · 8 comments
Owner

Found in the authorized sec-sharing source audit on origin/dev at c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Related sharing decisions: #479 / #461. No product change or live attack was made.

BLOCKER: public password work runs on the async request worker

Status: source-confirmed scheduling and resource-control defect. No DoS run
or latency measurement was made on the shared host.

Evidence on the audit base:

  • crates/plugins/files/src/public.rs:181: the public router adds a body
    limit and verified client IP middleware. It has no password work permit.
  • crates/plugins/files/src/public.rs:717: password attempts are reserved
    atomically for a link and client IP. This prevents an attempt-count race.
  • crates/plugins/files/src/public.rs:733: Argon2 verification runs
    synchronously in the async authorization function. The attempt counter
    does not cap simultaneous password work across the Instance.
  • crates/plugins/files/src/public.rs:422: password hashing for link
    creation and update is also synchronous.
  • crates/calternal-auth/src/store.rs:1216: App Password verification uses
    a shared permit, and line 1249 sends password work to spawn_blocking.
    This is an existing pattern, not a new dependency requirement.

Reasoned impact: uncached password checks occupy request workers with CPU
and memory work. Other Users share those workers. Existing per-link attempt
limits and TCP connection limits do not make this work nonblocking or give
it an Instance-wide work budget. The defect is present without host-load
measurements; its actual latency and memory impact are not measured here.

Fix: put public password hashing and verification behind a bounded shared
permit and run it on the blocking pool. Bound pending work and password
input before scheduling it. Keep the attempt reservation and successful
password cache behavior. A busy response must not change the grant.

Regression requirement: verify the permit bound, release on errors and
cancellation, and progress of unrelated async work while a password worker
is held. Keep the existing gallery and password-attempt expectations.

Staged status: round-7a does not change either Argon2 call in public.rs.

Duplicate check: reviewed all-state issue titles and related sharing/security reports. No issue for this specific defect was found.

Found in the authorized sec-sharing source audit on `origin/dev` at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Related sharing decisions: #479 / #461. No product change or live attack was made. BLOCKER: public password work runs on the async request worker Status: source-confirmed scheduling and resource-control defect. No DoS run or latency measurement was made on the shared host. Evidence on the audit base: - `crates/plugins/files/src/public.rs:181`: the public router adds a body limit and verified client IP middleware. It has no password work permit. - `crates/plugins/files/src/public.rs:717`: password attempts are reserved atomically for a link and client IP. This prevents an attempt-count race. - `crates/plugins/files/src/public.rs:733`: Argon2 verification runs synchronously in the async authorization function. The attempt counter does not cap simultaneous password work across the Instance. - `crates/plugins/files/src/public.rs:422`: password hashing for link creation and update is also synchronous. - `crates/calternal-auth/src/store.rs:1216`: App Password verification uses a shared permit, and line 1249 sends password work to `spawn_blocking`. This is an existing pattern, not a new dependency requirement. Reasoned impact: uncached password checks occupy request workers with CPU and memory work. Other Users share those workers. Existing per-link attempt limits and TCP connection limits do not make this work nonblocking or give it an Instance-wide work budget. The defect is present without host-load measurements; its actual latency and memory impact are not measured here. Fix: put public password hashing and verification behind a bounded shared permit and run it on the blocking pool. Bound pending work and password input before scheduling it. Keep the attempt reservation and successful password cache behavior. A busy response must not change the grant. Regression requirement: verify the permit bound, release on errors and cancellation, and progress of unrelated async work while a password worker is held. Keep the existing gallery and password-attempt expectations. Staged status: round-7a does not change either Argon2 call in `public.rs`. Duplicate check: reviewed all-state issue titles and related sharing/security reports. No issue for this specific defect was found.
Author
Owner

Fix detail: the blocking worker must own its permit until password work ends. Cancelling the HTTP request must not release the permit while its blocking work continues. Test permit retention and release with controlled worker synchronization, rather than a load attack. No host-load measurement is claimed.

Fix detail: the blocking worker must own its permit until password work ends. Cancelling the HTTP request must not release the permit while its blocking work continues. Test permit retention and release with controlled worker synchronization, rather than a load attack. No host-load measurement is claimed.
Author
Owner

Started sharefix on job/sharefix, base 2f4482ded066d9c5d9c59130377907f7fd2916c9 (job/merge-round-7a). Scope: #755 public download current grant and opened item checks; #756 bounded blocking password work. No pushes or deploys. Regression checks use isolated local data.

Started sharefix on `job/sharefix`, base `2f4482ded066d9c5d9c59130377907f7fd2916c9` (`job/merge-round-7a`). Scope: #755 public download current grant and opened item checks; #756 bounded blocking password work. No pushes or deploys. Regression checks use isolated local data.
Author
Owner

Source checks confirm both audit findings. #755: download and preview now recheck a stored grant under the namespace lock, compare the opened inode and change token with the target, and release the lock before streaming. The download counter conditional write includes the same grant ID, owner, path, item ID, password hash, permissions, expiry and options. #756: hashing and verification share two blocking-worker permits and sixteen admission slots; workers keep both permits after request cancellation. The existing successful-password cache and five-attempt pair limit remain. Added aggregate reservations use the existing table (no migration): sixty per link and thirty per verified IP per fifteen-minute window. These work budgets and aggregate thresholds are implementation decisions not specified in DESIGN. Controlled tests check authority changes, replacement identity, limit accounting, cancellation, errors and async progress. Validation is in progress; no success claim yet.

Source checks confirm both audit findings. #755: download and preview now recheck a stored grant under the namespace lock, compare the opened inode and change token with the target, and release the lock before streaming. The download counter conditional write includes the same grant ID, owner, path, item ID, password hash, permissions, expiry and options. #756: hashing and verification share two blocking-worker permits and sixteen admission slots; workers keep both permits after request cancellation. The existing successful-password cache and five-attempt pair limit remain. Added aggregate reservations use the existing table (no migration): sixty per link and thirty per verified IP per fifteen-minute window. These work budgets and aggregate thresholds are implementation decisions not specified in DESIGN. Controlled tests check authority changes, replacement identity, limit accounting, cancellation, errors and async progress. Validation is in progress; no success claim yet.
Author
Owner

Files Clippy completed successfully:

    Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/sharefix/crates/plugins/files)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 43m 25s

The duration includes waiting for the isolated cold build. Read-only review found no confirmed production defect. It identified missing deterministic coverage for a held descriptor and a selected folder child. The added test covers both, including a fresh path read and rejection of the former item ID. The full Files suite is building. The server build and server gates are queued. A bounded local lifecycle/three-User probe and local performance profile are written; neither has run yet.

Files Clippy completed successfully: ```text Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/sharefix/crates/plugins/files) Finished `dev` profile [unoptimized + debuginfo] target(s) in 43m 25s ``` The duration includes waiting for the isolated cold build. Read-only review found no confirmed production defect. It identified missing deterministic coverage for a held descriptor and a selected folder child. The added test covers both, including a fresh path read and rejection of the former item ID. The full Files suite is building. The server build and server gates are queued. A bounded local lifecycle/three-User probe and local performance profile are written; neither has run yet.
Author
Owner

The final Files binary compiled and the new password budget, cancellation, oversized-input and atomic rate-limit tests passed. The new folder descriptor test failed at the root-grant recheck. An isolated rerun confirmed that its generic index::record(Folder) refresh invalidated the Folder grant. The fixture now uses index::refresh_parent, the helper used by owner writes, to preserve the Folder identity. Every assertion remains unchanged. The corrected focused suite is queued. This is a new test fixture correction, not a change to an existing expectation.

The two-hour job budget has about 25 minutes left. The server build and server gates remained blocked on the Files test build directory. I stopped those pending commands to prioritize the corrected Files checks. The real-server probe and performance profile are written but have not run. They remain validation gaps until the current server can be built.

The final Files binary compiled and the new password budget, cancellation, oversized-input and atomic rate-limit tests passed. The new folder descriptor test failed at the root-grant recheck. An isolated rerun confirmed that its generic `index::record(Folder)` refresh invalidated the Folder grant. The fixture now uses `index::refresh_parent`, the helper used by owner writes, to preserve the Folder identity. Every assertion remains unchanged. The corrected focused suite is queued. This is a new test fixture correction, not a change to an existing expectation. The two-hour job budget has about 25 minutes left. The server build and server gates remained blocked on the Files test build directory. I stopped those pending commands to prioritize the corrected Files checks. The real-server probe and performance profile are written but have not run. They remain validation gaps until the current server can be built.
Author
Owner

Head: 06a71a516 (job/sharefix). The grant binding, descriptor regression, password worker/rate limits, local lifecycle probe and benchmark are committed as separate concerns.

Final-source Files Clippy passed:

    Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/sharefix/crates/plugins/files)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 20m 27s

The corrected focused public tests now have the build directory. The original full suite compiled, passed the password tests and failed the folder fixture; it was stopped after that fixture was corrected. Full Files validation and server/runtime/performance validation are not complete.

Head: `06a71a516` (`job/sharefix`). The grant binding, descriptor regression, password worker/rate limits, local lifecycle probe and benchmark are committed as separate concerns. Final-source Files Clippy passed: ```text Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/sharefix/crates/plugins/files) Finished `dev` profile [unoptimized + debuginfo] target(s) in 20m 27s ``` The corrected focused public tests now have the build directory. The original full suite compiled, passed the password tests and failed the folder fixture; it was stopped after that fixture was corrected. Full Files validation and server/runtime/performance validation are not complete.
Author
Owner

Committed fixes for #755 and #756 on job/sharefix. Validation is incomplete at the two-hour job limit.

Built:

  • Public download and inline preview recheck the current grant under the namespace mutation lock. The opened descriptor must match the current path token and selected item ID. Download counters update only the checked grant. The namespace lock ends before streaming.
  • Password creation, updates and verification use bounded blocking workers. Cancellation keeps each permit until the blocking work ends. Input length is checked before admission. Pair, link and verified-IP work reservations are atomic. The existing salted positive-password cache remains.
  • Deterministic grant, descriptor, worker, cancellation and rate-limit tests.
  • An isolated real-server lifecycle probe with a three-User matrix and a local performance profile.

Files:

  • crates/plugins/files/src/public.rs
  • crates/plugins/files/src/lib.rs: test fixture visibility only.
  • tests/adversarial/public_links.mjs
  • bench/public-links-755.mjs

Decisions:

  • Reuse current grant fields as the authority snapshot. No migration is needed. Counter changes do not invalidate concurrent reads.
  • Use two password workers, sixteen admitted requests and a five-second queue wait. Keep five guesses per link/IP pair. Add sixty work reservations per link and thirty per verified IP in fifteen minutes. Aggregate reservations remain after success; correct cached passwords bypass work.
  • Use the existing rate-limit table with wildcard aggregate keys, which cannot be real slugs or IP addresses.
  • The benchmark uses a 64 KiB typical read, a 32 MiB original burst and a bounded cold password burst. The baseline has no matching public-link/password metric. No comparison or measured result is claimed until it runs.

UX gaps closed: changed grants cannot authorize later original-byte reads; password CPU work no longer occupies async request workers. Correct gallery credentials keep the existing cache behavior. No UI files changed.
UX gaps left: no new UI scope. Broader sharing changes in DESIGN §54 remain with their jobs.

Known gaps: final full Files suite, server gates, real-server probe, three-User matrix and benchmark execution remain to be confirmed. No screenshots are needed for this backend-only change. No dependency or migration changes. No push or deploy.

Head: 06a71a516cba67b8e89d5c5182b53bdffd5d132f.
Base: 2f4482ded066d9c5d9c59130377907f7fd2916c9. The required fetch and merge of origin/dev ran once and returned Already up to date.

Gate output (verbatim excerpts):

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

Final-source cargo clippy -p calternal-plugin-files --all-targets -- -D warnings:

    Blocking waiting for file lock on build directory
    Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/sharefix/crates/plugins/files)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 20m 27s

Isolated #755 cargo test -p calternal-plugin-files public::tests -- --test-threads=2:

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

The initial full Files suite compiled. Its new folder fixture failed; the corrected fixture uses the owner write path parent refresh without changing assertions. The old run was stopped after that correction. Its password tests reported:

test public::tests::password_aggregate_limits_are_atomic ... ok
test public::tests::password_budget_bounds_waiters_and_retains_cancelled_workers ... ok
test public::tests::password_budget_releases_errors_and_rejects_long_input ... ok

The server build and Clippy commands were stopped while waiting for the Files build directory. Server tests did not start. JavaScript syntax checks passed without output. The production web build passed; web source did not change. No runtime or performance result is claimed.

Corrected focused test run, stopped during compilation at the job limit:

    Blocking waiting for file lock on build directory
   Compiling calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/sharefix/crates/plugins/files)

Original full Files run (the new fixture failure, unchanged assertions):

test public::tests::folder_link_checks_open_handle_and_selected_identity ... FAILED

Server Clippy output before cancellation:

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

Handoff: rerun the corrected focused tests and full Files suite, then build and run server Clippy/tests. Build the production web app, run tests/adversarial/public_links.mjs once with this server, and run bench/public-links-755.mjs once. Record local latency/CPU/RSS against the baseline; no matching metric exists yet. No actual three-User or runtime/performance result is claimed. The worktree is clean. Web build output was removed, and cargo clean was requested for the job target. Review artifacts remain only under ignored artifacts/.

Committed fixes for #755 and #756 on `job/sharefix`. Validation is incomplete at the two-hour job limit. Built: - Public download and inline preview recheck the current grant under the namespace mutation lock. The opened descriptor must match the current path token and selected item ID. Download counters update only the checked grant. The namespace lock ends before streaming. - Password creation, updates and verification use bounded blocking workers. Cancellation keeps each permit until the blocking work ends. Input length is checked before admission. Pair, link and verified-IP work reservations are atomic. The existing salted positive-password cache remains. - Deterministic grant, descriptor, worker, cancellation and rate-limit tests. - An isolated real-server lifecycle probe with a three-User matrix and a local performance profile. Files: - `crates/plugins/files/src/public.rs` - `crates/plugins/files/src/lib.rs`: test fixture visibility only. - `tests/adversarial/public_links.mjs` - `bench/public-links-755.mjs` Decisions: - Reuse current grant fields as the authority snapshot. No migration is needed. Counter changes do not invalidate concurrent reads. - Use two password workers, sixteen admitted requests and a five-second queue wait. Keep five guesses per link/IP pair. Add sixty work reservations per link and thirty per verified IP in fifteen minutes. Aggregate reservations remain after success; correct cached passwords bypass work. - Use the existing rate-limit table with wildcard aggregate keys, which cannot be real slugs or IP addresses. - The benchmark uses a 64 KiB typical read, a 32 MiB original burst and a bounded cold password burst. The baseline has no matching public-link/password metric. No comparison or measured result is claimed until it runs. UX gaps closed: changed grants cannot authorize later original-byte reads; password CPU work no longer occupies async request workers. Correct gallery credentials keep the existing cache behavior. No UI files changed. UX gaps left: no new UI scope. Broader sharing changes in DESIGN §54 remain with their jobs. Known gaps: final full Files suite, server gates, real-server probe, three-User matrix and benchmark execution remain to be confirmed. No screenshots are needed for this backend-only change. No dependency or migration changes. No push or deploy. Head: `06a71a516cba67b8e89d5c5182b53bdffd5d132f`. Base: `2f4482ded066d9c5d9c59130377907f7fd2916c9`. The required fetch and merge of `origin/dev` ran once and returned `Already up to date.` Gate output (verbatim excerpts): `cargo fmt --check`: exit 0, no output. Final-source `cargo clippy -p calternal-plugin-files --all-targets -- -D warnings`: ```text Blocking waiting for file lock on build directory Checking calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/sharefix/crates/plugins/files) Finished `dev` profile [unoptimized + debuginfo] target(s) in 20m 27s ``` Isolated #755 `cargo test -p calternal-plugin-files public::tests -- --test-threads=2`: ```text test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 155 filtered out; finished in 71.20s ``` The initial full Files suite compiled. Its new folder fixture failed; the corrected fixture uses the owner write path parent refresh without changing assertions. The old run was stopped after that correction. Its password tests reported: ```text test public::tests::password_aggregate_limits_are_atomic ... ok test public::tests::password_budget_bounds_waiters_and_retains_cancelled_workers ... ok test public::tests::password_budget_releases_errors_and_rejects_long_input ... ok ``` The server build and Clippy commands were stopped while waiting for the Files build directory. Server tests did not start. JavaScript syntax checks passed without output. The production web build passed; web source did not change. No runtime or performance result is claimed. Corrected focused test run, stopped during compilation at the job limit: ```text Blocking waiting for file lock on build directory Compiling calternal-plugin-files v0.0.1 (/home/kayg/Developer/calternal-wt/sharefix/crates/plugins/files) ``` Original full Files run (the new fixture failure, unchanged assertions): ```text test public::tests::folder_link_checks_open_handle_and_selected_identity ... FAILED ``` Server Clippy output before cancellation: ```text Blocking waiting for file lock on package cache Blocking waiting for file lock on build directory ``` Handoff: rerun the corrected focused tests and full Files suite, then build and run server Clippy/tests. Build the production web app, run `tests/adversarial/public_links.mjs` once with this server, and run `bench/public-links-755.mjs` once. Record local latency/CPU/RSS against the baseline; no matching metric exists yet. No actual three-User or runtime/performance result is claimed. The worktree is clean. Web build output was removed, and `cargo clean` was requested for the job target. Review artifacts remain only under ignored `artifacts/`.
Author
Owner

Cleanup completed. The worktree is clean. Web build output was removed.

     Removed 8854 files, 3.3GiB total
Cleanup completed. The worktree is clean. Web build output was removed. ```text Removed 8854 files, 3.3GiB total ```
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#756
No description provided.