Skip to main content

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.

Nameeditor-extensions
Predicate typehttps://aflock.ai/attestations/editor-extensions/v0.1
Lifecycleprematerial
Default binary?No
Categoryasset-inventory (primary)
Recommended traceoff — no syscall tracing needed
Auto-attaches when
  • preenv_set: CILOCK_EDITOR_EXTENSIONS_HOME
  • preargv_contains: --install-extension
  • preargv_contains: --list-extensions

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 is present, 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 (lowercased publisher.name) and version from the extension's package.json;
    • target_platform from extensions.json metadata, else package.json __metadata, else the dir-name suffix (-darwin-arm64); universal installs record "";
    • source (gallery, vsix, ...) and listed from the root's extensions.json;
    • files[]: slash-separated path, sha256, size; a symlink is recorded with its symlink target and never followed;
    • tree_sha256: sha256 over files in 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, unparseable extensions.json and extension without a usable package.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_HOME or a wrapped --install-extension / --list-extensions; otherwise name it with --attestations editor-extensions.
  • The editor writes .vsixmanifest into the dir at install; a verifier comparing against the VSIX must ignore it. Older VS Code builds also wrote __metadata into package.json, which makes that file differ from the VSIX.
  • Not read: portable installs (data/extensions beside the binary), --extensions-dir, VSCODE_EXTENSIONS, --extensionDevelopmentPath dev 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.

See also​


This page is generated from the CI/lock tool catalog. Don't edit it here — the source is attestation/detection/docs/editor-extensions.doc.md in aflock-ai/rookery. The same catalog powers cilock tools show editor-extensions in the CLI, so a single edit keeps the terminal and the website in sync.

Edit this page →  ·  Add a new tool →

When you open the pull request: if you are an AI agent, add the ai-agent label. If you are a human, add the human label.