The tool you approved is not the tool you are running.
opencode-integrity locks what OpenCode can load — plugin content, MCP tool
descriptions and schemas, and skill files — and detects when any of them
change. No proxy, no config rewrite, no cloud. Local, zero runtime
dependencies, OpenCode V1 and V2.
audit → see plugins, MCP tools and skills with capabilities and hashes
lock → write opencode.lock.json (commit it)
check → fail CI when anything drifts
runtime→ warn or deny changed MCP tools and skills during a session
An MCP tool description is not inert data: it is instructions the model reads.
A server can serve one toolset today and a different one tomorrow. Skills are
Markdown files that can be edited after review. Plugins are npm/Git code that
runs in the OpenCode server with the agent's privileges. OpenCode policies
(plugin.use, mcp.use, skill.use) only match names; they do not verify
content.
Existing MCP scanners are on-demand CLIs, and the strongest pinning tool
(mcp-pin) is a stdio proxy for other clients. This is the OpenCode-native
layer: it sees what OpenCode actually loaded, including remote MCP servers and
Code Mode, and covers plugins and skills too.
The V2 runtime requires OpenCode 2.2.1 or newer and is tested on 2.2.7; the V1 adapter requires 1.19.5 or newer.
{
"plugin": [
["@hamzaabili/opencode-integrity", { "mode": "warn" }]
]
}V1 can hash project plugins, MCP configuration and skills, and in enforce
mode it blocks changed MCP configuration and skills. The V1 API does not expose
live MCP tool descriptors, so tool-level drift is V2-only.
# 1. See what is loaded, without a lockfile
npx @hamzaabili/opencode-integrity audit
# 2. Inside OpenCode, capture the live MCP tool definitions
/integrity lock
# 3. Commit the lockfile
git add opencode.lock.json
# 4. In CI
npx @hamzaabili/opencode-integrity check --format sarif/integrity lock is important: only the running OpenCode instance can see the
tool descriptions and schemas returned by MCP servers (local and remote, with
or without Code Mode). The CLI locks project plugins, MCP configuration and
skills; it preserves runtime tool hashes when the server configuration did not
change.
| Asset | Fields |
|---|---|
| Plugin | package.json, every package file (capped at 5000 files / 50 MiB), version, source, detected capabilities |
| MCP server | transport, command, URL, cwd, environment/header names and redacted values, codemode |
| MCP tool | name, description, input/output schema, annotations, suspicious-instruction scan |
| Skill | full SKILL.md content, size, suspicious-instruction scan |
Secret-looking environment values and headers (TOKEN, KEY, SECRET,
PASSWORD, AUTHORIZATION, COOKIE, CREDENTIAL) are replaced with
[redacted] before hashing, so rotating a token does not create drift. Their
names still change the hash.
| Mode | Behavior |
|---|---|
warn (default) |
Inject a drift report into the session and log it; nothing is blocked. |
enforce |
Disable MCP servers whose configuration changed, remove changed skills, and deny changed MCP tools and skills before execution. |
enforce is fail-closed for asset drift, not a sandbox. A plugin that is
already loaded cannot be unloaded by a plugin hook; review plugin changes with
the lock diff and restart OpenCode. MCP and skill transforms are applied at
startup, so after /integrity lock in an existing session, reload to re-enable
previously quarantined assets.
npx @hamzaabili/opencode-integrity audit [--json] [--strict]
npx @hamzaabili/opencode-integrity lock
npx @hamzaabili/opencode-integrity check [--format text|json|sarif]
npx @hamzaabili/opencode-integrity diff
npx @hamzaabili/opencode-integrity policy [--deny-unlisted] [--include plugins,mcp,skills]checkexits1on drift or when no lockfile exists.- CLI
checkverifies project files/configuration; live MCP tool descriptors are only verifiable by the running V2 adapter. Use/integrity checkor theintegrity_statustool inside OpenCode for the full check. audit --strictexits1on drift or an unlocked project.policyemits native OpenCodemcp.use,skill.useandplugin.usestatements from the lockfile. Policies default to allow, so allow statements alone restrict nothing.--deny-unlistedprependsdeny *for each included class, producing a real allowlist; review it because it also blocks global or newly added assets. Aplugin.usedeny keeps@hamzaabili/opencode-integrityallowed by package name so the runtime plugin is not blocked by its own policy; an absolute-path install needs an explicit allow. Local.//../plugin targets are resolved against the config file directory, like OpenCode does. OpenCode's built-inopencodeandreportskills are not in the lock, soskill.use deny *also hides them; add explicit allows if you need them.--includeapplies topolicyonly. It is not a hash check; pair it withcheck/runtime enforcement.
| Option | Default | Meaning |
|---|---|---|
lockfile |
<project>/opencode.lock.json |
Lockfile path. |
mode |
"warn" |
warn or enforce. |
include |
["plugins","mcp","skills"] |
Asset classes to inspect. |
ignore |
[] |
Asset ids or globs to ignore, e.g. mcp:local-dev, skill:experimental-*. |
includeGlobal |
false |
Include global MCP config and, at runtime, global plugins/skills. Project-scoped assets are the default so a committed lock stays portable. |
trace |
unset | Reserved for redacted diagnostics. |
audit reports heuristic capabilities found in plugin entry code (network,
filesystem, process, environment, dynamic-code) and flags zero-width
characters, bidi controls, HTML comments, “ignore previous instructions”,
“do not tell”, and exfiltration language in tool descriptions and skills.
These are signals, not proof. They are deliberately deterministic and offline; a clean scan does not make a plugin safe.
| opencode-integrity | mcp-pin | mcp-scan / Cisco | Socket/npm audit | |
|---|---|---|---|---|
| OpenCode-native runtime | yes | no (proxy) | no (on-demand) | no |
| MCP tool descriptor pinning | yes | yes (stdio only) | scan only | no |
| Remote MCP | yes | no | yes | no |
| Plugins and skills | yes | no | no | packages only |
| Runtime deny/quarantine | yes | proxy block | no | no |
| Policy/SARIF output | yes | no | partial | no |
| Zero runtime deps | yes | yes | varies | varies |
The lockfile is a review artifact, not a signature. It catches changes between
the version you reviewed and the version that runs; it does not prove that the
reviewed version was safe. Use it with code review, OpenCode permissions,
opencode-sandbox, and least-privilege MCP servers.
Read SECURITY.md before enabling enforce on a shared machine.
- Bugs: open an issue
with
audit --jsonand the relevant lock diff (redact secrets). - Questions: Discussions.
- Security: private advisory, never a public issue.
bun install
bun run ci # biome + tsc + tests + build + smokeMIT. Zero runtime dependencies.
{ "$schema": "https://opencode.ai/config.json", "plugins": [ { "package": "@hamzaabili/opencode-integrity", "options": { "mode": "warn" } } ] }