Skip to content

Security review · APPLY5, runs started from a terminal in 0.0.1

security-reviewer on autopilot/apply-observed-runs at c66a29b, 2026-10-28: the design in architecture §13.8, dev-env §15.3 and P1 Tasks 25 and 26. There is no product code yet, so every finding becomes one line of the new §15.3 req. 12 and named tests in P1. Format: README.md.

SECURITY  step=APPLY5  review=block b/M/m=1/4/4  applied=9/9  rounds=1

Trust boundaries, now in §15.3: (i) to (iii) as in APPLY4, plus (iv) other users who can write a launch directory, such as a group-writable project directory, for runs started from a terminal.

Id Severity Finding Status
SEC-141 blocker the bridge wrote into <launch dir>/.spectio without checking it; another user who can write the launch directory could plant a symlink and make the bridge write elsewhere as the user applied: req. 12 Bridge, launch directory (O_DIRECTORY\|O_NOFOLLOW, fstat, fd-relative O_NOFOLLOW creates, spool 0600, otherwise nothing written); Java SecureDirectoryStream and EMBL NFSv4 inherited ACLs [to be validated] (inbox #64 note); Task 25 test_bridge_refuses_foreign_spectio_dir and its _snakemake twin
SEC-142 major the server read a terminal run's .spectio/ and engine logs by path; a swap after the check, a symlink or a FIFO could leak or hang applied: req. 12 Server, reads (one checked descriptor; logs O_NOFOLLOW\|O_NONBLOCK, regular file owned by the user, last 64 KiB, else "log not readable"); Task 25 test_spectio_dir_swapped_after_check_not_read, test_terminal_log_symlink_fifo_foreign_refused
SEC-143 major two terminal runs in one launch directory shared spool files applied: spool files <run ID>-<producer>-<nnnn>.jsonl (architecture §13.8), only the pointer's run ID read, other envelopes dropped and counted (req. 12); Task 25 test_two_terminal_runs_same_launch_dir_kept_apart
SEC-144 major the head check used the pointer's host and PID on any node; a terminal run could be marked lost, or reach the launcher applied: checked only when the boot ID equals the server's, else "head on , not checked"; no launcher or tmux call for cli-observed, boot reconciliation included (req. 12 No signal, architecture §13.8 Its end); Task 25 test_terminal_run_other_boot_id_never_head_lost, test_runner_never_signals_cli_observed
SEC-145 major nf-spectio, snakemake-logger-plugin-spectio, spectio and spectio-open are unclaimed (registry and PyPI 404 on 2026-10-28) while the repository is public, and the install lines were unpinned applied: req. 12 Names and pins (Thomas claims the four names before the guide merges; exact pins; bundled copies until then); inbox #66 rewritten, blocking the guide only; Task 25 test_guide_pins_bridge_versions
SEC-146 minor a loose ~/.spectio or observed/ would accept pointers from others; the pointer's keys were open-ended applied: req. 12 Bridge, pointer and Server, scan (real directories owned by the user, 0700; exact keys; a loose observed/ skipped with a warning, never blocks the start); Task 25 test_loose_spectio_home_no_pointer, test_pointer_keys_exact, test_loose_observed_dir_not_scanned
SEC-147 minor a pointer could alias a driven run or point into the runs root applied: the runs root wins; such pointers skipped and counted (req. 12 Server, scan); Task 25 test_pointer_cannot_alias_driven_run
SEC-148 minor every tick listed observed/, and ended pointers piled up applied: list at boot, then only when the mtime changes; ended pointers move to observed/ended/, read only by a rebuild (req. 12, dev-env §15.3 Where data lives); Task 25 test_scan_skips_unchanged_dir, test_ended_pointer_moved
SEC-149 minor the release dry run could pick up a user's Gradle cache or keys, and the Gradle plugin and wrapper were unpinned applied: req. 12 Release (fresh GRADLE_USER_HOME, pinned plugin and distributionSha256Sum, release from CI or a laptop, never the cluster; release steps owned by dev-env §12); Task 26 no-secrets-in-artefacts check in tests/packaging/test_bridges_release.sh