A developer laptop holds the keys to the supply chain: Git and registry tokens, cloud credentials and the editor that builds code. Allow-list editor extensions and pin them, keep Workspace Trust and automatic tasks off for unfamiliar repositories, configure package managers to skip install scripts and versions only hours old, use short-lived credentials instead of tokens in dotfiles, and enforce AI agent settings centrally so a repository file cannot grant an agent or a hook the right to run commands.
- Do allow-list extensions with the
AllowedExtensionspolicy (VS Code 1.96+); a marketplace listing or verified publisher badge is not a code review. - Do keep
task.allowAutomaticTasksoff and review.vscode/,.claude/and.cursor/changes like code: they can run commands when a project opens. - Do block dependency install scripts and very new versions:
ignore-scriptsandmin-release-agein npm 11, the default in npm 12,allowBuildsandminimumReleaseAgein pnpm. - Do replace long-lived tokens in
~/.npmrc,~/.awsand shell profiles with SSO, OS keychains and short-lived sessions. - Don't let agents auto-approve tools or terminal commands on machines with production credentials; disable it by policy.
- A dev container isolates the toolchain, not your credentials: it gets your SSH agent and Git credentials by default.
| Setting | Value | Note | Source |
|---|---|---|---|
| VS Code extensions | extensions.allowed or the AllowedExtensions policy | VS Code 1.96 or later; extensions not on the list are blocked and installed ones are disabled. | VS Code docs: manage extensions in enterprise |
| Automatic tasks on folder open | task.allowAutomaticTasks: off (the default) | Automatic tasks never run in an untrusted workspace. | VS Code docs: tasks |
| npm install scripts | npm 11: ignore-scripts=true; npm 12: blocked unless allowScripts approves them | npm 12.0.0 (2026-07-08) also defaults allow-git and allow-remote to none. | npm CLI release notes |
| npm release age | min-release-age=7 (days) | Added in npm 11.10.0 (2026-02-11). | npm config docs |
| pnpm builds and release age | allowBuilds allow-list; minimumReleaseAge in minutes | pnpm 11 defaults minimumReleaseAge to 1440 (one day) and removed onlyBuiltDependencies. | pnpm settings |
| npm login | Two-hour session tokens | Classic npm tokens were permanently revoked on 2025-12-09. | GitHub changelog, 2025-12-09 |
| Agent tool auto-approval | Off by policy: VS Code ChatToolsAutoApprove and ChatToolsTerminalEnableAutoApprove false; Claude Code disableBypassPermissionsMode | Managed settings override user and repository files. | VS Code and Claude Code enterprise docs |
- Run
code --list-extensions --show-versionson a managed machine and confirm that only allow-listed extensions are present and enabled. - Run
npm config get ignore-scripts min-release-age(npm 11) or confirmallowScriptsis in use (npm 12), and checkpnpm config get minimumReleaseAge. - Scan
~/.npmrc,~/.aws,~/.config/ghand~/.dockerwith gitleaks and confirm no long-lived tokens are stored in plain text. - Open a test repository that contains a
runOn: folderOpentask and confirm nothing runs until you trust the folder and allow automatic tasks. - Confirm the managed agent settings are applied (for Claude Code,
/statusnames the managed source) and that bypass or auto-approve modes cannot be enabled.
- Applies to
Laptops and cloud workspaces used to write, build and publish software, including editors based on VS Code, package managers for JavaScript and Python, and AI coding agents. NIST SSDF practice PO.5, secure environments for software development.
- Part of
- Workflow: Build an AppSec program
Learning goals and prerequisites
What you will learn
- Name the routes from a developer machine into the supply chain, with an incident for each.
- Allow-list editor extensions and stop repositories from running code when they are opened.
- Configure npm, pnpm, pip and uv to skip install scripts and brand-new versions.
- Constrain AI coding agents and MCP servers with managed settings, and check a machine for exposure.
Before you start
Developers, platform and IT engineers who manage developer machines, and AppSec engineers responding to extension or package compromises
- Use VS Code or a fork such as Cursor, and install packages with npm, pnpm, pip or uv.
- Know where your machine stores credentials for Git, the cloud and package registries.
- For the checks: a shell, the editor's command-line tool and gitleaks 8.19 or later.
Refresh first: Investigate a suspected malicious package safely
Go to the guided practiceA developer machine can push code, publish packages and reach cloud accounts, and it runs whatever the editor, the package manager and the agent decide to run. The incidents below each used one of four routes. None needed a vulnerability in the operating system; all of them used features working as designed.
| Route | Incident | What happened |
|---|---|---|
| Editor extension | GlassWorm, Open VSX, October 2025 onward | Malicious versions of extensions were published with publisher tokens that developers had leaked, according to the Eclipse Foundation, which runs Open VSX. Koi Security, which named the campaign, reported code hidden with invisible Unicode characters and theft of npm, GitHub and Open VSX credentials. The Foundation removed the extensions and revoked the tokens, and said the malware did not spread on its own and that bots inflated the 35,800 download figure. New waves followed until CrowdStrike, Google and the Shadowserver Foundation took down its command-and-control channels on 2026-05-26. |
| Editor extension | Amazon Q Developer for VS Code 1.84.0, July 2025 (CVE-2025-8217) | Injected code designed to call the Q Developer CLI shipped in an official marketplace release; a syntax error kept it from running. AWS told users to remove 1.84.0 and move to 1.85.0. |
| Editor extension | Red Hat, May 2026 (RHSB-2026-006) | A GitHub account compromised through a malicious VS Code extension pushed unauthorized commits that led to 32 malicious npm packages. |
| Install-time script | Nx s1ngularity, August 2025 | A postinstall script in malicious nx versions collected credentials and published them to a repository in the victim's GitHub account, and tried to use locally installed AI command-line tools to search the disk. The Nx Console extension installed nx@latest in the background, so opening the editor was enough. |
| Repository configuration | GitHub Copilot (CVE-2025-53773) and Cursor (CVE-2025-54135, CVE-2025-54136), 2025 | Prompt injection or a shared repository could make the agent write settings or MCP definitions that ran commands without approval. Copilot's case wrote chat.tools.autoApprove into .vscode/settings.json; fixed in the August 2025 Copilot and Visual Studio updates and in Cursor 1.3 and 1.3.9. |
| Repository configuration | Mini Shai-Hulud, May 2026 | Malicious npm packages wrote a SessionStart hook into .claude/settings.json and a runOn: folderOpen task into .vscode/tasks.json, so the payload could run again whenever a developer opened the project, even after the package was removed. |
Extensions run with the user's full permissions, with no sandbox, and update automatically. Treat them like dependencies: decide which ones you need, allow only those, and update deliberately. Since VS Code 1.96, the extensions.allowed setting, and the AllowedExtensions policy that device management can enforce, allow extensions by publisher, by ID, by exact version or only stable releases. Once it is set, everything else is blocked and already-installed extensions that are not allowed are disabled. Version ranges are not supported, so a pinned entry lists exact versions.
A verified publisher badge on the Visual Studio Marketplace shows that the publisher proved control of a domain; it says nothing about the code in the next release. VS Code forks such as VSCodium and Cursor install from Open VSX rather than the Visual Studio Marketplace, so the same extension name can come from a different registry with a different publisher account; allow-list in each editor your team uses. Open VSX has since added pre-publication checks for impersonation, embedded secrets and known malicious patterns, shorter token lifetimes and trusted publishing; checks lower the risk but are not a code review. Turning off automatic extension updates trades fast fixes for a review step; if you keep them on, an allow-list by exact version still stops a compromised update from installing.
{
"extensions.allowed": {
"microsoft": true,
"github": true,
"esbenp.prettier-vscode": true,
"dbaeumer.vscode-eslint": "stable"
},
"extensions.autoUpdate": false,
"security.workspace.trust.enabled": true,
"security.workspace.trust.untrustedFiles": "prompt",
"task.allowAutomaticTasks": "off"
}Opening a folder can run code without any install step: tasks set to runOn: folderOpen, workspace settings that point at executables, debugger launch configurations, extension behavior driven by workspace files, and agent hooks and MCP server definitions committed to the repository. VS Code's Workspace Trust starts unfamiliar folders in Restricted Mode, which limits the terminal, tasks, debugging, workspace settings, AI agents and extensions that have not opted in. Automatic tasks never run in an untrusted workspace, and task.allowAutomaticTasks is off by default, prompting once per workspace.
Do not trust a whole parent folder such as ~/src, because trust extends to every subfolder, including the next repository you clone there. Review changes to .vscode/, .devcontainer/, .claude/, .cursor/ and agent instruction files in pull requests with the same care as build scripts, and give them owners in CODEOWNERS (see /domains/supply-chain/ci-cd/source-repository-protection/).
Most package malware runs at install time, in preinstall, install and postinstall scripts or in a Python source distribution's build step, and most is caught and removed within hours to days. Two settings block both: refuse dependency install scripts except for packages you have reviewed, and refuse versions younger than a few days.
npm 11 offers ignore-scripts, a blunt switch that skips every dependency script, and min-release-age since 11.10.0. npm 12.0.0, released on 2026-07-08, blocks dependency lifecycle scripts by default unless the root package.json approves them in allowScripts, which npm install-scripts approve maintains with version-pinned entries; it also refuses git and remote tarball dependencies by default. pnpm has not run dependency build scripts by default since version 10; list the ones you allow in allowBuilds, keep strictDepBuilds on so new ones fail the install, and set minimumReleaseAge, which pnpm 11 defaults to one day. For Python, only-binary = :all: stops pip from building source distributions, and uv's exclude-newer accepts a duration such as 7 days.
Release-age delays also delay security fixes. When you must take a fix at once, exempt that package by name where the tool supports it, such as pnpm's minimumReleaseAgeExclude, rather than turning the delay off for everything. /domains/supply-chain/dependencies/package-attack-response/ covers what to do when a malicious version did install.
# ~/.npmrc (npm 11.10 or later)
ignore-scripts=true # no dependency lifecycle scripts; for a reviewed build: npm rebuild <pkg> --ignore-scripts=false
min-release-age=7 # only install versions published more than 7 days ago
allow-git=none # no git dependencies (npm 11.9+; the default from npm 12)
# npm 12: dependency install scripts are blocked unless package.json allowScripts approves them
# npm install-scripts ls list packages whose scripts were skipped
# npm install-scripts approve esbuild record a pinned approval after review
# npm rebuild esbuild run the newly approved script
# (drop ignore-scripts=true on npm 12: it overrides allowScripts)
# pnpm-workspace.yaml (pnpm 10.26 or later; dependency build scripts are off by default)
# minimumReleaseAge: 10080 # minutes; pnpm 11 defaults to 1440
# trustPolicy: no-downgrade # fail if a package's trust level dropped from earlier releases
# strictDepBuilds: true # unreviewed build scripts fail the install
# allowBuilds:
# esbuild: true
# ~/.config/pip/pip.conf: never build an sdist, which runs the package's own build code
# [install]
# only-binary = :all:
# ~/.config/uv/uv.toml
# exclude-newer = "7 days"
# no-build = trueEvery incident above ended in credential theft, and the payloads looked in the same places: ~/.npmrc, ~/.pypirc, ~/.netrc, ~/.aws/credentials, ~/.config/gh, ~/.docker/config.json, kubeconfig files, .env files and shell profiles, plus environment variables. Remove the long-lived values from those places.
- Cloud: use SSO sessions (
aws sso login,gcloud auth login,az login) instead of access keys in files. - npm: classic tokens were revoked on 2025-12-09 and
npm loginnow issues two-hour sessions; do not paste granular tokens into~/.npmrc, and publish from CI with trusted publishing. - Git and GitHub: let the credential helper and
ghstore tokens in the OS keychain; sign and authenticate with hardware-backed SSH keys (ed25519-sk). - Never put tokens in shell profiles or committed
.envfiles; load them per command from a secret manager. - Scan the usual locations regularly, and treat anything found as leaked: /domains/identity-crypto/secrets/leaked-secret-response/.
A dev container keeps a project's toolchain, install scripts and extensions off the host file system, which limits what a malicious dependency can read. It is not a credential boundary. The VS Code Dev Containers extension copies your .gitconfig, shares your Git credential helper and forwards your SSH agent when one is running, so code in the container can push to anything you can. A postCreateCommand, a postStartCommand or the image's own metadata runs as soon as the container starts, and opening a repository in a container counts as trusting it.
Keep the container unprivileged: a non-root user, dropped capabilities, no host home directory mount and never the Docker socket, which is equivalent to root on the host. Pin the base image by digest in real projects, and install dependencies without scripts.
{
"name": "payments-api",
"image": "mcr.microsoft.com/devcontainers/typescript-node:22",
"remoteUser": "node",
"runArgs": ["--cap-drop=ALL", "--security-opt=no-new-privileges"],
"postCreateCommand": "npm ci --ignore-scripts",
"customizations": {
"vscode": {
"extensions": ["dbaeumer.vscode-eslint"]
}
}
}An agent with permission to run commands is an install script you talk to. Auto-approval modes let prompt injection from a file, issue or web page turn into commands, and agent configuration in the repository (hooks, MCP server definitions, permission rules) runs with your credentials. MCP servers started with npx or uvx download and run a package at launch, so pin their versions and allow them by name.
Enforce agent settings centrally rather than per user. VS Code's enterprise policies ChatToolsAutoApprove and ChatToolsTerminalEnableAutoApprove set to false stop developers from turning on global or terminal auto-approval. Claude Code's managed settings, deployed to /Library/Application Support/ClaudeCode/managed-settings.json on macOS or /etc/claude-code/managed-settings.json on Linux, override user and project files: the example disables bypass-permissions mode, runs only hooks from managed settings, which stops a repository's SessionStart hook, and allows only the MCP servers you list, matched by URL because anyone can give a server a familiar name. /domains/ai-emerging/agent-security/ai-assisted-development/ and /domains/ai-emerging/agent-security/mcp-multiagent-security/ cover agent permissions and MCP in depth.
{
"permissions": {
"disableBypassPermissionsMode": "disable"
},
"allowManagedHooksOnly": true,
"allowManagedMcpServersOnly": true,
"allowedMcpServers": [
{"serverUrl": "https://api.githubcopilot.com/*"}
]
}- Full-disk encryption, screen lock and automatic OS updates; endpoint detection that covers developer tools.
- An editor extension allow-list enforced by policy, with extensions reviewed before they are added.
- Workspace Trust on, no trusted parent folders, automatic tasks off.
- Package managers that block dependency install scripts and very new versions, with a lockfile installed by
npm cior its equivalent. - No long-lived cloud, registry or Git tokens in files; SSO and keychains instead.
- Agent auto-approval disabled and MCP servers allow-listed through managed settings.
- Separate accounts or machines for publishing and production access where the risk justifies it.
When an advisory names an extension or package version, list what each machine has, confirm the package-manager policy that was in force, look for plaintext credentials the payload could have read, and search repositories for configuration that would run the payload again. If you find an affected version, isolate the machine and remove persistence before rotating credentials, because some payloads watch for rotation, then follow /domains/supply-chain/dependencies/package-attack-response/.
# 1. Editor extensions and versions (forks ship their own CLI, such as cursor or codium)
code --list-extensions --show-versions
# 2. Package-manager policy actually in force
npm config get ignore-scripts min-release-age allow-git
pnpm config get minimumReleaseAge
# 3. Plaintext credentials in the usual places (gitleaks 8.19+), values redacted
for p in ~/.npmrc ~/.pypirc ~/.netrc ~/.aws ~/.config/gh ~/.docker ~/.kube; do
[ -e "$p" ] && gitleaks dir --no-banner --redact -v "$p"
done
# 4. Repository files that run code when a project or agent session opens
grep -rl '"SessionStart"' ~/src/*/.claude/ 2>/dev/null
grep -rl '"folderOpen"' ~/src/*/.vscode/ 2>/dev/null
grep -rl '"command"' ~/src/*/.cursor/mcp.json ~/src/*/.vscode/mcp.json 2>/dev/nullGuided practice
An advisory says that extension acme.markdown-preview version 2.4.1 on Open VSX was malicious for 30 hours and stole tokens from the usual dotfiles. Decide, for each machine below, whether it is exposed, what to rotate and what to change. Treat the facts as true.
Supplied scenario material
M1: VS Code, AllowedExtensions policy lists acme.markdown-preview at 2.3.9 only; ~/.npmrc has no token; AWS via SSO.
M2: Cursor (installs from Open VSX); acme.markdown-preview 2.4.1 installed during the window; ~/.npmrc contains a granular npm token with publish rights; ~/.aws/credentials has an access key.
M3: VSCodium; extension not installed; ignore-scripts=false; installed a dependency whose postinstall ran during the same week (unrelated to the advisory).
M4: Cursor; acme.markdown-preview 2.4.1 installed; no tokens in files; gh token in the macOS keychain; the user's repositories contain a new .vscode/tasks.json with runOn: folderOpen added two days ago by a commit from this machine.
M5: VS Code inside a dev container; extension 2.4.1 installed in the container; SSH agent forwarded from the host; Docker socket mounted.1. Clear M1 and M3
Decide whether M1 and M3 are exposed to this advisory.
Expected result and reasoning
M1 is not exposed: the policy pins 2.3.9, so 2.4.1 could not install. M3 does not have the extension, so it is not exposed to this advisory, but
ignore-scripts=falseis a separate gap to fix.2. Scope M2
List what M2's attacker could have taken and the response order.
Expected result and reasoning
The extension ran with the user's permissions, so the npm publish token and the AWS access key must be treated as stolen. Remove the extension, check for persistence, then revoke both credentials, check npm for versions published during the window and AWS CloudTrail for use of the key, and replace them with trusted publishing and SSO.
3. Read M4's evidence
What does the new folderOpen task mean, and what do you do?
Expected result and reasoning
A task added by a commit from this machine suggests the payload wrote persistence into repositories. Treat M4 as compromised even though no tokens were in files: remove the task from every repository and branch it touched, review the commits, revoke the gh token (the keychain protects it at rest, not from code running as the user) and rotate other credentials after cleanup.
4. Decide on M5
Did the dev container contain the damage?
Expected result and reasoning
No. The forwarded SSH agent let the extension use the user's keys while the container ran, and the mounted Docker socket gives root on the host. Treat the host as exposed, rotate SSH keys used through the agent, and rebuild the container without the socket mount.
5. Change the defaults
Write three changes that would have reduced exposure across the fleet.
Expected result and reasoning
Enforce an extension allow-list in every editor, including Cursor and VSCodium, with exact versions where the editor supports them (Cursor's list works by publisher or extension ID); remove long-lived tokens from dotfiles in favor of SSO and trusted publishing; and keep automatic tasks off, with CODEOWNERS review for
.vscode/and agent configuration so persistence in repositories is noticed.
The extension, versions and machines are invented. Real triage needs endpoint telemetry for the window, the advisory's indicators, and registry and cloud audit logs to see whether stolen credentials were used.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- Eclipse Foundation: Open VSX security update, October 2025. 27 October 2025.
- Eclipse Foundation: Fewer keys under the doormat, why trusted publishing matters for Open VSX. 9 October 2026.
- CrowdStrike: Disrupting Glassworm, inside CrowdStrike's takedown of a developer-targeting botnet. 26 May 2026.
- AWS security bulletin AWS-2025-015: Amazon Q Developer for VS Code (CVE-2025-8217). 23 July 2025, updated 25 July 2025.
- Nx security advisory GHSA-cxm3-wv7p-598c: malicious versions of Nx and some supporting plugins were published. 27 August 2025.
- Nx: s1ngularity, what happened, how we responded and what we learned
- Embrace The Red: GitHub Copilot remote code execution via prompt injection (CVE-2025-53773). 12 August 2025.
- Sonar: Mini Shai-Hulud targets AI coding agents (2026-05-26)
- Red Hat RHSB-2026-006: Supply chain compromise of @redhat-cloud-services npm packages. 1 June 2026.
- VS Code docs: Manage extensions in enterprise environments
- VS Code docs: Workspace Trust
- VS Code docs: Manage AI settings in enterprise environments
- npm CLI v12.0.0 release notes. 8 July 2026.
- pnpm: build settings (allowBuilds, strictDepBuilds)
- Claude Code docs: Deploy managed settings