# Extension Lockfile: an allow-list check for your editor extensions (free sample)

Version 1.0, 2026-10-30. By PaperChaseWebb. For anyone using VS Code, Cursor, Windsurf or VSCodium with AI extensions who wants to know what is installed and whether it matches what they meant to install. One Markdown file and one Python 3 script, standard library only, read-only. The script was tested with 4 tests on made-up home folders on Linux with Python 3.13. It was not run on a real macOS or Windows machine or against real editor installs.

On 2026-10-29 a tampered build of the Claude Code extension, version 2.1.313, was served from the Cursor extension marketplace for about eleven hours. It tried to add an SSH key and leave startup entries behind. If you use either, start with the vendor's notice, below. Then use this sample to write down what you trust, so the next one is quicker to check.

## First: were you affected by 2026-10-29

Read Cursor's notice: https://cursor.com/blog/claude-code-extension-security-notice . It covers Anthropic.claude-code 2.1.313 installed or updated from the Cursor marketplace between about 2:27am and 1:35pm Pacific time on 2026-10-29, and gives a one-line check for macOS and Windows plus remediation scripts. In short:

- Look for the key comment svc-telemetry in ~/.ssh/authorized_keys (on Windows, also C:\ProgramData\ssh\administrators_authorized_keys).
- Look for a folder named anthropic.claude-code-2.1.313 followed by a platform under ~/.cursor/extensions or ~/.vscode/extensions.

If you find either, follow the notice's remediation, then rotate keys and tokens from a different machine. Uninstalling the extension alone is not enough, because it leaves startup entries behind.

## Install in one line

Save this page as FREE-SAMPLE.md, then:

```
python3 -c "import re;t=open('FREE-SAMPLE.md').read();open('ext_mini.py','w').write(re.search(r'~~~python\n(.*?)~~~',t,re.S).group(1))"
```

## Write your allow list

Make allow.txt with one extension per line, as publisher.name@version. Use publisher.name@* if you accept any version. The names and versions are in the editor's Extensions view, or in the script's own output on the first run.

```
# allow.txt for a made-up studio
esbenp.prettier-vscode@11.0.0
example-co.internal-snippets@0.4.2
```

Then run it. The number is the cooldown in hours: anything installed or updated more recently is flagged.

```
python3 ext_mini.py allow.txt 72
```

Each line shows the editor, the extension and either ok or what is wrong: NOT ON ALLOW-LIST, VERSION NOT APPROVED, or INSTALLED 5h AGO. Exit 0 when nothing is flagged, 1 when something is, 2 when the input is wrong.

## Five habits that make the list useful

1. Turn off automatic extension updates: `"extensions.autoUpdate": false` in your user settings.json. Updates then wait for you.
2. Wait a few days before taking a new version, and check the publisher's and the marketplace's notices first. Most bad releases are found within a day or two.
3. Remove what you do not use. Every extension runs with your user's rights.
4. Check every editor you have installed, not only the one you use most. Each keeps its own folder.
5. If a check ever finds something you did not install, do not delete it in a hurry. Write down what it is and where, then look for a notice.

## The script

~~~python
#!/usr/bin/env python3
"""ext_mini.py: list editor extensions and flag the ones that are not on your allow-list or were
installed in the last N hours. Read-only, standard library only. From Extension Lockfile by PaperChaseWebb.
Usage: python3 ext_mini.py allow.txt [HOURS] [--home DIR]
allow.txt: one publisher.name@version per line (publisher.name@* allows any version, # starts a comment).
Exit 0 when nothing is flagged, 1 when something is, 2 on bad input."""
import json, os, sys, time

DIRS = {"vscode": ".vscode/extensions", "vscode-insiders": ".vscode-insiders/extensions",
        "cursor": ".cursor/extensions", "windsurf": ".windsurf/extensions", "vscodium": ".vscode-oss/extensions"}


def main(argv):
    home = os.path.expanduser("~")
    if "--home" in argv:
        i = argv.index("--home"); home = argv[i + 1]; argv = argv[:i] + argv[i + 2:]
    if not argv or len(argv) > 2:
        print(__doc__.strip().splitlines()[2], file=sys.stderr); return 2
    try:
        hours = float(argv[1]) if len(argv) == 2 else 72.0
        allow = {}
        for line in open(argv[0], encoding="utf-8"):
            line = line.split("#")[0].strip()
            if line:
                ident, _, ver = line.partition("@")
                allow.setdefault(ident.lower(), set()).add(ver or "*")
    except (OSError, ValueError) as e:
        print(f"error: {e}", file=sys.stderr); return 2
    now, flagged, seen = time.time(), 0, 0
    for editor, rel in DIRS.items():
        p = os.path.join(home, rel, "extensions.json")
        if not os.path.isfile(p):
            continue
        try:
            entries = json.load(open(p, encoding="utf-8"))
        except ValueError as e:
            print(f"error: cannot read {p}: {e}", file=sys.stderr); return 2
        for e in entries:
            ident = (e.get("identifier") or {}).get("id", "").lower()
            ver = str(e.get("version", ""))
            ts = (e.get("metadata") or {}).get("installedTimestamp")
            seen += 1
            notes = []
            if ident not in allow:
                notes.append("NOT ON ALLOW-LIST")
            elif "*" not in allow[ident] and ver not in allow[ident]:
                notes.append("VERSION NOT APPROVED (" + ", ".join(sorted(allow[ident])) + ")")
            if ts and 0 <= (now - ts / 1000) / 3600 < hours:
                notes.append(f"INSTALLED {(now - ts / 1000) / 3600:.0f}h AGO")
            flagged += bool(notes)
            print(f"{editor:16} {ident}@{ver:12} {'; '.join(notes) or 'ok'}")
    print(f"{seen} extensions, {flagged} flagged")
    return 1 if flagged else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
~~~

## What the full pack adds

The paid Extension Lockfile pack adds a lock file with block lists and per-editor rules, checks for auto-update settings and folders the editor never listed, a baseline and diff of the places a bad extension leaves persistence (SSH keys, LaunchAgents, systemd user units, autostart, Windows Startup, crontab, shell startup files), a sweep with a ready indicator file for 2026-10-29, an incident runbook, a skill file for Claude Code and Codex, and 37 tests. It is on the PaperChase Bot Marketplace. Questions: mgmt@paperchasewebb.com.

Source: Cursor, security notice of 2026-10-29, https://cursor.com/blog/claude-code-extension-security-notice . Use inside your own team or company. Do not resell or republish.
