Skip to content

kit 3.1.0

Choose a tag to compare

@sandstream sandstream released this 24 Jul 22:22
de3314e

Added

  • kit agent-config now wires GitHub Copilot — the managed "use kit" rules
    block is written to .github/copilot-instructions.md, Copilot's canonical
    custom-instructions file (VS Code / Visual Studio). Detected when a .vscode/
    dir is present or the file already exists; writeAgentConfig creates the
    nested parent dir (.github/) as needed. Fifth rules-file target alongside
    CLAUDE.md / AGENTS.md / .cursorrules / .clinerules (Antigravity is already
    covered via its AGENTS.md support). Advisory only — memory indexing and a
    blocking install-gate for Copilot still need its transcript format + pre-tool
    hook surface verified against primary sources; the git-hook floor and the MCP
    server (kit mcp) apply to Copilot today regardless.

  • Memory sync blobs are now gzip-compressed before encryption (~3× smaller). A
    SQLite store compresses well (a real store: 6.3 MB → 2.1 MB; large stores
    ~139 MB → ~30 MB), which keeps the blob under a git host's 100 MB file limit and
    speeds every transport. Compression happens INSIDE the encryption, so the remote
    still only ever sees ciphertext. Fully backward-compatible on read: an older,
    uncompressed blob decrypts to a raw SQLite file (no gzip header) and is passed
    through untouched — no new format version, works for both the passphrase and
    public-key modes. This is what makes a private GitHub repo viable as the
    ephemeral-session hub (alongside a self-hosted remote, which has no size cap).

  • Public-key memory sync — an ephemeral session can push with NO secret. The
    passphrase mode (AES-256-GCM + scrypt) requires the same secret on every
    machine that pushes
    , which an ephemeral cloud session can't safely hold (no
    secret-store, no SSH key). New asymmetric mode flips it: kit memory keygen
    mints an X25519 keypair; the public recipient string (kitmem-pub-…, not a
    secret — safe in a setup script, env var, or the repo) goes in [memory.sync]
    as recipient = "…", and push encrypts to it (MAGIC_V3: ephemeral X25519 →
    HKDF-SHA256 → AES-256-GCM, libsodium sealed-box shape, zero new deps — pure
    node:crypto). Only machines holding the private key (~/.kit/memory-key.json, 0600) can decrypt on pull. So an ephemeral session needs only a public key + a
    reachable private repo to contribute its memory — nothing secret leaves it, and
    push_on_end/pull_on_start work passphrase-free when a recipient is set.

  • External timestamp anchor (command transport) — closes the same-machine
    forge gap.
    The keyless chain + machine-local HMAC anchor proves what
    happened on this machine
    , but anyone with that machine's anchor key (UID) can
    re-seal a rewritten log undetectably. kit audit anchor --external now folds
    an external authority's receipt into the seal: the documented
    resolveExternalAnchor() extension point is implemented via a command
    transport — set KIT_EXTERNAL_ANCHOR_CMD to any program (an RFC3161 TSA
    client, an append-only log writer, a notary) and kit pipes the tip/count/log
    path to it via KIT_ANCHOR_TIP/KIT_ANCHOR_COUNT/KIT_ANCHOR_LOGPATH,
    expecting a JSON receipt { token, authority?, timestamp? } on stdout. The
    receipt is stored on the anchor record (external). kit still ships no
    network client
    (no-egress default) — the operator wires the authority.

  • kit audit verify --require-external fails any seal that carries only an
    HMAC anchor (no external receipt), so a policy can demand third-party
    countersignature on every seal. Fail-closed throughout: --external with no
    command configured, a non-zero exit, non-JSON output, or a missing token all
    hard-error rather than silently degrading to HMAC-only.

Fixed

  • PAL finding sync no longer device-blind — a real security blocker can't be
    silently cleared across devices.
    The adversarial pass on the 3.0 surface
    found that palSyncFindings auto-closed findings purely by source-tag + scope,
    with no origin_device predicate — and because the finding id was derived
    only from sourceTag + dedupKey (repo-independent), the id was identical
    across machines on a shared/synced store. So a kit check on device B that
    didn't see device A's open finding would permanently close A's blocker (and
    two different repos that merely shared a directory basename reconciled into
    each other's findings). The reconcile now (a) folds the scope into the id so
    different repos get distinct rows, (b) scopes findings by the absolute
    project root rather than its basename, and (c) device-fences the auto-close
    (origin_device = thisDevice OR NULL), so a scan only clears findings this
    device owns (legacy NULL-origin rows stay reconcilable by any device). A
    genuinely-cleared finding now lingers until the owning device re-scans —
    fail-safe (a stale reminder, never a dropped blocker).

Security

  • Device identity is now an unguessable persisted token, not a hostname hash.
    deviceId() previously derived the per-device id from sha256(hostname+user)
    — guessable by any peer on a shared store (enabling cross-device
    surface/suppression of PAL items) and unstable across hostname churn (a DHCP
    lease or machine rename would silently orphan a device's own items). It now
    prefers a random id persisted once to <memoryDir>/device-id (0600), and
    validates the KIT_DEVICE_ID override against [A-Za-z0-9_-]{1,64} (a
    malformed value is ignored rather than trusted). The hostname hash remains only
    as a last-resort fallback when the id file can't be written.
  • Memory-sync git transport hardened against option injection. The
    remote/branch from ~/.kit/sync.toml are passed to git as positional
    argv (no shell — OS metacharacters were already inert), but a value starting
    with - is parsed as an option (a remote of --upload-pack=<cmd> turns
    git clone into command execution) and ext::/fd:: remote helpers run
    commands by design. loadSyncConfig now rejects both, call sites pass
    --end-of-options before positional operands, and git runs with
    protocol.ext/protocol.fd disabled. (Config is operator-owned, so this is
    defense-in-depth — and future-proofs any sync init that ingests a remote.)
  • The transport = "command" child no longer inherits kit secrets. It moves
    only the already-encrypted blob, so it never needs KIT_MEMORY_PASSPHRASE
    yet the whole process.env (passphrase included) was handed to it, so a
    transport that logged its environment (aws --debug, a set -x shell) would
    spill the passphrase right next to the ciphertext it protects. The child env is
    now stripped of every kit-managed passphrase/secret/token/key; the operator's
    own provider credentials still pass through.

Full changelog: https://github.com/sandstream/kit/blob/v3.1.0/CHANGELOG.md

Verify this release:

git tag -v v3.1.0
npm audit signatures