kit 3.1.0
Added
-
kit agent-confignow 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;writeAgentConfigcreates the
nested parent dir (.github/) as needed. Fifth rules-file target alongside
CLAUDE.md / AGENTS.md / .cursorrules / .clinerules (Antigravity is already
covered via itsAGENTS.mdsupport). 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]
asrecipient = "…", 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_startwork 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 --externalnow folds
an external authority's receipt into the seal: the documented
resolveExternalAnchor()extension point is implemented via a command
transport — setKIT_EXTERNAL_ANCHOR_CMDto any program (an RFC3161 TSA
client, an append-only log writer, a notary) and kit pipes the tip/count/log
path to it viaKIT_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-externalfails 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:--externalwith no
command configured, a non-zero exit, non-JSON output, or a missingtokenall
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 thatpalSyncFindingsauto-closed findings purely by source-tag + scope,
with noorigin_devicepredicate — and because the finding id was derived
only fromsourceTag + dedupKey(repo-independent), the id was identical
across machines on a shared/synced store. So akit checkon 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 fromsha256(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 theKIT_DEVICE_IDoverride 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/branchfrom~/.kit/sync.tomlare passed togitas positional
argv (no shell — OS metacharacters were already inert), but a value starting
with-is parsed as an option (aremoteof--upload-pack=<cmd>turns
git cloneinto command execution) andext::/fd::remote helpers run
commands by design.loadSyncConfignow rejects both, call sites pass
--end-of-optionsbefore positional operands, andgitruns with
protocol.ext/protocol.fddisabled. (Config is operator-owned, so this is
defense-in-depth — and future-proofs anysync initthat ingests a remote.) - The
transport = "command"child no longer inherits kit secrets. It moves
only the already-encrypted blob, so it never needsKIT_MEMORY_PASSPHRASE—
yet the wholeprocess.env(passphrase included) was handed to it, so a
transport that logged its environment (aws --debug, aset -xshell) 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