Bug bash: security #1223

Open
opened 2026-10-06 19:02:20 +00:00 by kayg · 16 comments
Owner

AREA: Defensive security audit of routes added or changed since 2026-10-01 (git log origin/dev --since=2026-10-01 -- crates apps/web/src/routes), on a real local server. For each route: classify it in the cross-user isolation matrix (tests/adversarial, the #331 probe) and add it if missing; verify authorization with 3 users and an Admin (read, write, list, existence leaks via status codes, timing or error text); input validation (path handling must go through calternal-fs, never string-built paths; Unicode normalisation; oversized and malformed bodies; content-type confusion); CSRF/origin checks on state-changing routes; HTML/Markdown/SVG rendering in previews and mail (script, event handlers, remote loads); link preview fetcher (private address ranges, redirects to private ranges, DNS rebinding, size and time limits); licence verification (signature, expiry, tampering); import parsers (YNAB/Actual/CSV, zip bombs, huge files). Write findings to artifacts/bash-security/findings.md as you go (the job may stop early). Fix clear ones with regression tests in tests/adversarial or the crate; file each as its own issue with severity. Word everything as defensive test cases.

Tracking issue for the 2026-10-07 bug bash.

AREA: Defensive security audit of routes added or changed since 2026-10-01 (git log origin/dev --since=2026-10-01 -- crates apps/web/src/routes), on a real local server. For each route: classify it in the cross-user isolation matrix (tests/adversarial, the #331 probe) and add it if missing; verify authorization with 3 users and an Admin (read, write, list, existence leaks via status codes, timing or error text); input validation (path handling must go through calternal-fs, never string-built paths; Unicode normalisation; oversized and malformed bodies; content-type confusion); CSRF/origin checks on state-changing routes; HTML/Markdown/SVG rendering in previews and mail (script, event handlers, remote loads); link preview fetcher (private address ranges, redirects to private ranges, DNS rebinding, size and time limits); licence verification (signature, expiry, tampering); import parsers (YNAB/Actual/CSV, zip bombs, huge files). Write findings to artifacts/bash-security/findings.md as you go (the job may stop early). Fix clear ones with regression tests in tests/adversarial or the crate; file each as its own issue with severity. Word everything as defensive test cases. Tracking issue for the 2026-10-07 bug bash.
Author
Owner

Started defensive audit on job/bash-security at base ff53d7b8b4. Investigating #1236 first, then changed-route classification and isolated local-server probes. Evidence will remain under artifacts/bash-security/.

Started defensive audit on job/bash-security at base ff53d7b8b4da5a21f862d57dcc3496f8c019f647. Investigating #1236 first, then changed-route classification and isolated local-server probes. Evidence will remain under artifacts/bash-security/.
Author
Owner

Found and filed #1297: the merged Admin feature flag update route lacked a cross-User identifier class, so the fail-closed inventory gate stopped before live checks. Adding explicit Instance-key classification, an offline regression and three-User/anonymous plus cookie-origin denial cases. No authorization bypass was found in this static check.

Found and filed #1297: the merged Admin feature flag update route lacked a cross-User identifier class, so the fail-closed inventory gate stopped before live checks. Adding explicit Instance-key classification, an offline regression and three-User/anonymous plus cookie-origin denial cases. No authorization bypass was found in this static check.
Author
Owner

Also found and filed #1298: Notification Mark unread lacks its existing User-owned Notification ID class. The feature-key classification now has a focused regression; the next fail-closed gate finding is this Notification route.

Also found and filed #1298: Notification Mark unread lacks its existing User-owned Notification ID class. The feature-key classification now has a focused regression; the next fail-closed gate finding is this Notification route.
Author
Owner

Classification fixes: #1297 in 23418ea5c and #1298 in 66e905fe6. The complete merged inventory now classifies 429 operations and 1206 generated entry points; 53 Admin policies agree with Rust guards; all 35 classification regressions pass. Web check passes after refreshing 140 exact Files performance pins, with no exception-count or ratchet increase. Rust gates and the full two-worker web test suite are running before the real-server round.

Classification fixes: #1297 in 23418ea5c and #1298 in 66e905fe6. The complete merged inventory now classifies 429 operations and 1206 generated entry points; 53 Admin policies agree with Rust guards; all 35 classification regressions pass. Web check passes after refreshing 140 exact Files performance pins, with no exception-count or ratchet increase. Rust gates and the full two-worker web test suite are running before the real-server round.
Author
Owner

Post-merge web gates passed. No UI source was changed. Full output is retained in artifacts/bash-security/web-check.log and web-test.log.

svelte-check found 0 errors and 1 warning in 1 file

 Test Files  287 passed (287)
      Tests  2035 passed (2035)
   Start at  04:14:11
   Duration  521.35s (environment 27%, transform 26%, import 24%, tests 17%, setup 5%)

The warning is an empty shared AttachmentDeck CSS ruleset. Files Rust gates are still compiling dependencies. The real-server security round is pending the own-worktree server build.

Post-merge web gates passed. No UI source was changed. Full output is retained in `artifacts/bash-security/web-check.log` and `web-test.log`. ```text svelte-check found 0 errors and 1 warning in 1 file Test Files 287 passed (287) Tests 2035 passed (2035) Start at 04:14:11 Duration 521.35s (environment 27%, transform 26%, import 24%, tests 17%, setup 5%) ``` The warning is an empty shared AttachmentDeck CSS ruleset. Files Rust gates are still compiling dependencies. The real-server security round is pending the own-worktree server build.
Author
Owner

Password budget follow-up: 2c2ecbee1bf746dbaf4f3a9e51ebdab94df79e33. The helper borrows the selected budget directly. Production still uses the process-wide budget; only tests can select isolated work. Refreshed existing exact performance pins with unchanged rule counts and ratchets.

Files clippy passed:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 33m 48s

The current test binary has passed the new deterministic admission-refusal and counter-reservation regressions. The full Files suite is still running. The real local server/CLI/sync build has started.

Password budget follow-up: `2c2ecbee1bf746dbaf4f3a9e51ebdab94df79e33`. The helper borrows the selected budget directly. Production still uses the process-wide budget; only tests can select isolated work. Refreshed existing exact performance pins with unchanged rule counts and ratchets. Files clippy passed: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 33m 48s ``` The current test binary has passed the new deterministic admission-refusal and counter-reservation regressions. The full Files suite is still running. The real local server/CLI/sync build has started.
Author
Owner

Filed #1300: the existing atomic-write defensive boundary test masks a deadline with a test cleanup panic (JoinHandle polled after completion). Full Files suite: 261 passed, 1 failed, 4 ignored; 436.60 seconds. Password regressions and all four password route tests passed.

I will repair test-only task cleanup and prove the failure path deterministically. Workload, deadline and existing assertions stay unchanged. Evidence: artifacts/bash-security/test-files.log.

Filed #1300: the existing atomic-write defensive boundary test masks a deadline with a test cleanup panic (`JoinHandle polled after completion`). Full Files suite: 261 passed, 1 failed, 4 ignored; 436.60 seconds. Password regressions and all four password route tests passed. I will repair test-only task cleanup and prove the failure path deterministically. Workload, deadline and existing assertions stay unchanged. Evidence: `artifacts/bash-security/test-files.log`.
Author
Owner

Continuing defensive reliability and privacy checks on job/bash-security, starting HEAD caa4e0d96f14c0f0836840c9446417cbac19319f. Recorded static review and #1236 work are retained. Next: own release server and production web build, then the committed security-origin-licence and cross-User probes. Existing uncommitted #1300 test cleanup will be verified and committed separately. Only summary lines and pass/fail statuses will be reported.

Continuing defensive reliability and privacy checks on `job/bash-security`, starting HEAD `caa4e0d96f14c0f0836840c9446417cbac19319f`. Recorded static review and #1236 work are retained. Next: own release server and production web build, then the committed security-origin-licence and cross-User probes. Existing uncommitted #1300 test cleanup will be verified and committed separately. Only summary lines and pass/fail statuses will be reported.
Author
Owner

The required origin/dev merge is 3d35bd8b0. Conflict resolution keeps the #1223 origin/Licence section and the current dev DNS preview checks once. The merged release selectors stay in place. The two feature selector regressions used different class names for the same Instance key. The #1297 test now uses dev’s feature_flag_id; authority assertions stay the same. Offline classification: 20 tests, OK.

The pending #1300 cleanup is superseded by dev’s #1213 cleanup helper and deterministic regression. The incoming fix prevents the same double-poll failure. No extra Rust cleanup helper was added.

The required origin/dev merge is `3d35bd8b0`. Conflict resolution keeps the #1223 origin/Licence section and the current dev DNS preview checks once. The merged release selectors stay in place. The two feature selector regressions used different class names for the same Instance key. The #1297 test now uses dev’s `feature_flag_id`; authority assertions stay the same. Offline classification: 20 tests, OK. The pending #1300 cleanup is superseded by dev’s #1213 cleanup helper and deterministic regression. The incoming fix prevents the same double-poll failure. No extra Rust cleanup helper was added.
Author
Owner

Release build stopped in phf_codegen: the shared sccache daemon could not create a temporary directory in another job’s removed worktree directory (os error 2). This is build infrastructure, not a live product finding. Retry uses the same release build and four build jobs, with RUSTC_WRAPPER empty. No shared daemon was stopped. The merged production web build passes. Live checks have not run yet.

Release build stopped in `phf_codegen`: the shared sccache daemon could not create a temporary directory in another job’s removed worktree directory (os error 2). This is build infrastructure, not a live product finding. Retry uses the same release build and four build jobs, with `RUSTC_WRAPPER` empty. No shared daemon was stopped. The merged production web build passes. Live checks have not run yet.
Author
Owner

Committed summary-only runner: 3f05f7052. Files: tests/adversarial/summary_probe.py, tests/adversarial/test_summary_probe.py. Three child-process regressions pass: payload stdout/stderr suppression; exception function reporting without the message; failure preservation for a finding and string SystemExit. No product behavior changed.

Committed summary-only runner: `3f05f7052`. Files: `tests/adversarial/summary_probe.py`, `tests/adversarial/test_summary_probe.py`. Three child-process regressions pass: payload stdout/stderr suppression; exception function reporting without the message; failure preservation for a finding and string SystemExit. No product behavior changed.
Author
Owner

Post-merge bun run --cwd apps/web check passes:

svelte-check found 0 errors and 1 warning in 1 file

Full web tests: 286 files passed, one file had one test exceed the five-second execution limit. The failure is themePicker.svelte.test.ts → reveals the trigger in the sheet scrollport before opening: Error: Test timed out in 5000ms. All other 2,035 tests passed. The unchanged focused file passes 7/7. Retry uses bun run --cwd apps/web test --maxWorkers=2 --testTimeout=30000. This changes only the command’s execution watchdog for the busy host; no test source, assertion or product code changes.

Post-merge `bun run --cwd apps/web check` passes: ```text svelte-check found 0 errors and 1 warning in 1 file ``` Full web tests: 286 files passed, one file had one test exceed the five-second execution limit. The failure is `themePicker.svelte.test.ts` → `reveals the trigger in the sheet scrollport before opening`: `Error: Test timed out in 5000ms.` All other 2,035 tests passed. The unchanged focused file passes 7/7. Retry uses `bun run --cwd apps/web test --maxWorkers=2 --testTimeout=30000`. This changes only the command’s execution watchdog for the busy host; no test source, assertion or product code changes.
Author
Owner

Post-merge full web retry passes. Command: bun run --cwd apps/web test --maxWorkers=2 --testTimeout=30000. The unchanged workload and assertions use a 30-second execution watchdog on the busy host.

 Test Files  287 passed (287)
      Tests  2036 passed (2036)
   Start at  21:22:22
   Duration  811.50s (transform 28%, environment 26%, import 24%, tests 16%, setup 6%)

The default five-second run had one timeout; the isolated file passed 7/7. Both attempts remain in the local gate logs. Release compilation is still running.

Post-merge full web retry passes. Command: `bun run --cwd apps/web test --maxWorkers=2 --testTimeout=30000`. The unchanged workload and assertions use a 30-second execution watchdog on the busy host. ```text Test Files 287 passed (287) Tests 2036 passed (2036) Start at 21:22:22 Duration 811.50s (transform 28%, environment 26%, import 24%, tests 16%, setup 6%) ``` The default five-second run had one timeout; the isolated file passed 7/7. Both attempts remain in the local gate logs. Release compilation is still running.
Author
Owner

Post-merge full web retry passes. Command: bun run --cwd apps/web test --maxWorkers=2 --testTimeout=30000. The unchanged workload and assertions use a 30-second execution watchdog on the busy host.

 Test Files  287 passed (287)
      Tests  2036 passed (2036)
   Start at  21:22:22
   Duration  811.50s (transform 28%, environment 26%, import 24%, tests 16%, setup 6%)

The default five-second run had one timeout; the isolated file passed 7/7. Both attempts remain in the local gate logs. Release compilation is still running.

Post-merge full web retry passes. Command: `bun run --cwd apps/web test --maxWorkers=2 --testTimeout=30000`. The unchanged workload and assertions use a 30-second execution watchdog on the busy host. ```text Test Files 287 passed (287) Tests 2036 passed (2036) Start at 21:22:22 Duration 811.50s (transform 28%, environment 26%, import 24%, tests 16%, setup 6%) ``` The default five-second run had one timeout; the isolated file passed 7/7. Both attempts remain in the local gate logs. Release compilation is still running.
Author
Owner

Defensive reliability and privacy checks are running against the own release server with a disposable data directory. The origin/Licence phase passes:

server alive at end: True
==== ROUND 2 FINDINGS 0
==== ROUND 2 SLOW 0
PROBE attack2 PASS exit=0

The cross-User matrix is now running. Request and response bodies are omitted. The first startup refusal was a local runner omission: the data directory must exist before the server starts. The runner now follows the committed setup behavior. No product change was needed.

Defensive reliability and privacy checks are running against the own release server with a disposable data directory. The origin/Licence phase passes: ```text server alive at end: True ==== ROUND 2 FINDINGS 0 ==== ROUND 2 SLOW 0 PROBE attack2 PASS exit=0 ``` The cross-User matrix is now running. Request and response bodies are omitted. The first startup refusal was a local runner omission: the data directory must exist before the server starts. The runner now follows the committed setup behavior. No product change was needed.
Author
Owner

Completed the requested live defensive reliability and privacy checks on job/bash-security.

Head: 3f05f7052a13d180b851c65e03bee01706928c56. Required origin/dev merge: 3d35bd8b06453f6b77e3ad412f22b1831e290f4c.

Check Status Result
security-origin-licence PASS 0 findings; 0 SLOW cases; server ready at the end
xuser_matrix.py PASS 203 operations replayed; 938 foreign-ID/missing-ID comparisons; 122 Job/Mail/quota comparisons; 0 denial failures

Live summary output, verbatim:

server alive at end: True
==== ROUND 2 FINDINGS 0
==== ROUND 2 SLOW 0
PROBE attack2 PASS exit=0
Calendar subscription ownership matrix: 3 mutation rows, 15 owned-ID/missing-ID comparisons; A's settings and fetch state unchanged
Photos Share contracts: timeline, buckets, day, search, item, stack, Calendar, Files ID, original, thumbnail and Public link pass before/after revoke
Two-User OpenAPI matrix: 429 operations classified; 203 operations replayed; 938 A-ID vs missing-ID comparisons across B, C, D and anonymous; 39 identifier routes classified with no local fixture factory; median absolute timing delta 1.8 ms
Job/Mail/quota ownership checks: 122 comparisons; 0 denial failures
PROBE xuser_matrix PASS exit=0

Built the own release server and CLI and the production web app. The fixture used real passkey registration, a disposable data directory, the existing proxy and the existing DNS fixture. Both live checks finished on that server. No product defect was found by these checks. No request or response bodies were put in the reports.

Added a shared summary-only probe runner and three regressions. The runner suppresses payload output and exception messages and preserves each check's exit status. Its failing-test run had 3 failures before the runner existed; the implemented runner passes all 3.

Files changed in this continuation: tests/adversarial/summary_probe.py, tests/adversarial/test_summary_probe.py; merge resolutions in tests/adversarial/attack2.py, tests/adversarial/run.sh, tests/adversarial/xuser_matrix.py, and tests/adversarial/test_xuser_classification.py. Results are in artifacts/bash-security/findings.md and the summary/gate logs. Review artifacts remain uncommitted.

Gates and build output, verbatim:

cargo fmt --check: exit 0, no output. No Rust source was changed in this continuation, so crate clippy and test gates were not repeated.

Release build:

    Finished `release` profile [optimized] target(s) in 42m 18s

bun run --cwd apps/web check:

svelte-check found 0 errors and 1 warning in 1 file

bun run --cwd apps/web test --maxWorkers=2 --testTimeout=30000:

 Test Files  287 passed (287)
      Tests  2036 passed (2036)
   Start at  21:22:22
   Duration  811.50s (transform 28%, environment 26%, import 24%, tests 16%, setup 6%)

Classification regressions:

....................................
----------------------------------------------------------------------
Ran 36 tests in 4.188s

OK

Summary runner regressions:

...
----------------------------------------------------------------------
Ran 3 tests in 0.763s

OK

Known gaps: 39 classified identifier routes have no local fixture factory. This pass is not live coverage of those identities. The default five-second web run had one timeout in themePicker.svelte.test.ts / reveals the trigger in the sheet scrollport before opening; all other 2,035 tests passed. The unchanged focused file passed 7/7, and the full retry above passed. The remaining CSS warning is the existing empty AttachmentDeck ruleset. These two live checks do not supply a valid signed Licence expiry fixture or run the separate Money import parser probe.

Decisions: no new product design decision. Kept dev's canonical feature_flag_id class when resolving the #1297/#1202 duplicate tests; HTTP authority expectations did not change. Kept dev's #1213 cleanup fix and regression, which supersede the pending #1300 patch. Used a 30-second test execution watchdog for the busy-host web retry; no test workload or assertion changed. Bypassed the shared sccache daemon after it referenced another job's removed temporary directory. The first server startup failed because the local runner omitted data directory creation; corrected the runner to match the committed setup.

Cleanup completed. The disposable server, proxy and fixture data are gone. Cargo output:

     Removed 9577 files, 3.6GiB total

Removed /mnt/hdd/targets/bash-security2, the worktree's local target output, and apps/web/build and .svelte-kit. Working tree is clean. No push, deploy, or merge into dev/main was performed.

Completed the requested live defensive reliability and privacy checks on `job/bash-security`. Head: `3f05f7052a13d180b851c65e03bee01706928c56`. Required origin/dev merge: `3d35bd8b06453f6b77e3ad412f22b1831e290f4c`. | Check | Status | Result | |---|---|---| | security-origin-licence | PASS | 0 findings; 0 SLOW cases; server ready at the end | | xuser_matrix.py | PASS | 203 operations replayed; 938 foreign-ID/missing-ID comparisons; 122 Job/Mail/quota comparisons; 0 denial failures | Live summary output, verbatim: ```text server alive at end: True ==== ROUND 2 FINDINGS 0 ==== ROUND 2 SLOW 0 PROBE attack2 PASS exit=0 Calendar subscription ownership matrix: 3 mutation rows, 15 owned-ID/missing-ID comparisons; A's settings and fetch state unchanged Photos Share contracts: timeline, buckets, day, search, item, stack, Calendar, Files ID, original, thumbnail and Public link pass before/after revoke Two-User OpenAPI matrix: 429 operations classified; 203 operations replayed; 938 A-ID vs missing-ID comparisons across B, C, D and anonymous; 39 identifier routes classified with no local fixture factory; median absolute timing delta 1.8 ms Job/Mail/quota ownership checks: 122 comparisons; 0 denial failures PROBE xuser_matrix PASS exit=0 ``` Built the own release server and CLI and the production web app. The fixture used real passkey registration, a disposable data directory, the existing proxy and the existing DNS fixture. Both live checks finished on that server. No product defect was found by these checks. No request or response bodies were put in the reports. Added a shared summary-only probe runner and three regressions. The runner suppresses payload output and exception messages and preserves each check's exit status. Its failing-test run had 3 failures before the runner existed; the implemented runner passes all 3. Files changed in this continuation: `tests/adversarial/summary_probe.py`, `tests/adversarial/test_summary_probe.py`; merge resolutions in `tests/adversarial/attack2.py`, `tests/adversarial/run.sh`, `tests/adversarial/xuser_matrix.py`, and `tests/adversarial/test_xuser_classification.py`. Results are in `artifacts/bash-security/findings.md` and the summary/gate logs. Review artifacts remain uncommitted. Gates and build output, verbatim: `cargo fmt --check`: exit 0, no output. No Rust source was changed in this continuation, so crate clippy and test gates were not repeated. Release build: ```text Finished `release` profile [optimized] target(s) in 42m 18s ``` `bun run --cwd apps/web check`: ```text svelte-check found 0 errors and 1 warning in 1 file ``` `bun run --cwd apps/web test --maxWorkers=2 --testTimeout=30000`: ```text Test Files 287 passed (287) Tests 2036 passed (2036) Start at 21:22:22 Duration 811.50s (transform 28%, environment 26%, import 24%, tests 16%, setup 6%) ``` Classification regressions: ```text .................................... ---------------------------------------------------------------------- Ran 36 tests in 4.188s OK ``` Summary runner regressions: ```text ... ---------------------------------------------------------------------- Ran 3 tests in 0.763s OK ``` Known gaps: 39 classified identifier routes have no local fixture factory. This pass is not live coverage of those identities. The default five-second web run had one timeout in `themePicker.svelte.test.ts` / `reveals the trigger in the sheet scrollport before opening`; all other 2,035 tests passed. The unchanged focused file passed 7/7, and the full retry above passed. The remaining CSS warning is the existing empty AttachmentDeck ruleset. These two live checks do not supply a valid signed Licence expiry fixture or run the separate Money import parser probe. Decisions: no new product design decision. Kept dev's canonical `feature_flag_id` class when resolving the #1297/#1202 duplicate tests; HTTP authority expectations did not change. Kept dev's #1213 cleanup fix and regression, which supersede the pending #1300 patch. Used a 30-second test execution watchdog for the busy-host web retry; no test workload or assertion changed. Bypassed the shared sccache daemon after it referenced another job's removed temporary directory. The first server startup failed because the local runner omitted data directory creation; corrected the runner to match the committed setup. Cleanup completed. The disposable server, proxy and fixture data are gone. Cargo output: ```text Removed 9577 files, 3.6GiB total ``` Removed `/mnt/hdd/targets/bash-security2`, the worktree's local target output, and `apps/web/build` and `.svelte-kit`. Working tree is clean. No push, deploy, or merge into dev/main was performed.
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
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-v0#1223
No description provided.