Approved last month, malicious today: a re-verification cadence for MCP servers and skills
An approval is only valid for the exact thing you reviewed. Pin every approved MCP server and skill to an immutable version or content hash. Re-verify whenever that version or hash changes. Then run a scheduled sweep, because new threat reports arrive even when your artifacts do not change. The first malicious MCP server in the wild, postmark-mcp, turned bad in version 1.0.16. OWASP's Agentic Skills Top 10 lists "Update Drift" as AST07. Install-time vetting alone does not survive either.
Outcome, time and roles
Outcome. Every approved MCP server and skill has a recorded version or hash, a last-verified date, and a trigger that forces a re-check when it changes.
Time. About half a day to build the inventory for a team with a few dozen artifacts. After that, minutes per change.
Roles. One platform owner with write access to managed settings. One reviewer who is not the author of the artifact. Anthropic's enterprise skills guidance states it directly: "Skill authors should not be their own reviewers."
Use this runbook if, do not use if
Use it if you already approve MCP servers or skills before use and want approvals to stay true.
Do not use it yet if you have no inventory. Build that first. A cadence for unknown artifacts is a cadence for nothing.
Why approvals decay
Three documented mechanisms change an approved artifact after review.
A new version from the same name. postmark-mcp was a copy of Postmark's official library. In version 1.0.16, released 17 September 2025, one added line blind-copied every email the server sent to an outside address. It had 1,643 downloads in total, The Hacker News reported. Anyone who approved an earlier version and let it float to the latest would have received the change silently.
A dependency changes underneath. On 31 March 2026, [email protected] was published from a hijacked maintainer account. It added a dependency, [email protected], with a postinstall hook. The bad axios version stayed on npm for about 2 hours 53 minutes, StepSecurity found. An approved MCP server that depends on axios with a loose version range could pull it in without its own version number changing.
The tools change mid-session. The MCP specification lets a server tell the client its tool list changed, through notifications/tools/list_changed. Backslash Security's checklist puts the risk plainly: "A server can redefine its tool descriptions on a later run."
OWASP's AST07 page sums it up: "Skills are installed and forgotten." The project is an incubator at version 0.0.0, so treat it as a checklist, not a standard.
Pre-flight: build the inventory
OWASP's AST07 mitigations ask for "an inventory of installed skills with version, hash, and last-verified timestamp." Extend that to MCP servers. One row per artifact:
artifact | type | source | pinned_to | hash | approved_by | last_verified | trigger
Fill pinned_to with an exact version, a commit SHA, or none (remote). A row that says latest is a finding.
Verification. Pick three rows at random. Confirm the pinned version is what developers actually run. If it is not, stop and fix distribution before going further.
The cadence by artifact type
The intervals below are threat.cafe editorial recommendations, not a vendor standard. The logic behind them: a pinned artifact cannot change, so its scheduled check is against new threat reports. An unpinned or remote one can change at any time, so it needs a check on every observable change plus a short sweep interval.
ARTIFACT TYPE PIN TO RE-CHECK WHEN SWEEP
-------------------------- --------------------- ------------------------- ---------
Local MCP server exact version in the version or lockfile monthly,
(npm / PyPI package) launch command; changes; any dependency against
lockfile integrity in the tree changes new reports
Skill in your Git repo commit SHA + hash of any commit touching the monthly
the skill folder skill folder
Skill via Claude API explicit version on a new version is monthly
(/v1/skills) every request uploaded
Skill synced from not pinnable by user; your claude.ai skills weekly
claude.ai to Claude Code review at source change
Remote MCP server not pinnable; snapshot tools/list differs from weekly,
(HTTP) tools/list at snapshot; list_changed plus at
approval received session start
-------------------------- --------------------- ------------------------- ---------
Step-by-step execution
Step 1: Pin local MCP servers
Write the exact version into the launch command. A command like npx -y PACKAGE fetches the newest version on each run. A command with [email protected] does not.
Record the registry's integrity hash for that version:
npm view [email protected] dist.integrity
Verify. Run it again next week. The value must be identical. If it changes for the same version string, stop and treat it as an incident.
Enforce. In Claude Code, an allowlist serverCommand entry must match "every argument, in order," per the managed MCP docs. Put the pinned version in the entry. A new version then fails the allowlist until someone approves it.
Step 2: Hash skills you store yourself
Skills are folders of files, so hash the folder:
find ./skills/SKILL_NAME -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum
Store the result in the inventory. Anthropic's guidance says to "compute checksums of reviewed Skills and verify them at deployment time" and to use signed commits.
Verify. Re-run the command at deployment. A mismatch blocks the deploy.
Step 3: Pin API skills by version
Anthropic's docs are explicit: "If you omit version, requests use the latest version, so a new version uploaded by anyone in the workspace immediately changes what production agents run." Set version on every request.
Verify. Search your code for skill references with no version. Each one is a floating approval.
Step 4: Watch synced skills at the source
Claude Code downloads skills enabled on a user's claude.ai account into ~/.claude/skills/synced/, according to the Claude Code skills docs. It checks for changes about every 10 minutes while a session is active. Users cannot pin these. Review them where they are managed, on claude.ai. If that review cannot keep pace, set syncClaudeAiSkills to false in managed settings.
Step 5: Snapshot remote server tools
You cannot pin a URL. You can record what it offers. The MCP Inspector's CLI lists a remote server's tools:
npx @modelcontextprotocol/inspector --cli https://SERVER_URL --transport http --method tools/list > tools-approved.json
Hash the file. On each sweep, list the tools again and compare.
Verify. Any difference in a tool's name, description or input schema is a re-review, even if the change looks small. Descriptions are text the model reads.
Common failure modes and fixes
Failure 1: the pin is in the docs, not in the config. The wiki says version 1.2.3, but developers copied an older command without a version. Fix: enforce the pin in the managed allowlist, not in documentation. A command without the pinned version should fail to load.
Failure 2: the lockfile pins the server but not its tree. Pinning the top-level package does not freeze its dependencies if it is launched in a way that resolves them fresh. Fix: install from a committed lockfile with npm ci, and re-check when the lockfile changes. The axios case shows why the whole tree matters.
Failure 3: the sweep checks nothing new. A monthly sweep that re-reads the same scan result adds nothing. Fix: the sweep compares your inventory against new reports published since last_verified. A known-bad list or advisory feed is the input. The date column makes this cheap.
Failure 4: remote changes arrive between sweeps. A weekly snapshot misses a change on day two. Fix: add a session-start comparison for remote servers on your highest-risk list, and treat notifications/tools/list_changed as a re-check trigger where your client exposes it.
Failure 5: the reviewer is the author. Fix: two names on every approval row.
Rollback
When a re-check fails:
- Block first. Add the exact version, command or URL to the managed denylist. In Claude Code, "nothing overrides a denylist match."
- Restore the last good version. Anthropic's guidance: "Maintain the previous version as a fallback." For a local server, roll the pinned version back. For an API skill, point requests at the previous version.
- Check exposure. Find which machines ran the changed version and when. For
postmark-mcp, The Hacker News relayed advice to rotate credentials and review email logs for the outside BCC address. - Record it. Update
last_verifiedand note the failed version in the inventory, so nobody re-approves it.
Health check. After rollback, list loaded MCP servers on one machine (claude mcp list in Claude Code) and confirm the blocked version is gone.
Is this realistic for a small team?
Illustrative example: a team with 20 approved artifacts that each change about once a month has around 20 re-checks a month. That is one a working day. Most are a diff review of a version bump. The weekly remote sweep is a script. The cost is real but small, and it falls on a calendar instead of in an incident.
The threat.cafe board records the version at which each documented MCP server or package turned malicious, which is the data the monthly sweep compares against.
Sources
- First malicious MCP server found stealing emails in rogue Postmark-MCP package — The Hacker News, 29 September 2025
- AST07 Update Drift — OWASP Agentic Skills Top 10 (incubator), accessed 15 October 2026
- OWASP Agentic Skills Top 10 — OWASP, accessed 15 October 2026
- axios compromised on npm: malicious versions drop remote access trojan — StepSecurity, March 2026
- An MCP server vetting checklist for enterprise security teams — Backslash Security, 8 October 2026
- Tools — Model Context Protocol specification 2025-11-25
- Skills for enterprise — Anthropic Claude Platform docs, accessed 15 October 2026
- Control MCP server access for your organization — Anthropic Claude Code docs, accessed 15 October 2026
- Extend Claude with skills — Anthropic Claude Code docs, accessed 15 October 2026
- MCP Inspector CLI README — modelcontextprotocol on GitHub, accessed 15 October 2026