Skip to content

Repository files navigation

Ossprey plugin for Cursor

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.

What it does

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.

Fail-open by design

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.

Requirements

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.

Setup

For Windows, see Setup on Windows below.

macOS / Linux

  1. 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
  2. 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 whoami shows the session and ossprey logout removes 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.

  3. Install the plugin:

    curl -fsSL https://raw.githubusercontent.com/ossprey/cursor/main/install.sh | sh

    The 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 --uninstall to remove the plugin, and restart Cursor (or run "Reload Window") after either. From inside a checkout, sh install.sh installs 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_KEY in 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_KEY

    Note: a stored ossprey login session takes precedence over an API key, so ossprey logout first if you want to force key auth.

Setup on Windows

  1. 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\ossprey and rewrites the installed copy's hook wiring to run through a batch shim (Windows cannot execute the .sh entrypoints). -InstallCli additionally runs the CLI's official Windows installer, which downloads the matching ossprey-windows-<arch>.exe, verifies its sha256, installs it to %LOCALAPPDATA%\Programs\ossprey, and adds that directory to your user PATH — 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.sh path on Windows — that script is Linux/macOS only.)

    Re-run the installer any time to update, pass -Uninstall to remove the plugin, and restart Cursor (or run "Reload Window") after either. From inside a checkout, powershell -ExecutionPolicy Bypass -File install.ps1 installs that working tree instead.

  2. Sign in to Ossprey: run ossprey login in a new terminal (so the updated PATH is picked up) — same browser flow as above. For headless setups pass -Key YOUR_API_KEY to the installer instead.

  3. Install Python 3 if you don't have it — winget install Python.Python.3.12. The hooks run hooks/ossprey_hook.py through a batch shim that looks for the py launcher, then python3, then python; without one they fail open and nothing is checked. The Microsoft Store's python.exe alias stub does not count — the shim probes each candidate by running it, so a stub is skipped rather than mistaken for an interpreter.

Platform support

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.

Configuration

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.

Testing

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 / Linux
pwsh -NoProfile -File test\run-tests.ps1    # Windows

The 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-package

The 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.

Repository layout

.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)

Support

About

Cursor Plugin

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages