Skip to content

v5.16.0

Choose a tag to compare

@Kanevry Kanevry released this 29 Oct 04:13
· 3 commits to main since this release

Added

  • The security delta checks package floors (#1769, stage 3): a sixth, offline check package-floor compares the npm packages a range introduced in a lockfile, and a packageManager value changed in a package.json of the range, against the projects-baseline security-floor register scripts/lib/security-floors.json.
    • The register comes from the host-local baseline. Only when no baseline is configured or it has no register file, the repo register as committed at head is used (scripts/security-floors.json, then scripts/lib/security-floors.json, the copy projects-baseline vendors). Without either the check is skipped / no-register. A configured but unavailable baseline degrades the check (baseline-unavailable) and never falls back to the repo register. It is read only when the range has a candidate.
    • A version below the floor of its major line is a critical finding with the floor as fixedIn. A bad line or a major older than every registered line is critical without fixedIn. A newer or missing middle line passes. A register older than 30 days degrades the check (stale-register) and keeps its findings.
    • Under security-delta.mode: strict an introduced package-floor finding with a known floor now blocks the close (exit 3), next to the malicious-package and fixable-critical rules. A finding without a floor only warns.
    • Register text (source, bad) never reaches findings, reasons, the report or the event. orchestrator.security.delta gains package-floor in checks and by_class.
  • Discovery probe insecure-infra-defaults (#1770, category infra): runs the baseline's pattern SSOT scripts/check-insecure-defaults.sh --json and reports one medium finding per class and file (today compose-public-port, ssh-host-key-disabled, caddy-panel-public). The probe carries no copy of the patterns; new classes arrive through the SSOT.
    • The baseline is resolved host-locally only (SO_BASELINE_PATH, then owner.yaml paths.baseline-path); the scanned repo's config is never read and a baseline inside the scanned repo is rejected. The script runs via execFile without a shell, with the baseline root as cwd and PYTHONSAFEPATH=1.
    • Without a baseline the probe is skipped / no-baseline. Discovery only, never session-end Phase 2.4. This change adds the probe; it reports no results from other repos.
  • Every session start checks whether the loaded plugin install can load its runtime dependencies (#1848). The Codex marketplace short form installs each version into a new cache directory without node_modules. Codex reports the plugin installed and enabled, the MCP server answers, and every script that imports js-yaml, zod or another runtime dependency fails (measured 2026-10-28 on 5.14.0: 0 of 11 dependencies in six fresh caches).
    • A new banner probe, runtime-readiness, checks the plugin root the running hook code was loaded from. So every cache change is re-checked without anyone remembering to. It names the missing packages, the path and the repair command. It imports only node:* modules, starts no process and installs nothing. An unreadable package.json is a degraded warning, never the clean null.
    • node scripts/runtime-readiness.mjs [--root <dir> | --codex | --claude] [--repair] [--json] checks one install: declared dependencies, two import probes in a fresh child process and the owner-config source (only source/reason, never a value).
      • Exit codes: 0 ready, 3 not ready, 1 usage or target error, 2 system error.
      • --repair runs npm ci (or npm install) with --ignore-scripts, adds --omit=dev only outside a git checkout, then re-checks in a new process.
      • --codex and --claude pick the enabled install, refuse to guess between several, and refuse cache paths that leave the cache directory.
  • A repeatable install smoke for Claude Code and Codex with a model (#1764). node scripts/install-smoke.mjs --harness <claude|codex|codex-clone> --model <id> installs the plugin from the GitHub mirror into a throwaway profile. It then checks readiness and drives /bootstrap and one feature session through a model endpoint taken from SMOKE_* environment variables, and prints a JSON receipt.
    • A session passes only at a plan that offers /go, with no project file changed since bootstrap (committed or not) and a successful tool call that opened the session skill. The smoke answers up to three questions with "1", but never past a SEC-008 detector hit.
    • Keys stay out of every tool call's environment: Claude Code through an apiKeyHelper file, Codex through env_key with SMOKE_* excluded from its tool shells.
    • The smoke is not a sandbox: the model steps run as the maintainer's OS user. docs/install.md names the residual risk.
  • The setup docs and the guide record the 2026-10-28 runs on plugin 5.14.0, including a model session (#1764).
    • Claude Code 2.1.312: install, bootstrap and a session that ends at the plan.
    • Codex CLI 0.160.1:
      • The clone installer passes.
      • The short form needs the repair step.
      • With gpt-6-sol the session wrote the file without a plan, recorded as model behaviour.
    • Cursor: a model session needs a Cursor login.
    • Pi: not installed on the test host.
    • Found on the way: the telemetry consent question also stops headless claude -p sessions (#1872). A model-chosen command printed the session's own messaging token in 2 of 5 Claude runs (#1873).

Changed

  • The Claude Code SessionStart hook timeout is 10 s, was 5 s (#1859, reported in GH#76 by dviolante): on a Windows 11 host the first SessionStart ran close to the 5 s limit. SessionEnd already had 10 s. The Codex hooks-codex.json SessionStart timeout is unchanged at 5. Comments and docs/events-schema.md that quoted 5 s now say 10 s.

Fixed

  • Closed issues no longer keep a status:* label (#1876): issues closed by Closes #N on an MR merge kept their status label, so every label query without a state filter counted wrong (59 "on review" instead of 31). node scripts/lib/issue-close-strip-labels.mjs --strip-closed [--apply] lists every closed issue that still carries any status:* label (labels read from the project's label list, paginated) and, with --apply, removes only those labels and reads the counts back; without --apply it is a dry run. gitlab-ops § Status Labels makes this run part of the check after every merge that closes issues. The one-time sweep on 2026-10-29 cleared 367 closed issues; every status:* label now counts 0 closed issues.
  • Headless runs are no longer stopped by the telemetry consent question, and the question is English by default (#1872): the SessionStart hook queued the one-time consent nudge for every run outside CI, so claude -p "/session-orchestrator:session …" ended on the question instead of the plan. The hook now treats CI and CLAUDE_CODE_ENTRYPOINT=sdk-* as non-interactive and queues no nudge there. The nudge text follows owner.language (German only for de, English otherwise). Named limit: that the SessionStart hook itself sees sdk-cli under claude -p was measured only for tool child processes.
  • Session start tells the model to read environment files by key name only (#1873): the instruction "ALWAYS check .env or .env.local" invited unfiltered env, which printed CLAUDE_CODE_MESSAGING_TOKEN in 2 of 5 install-smoke runs. skills/session-start/SKILL.md and the Phase 4 reference now require names, presence or length only (SEC-008), with three safe command forms, and ban cat .env* and bare env/printenv.
  • Skill snippets start programs through native-command.mjs (#1870, follow-up to #1864): six node -e snippets in skills/ imported node:child_process directly and started git by its bare name, which on Windows can run a git.exe from the user's repo. They now import the drop-ins from scripts/lib/native-command.mjs. A new blocking validate-plugin check, check-skill-child-process.mjs, reports any child_process import inside a fenced code block of skills/**/*.md.