editor-extensions attestor
Inventories the extensions installed for VS Code, VS Code Insiders, Cursor, Windsurf and VSCodium, plus the dirs their remote servers install into, and signs a sha256 for every file. Pure Go: no tracing, no privileges, same behaviour on macOS, Windows and Linux.
| Name | editor-extensions |
|---|---|
| Predicate type | https://aflock.ai/attestations/editor-extensions/v0.1 |
| Lifecycle | prematerial |
| Default binary? | No |
| Category | asset-inventory (primary) |
| Recommended trace | off — no syscall tracing needed |
| Auto-attaches when |
|
The facts in this box are generated from the CI/lock binary's own catalog (cilock tools list). Do not hand-edit — run npm run gen:catalog.
Validated invocation
cilock attest --step editor-extensions --attestations editor-extensions \
--signer-file-key-path key.pem --outfile editor-extensions.json
Point it at another home directory (a fixture, a mounted image) with CILOCK_EDITOR_EXTENSIONS_HOME;
a relative value is resolved against --workingdir.
What gets captured
roots[]: one entry per editor dir it looks in, recorded home-relative (~/.cursor/extensions), whether it ispresent, and the sha256 of each top-level file (extensions.json,.obsolete). Dirs read, under the home directory (%USERPROFILE%on Windows):.vscode,.vscode-insiders,.cursor,.windsurf,.vscode-oss(VSCodium), and.vscode-server,.vscode-server-insiders,.cursor-server,.windsurf-server,.vscodium-server, each/extensions.extensions[], one per directory under a root:id(lowercasedpublisher.name) andversionfrom the extension'spackage.json;target_platformfromextensions.jsonmetadata, elsepackage.json__metadata, else the dir-name suffix (-darwin-arm64); universal installs record"";source(gallery,vsix, ...) andlistedfrom the root'sextensions.json;files[]: slash-separated path,sha256,size; a symlink is recorded with itssymlinktarget and never followed;tree_sha256: sha256 overfilesin path order, one compact JSON array per line:["f","<sha256>","<path>"]for a file,["l","<target>","<path>"]for a symlink. JSON escaping keeps it unambiguous whatever a path contains;complete: every file under the dir was hashed.
gaps[]: every cap hit, unreadable path, symlinked extension dir, unparseableextensions.jsonand extension without a usablepackage.json. Nothing is skipped without a gap.limits: the caps in force (files and bytes hashed, directory entries visited). Every byte read counts against the byte cap, and every entry, directories included, against the entry cap. Directories are read in batches, so a huge one is never listed whole first.
Subjects: editor-extension:<editor>:<id>@<version>[+<platform>] (or editor-extension:<editor>:dir:<name>
without an identity), digest = tree_sha256.
Why this shape
A file digest is what proves tampering: the Cursor 2.1.312 build differed from upstream 2.1.312 only in
extension.js. The per-file list lets a verifier name the file; the tree digest lets the fleet search
for one bad tree by subject digest. Identity comes from package.json because that is what the editor
loads, and it is parsed from the same bytes that were hashed.
Validate it locally
make test MODULES="plugins/attestors/editor-extensions"
How a verifier consumes this
A policy reads extensions[] and joins on id, version, target_platform and each file's sha256
against an upstream reference set; complete and gaps say whether the inventory may back a "not
affected" claim at all. An extension with complete: false, or any gap, cannot. The subject digest
(tree_sha256) finds every machine carrying one known-bad tree.
Gotchas
- Opt-in. Its gate fires on
CILOCK_EDITOR_EXTENSIONS_HOMEor a wrapped--install-extension/--list-extensions; otherwise name it with--attestations editor-extensions. - The editor writes
.vsixmanifestinto the dir at install; a verifier comparing against the VSIX must ignore it. Older VS Code builds also wrote__metadataintopackage.json, which makes that file differ from the VSIX. - Not read: portable installs (
data/extensionsbeside the binary),--extensions-dir,VSCODE_EXTENSIONS,--extensionDevelopmentPathdev extensions, and builtin extensions inside the app bundle. Run it on each side of WSL or a remote host: it reads the running OS's home only. - It proves what is on disk when it runs, not what the editor process loaded.