Supply-chain malware protection for Cursor. The plugin watches what the Cursor agent installs and edits, checks open-source packages against the Ossprey known-malware platform, and blocks malicious packages before they reach the machine.
| Component | Behaviour |
|---|---|
beforeShellExecution hook |
Intercepts npm install / yarn add / pnpm add / bun add / pip install / poetry add / uv add / uv pip install commands (including python -m pip and compound a && b commands), runs ossprey check on every package named on the command line, and denies the command on a malware verdict. |
afterFileEdit hook |
When the agent edits a dependency manifest or lockfile (package.json, requirements.txt, pyproject.toml, poetry.lock, uv.lock, …) it kicks off a background ossprey scan of that directory. |
stop hook |
If a background scan found malware, the agent gets a followup message telling it to remediate before the session ends. |
| Rule | Teaches the agent to prefer ossprey <manager> install … wrapped installs, run ossprey scan . after dependency changes, and never bypass a malware block. |
| Skill | On-demand reference for scanning workflows and interpreting results. |
Everything goes through the Ossprey CLI
— the hooks shell out to ossprey check and ossprey scan, and the CLI
handles authentication and talking to the Ossprey API. The plugin registers
no MCP server.
The hooks never block development on infrastructure problems. A missing CLI,
missing credentials, network error, or timeout results in allow plus a
warning message to the agent. The only thing that blocks a command is an
explicit malware verdict from the Ossprey API. Blocking decisions happen in
the beforeShellExecution hook, so a malicious package is stopped even if
the agent ignores the rule.
When the CLI reports it has no credentials, the hooks go one step further
than a warning: they tell the agent to offer to run ossprey login in the
terminal on your behalf — the device flow prints a URL and one-time code,
you approve it in the browser, and the command stores the session — then
confirm with ossprey whoami and re-run whatever went unchecked.
| Dependency | Needed for | If it's missing |
|---|---|---|
| Cursor, recent enough for local plugins and agent hooks (hooks landed in 1.7) | Loading the plugin at all | The plugin never runs |
Python 3 (3.8+), on PATH as python3 / python, or the py launcher on Windows |
The hook logic (hooks/ossprey_hook.py) — the .sh / .ps1 entrypoints are thin wrappers around it |
Hooks fail open: installs are allowed, nothing is checked, and the agent is warned |
Ossprey CLI on PATH as ossprey |
Every verdict — the hooks shell out to ossprey check and ossprey scan |
Hooks fail open with a warning telling the agent the install was not checked |
Ossprey credentials — an ossprey login session or OSSPREY_API_KEY (free account) |
Talking to the Ossprey API | Hooks fail open and steer the agent to run ossprey login for you |
git |
Remote installs (the installer clones this repo) | Install from a local checkout instead |
POSIX sh (macOS/Linux) or PowerShell 5.1+ (Windows) |
Running the hook entrypoints and the installer | Use the installer for your platform: install.sh or install.ps1 |
Nothing else: no Node, no MCP server, no Cursor extension, and no Ossprey
daemon. Network access to api.ossprey.com is needed for verdicts, and
github.com for installs and updates.
Note the pattern in the table — every dependency except Cursor itself degrades to fail open rather than breaking your workflow. That is deliberate (see Fail-open by design), but it also means a broken setup is quiet: if you want to confirm the plugin is really protecting you, run the live end-to-end test below.
For Windows, see Setup on Windows below.
-
Install the Ossprey CLI (the hooks shell out to it):
curl -fsSL https://github.com/ossprey/ossprey-cli/releases/latest/download/install.sh | sudo sh -
Sign in to Ossprey (free account at ossprey.com):
ossprey login
A browser opens, you confirm a one-time code, and the CLI stores the tokens locally. Tokens refresh automatically;
ossprey whoamishows the session andossprey logoutremoves it. No key to create, copy, or rotate — and nothing to configure in the environment Cursor runs in, because the hooks shell out to the CLI, which finds the login itself. -
Install the plugin:
curl -fsSL https://raw.githubusercontent.com/ossprey/cursor/main/install.sh | shThe installer clones this repo into Cursor's local plugin directory (
~/.cursor/plugins/local/ossprey— a real directory, because Cursor does not load symlinked plugins). Re-run the installer any time to update, pass--uninstallto remove the plugin, and restart Cursor (or run "Reload Window") after either. From inside a checkout,sh install.shinstalls that working tree instead — handy for testing a branch in Cursor before release.For headless setups where a browser login is impractical, use an API key instead (create one at dashboard.ossprey.com): either export
OSSPREY_API_KEYin the environment Cursor runs in, or let the installer save it where the hooks pick it up:curl -fsSL https://raw.githubusercontent.com/ossprey/cursor/main/install.sh \ | sh -s -- --key YOUR_API_KEYNote: a stored
ossprey loginsession takes precedence over an API key, soossprey logoutfirst if you want to force key auth.
-
Install the plugin and the Ossprey CLI in one go (PowerShell 5.1+ or pwsh):
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/ossprey/cursor/main/install.ps1))) -InstallCli
The installer clones the plugin into
%USERPROFILE%\.cursor\plugins\local\osspreyand rewrites the installed copy's hook wiring to run through a batch shim (Windows cannot execute the.shentrypoints).-InstallCliadditionally runs the CLI's official Windows installer, which downloads the matchingossprey-windows-<arch>.exe, verifies its sha256, installs it to%LOCALAPPDATA%\Programs\ossprey, and adds that directory to your userPATH— no WSL, no admin rights.Prefer separate steps? The two installs, individually:
# the CLI (from its own releases) irm https://github.com/ossprey/ossprey-cli/releases/latest/download/install.ps1 | iex # the plugin irm https://raw.githubusercontent.com/ossprey/cursor/main/install.ps1 | iex
(There is no
install.shpath on Windows — that script is Linux/macOS only.)Re-run the installer any time to update, pass
-Uninstallto remove the plugin, and restart Cursor (or run "Reload Window") after either. From inside a checkout,powershell -ExecutionPolicy Bypass -File install.ps1installs that working tree instead. -
Sign in to Ossprey: run
ossprey loginin a new terminal (so the updatedPATHis picked up) — same browser flow as above. For headless setups pass-Key YOUR_API_KEYto the installer instead. -
Install Python 3 if you don't have it —
winget install Python.Python.3.12. The hooks runhooks/ossprey_hook.pythrough a batch shim that looks for thepylauncher, thenpython3, thenpython; without one they fail open and nothing is checked. The Microsoft Store'spython.exealias stub does not count — the shim probes each candidate by running it, so a stub is skipped rather than mistaken for an interpreter.
macOS, Linux, and Windows are supported, and all three are covered by CI.
The hooks are thin wrappers around hooks/ossprey_hook.py: POSIX sh on
macOS/Linux, and a batch shim on Windows (hooks/ossprey-hook.cmd, wired up
by hooks/hooks.windows.json, which the installer applies as
hooks/hooks.json). Windows cannot execute the .sh entrypoints, which is
why the wiring differs; the Python logic underneath is identical on every
platform.
The Windows entrypoint is a .cmd rather than a PowerShell script for a
specific reason: Cursor delivers the hook payload on stdin, and cmd.exe
lets the Python child inherit that handle untouched, the same way exec
does in the sh entrypoints. A PowerShell wrapper has to read stdin and
re-write it to the child, and Windows PowerShell exposes a -File script's
redirected stdin through neither $input nor [Console]::In — so the
payload vanished and every hook silently checked nothing.
All knobs are environment variables:
| Variable | Default | Purpose |
|---|---|---|
OSSPREY_API_KEY |
from ~/.config/ossprey/env |
API key (only needed without ossprey login) |
OSSPREY_BIN |
ossprey on PATH |
CLI binary override |
OSSPREY_HOOK_TIMEOUT |
60 |
Seconds to wait for ossprey check |
OSSPREY_HOOK_DEBOUNCE |
30 |
Min seconds between rescans of a directory |
OSSPREY_HOOK_MAX_FOLLOWUPS |
2 |
Max stop-hook remediation prompts per conversation |
OSSPREY_HOOK_CHECK_ARGS / OSSPREY_HOOK_SCAN_ARGS |
— | Extra CLI args (e.g. --url for a staging API, --dry-run-malicious for demos) |
The hooks read any KEY=VALUE lines in ~/.config/ossprey/env
(install.sh --key / install.ps1 -Key write it; on Windows that is
%USERPROFILE%\.config\ossprey\env) as defaults, so every variable above
can be set there instead of in Cursor's process environment. Real
environment variables win over the file.
The hook suite runs without Cursor and without network access — payloads are
piped into the hook scripts exactly as Cursor sends them, and the CLI is
replaced by test/mock/ossprey:
sh test/run-tests.sh # macOS / Linuxpwsh -NoProfile -File test\run-tests.ps1 # WindowsThe POSIX suite covers the sh entrypoints and install.sh, plus
install.ps1 when pwsh is on the PATH. The PowerShell suite covers the
same hook behaviour driven through the Windows entrypoint and the .cmd
mock CLI (Windows can't execute the shebang mock, and neither can Python's
subprocess); those parts need Windows and are skipped elsewhere, so
running it on macOS/Linux exercises install.ps1 only. Both suites run in
CI, on ubuntu-latest and windows-latest.
For a live end-to-end test inside Cursor, install the plugin and ask the
agent to install @ossprey/test-package
— our harmless npm package that the Ossprey platform always flags as
malicious (think EICAR for supply-chain malware):
npm install @ossprey/test-packageThe install must be denied with a malware message. The package contains no malicious code, so nothing bad happens even if a blocking layer is misconfigured and it does get installed.
Alternatively, set OSSPREY_HOOK_CHECK_ARGS=--dry-run-malicious and ask the
agent to install any package — same expected result, without touching the
live verdict path. Unset the variable afterwards.
.cursor-plugin/plugin.json plugin manifest
install.sh install / update / uninstall the plugin (macOS/Linux)
install.ps1 same for Windows; -InstallCli also installs the CLI
hooks/hooks.json hook wiring (POSIX entrypoints)
hooks/hooks.windows.json Windows hook wiring (applied as hooks.json by install.ps1)
hooks/ossprey_hook.py guard / audit / report logic
hooks/ossprey-*.sh thin sh entrypoints (fail open without python3)
hooks/ossprey-hook.cmd Windows entrypoint (fails open without Python 3)
rules/ossprey.mdc always-on agent guidance
skills/ossprey/SKILL.md scanning workflow reference
test/run-tests.sh POSIX hook + install.sh suite
test/run-tests.ps1 PowerShell hook + install.ps1 suite
test/mock/ossprey[.cmd] mock CLI (sh script + Windows .cmd sibling)
- Docs: docs.ossprey.com
- Issues: github.com/ossprey/cursor/issues
- Email: support@ossprey.com