Skip to content

Instantly share code, notes, and snippets.

@lajarre
Created April 1, 2026 09:51
Show Gist options
  • Select an option

  • Save lajarre/cb5b5d93024bbf9165c60980ab2ea32b to your computer and use it in GitHub Desktop.

Select an option

Save lajarre/cb5b5d93024bbf9165c60980ab2ea32b to your computer and use it in GitHub Desktop.
Securing a developer workstation in 2026 — practical guide covering supply chain attacks, secrets hygiene, SSH keys, and AI agent risks

securing a developer workstation in 2026

Supply chain attacks against package registries are no longer exotic. In Q1 2026 alone: LiteLLM's PyPI package was compromised, a worm self-propagated through npm (CanisterWorm), install-hook exploits hit the npm ecosystem (PhantomRaven / Shai-Hulud 2.0), and the axios maintainer account was taken over to push a RAT dropper.

The common thread: your machine trusts upstream by default, and a single poisoned dependency or stolen credential can cascade into full compromise.

When coding agents enter the picture — Pi, Claude Code, Cursor, Copilot, Codex, whatever comes next — the blast radius widens. Agents run with your shell, your tokens, your SSH keys, and your ambient permissions. Often autonomously, across many repos, for hours.

This is a practical guide. Each section says who it's for, how urgent it is, and what to actually do. It's based on a real hardening sprint on a macOS developer machine, not a theoretical framework.


1. package manager install cooldowns

Is this for me? Yes, if you use npm, pnpm, uv, or bun — i.e. if you install packages from public registries.

Urgency: high. This is probably the single highest-leverage change. Most supply chain attacks are caught within hours to days. A 7-day cooldown means you never install a version the community hasn't had time to inspect.

why

By default, pnpm install / uv add / bun install will happily pull a package published 30 seconds ago. If an attacker compromises a maintainer account and pushes a malicious version, you get it immediately.

what to do

Add a minimum release age to your global package manager config. This is a one-time setup — a few lines in a config file, then you forget about it.

Manager Config file Setting
pnpm ≥ 10 ~/.config/pnpm/rc minimumReleaseAge=10080
uv ~/.config/uv/uv.toml exclude-newer = "7 days"
bun ~/.bunfig.toml [install]minimumReleaseAge = 604800
npm No equivalent; use lockfiles + npm ci

pnpm 10 also offers blockExoticSubdeps=true (blocks git/tarball transitive deps) and trustPolicy=no-downgrade (blocks silent provenance drops). Both worth enabling.

What I skipped: blocking all install scripts (strictDepBuilds, ignore-scripts, no-build). Too much collateral damage on a general dev machine — native modules, PostCSS, etc. all break. The cooldown is the right tradeoff.


2. plaintext secrets on disk

Is this for me? Yes, if you have API keys, tokens, or credentials in dotfiles, ~/.npmrc, ~/.aws/credentials, or shell profile files. (You almost certainly do.)

Urgency: high. A plaintext token on disk is the first thing any malicious code exfiltrates. It's cat ~/.npmrc away from compromise.

why

Most developers accumulate secrets in dotfiles over years. export GITHUB_TOKEN=ghp_... in .zshrc. An auth token in ~/.npmrc. AWS access keys in ~/.aws/credentials. Every process running as your user can read all of these.

what to do

Step 1: Find them.

rg -l '(ghp_|npm_|AKIA|sk-|xoxb-|xoxp-)' ~/.*rc ~/.config/ ~/.aws/ 2>/dev/null

Step 2: Move them to your OS secret store. On macOS, that's Keychain:

security add-generic-password -a "$USER" -s "MY_API_KEY" -w "the-secret-value" -U

Then fetch at runtime:

getsecret() { security find-generic-password -a "$USER" -s "$1" -w 2>/dev/null; }
export MY_API_KEY="$(getsecret MY_API_KEY)"

On Linux, secret-tool (libsecret) or pass serve the same role. 1Password CLI (op run, op read) works cross-platform.

Step 3: Apply a loading policy. Not every secret needs to be in every shell:

  • Auto-load: low-risk dev keys you use constantly (LLM API key, search API key).
  • On-demand: bot tokens, workspace tokens, CI credentials — load only when needed.
  • Never in shell: production database passwords, admin tokens.

Gotcha: check all shell entrypoints. Removing source ~/.secrets from ~/.zshrc doesn't help if ~/.zshenv or ~/.bashenv also sources it.


3. SSH key passphrases

Is this for me? Yes, if you use SSH keys for GitHub, servers, or Git signing. Takes 5 minutes to check.

Urgency: high for unencrypted keys, low if already protected. An unencrypted private key is instant push access to every repo you can reach.

why

Many developers generated their SSH key years ago with an empty passphrase. That key sits at ~/.ssh/id_rsa, readable by every process running as your user. Any malicious npm postinstall script can cat it and phone home.

what to do

Check right now:

ssh-keygen -y -P "" -f ~/.ssh/id_rsa >/dev/null 2>&1 && echo "⚠️  UNENCRYPTED" || echo "✓ protected"

(Repeat for id_ed25519, id_ecdsa, etc.)

If unencrypted:

  1. Generate a new passphrase-protected ed25519 key: ssh-keygen -t ed25519
  2. Upload the public key to GitHub / your hosts.
  3. In ~/.ssh/config, set IdentityFile and IdentitiesOnly yes per host.
  4. On macOS: ssh-add --apple-use-keychain ~/.ssh/id_ed25519 — type the passphrase once, Keychain handles it forever.
  5. Archive the old key.

Bonus: use separate keys for authentication vs. Git commit signing. A compromised auth key shouldn't require signing key rollover.


4. startup items and background services

Is this for me? Yes, if you're on macOS and have installed things over the years. Takes 10 minutes to audit.

Urgency: medium. Not an active exploit vector by itself, but reduces your ambient attack surface and helps you notice if something new appears.

why

LaunchAgents, login items, and cron jobs run automatically with your full user permissions. Applications install them silently. After a few years, you may have services running that you forgot about — updaters, gateways, telemetry daemons. These are also where malware hides.

what to do

Audit:

ls ~/Library/LaunchAgents/
launchctl list | grep -v com.apple
crontab -l

Plus: System Settings → General → Login Items.

Disable what you don't need. On macOS, launchctl bootout alone doesn't survive reboot. You must also move the plist:

mkdir -p ~/Library/LaunchAgents.disabled
mv ~/Library/LaunchAgents/com.example.agent.plist ~/Library/LaunchAgents.disabled/

Common candidates: Google Keystone/updater agents, app-specific shepherds (Grammarly, Figma, etc.), services you installed once for a project and never touched again.


5. npm trusted publishing (for package maintainers)

Is this for me? Only if you publish packages to npm. Skip if you don't.

Urgency: medium-high if you publish. Your NPM_TOKEN is a skeleton key to every package you own. Trusted publishing eliminates it.

why

The classic npm publish workflow stores a long-lived write token (NPM_TOKEN) as a GitHub Actions secret. If that token leaks — via CI logs, a misconfigured workflow, or a compromised GitHub Action — anyone can publish to your packages. This is exactly how maintainer-account-takeover attacks work.

what to do

  1. On npmjs.com: link each package to a specific GitHub repo + workflow (trusted publishing / OIDC).
  2. In your GitHub Actions workflow: use permissions: id-token: write and provenance: true.
  3. Remove NODE_AUTH_TOKEN / NPM_TOKEN from the workflow.
  4. Delete the NPM_TOKEN secret from your GitHub repo settings.
  5. Revoke the old token on npmjs.com.

Now only a CI run from the correct repo/branch/workflow can publish. No token exists to steal.


6. pin your AI agent extensions

Is this for me? Yes, if you use AI coding agents that install third-party packages (Pi extensions, MCP servers, Cursor plugins, etc.).

Urgency: high if you use agents regularly. Agent extensions get full shell access — they run as you, with all your permissions.

why

AI agent ecosystems have plugin/extension registries, often backed by npm. These packages run with the same privileges as the agent itself — which means your full user session. If an extension version is "floating" (not pinned), a compromised version auto-installs on next agent launch. You'd never know.

what to do

Pin every agent package to an exact version in your agent settings. For Pi, that means version-pinning in ~/.pi/agent/settings.json:

"npm:some-extension@1.2.3"     ← pinned
"npm:some-extension"           ← floating, dangerous

The same principle applies to any agent that installs packages: check your config, pin versions, upgrade deliberately.

After pinning, it's worth a quick audit of what you have installed: check for unexpected install scripts, suspicious dependencies, packages you don't recognize.


7. agent-specific risks and isolation

Is this for me? Yes, if you run AI coding agents with shell/file access — especially autonomously or for extended periods.

Urgency: medium now, increasing fast. The agent ecosystem is young and moving fast; attack surface grows with every new tool/MCP server/extension.

why

Coding agents inherit your full environment. Your $PATH, env vars, SSH agent, AWS credentials, Keychain access — all of it. A compromised agent extension, a prompt injection in a fetched web page, or a malicious MCP tool can:

  • Read any file you can read.
  • Push to any repo your SSH key reaches.
  • Exfiltrate any env var or Keychain-accessible secret.
  • Install packages, run arbitrary commands.

The earlier sections reduce ambient exposure (fewer secrets lying around, passphrase-protected keys, pinned extensions). But the proper boundary is process-level isolation.

what to do now

  1. Pin agent packages (§6).
  2. Minimize ambient secrets — the Keychain/secret-manager approach (§2) means agents can't just cat a secrets file.
  3. Review what tools your agent has. Does it need unrestricted shell? Unrestricted file write? Network access? Tighten where you can.
  4. Don't share your personal SSH key / admin tokens with agent processes. Use scoped credentials.

what to do next: dedicated agent user

The proper next step — which most setups don't have yet, including mine — is running agents under a dedicated non-admin OS user:

  • Separate home directory, shell config, SSH keys, caches.
  • No sudo, no admin group.
  • Explicit allowlist to specific repos/worktrees only.
  • Cannot read your personal secrets, Downloads, Messages, etc.
  • Scoped Git credentials (fine-grained PAT or deploy keys, not your personal key).
  • No package publishing rights.

This creates a real privilege boundary. Even if an agent is fully compromised, the blast radius is limited to the repos and tools you explicitly granted. I have a draft spec for this but haven't implemented it yet — it's the next step.


quick-start priority order

If you do nothing else, do these three. They take under 30 minutes combined and cover the highest-risk vectors:

  1. Check your SSH keys (§3) — 5 minutes. One command tells you if you're exposed.
  2. Add package manager cooldowns (§1) — 10 minutes. A few config lines, then forget about it.
  3. Find and move your plaintext secrets (§2) — 15 minutes. rg for token patterns, move to Keychain/secret-manager.

Then, when you have more time:

  1. Pin agent extensions (§6) — if you use AI agents.
  2. Audit startup items (§4) — if you've been on the same machine for a while.
  3. Set up trusted publishing (§5) — if you publish npm packages.
  4. Agent isolation (§7) — the longer-term architecture.

April 2026. Based on a real workstation hardening sprint, not a theoretical checklist. All of the non-agent sections apply to any macOS/Linux developer machine. The agent sections apply if you use AI coding tools with shell access.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment