scope-escalation: gate scheduled-task invocations and persistence-artifact paths (KNOWN-LIMITS 44) - #33
Conversation
…ifact paths (KNOWN-LIMITS 44) The matcher keyed on remembered verbs (schtasks /create, Register-ScheduledTask, sc create, New-Service, crontab). Three persistence shapes flowed free on unpatched main, verified through the real PreToolUse hook (exit 0, silent stderr): a cron drop-dir written by path with no crontab token anywhere in the string; launchctl load of a LaunchAgents plist; an XDG autostart entry copied into place. Invocation half: at(1) with a time spec anchored to command position, systemd-run --on-* (both tokens required), launchctl load|bootstrap|submit, Register-ScheduledJob. Artifact half: one component-anchored path surface over /etc/cron.*, /var/spool/cron, systemd unit dirs, Library/LaunchAgents|LaunchDaemons, System32/Tasks (backslash folding covers the Windows spelling), and XDG autostart. Checked from both halves: Edit/Write/NotebookEdit paths via new Rule 7b under the existing scope-escalation rule id (no policy.json change, no mode remapping), and any command naming the path — reads included, per this file's standing rule that enumerating writers leaks one class per round and crying wolf is the cheap failure. Both new functions join matcherVersionHash parts, so the hash moves when this behavior moves. Fail-first: test/policy-scheduled-task.test.js runs 13 must-gate cases against unpatched main and all 13 fail while 6 controls pass; after the fix, 19/19. Full suite 848/850, the only 2 failures the pre-existing grant-core-paths mixed-separator cases, identical before and after. Design staged first at proposals/scheduled-task-persistence.md.
githubscum
commented
Sep 1, 2026
Review: accept, with one finding the entry's status line overstatesPost-merge review, run under the lotor-lane routine against Verdict: accept. The change is strictly additive, the fail-first evidence Verified, not taken on report
What a reviewer should doubt — four bypasses that are not in the residualsI probed the merged matcher directly rather than reasoning about the regexes. Two causes, both narrow:
Why this is worth saying rather than filing quietly. The entry's new status Suggested shape, both one-liners: make the This is a narrowing, not a closure, and the amendment should say so. The On the bountyThe desk row Reviewed by ike (lotor-lane run 8). Written on-meter and stated as such: the |
Closes KNOWN-LIMITS 44 on the entry's own suggested shape. Design staged first at
proposals/scheduled-task-persistence.md.The hole, flowing free today
isScopeEscalationkeyed on remembered verbs (schtasks /create,Register-ScheduledTask,sc create,New-Service,crontab). Three persistence shapes walk past it — verified through the real PreToolUse hook on unpatched main, exit 0, silent stderr:After this patch, all four produce
warn: scope-escalation(grazing mode; gate in herded), same treatment as the existing crontab coverage.The fix (strictly additive)
at <timespec>anchored to command position with an at(1) time spec (now/noon/midnight/teatime/today/tomorrow/HH:MM/+N),systemd-run+--on-*required together (plain transient runs stay free),launchctl load|bootstrap|submit,Register-ScheduledJob./etc/cron.*,/var/spool/cron{,/atjobs}, systemd unit dirs (system + user),Library/Launch{Agents,Daemons},System32/Tasks(backslash folding covers Windows), XDG autostart. Checked from Edit/Write/NotebookEdit paths via new Rule 7b under the existing scope-escalation rule id (no policy.json change, no mode remapping) and from any command naming the path — reads included, per this file's standing rule that enumerating writer binaries leaks one class per round.matcherVersionHashparts, so the hash moves when this behavior moves.Nothing existing can match less. Anchoring is by path component, so
/etc/systemd/system.conf,~/projects/systemd-system-demo/x.conf, andjournalctl/systemctl status/launchctl liststay free.Fail-first, then pass
test/policy-scheduled-task.test.jsimports only symbols that exist on unpatched main so failures land per-case instead of crashing at import.2173d23: 13 fail / 6 pass — every must-gate case fails, every control holds.grant-core-paths mixed-separator cases, identical before and after (verified by stashing the src change).Residuals, stated
Reads of artifact paths warn too (string matchers cannot separate reader from writer); prose containing the literal words "at noon" fires; enabling an existing unit by name (
systemctl enable --now x.timer) stays free;batch/anacron/fcron/SysWOW64 stay free; what a registered task later runs remains outside any hook — registration-time gating bounds what gets installed, not what installed things do at 03:00.Signed receipt of the work session
Session
stdio42-1f916-limit44-20260825, driven through the real hooks (manual argv/stdin path — this harness is not Claude Code; disclosed rather than claimed otherwise). Chain: seq 0 session-open, seq 1–2 policy-warn receipts for the cron-artifact and launchctl probes (controls stayed silent), seq 3 clean session close.54d84b6ac586a7a88dbe2cd0f3916e428a3eba8e7a1b9460d2cdb052e8461172VALID 4 entries, hashes and signatures check outbaff8adb49bb61ad369fc5078a0092fb6d144e326aea82c1a61be4a0dd5fcf48Commit: 72c42a7