84 npm packages turned malicious in one May attack

Let your agent touch prod.Keep the keys.

Sallyport holds your credentials in a local vault and runs API calls and SSH commands for the agent. You approve runs with a click or Touch ID. There is no command that reveals a stored key, and no export button either.

$brew install --cask olegsotnikov/tap/sallyport

macOS 14+ · Apple Silicon · Apache-2.0 · Free · no account · no cloud

Built for engineers who let Claude Code, Cursor, or Codex touch real infrastructure

Claude CodeCodexCursorWindsurfClineLangChainCrewAIOpenHandsGooseZed
Try it yourself

Approval where you already are

This key requires approval for every use. The call waits for your decision.

claude · ~/infra

sallyport · http.request

PATCH api.cloudflare.com/client/v4/zones/…/dns_records

key cloudflare · flagged approval per call

waiting for approval…

Approval required

http.request → api.cloudflare.com

Developer ID Application: Anthropic PBC

claude node zsh

Key:cloudflareper call
Click Approve to let the call through.or press A/D
The problem

Your agent can read your keys right now

API tokens in .env, SSH keys on disk, OAuth grants in config files. The agent and every package it pulls in can read them all, and one poisoned README can walk them out the door.

Secrets in plain sight

Tokens in .env and shell variables, SSH keys on disk, OAuth tokens in config files. Readable by the agent, its child processes, and whatever npm installed today.

Prompt injection becomes theft

A malicious page or README tells the agent to send a token somewhere new, and the agent complies. It was built to follow instructions.

You find out from the invoice

Without a gateway there is no record of which calls the agent made, where it sent them, or what they carried. Stolen keys surface weeks later, on someone else's schedule.

Three that already happened

May 2026

TanStack npm attack

84 malicious versions across 42 packages harvested cloud credentials, GitHub tokens, and SSH keys at install time, then spread through their victims' own packages.

Late 2025

Shai-Hulud worm

Compromised npm packages stole secrets on install and used the stolen credentials to infect the next round of packages.

Aug 2025

Nx S1ngularity

A poisoned build tool scanned developer machines for secrets, tried local AI tools, and shipped the loot out through the GitHub CLI.

All three harvested keys from environment variables and files on disk. Sallyport keeps usable keys out of both.

Your agent doesn't need the keys

The agent sends a request: call this API, run this command on that host. Sallyport attaches the credential inside the app, executes the call, and returns the result. The key never appears in the agent's environment, and a sealed journal shows what ran and who approved it.

The trust model

Fixed decision ladder

Every action climbs the same compiled ladder. There is no policy language to misconfigure and no rules engine to rot; an optional allowlist can skip the session prompt for code identities you trust.

The trust model

1

vault gate

The vault is absolute

The vault starts locked. Secure Enclave and Touch ID gate access to its data. Until unlock, Sallyport denies all actions.

2

per-call keys

Flagged keys ask every time

A key marked "Approval per call" requires a click or Touch ID for every use, including inside an approved session.

3

session gate

One approval per agent run

A new agent process shows one card with its code-signing authority. Approval lasts until the process exits, you revoke it, or the vault locks.

4

observe

Run and record

With per-session approval off, actions run without session cards and remain in the journal.

Vault gate, per-call key, session gate, observe. If you have to map that onto a procurement document: zero standing privileges for a non-human identity, access granted just in time, a human in the loop.

What's inside

Everything ships in one signed app

One signed Mac app holds the vault, the approvals, and the journal. There is no account to create and no cloud service behind it.

Absolute vault gate

While locked, Sallyport denies every agent action and all management operations except status.

Encrypted at rest

The vault encrypts key names, providers, host bindings, SSH inventory, settings, and secret values.

Process provenance

Each card shows the caller's code-signing authority and parent chain, including any unsigned process.

Touch ID for changes

Configuration changes can require Touch ID. A synthetic click cannot satisfy the biometric prompt.

Write-blind journal

Sallyport encrypts agent runs and per-call activity as it writes them, then links the ciphertext with a hash chain.

SSH that sees the command

Sallyport sees the full SSH command, checks the host key, and seals a session recording. The private key stays in the app.

Observe mode

Observe mode removes session cards but retains the vault gate, per-call approval, and journal.

No recovery

The vault has no export, reveal, or recovery key. If you lose it, re-issue the stored credentials at their providers.

Channels

HTTP, SSH, and MCP

Your agent connects to Sallyport as a regular MCP server. Everything it does walks the same ladder into the same journal.

HTTP APIs

http.request → api.github.com

The app makes the HTTPS call and injects the credential only during execution. Cross-host redirects do not carry it.

SSH

ssh.exec → deploy@prod-03

A stateless helper opens the connection. The app handles SSH signatures, pins host keys, and seals a session recording; the private key does not enter the helper.

MCP servers

github.create_issue

Proxy local stdio or remote MCP servers through the same gate. Sallyport seals and refreshes OAuth 2.1 tokens.

By the numbers

The stakes

84

malicious package versions in a single npm attack that hunted credentials

0

accounts, cloud services, or telemetry: Sallyport runs entirely on your Mac

1 tap

Touch ID approves a flagged call; a synthetic click cannot fake it

~2 min

from brew install to your first gated call

Open source

Read the code that holds your keys

The vault, the approval gate, the journal, and the executor that touches your credentials are all public under Apache-2.0. Clone it and follow a key from your Touch ID tap to the wire.

Apache-2.0 · the whole thing

Audit it yourself

Every line that stores, unlocks, or spends a credential is on GitHub. Read the parts that worry you, then decide.

Nothing phones home

No account, no cloud service, no telemetry. Sallyport talks to the hosts you point it at and to nothing else.

Yours to keep

Apache-2.0 means fork it, patch it, and ship it inside your company. Your copy keeps working whatever we do next.

FAQ

Frequently asked questions

How is Sallyport licensed?
Sallyport is free to download and use. The core is open source under Apache-2.0. Planned enterprise components will use a commercial license.
Where do my secrets live?
Inside a local vault on your Mac. Secure Enclave and Touch ID gate access to the vault encryption key. Sallyport encrypts secret values and metadata at rest and does not upload vault data.
Does it phone home?
No. There is no cloud service, no account, and no telemetry. Sallyport works offline except for the services you point it at and the update feed.
What are the system requirements?
A Mac with Apple Silicon running macOS 14 or newer. Intel Macs are not supported.
What happens while the vault is locked?
Agent actions stop. Management requests return only status; all other operations are denied. Unlock requires Touch ID, and locking clears sessions and pending approvals.
Does it work with Claude Code and other agents?
Any MCP client can use the bundled sp mcp shim to access http.request, ssh.exec, and configured upstream MCP servers. Claude Code, Cursor, and Codex are what we test against.
Can I run Claude Code with --dangerously-skip-permissions?
The flag people call YOLO mode turns off Claude Code's own confirmation prompts. It changes nothing about your credentials: they sit in .env files and shell variables the agent could read all along. Those prompts were never what protected your keys. Point the agent at Sallyport and there is no key in its environment to skip past.
Is it safe to install an MCP server someone posted online?
Treat it like any dependency: it runs with your rights, and its tool descriptions land in the model's context as instructions. Route it through Sallyport and every call it makes needs approval and lands in the journal. Local stdio servers get the credential you bind to them, so bind the narrowest one you have and flag it for approval per call.
How is this different from HashiCorp Vault or the 1Password CLI?
Those store secrets well, then hand them over. op run and vault read both finish with the credential in the environment of whatever runs next, which on your machine is the agent. Sallyport does not hand it over; it makes the call and returns the response. Run both if you like: keep the source of truth where it is, and let Sallyport hold the copy your agent works through.
Can the agent ever see my keys?
No. There is no command that reveals a stored credential, no export, and no recovery route. Sallyport hands back whatever the target service replied with, so a service that echoes your key into its own response is showing you its bug, and the journal shows you the call that caught it.
What if I lose access to my Mac?
There is no recovery key. Re-initialize the vault and re-issue credentials at their providers.

Your agent is one prompt injection away from your keys.

Free download. Apple Silicon, macOS 14 or newer. No account, ever.

$brew install --cask olegsotnikov/tap/sallyport

macOS 14+ · Apple Silicon

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov