Repository navigation
v5.17.0
Added
-
Private vulnerability reports in the security pulse (#1902): the detached refresher checks up to three GitHub fetch remotes in parallel after OSV, paginates all triage reports with timestamp-only output, and caches counts and timestamps. Readable remotes remain visible when another remote fails; the cache and banner reasons record the unreadable count. Each remote has a 10-second timeout. Session-start reports open submissions and warns about lost access only after a previous successful measurement.
-
Error pages in the
nextjs-minimalarchetype (#1885): both generators (templates/nextjs-minimal/and the inline steps inskills/bootstrap/standard-template.md) now createapp/error.tsxwithretry(),app/global-error.tsxwith its own<html>/<body>, andapp/not-found.tsx, per projects-baselinefrontend.md§ Error Pages. Nopages/500.tsx.retryneeds Next ≥ 16.3, so the archetype moves to Next^16.4.2. Intemplates/nextjs-minimal,next lintbecomeseslint .with the native flat config ofeslint-config-next16. -
German command reference
/de/commands(#1766): the same 31 cards as/commands, with an EN/DE switch on/commands,/de/commandsand/guide, hreflang, sitemap andSITEMAP_PAGES./guidehas no German twin; its DE link goes to/deand says so. The German home page now links the German reference. The site tests hold both pages to the same roster, order, hreflang and language-switch rules. -
Tool switcher on
/commands(#1776): Claude Code, Codex, Cursor or Pi. Each card then shows only that tool's form, and command mentions elsewhere on the page follow the choice (the Codex form is read from each command's own card). Without JavaScript all forms stay visible. The choice is kept inlocalStorage(so-tool) and can be set with?tool=. The/plancard gains a worked example and the PRD path. -
A Full Gate receipt can stand in for a repeated run (#1897): the Quality wave and session-end both ran the Full Gate, mostly on the same tree.
run-quality-gate.mjs --variant full-gate --receiptkeys a receipt on the content tree (tracked plus untracked, non-ignored files) and the gate definition (the three commands, Node and plugin version). A green run writes one only when the tree did not change while it ran. A receipt younger than 12 h is published instead of running the suite again, markedreused_fromin the envelope and in theorchestrator.quality_gate.passedevent. The flag is opt-in: the Quality wave and session-end pass it, pre-push, CI and other callers are unchanged. No receipt is given below the repository top level, for index entries marked assume-unchanged or skip-worktree, in a sparse checkout, or with an inheritedGIT_DIR/GIT_WORK_TREE. Receipts live in<git common dir>/so-gate-receipts.jsonl, shared by all worktrees.
Changed
-
The destructive guard reads words the shell still expands, and zsh's precommand modifiers (#1892). Before, each of these let the whole command through, rule included:
- An option word with an expansion counts as a possible
-rand-fand passes no path exception, because it may split off a path (rm -rf${x} /data,rm "-rf$x" /data,rm -rf${x} node_modules). - An rm word that reads
rmonce empty expansions drop counts asrm(r${x}m,rm$(true),rm${${x:-}:+}). builtin,noglob,nocorrectand zsh's-are unwrapped likecommand, sobuiltin eval "…",- bash -c '…'andnoglob git clean -fdxare judged by their payload or subcommand.- Named limit: a bare variable (
rm -f "$T") could carry-rftoo, but 437 real rm commands in 7 days carry one, so it stays a plain operand. So does an expansion that is not empty ($Rholdingrm).
- An option word with an expansion counts as a possible
-
The destructive guard reads an empty expansion inside a word (#1893): with
xunset, bash runsgit res${x}et --hardasgit reset --hard, and every git rule (reset, push --force, clean -f) allowed it. Each command is now also judged as it reads with empty expansions:$name,${…},$((…)),$( … )and backticks read as empty,${x:-w}asw, and a match in either reading counts. An unknown ref before--stays unjudged as before (git checkout $REF -- f). A default word nested deeper than 8 levels counts as a match (fail closed). Measured on 145,960 real Bash commands from 7 days against 13 rules: 0 verdict changes, mean cost per command 0.36 → 0.50 ms. -
$"…"strings and$"…"/$'…'here-doc delimiters are read as bash runs them (#1895): bash without a message catalog drops the$of$"…", sogit reset $"--hard"andgit push $"--force"ran while the guard read$--hardand allowed them. The pattern rules now take the bash reading; path resolution keeps zsh's, so> $"PWD"/fstays unresolved. A here-doc delimited by<<$"EOF"or<<$'E\x4fF'(escapes decoded) now ends where bash ends it, so the lines after it are judged as commands instead of body text. -
so-hud reads a data command's quoted pattern as text (#1891):
grep -E "denied|git push|deny" …asked for a push, because the|inside the quoted pattern split offgit pushas a command of its own. A segment cut out of a quoted word of a data command (grep, rg, echo, …) now counts as text, unlessevalparses the word again or the output is piped into a command that runs it (| sh). -
Project cleanup exceptions cannot traverse escaping symlinks (#1886).
rm -rfnow checks the real filesystem location undernode_modules,.orchestrator/tmpand the state-directoryfilescopes, including a trailing/or/..- Removing a bare symlink stays allowed. A resolution error blocks the exception.
- A target the shell still resolves no longer passes any exception,
/tmpincluded. That covers a..segment (rm follows it through a link),$, a backtick, a zsh(, and a glob or brace in a directory segment (node_modules/*/). - A glob in the final segment (
node_modules/*) stays allowed. - A relative target is judged where the shell runs it: from the hook payload's
cwdand every literalcdon the line. Thecdtarget is added as a candidate rather than replacing the old directory, because a failedcdleaves the shell in place. Acdwith.., a subshell, a substitution,pushd/popd,evalorsourcemake the directory unknown. - A
cdbehindbuiltinor a zsh precommand modifier (noglob,nocorrect,-) counts, and so does zshchdir. A barecdorcd ~is unknown when the line may set HOME. A relativecdis unknown when the line or the environment may set CDPATH. $TMPDIRis expanded only in the first command of the line, only when TMPDIR is set, and only when no other word of that command expands something: zsh expands words left to right, sorm -rf -v${${TMPDIR::=/usr}:+} $TMPDIR/libdeleted/usr/lib. There is noos.tmpdir()fallback any more. A trailing/on a/tmptarget follows the link, as in the project branch.- Replay of 801 real recursive-rm commands from 7 days: 797 verdicts unchanged. Of the 4 new denials, 3 cost a real command: twice
$TMPDIRafter another command, oncecd "$P". The fourth was a review probe. The third review's fixes changed none of 785 replayed verdicts.
-
rm -rf "$D"on an mktemp variable stays blocked, now with a hint (#1883): the deny reason says why and what works instead (skip the cleanup, since the OS clears$TMPDIR, or remove the literal temp path). A text-level grant was built and rejected: on the 94 real denials it proved only 1 cleanup, and an adversarial review found 6 bypasses (rebinding through zsh builtins,$Eholdingeval, a PATH-hijackedmktemp, macOSmktemp -tprinting two paths). -
Templates-first respects Session Config (#1882): under the strict hook profile,
enabled: falsebypasses the guard andhostslimits enforcement. Config is read only for matched create calls; read/parse failures warn and keep enforcing. -
Hooks block less by default; the hook profile is a host setting (#1881). Over 7 days on the maintainer's host the hooks denied 792 of 185,316 tool calls, most of them false alarms.
- New default profile
standard:pre-bash-templates-firstandpre-tool-state-md-path-guardare off, andworktree-remove-unsafewarns when it cannot measure the target (110 of its 136 denials). A measured hit and--forcestill block.strict(the oldfull, which stays an alias) keeps everything as before. owner.yamltakeshooks.profile(strict,full,standard) andhooks.disabled, so the profile no longer has to be exported per process. owner.yaml can relax but never disarm:minimal,offand disabling a safety hook stay env-only, because an agent can append to the file.SO_HOOK_PROFILE/SO_DISABLED_HOOKSstill win. An unknownSO_HOOK_PROFILEis now ignored instead of meaningfull.- When one command removes several worktrees, a measured hit now wins over an earlier target the guard could not measure. A removal behind an unknown directory (
bash -c '…',$(…),git -C $X) is measured from an absolute target itself or from the payload cwd, so wrapping a denied removal no longer turns the deny into a warning. Only a call whose every word is literal and that names one target may warn: a--forcehidden in$F,$(…), a glob or past the payload depth cap still blocks.
- New default profile