How we scan

Every MCP server and skill listed here that has a README is scanned for risky commands. The scan answers one question: do the commands the README tells you, or your agent, to run ask for something dangerous?

How it works

  • We read the README from GitHub and look only at command-like text: lines inside code blocks, and inline code. Sentences that merely mention a risky command are not counted.
  • Each rule below is a fixed pattern. There is no model and no judgement call, so the same README always gets the same result.
  • A project counts as established when it has at least 1,000 stars and is at least 180 days old. For those, a serious pattern is graded Caution instead of Unsafe, and minor ones are not held against it.
  • READMEs are read again every 30 days, and grades are recomputed daily as projects gain a track record.

The results

SafeNo rule matched, or only minor ones in an established project.
CautionA medium rule matched; or a high rule in an established project; or a minor rule in a project that is not established.
UnsafeA high rule matched in a project that is not established. Install commands are not offered.
RejectA critical rule matched. Install commands are not offered.
Not scannedNo README was found under the usual names, or it has not been read yet. This is not the same as safe.

The rules

20 rules, shown exactly as they are applied.

CriticalAlways graded Reject.

Sends secrets over the networkA command that reads credentials or environment secrets and pipes them into a network tool is how data is stolen.

Pattern · secrets-to-network

(?:cat|echo|base64)\b[^|\n]*(?:\.env\b|id_rsa|id_ed25519|\.ssh\/|\.aws\/credentials|\.npmrc|\.netrc|\.git-credentials|\bprintenv\b|\$\{?[A-Z0-9_]*(?:API_?KEY|TOKEN|SECRET|PASSWORD)\b)[^|\n]*\|[^\n]*\b(?:curl|wget|nc|ncat|netcat)\b|(?:^|[\s;&|])(?:printenv|env)\s*\|[^\n]*\b(?:curl|wget|nc|ncat|netcat)\b|\b(?:curl|wget)\b[^\n]*(?:-d|--data(?:-binary|-raw)?|-F|--post-data)[ =][^\n]*(?:\$\(\s*cat\b[^)\n]*(?:\.env\b|id_rsa|id_ed25519|\.ssh\/|\.aws\/credentials|\.npmrc|\.netrc|\.git-credentials|\bprintenv\b|\$\{?[A-Z0-9_]*(?:API_?KEY|TOKEN|SECRET|PASSWORD)\b)|@~?\/?[^\s]*(?:id_rsa|id_ed25519|\.aws\/credentials|\.env\b))
Writes downloaded code into startup files or cronAppending a download to your shell startup files or crontab makes it run again and again without asking.

Pattern · payload-in-startup

\b(?:curl|wget)\b[^\n]*(?:>>?|\|\s*(?:sudo\s+)?tee\b)[^\n]*(?:\.bashrc|\.zshrc|\.bash_profile|\.zprofile|\.profile|\/etc\/profile|\/etc\/cron|crontab)|\becho\b[^\n]*\b(?:curl|wget)\b[^\n]*>>?[^\n]*(?:\.bashrc|\.zshrc|\.bash_profile|\.zprofile|\.profile|\/etc\/profile|\/etc\/cron|crontab)

HighUnsafe, or Caution in an established project.

Downloads a script and runs itPiping a download into a shell runs whatever the server sends, unseen.

Pattern · pipe-to-shell

\b(?:curl|wget)\b(?![^|\n]*(?:raw\.githubusercontent\.com|astral\.sh|sh\.rustup\.rs|bun\.sh|deno\.land|get\.docker\.com|brew\.sh|install\.python-poetry\.org|get\.pnpm\.io|fnm\.vercel\.app|claude\.ai|ollama\.com|get\.helm\.sh|nodesource\.com))[^|\n]*\|\s*(?:sudo\s+)?(?:ba|z|da)?sh\b|\b(?:ba|z)?sh\s+<\(\s*(?:curl|wget)\b(?![^)\n]*(?:raw\.githubusercontent\.com|astral\.sh|sh\.rustup\.rs|bun\.sh|deno\.land|get\.docker\.com|brew\.sh|install\.python-poetry\.org|get\.pnpm\.io|fnm\.vercel\.app|claude\.ai|ollama\.com|get\.helm\.sh|nodesource\.com))|\b(?:iwr|irm|Invoke-WebRequest|Invoke-RestMethod)\b(?![^|\n]*(?:raw\.githubusercontent\.com|astral\.sh|sh\.rustup\.rs|bun\.sh|deno\.land|get\.docker\.com|brew\.sh|install\.python-poetry\.org|get\.pnpm\.io|fnm\.vercel\.app|claude\.ai|ollama\.com|get\.helm\.sh|nodesource\.com))[^|\n]*\|\s*(?:iex|Invoke-Expression)\b
Opens a remote shellThese commands give someone else an interactive shell on your machine.

Pattern · reverse-shell

\/dev\/tcp\/|\b(?:nc|ncat|netcat)\b[^\n]*\s-[a-z]*e\b|\bsocat\b[^\n]*\bexec:|\bmkfifo\b[^\n]*\|\s*(?:nc|ncat)\b
Runs encoded or hidden codeDecoding text and executing it hides what actually runs.

Pattern · encoded-execution

\bbase64\s+(?:-d|--decode|-D)\b[^\n]*\|\s*(?:sudo\s+)?(?:ba|z)?sh\b|\beval\s+["']?\$\([^)\n]*\bbase64\b|\bpowershell(?:\.exe)?\b[^\n]*\s-e(?:nc|ncodedcommand)?\s+[A-Za-z0-9+/=]{20,}|\bpython3?\s+-c\b[^\n]*\bexec\([^\n]*\bb64decode\b
Turns off a system protectionDisabling Gatekeeper, SIP, SELinux or antivirus removes a safeguard for everything on the machine, not just this tool.

Pattern · disables-protection

\bspctl\s+--master-disable\b|\bcsrutil\s+disable\b|\bsetenforce\s+0\b|Set-MpPreference\b[^\n]*-DisableRealtimeMonitoring|\bchmod\s+(?:-R\s+)?777\s+\/(?:\s|$)
Deletes or overwrites broadlyA mistyped or malicious path here can wipe a disk or a home directory.

Pattern · destructive-command

\brm\s+-[a-z]*r[a-z]*f?[a-z]*\s+(?:--no-preserve-root\s+)?(?:\/|~|\$HOME)\/?(?:\s|$|\*)|\bmkfs(?:\.\w+)?\s+\/dev\/|\bdd\b[^\n]*\bof=\/dev\/(?:sd|nvme|disk)|:\(\)\s*\{\s*:\|:&\s*\}\s*;\s*:

MediumCaution.

Runs an install script from a well-known hostCommon practice for popular tools, but it still runs a script you have not read.

Pattern · pipe-to-shell-known-host

\b(?:curl|wget)\b[^|\n]*(?:raw\.githubusercontent\.com|astral\.sh|sh\.rustup\.rs|bun\.sh|deno\.land|get\.docker\.com|brew\.sh|install\.python-poetry\.org|get\.pnpm\.io|fnm\.vercel\.app|claude\.ai|ollama\.com|get\.helm\.sh|nodesource\.com)[^|\n]*\|\s*(?:sudo\s+)?(?:ba|z|da)?sh\b|\b(?:ba|z)?sh\s+<\(\s*(?:curl|wget)\b[^)\n]*(?:raw\.githubusercontent\.com|astral\.sh|sh\.rustup\.rs|bun\.sh|deno\.land|get\.docker\.com|brew\.sh|install\.python-poetry\.org|get\.pnpm\.io|fnm\.vercel\.app|claude\.ai|ollama\.com|get\.helm\.sh|nodesource\.com)|\b(?:iwr|irm)\b[^|\n]*(?:raw\.githubusercontent\.com|astral\.sh|sh\.rustup\.rs|bun\.sh|deno\.land|get\.docker\.com|brew\.sh|install\.python-poetry\.org|get\.pnpm\.io|fnm\.vercel\.app|claude\.ai|ollama\.com|get\.helm\.sh|nodesource\.com)[^|\n]*\|\s*iex\b
Asks for administrator rightsWith sudo, a mistake or a compromised package can change the whole system.

Pattern · sudo

(?:^|[\s;&|(])sudo\s+\S
Sets itself up to run automaticallyStartup entries, services and scheduled jobs keep running after you stop paying attention.

Pattern · persistence

\|\s*crontab\b|\bcrontab\s+-e\b|\blaunchctl\s+(?:load|bootstrap)\b|\bsystemctl\s+(?:--user\s+)?enable\b|\bschtasks\s+\/create\b|CurrentVersion\\+Run\b
Tells the agent to skip permission promptsWithout prompts, the agent can run commands and edit files with no chance for you to say no.

Pattern · skips-agent-permissions

--dangerously-skip-permissions\b|--dangerously-bypass-approvals-and-sandbox\b|--yolo\b|--trust-all-tools\b|\bbypassPermissions\b|--allow-all-tools\b
Reads stored credentialsSSH keys, cloud credentials and keychains unlock far more than this tool needs.

Pattern · credential-files

\b(?:cat|cp|scp|tar|zip|base64)\b[^\n]*(?:~|\$HOME)\/(?:\.ssh\/(?![\w.-]*\.pub\b)|\.aws\/credentials|\.netrc|\.git-credentials|\.config\/gcloud)|\bsecurity\s+find-(?:generic|internet)-password\b
Downloads a file and makes it executableA binary fetched outside a package manager has no signature or version check.

Pattern · downloads-and-runs-binary

\b(?:curl|wget)\b[^\n]*(?:-o|-O|--output)\b[^\n]*(?:&&|;)\s*(?:sudo\s+)?chmod\s+\+x\b
Gives a container access to the hostA privileged container, the Docker socket or a mount of the whole disk removes the isolation a container is for.

Pattern · container-host-access

\bdocker\b[^\n]*(?:--privileged\b|\/var\/run\/docker\.sock|-v\s+\/:\/|--pid[= ]host\b)
Removes the macOS quarantine flag from an appCommon for unsigned apps, but it skips the check macOS would otherwise make before first launch.

Pattern · bypasses-gatekeeper

\bxattr\s+-[a-z]*d[a-z]*\s+com\.apple\.quarantine\b

LowCaution, or ignored in an established project.

Adds a line to your shell startup fileUsually a PATH entry or an environment variable; check what the line does, since it runs in every new terminal.

Pattern · edits-shell-profile

(?:>>|\|\s*tee\s+-a)\s*~?\/?[^\s]*(?:\.bashrc|\.zshrc|\.bash_profile|\.zprofile|\.profile)\b
Turns off certificate checksWithout certificate checks, a download can be swapped in transit.

Pattern · tls-verification-off

\bcurl\b[^\n]*\s(?:-k|--insecure)\b|\bwget\b[^\n]*--no-check-certificate\b|NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"]?0|\bgit\b[^\n]*http\.sslVerify[= ]false
Runs an install script from the repositoryThe script can do anything; skim it first.

Pattern · runs-repo-script

(?:^|[\s;&|])(?:ba|z)?sh\s+(?:\.\/)?[\w./-]*(?:install|setup|bootstrap)[\w.-]*\.sh\b|(?:^|[\s;&|])\.\/[\w./-]*(?:install|setup|bootstrap)[\w.-]*\.sh\b
Overrides the system package safeguardsInstalling into the system Python or as root can break other software.

Pattern · system-package-override

--break-system-packages\b|\bnpm\b[^\n]*--unsafe-perm\b

InfoShown for information. Never changes the result.

Needs an API key or tokenNormal for tools that call a service. Give it the narrowest key you can.

Pattern · needs-credentials

\b[A-Z][A-Z0-9_]*(?:API_?KEY|ACCESS_TOKEN|AUTH_TOKEN|_TOKEN|_SECRET|PASSWORD)\b["']?\s*[=:]

What the scan cannot tell you

  • It reads the README, not the code. A project can have a harmless README and harmful code, or the other way round.
  • Patterns match text, not intent. A legitimate tool that needs sudo is flagged the same as a careless one, and a README that shows an attack in order to warn about it is flagged as if it asked you to run it.
  • Anything written in a way the patterns do not cover is missed. Scripts the README links to are not followed.
  • This is a first filter, not a security audit. Read what you install, and run unfamiliar tools where they cannot reach your keys.
  • Maintainers who think a result is wrong can tell us through the contact page; we fix the rule for everyone rather than making exceptions.