Keel
joseconti/declaracion-renta-espana
A skill your agent uses for ANY new software project from idea to release — websites, WordPress/WooCommerce plugins, MCP servers, web apps, components, or libraries.
Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth…
$ npx skills add cyanheads/pubmed-mcp-server --skill security-pass -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install cyanheads/pubmed-mcp-server security-pass --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/cyanheads/pubmed-mcp-server.git skills-src && mkdir -p .claude/skills && cp -r skills-src/framework-skills/security-pass .claude/skills/security-pass && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "security-pass" agent skill from https://github.com/cyanheads/pubmed-mcp-server/tree/main/framework-skills/security-pass into .claude/skills/security-pass/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-pass", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/cyanheads/pubmed-mcp-server/tree/main/framework-skills/security-passType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add cyanheads/pubmed-mcp-server --skill security-pass -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install cyanheads/pubmed-mcp-server security-pass --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cyanheads/pubmed-mcp-server.git skills-src && mkdir -p .agents/skills && cp -r skills-src/framework-skills/security-pass .agents/skills/security-pass && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "security-pass" agent skill from https://github.com/cyanheads/pubmed-mcp-server/tree/main/framework-skills/security-pass into .agents/skills/security-pass/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-pass", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add cyanheads/pubmed-mcp-server --skill security-pass -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install cyanheads/pubmed-mcp-server security-pass --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cyanheads/pubmed-mcp-server.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/framework-skills/security-pass .cursor/skills/security-pass && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "security-pass" agent skill from https://github.com/cyanheads/pubmed-mcp-server/tree/main/framework-skills/security-pass into .cursor/skills/security-pass/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-pass", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/cyanheads/pubmed-mcp-server.git --path framework-skills/security-pass--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add cyanheads/pubmed-mcp-server --skill security-pass -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install cyanheads/pubmed-mcp-server security-pass --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cyanheads/pubmed-mcp-server.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/framework-skills/security-pass .gemini/skills/security-pass && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "security-pass" agent skill from https://github.com/cyanheads/pubmed-mcp-server/tree/main/framework-skills/security-pass into .gemini/skills/security-pass/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-pass", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install cyanheads/pubmed-mcp-server security-passInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add cyanheads/pubmed-mcp-server --skill security-pass -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/cyanheads/pubmed-mcp-server.git skills-src && mkdir -p .github/skills && cp -r skills-src/framework-skills/security-pass .github/skills/security-pass && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "security-pass" agent skill from https://github.com/cyanheads/pubmed-mcp-server/tree/main/framework-skills/security-pass into .github/skills/security-pass/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-pass", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add cyanheads/pubmed-mcp-server --skill security-pass -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install cyanheads/pubmed-mcp-server security-pass --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cyanheads/pubmed-mcp-server.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/framework-skills/security-pass .opencode/skills/security-pass && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "security-pass" agent skill from https://github.com/cyanheads/pubmed-mcp-server/tree/main/framework-skills/security-pass into .opencode/skills/security-pass/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "security-pass", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
security-passReview an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth…
Security Pass is an agent skill from cyanheads/pubmed-mcp-server. Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth shape, input sinks (URL / path / roots / shell / schema strictness / ReDoS), tenant isolation, leakage through errors and telemetry, unbounded resources, and HTTP-mode deployment surface. Use before a release, after a batch of handler changes, or when the user asks for a security review, audit, or hardening pass…
Its SKILL.md is about 6.4k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Security, covering Multi-tenancy, Security review and MCP servers. It works with Model Context Protocol. The repository describes itself as: Search PubMed/Europe PMC, fetch articles and full text (PMC/EPMC/Unpaywall), citations, MeSH terms via MCP. STDIO or Streamable HTTP. The licence is Apache-2.0.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 5a417fb. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
buncursornpmjqFrom the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
MCP_REQUEST_STATE_KEYAPI_KEYFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Security Pass loads about 6.4k tokens when it runs. Until then it costs about 145 tokens; SKILL.md has 2,953 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from cyanheads/pubmed-mcp-server at commit 5a417fb, republished under its Apache-2.0 licence (© cyanheads). 2,953 words, ~6,385 tokens.
.claude/skills/security-pass/SKILL.md (or your agent's skills folder).An MCP server is a new attack surface with unique properties — tool output feeds back into the LLM's context, scopes gate what the model can do on the user's behalf, and per-request state must stay tenant-scoped. This skill walks a server through eight axes shaped around what the server builder actually controls. Framework-level concerns (transport, JSON-RPC parsing, auto-correlation, error classification) are out of scope — mcp-ts-core handles those.
Read the code. Don't trust patterns from memory.
Gather before starting. Ask if unclear:
Surface what you're auditing before diving in. Paths below assume the mcp-ts-core layout — adjust to your repo.
find src/mcp-server/tools/definitions -name "*.tool.ts" | sort
find src/mcp-server/resources/definitions -name "*.resource.ts" 2>/dev/null | sort
find src/mcp-server/prompts/definitions -name "*.prompt.ts" 2>/dev/null | sort
find src/services -maxdepth 1 -mindepth 1 -type d | sortNote: tool / resource / prompt counts, auth mode, storage provider, upstream APIs, which tools have destructiveHint, which handlers request a consent round via ctx.requestInput, which services hold module-scope state, whether the server reads roots.
If transport is streamable HTTP or SSE, also capture:
127.0.0.1 for local, or 0.0.0.0 / public interface?)/healthz, /sse, metadata endpoints) — do they leak tool lists or tenant hints?aud) checked, resource indicators usedIf CANVAS_PROVIDER_TYPE=duckdb is set, also capture:
MCP_AUTH_MODE=none collapses the composite (tenantId, canvasId) scope to ('default', canvasId), where the ID is the only differentiatorCANVAS_MAX_CANVASES_PER_TENANT, CANVAS_TTL_MS, CANVAS_ABSOLUTE_CAP_MS, CANVAS_EXPORT_PATH valuesUse TaskCreate — one task per axis. Mark complete as you go.
Run fuzzTool in parallel. @cyanheads/mcp-ts-core/testing/fuzz catches crashes, memory leaks, and prototype pollution automatically on each tool — start it now so results are ready when you reach Axis 5.
Anything the server sends to the client that reaches the LLM's context is a potential injection surface: tool output, resource content, prompt text, and the metadata the LLM reads to decide what to call. Relayed upstream content (tickets, scraped text, emails, DB rows) can carry adversarial instructions even when your code is honest.
Look in:
*.tool.ts — output schema + format()*.resource.ts — content returned from resources/read*.prompt.ts — templated message contentdescription, title, annotations, and inputSchema field descriptions (templated from untrusted data?)Check:
format() wrap untrusted content in delimiters (blockquote, fenced code, <data> tags)?resources/read) framed the same way tool output is?Smell: return { body: await fetch(url).then(r => r.text()) } rendered directly in format(). Or: description: \Look up ${tenant.customLabel}`wherecustomLabel` is tenant-supplied.
Every auth: [...] entry is a blast-radius dial.
Look in: every *.tool.ts — auth: array.
grep -rn "auth: \[" src/mcp-server/tools/definitions/Check:
['admin'], ['*'], or []?MCP_AUTH_DISABLE_SCOPE_CHECKS=true set in production? When on, both withRequiredScopes and checkScopes early-return — every authenticated user gets every tool, and runtime tenant patterns like team:${input.teamId}:write no longer guard. Acceptable only when paired with a real server-side ACL (path filter, allowlist, upstream API enforcement).Smell: every tool shares the same scope string. Or: MCP_AUTH_DISABLE_SCOPE_CHECKS=true set without a documented compensating ACL — confirm the deployment relies on a meaningful access control layer below the framework before approving.
ctx.requestInput moves consent off the LLM and onto the user: the handler returns an input_required result and only runs the side effect once it is re-entered and redeems the record it stored when it asked. An accepted response on ctx.inputs alone proves nothing — a client can send one on a call nothing prompted for. Destructive tools without that round trust the LLM not to be tricked.
Look in: handlers with destructiveHint: true or side-effecting verbs in names (delete_*, send_*, pay_*, publish_*, drop_*).
grep -rn "destructiveHint" src/mcp-server/tools/definitions/
grep -rnE "ctx\.requestInput|ctx\.inputs" src/mcp-server/tools/definitions/Check:
ctx.inputs for the confirmation and returns ctx.requestInput(...) before the side effect?ctx.inputs.accepted(key, Schema) with a schema, not the bare overload. The SDK never re-validates a response against the schema its request advertised, and the payload is LLM-mediated: "user confirmed" does not mean "user authored these exact fields."ctx.inputs.view(key) and thrown on, never re-asked (a re-ask loops until the round budget runs out) and never treated as consent.requestState influences authorization, resource access, or which target gets mutated, MCP_REQUEST_STATE_KEY is set (≥ 32 bytes, the same on every instance a retry can reach). The framework then seals the string a handler returns and the SDK rejects any other state before the handler runs; unset, the state round-trips through the client and comes back attacker-controlled.elicitation.form — a URL-only client included — cannot pre-answer a form gate. A client that declared it still can: it may send inputResponses — and a requestState of its own, or one it was issued earlier — on the very first call, so a handler that only compares client-carried state against a fresh resolution deletes on an answer nobody was shown. A sealed state closes forgery but not replay within its 900 s lifetime. Keep what the prompt confirmed in a ctx.state record keyed by a random id — the operation (tool name, or the resource URI read), the caller (ctx.auth clientId and sub), the target, and a content hash so a same-path swap is caught — send only the id, redeem it before anything else in the handler, and ask again on an unknown, used, or expired id or on any field that differs from this call. Without the operation, an id minted by another gated tool or resource confirms this one; without the caller, another user in the same tenant redeems an id they were handed while MCP_REQUEST_STATE_KEY is unset. The record's storage is shared by every instance a 2026-07-28 retry can reach — filesystem, supabase, or cloudflare-d1, never cloudflare-kv, whose eventual consistency widens the race below.ctx.state gains an atomic take (#593), an action that must not repeat — a payment, a send, a publish — is idempotent per record (the record id as the upstream idempotency key), or the risk is accepted knowingly and recorded in the findings.ctx.requestInput is present on every transport and both protocol eras — the 2025-era shim issues the real elicitation/create round trip, the 2026-07-28 client fulfils the embedded request directly. A client that never retries simply leaves the destructive step un-run, which fails safe. Keep destructiveHint: true so client-side approval flows still surface the risk, and do not accept "proceed anyway when the round is unavailable" as a fallback. On a 2025-era connection whose client lacks the capability, ctx.requestInput throws client_capability_missing inside the handler; catching that to run the side effect is exactly this bypass — let it propagate. So is skipping the prompt because ctx.clientCapabilities lacks elicitation: that property decides whether to ask for optional context, never whether consent is needed.Smell: destructiveHint: true file with no ctx.requestInput in it. Or ctx.inputs.accepted('confirm') with no schema argument — the content could be anything. Or a gate that proceeds on ctx.inputs without redeeming a ctx.state record, whose record omits the operation or the caller, or that compares a target carried in requestState. Or a non-repeatable action behind a gate with no idempotency key. Or a handler that re-issues the same request after a decline.
What credentials the server holds, and the blast radius if one leaks.
Look in: src/services/*, src/config/server-config.ts.
Check:
aud, or passthrough the caller's?Smell: one global API_KEY used across all tenants + retry loop with no upper bound.
LLM-supplied inputs feel internal but aren't. Classic sinks apply, amplified. Sampling responses and roots-derived paths are MCP-specific sinks that look internal but carry LLM/client trust.
Look in: all handlers.
# URL sinks — SSRF
grep -rn "z.string().url()" src/
# Path sinks — traversal
grep -rnE "readFile|writeFile|readdirSync|createReadStream|statSync" src/
# Shell sinks — command injection
grep -rnE "\b(exec|spawn|execSync|spawnSync)\b" src/
# Merges — prototype pollution
grep -rnE "Object\.assign\b|structuredClone" src/
# Lookups — prototype chain read through an object literal
grep -rnE "\[[a-zA-Z_$][a-zA-Z0-9_$.]*\] *\?\? |\[[a-zA-Z_$][a-zA-Z0-9_$.]*\] *\|\| " src/
# Roots — client-shared filesystem
grep -rnE "roots/list|ctx\.roots" src/
# Schema laxity — fields sneaking past validation
grep -rnE "\.passthrough\(\)|\.loose\(\)|looseObject\(|\.catchall\(" src/mcp-server/Check:
file://, ftp://, localhost, DNS rebind?path.resolve + assert startsWith(root + sep))?. and ..? encodeURIComponent leaves dots untouched, and the URL parser resolves dot segments, so a file key or archive-member input of ../../me retargets the request (credentials attached) at another endpoint on the same host. Redirects on authenticated requests should follow only within the upstream origin.\ from the caller then silently drops every filter clause while the tool still reports them as applied..regex() in an input schema, and every regex a handler or normalizer runs over caller text, sees attacker-length strings before any length cap applies. Loose "raw" patterns that admit un-normalized input (\s* runs, optional quotes and separators around a repeated group) are the usual source of polynomial backtracking. Time each one against a long adversarial string (thousands of spaces, then a character that forces failure). Milliseconds is fine; seconds is a finding.__proto__, constructor, prototype keys?Map, not an object literal? The read direction of the same defect: TABLE[key] ?? fallback walks the prototype chain, so a key of constructor (or toString, valueOf) returns a function the ?? does not catch — it is not nullish — and string coercion then emits function Object() { [native code] } into the output. Lowercasing the key masks the camelCase members and leaves constructor reachable, so it is not a fix. A Map has no prototype chain; Object.create(null) works too..strict() — unknown fields rejected, not silently passed to downstream code that destructures with ...rest?.passthrough() / .loose() / .catchall() — no accidental exfiltration of fields your schema didn't declare?
Smell: z.string().url() with no allowlist; readFile(input.path) with no canonicalization.ctx.state is tenant-scoped. Module-scope state is not.
Look in: src/services/*.
grep -rnE "^(const|let) .* = new (Map|Set|WeakMap|Array)" src/services/
grep -rn "^let " src/services/Check:
Map / Set / cache near tenant-handling code?logger while carrying per-tenant data (bypassing auto-correlated ctx.log)?Smell: service file with top-level const cache = new Map().
What accidentally reaches the LLM, user, or observability sinks.
Look in: throw new McpError(...) and ctx.fail(reason, msg, data) sites, error factory calls (notFound, httpErrorFromResponse, …), McpError.data fields (the data arg flows through both paths), output schemas, and every logging / telemetry surface — not just ctx.log.
grep -rnE "new McpError|ctx\.fail\(|httpErrorFromResponse\(" src/
grep -rnE "\b(ctx\.log|console\.(log|info|warn|error|debug)|logger\.)" src/
grep -rnE "(Sentry\.|captureException|setTag|setContext|addBreadcrumb)" src/
grep -rnE "(setAttribute|setAttributes|span\.)" src/ # OpenTelemetryCheck:
data fields (whether passed via ctx.fail(reason, msg, data), new McpError(code, msg, data), or factory calls) carry upstream response bodies, auth headers, stack traces?httpErrorFromResponse body capture sweeping in too much (default 500-byte cap is fine for most APIs but consider captureBody: false when the upstream returns auth-bearing payloads)?format() renders fields that shouldn't leave the server?ctx.log.info(msg, body) where body is the raw request (may contain secrets)?console.* calls near auth / token / request-body handling — bypasses structured redaction?=== or == instead of constant-time (timingSafeEqual / crypto.timingSafeEqual) — leaks length and prefix via timing?Smell: throw new McpError(code, upstream.message, { raw: upstream.body }) or throw ctx.fail('upstream_failed', e.message, { raw: e.response.body }). Or: if (apiKey === expected) on a request-auth path.
Unbounded = DoS of self, upstream, or the LLM's context window (billing-DoS is real).
Look in: handlers with loops, pagination, retries, or inputs that feed JSON.parse / schema validation.
grep -rnE "while\s*\(|for\s*\(.*of" src/mcp-server/tools/definitions/
grep -rnE "cursor|nextPage|paginate" src/
grep -rn "JSON.parse\b" src/Check:
0, null)?JSON.parse / Zod .parse() inputs have a size + nesting-depth limit applied before parse?delete_record 10k/sec hits you before it hits upstream)?Smell: while (cursor) { results.push(...); cursor = next; } with no max count. Or: JSON.parse(await req.text()) with no Content-Length check upstream.
CANVAS_PROVIDER_TYPE=duckdb)DataCanvas is opt-in and deliberately trades isolation for cross-agent token-shareable working sets — designed for public-data tabular servers (BrAPI, OpenAlex, etc.) where session-pinning isn't desired. The trade only holds when the deployment matches that assumption. Skip this axis entirely when canvas is disabled (CANVAS_PROVIDER_TYPE=none, the default).
Look in: src/config/server-config.ts, the setCanvas(core.canvas) wiring in setup() and every tool reading the canvas accessor, deployment config (wrangler / Dockerfile / proxy).
Check:
(tenantId, canvasId) scope collapses to ('default', canvasId) in MCP_AUTH_MODE=none — anyone with the canvasId attaches.CANVAS_MAX_CANVASES_PER_TENANT sized for the memory budget — default 100 is the floor; raising it lets a single tenant exhaust memory faster.CANVAS_TTL_MS / CANVAS_ABSOLUTE_CAP_MS not absurdly long. Defaults (24 h sliding / 7 d absolute) are reasonable; longer widens the window an unreferenced canvasId stays guessable.CANVAS_EXPORT_PATH doesn't point into a shared mount, the repo, or a directory another service serves from. The path-sandbox blocks .. traversal but doesn't prevent the configured root from being a bad choice.assertReadOnlyQuery), and Axis 7 (errors from canvas operations don't leak the failed SQL string back through McpError.data) all apply.Smell: MCP_AUTH_MODE=none deployment registering per-user data (recent activity, account state, cart contents) onto a canvas. Or: CANVAS_EXPORT_PATH=/srv/static with a static file server pointing at the same root.
Fast, sometimes high-leverage. Outside the eight axes.
bun audit — any direct high/critical?package.json — postinstall / lifecycle scripts on added deps?npm view <pkg> --json | jq .dist.attestations — missing attestation on a security-critical dep is a yellow flag.env.example — placeholder values only, never real?ConfigSchema — fails loudly on missing required keys (not silent defaults)?process.env.* reads outside the config parser (bypasses validation)?fuzzTool results from Step 1 — triage crashes / leaks as Axis 5 / Axis 8 findings.Three sections. Summary → findings → numbered options.
Definitions reviewed, axes covered, count by severity, the single most important finding.
Group by severity. Each 3–5 lines.
| Severity | Meaning |
|---|---|
| critical | Exploitable now: auth bypass, exfiltration, arbitrary code/file/network access |
| high | Structural gap with clear attacker benefit even without immediate PoC (destructive op without a consent round, admin scope on read tool, SSRF-capable URL input) |
| medium | Defense-in-depth gap weakening a boundary (missing per-tenant rate limit, error carries upstream response) |
| low | Hardening / polish (tighter output schema, narrower error data, minor comment) |
Format:
**<file_or_tool> — Axis <N> — <critical|high|medium|low>**
Issue: <one line: what's wrong>
Impact: <one line: what can go wrong>
Fix: <one line: the change>Numbered, cherry-pickable.
1. Add SSRF guard to `fetch_url.tool.ts` — block private IPs + non-http schemes (critical, #1)
2. Gate `delete_record.tool.ts` behind a `ctx.requestInput` confirmation round (high, #3)
3. Split `admin` into `record:read` + `record:write` across 4 tools (high, #4)
4. Move `const tokenCache = new Map()` out of module scope in `auth-service.ts` (medium, #7)
5. Cap pagination loop in `list_all_tickets` at 1000 items (medium, #9)
6. Strip upstream response body from `McpError.data` in `sync-service.ts` (low, #11)End with:
Pick by number (e.g. "do 1, 3, 5" or "expand on 2").
fuzzTool started in parallelctx.requestInput round that redeems a ctx.state record bound to the operation, caller, and target, the response schema-validated, decline/cancel terminal, non-repeatable actions idempotent per record, MCP_REQUEST_STATE_KEY set where state drives a mutationctx.log / console.* / telemetry / constant-time comparisonsCANVAS_PROVIDER_TYPE=duckdb: Axis 9 — public-data assumption holds, external rate limiting in place, max-canvases-per-tenant + TTLs sized for the deployment, CANVAS_EXPORT_PATH doesn't escape into shared / served paths, assertReadOnlyQuery is the only SQL pathbun audit, lifecycle scripts, .env.example, config validation, new-dep provenance© cyanheads, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in framework-skills/security-pass of cyanheads/pubmed-mcp-server.
Open the folder on GitHubat commit 5a417fb
Security Pass next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Security Pass this skillcyanheads/pubmed-mcp-server | 155 | — | ~6.4k | Automated safety check: Pass | Apache-2.0 | |
| Keeljoseconti/declaracion-renta-espana | 190 | — | ~11k | Automated safety check: Warn | GPL-3.0-or-later | |
| MCP Server Security Auditawarexone/Agentic-Bug-Hunter | 5.3k | — | ~1.9k | Automated safety check: Warn | MIT | |
| MCP Security Auditgithub/awesome-copilot | 40k | — | ~3.1k | Automated safety check: Pass | MIT | |
| MCP Implementation Security Reviewgithub/awesome-copilot | 40k | — | ~5.2k | Automated safety check: Pass | MIT | |
| Security Reviewktnyt/cclsp | 675 | — | ~565 | Automated safety check: Pass | MIT |
joseconti/declaracion-renta-espana
A skill your agent uses for ANY new software project from idea to release — websites, WordPress/WooCommerce plugins, MCP servers, web apps, components, or libraries.
awarexone/Agentic-Bug-Hunter
Audits MCP servers and their client configs for tool poisoning, prompt injection, over-privileged tools, injection bugs, secret leaks and missing approval gates.
github/awesome-copilot
Audit MCP (Model Context Protocol) server configurations for security issues.
github/awesome-copilot
Review the implementation source code of MCP (Model Context Protocol) servers, clients, and tool handlers against a security baseline — authentication, sessions, rate limiting, input-schema…
ktnyt/cclsp
Request a security expert assessment for code changes that touch child process spawning, file system access, configuration loading, or environment variable handling.
stacklok/toolhive-studio
Remediate security vulnerabilities found by Grype or pnpm audit.
cyanheads/pubmed-mcp-server
Scaffold an MCP App tool + UI resource pair. An agent skill from cyanheads/pubmed-mcp-server.
cyanheads/pubmed-mcp-server
Scaffold a new MCP prompt template. An agent skill from cyanheads/pubmed-mcp-server.
cyanheads/pubmed-mcp-server
Scaffold a new MCP resource definition. An agent skill from cyanheads/pubmed-mcp-server.
cyanheads/pubmed-mcp-server
Scaffold a new service integration. An agent skill from cyanheads/pubmed-mcp-server.
cyanheads/pubmed-mcp-server
Scaffold a test file for an existing tool, resource, or service.
cyanheads/pubmed-mcp-server
Authentication, authorization, and multi-tenancy patterns for @cyanheads/mcp-ts-core.
Works with
Categories
Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth…. Security Pass is an agent skill from cyanheads/pubmed-mcp-server. Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth shape, input sinks (URL / path / roots / shell / schema strictness / ReDoS), tenant isolation, leakage through errors and telemetry, unbounded resources, and HTTP-mode deployment surface.
Security Pass fits situations like: asks for a security review; tasks that involve Multi-tenancy; tasks that involve Security review.
Run `npx skills add cyanheads/pubmed-mcp-server --skill security-pass -a claude-code`. Or copy the skill folder (framework-skills/security-pass in cyanheads/pubmed-mcp-server) into .claude/skills/security-pass in your project. Claude Code loads it when a task matches its description.
Run `npx skills add cyanheads/pubmed-mcp-server --skill security-pass -a codex`. Or copy the skill folder (framework-skills/security-pass in cyanheads/pubmed-mcp-server) into .agents/skills/security-pass in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add cyanheads/pubmed-mcp-server --skill security-pass -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/security-pass, .gemini/skills/security-pass, .github/skills/security-pass and .opencode/skills/security-pass in your project.
Going by SKILL.md and its folder, Security Pass needs the command-line tools its instructions call (bun, cursor, npm and jq) and credentials named MCP_REQUEST_STATE_KEY and API_KEY. Our summary lists: A credential in MCP_REQUEST_STATE_KEY.
SKILL.md names 1 domain. As links in the text: github.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Security Pass is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.4k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Security Pass: Keel (joseconti/declaracion-renta-espana, 190 stars), MCP Server Security Audit (awarexone/Agentic-Bug-Hunter, 5.3k stars), MCP Security Audit (github/awesome-copilot, 40k stars) and MCP Implementation Security Review (github/awesome-copilot, 40k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
cyanheads (a GitHub user) maintains it in cyanheads/pubmed-mcp-server, which has 155 GitHub stars. The repository holds 30 skills in this directory. The repository was last updated on October 4, 2026.
Source: cyanheads/pubmed-mcp-server on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.