Skill and plugin integrity (supply-chain lock)
このコンテンツはまだ日本語訳がありません。
Skills (SKILL.md folders) and plugins (extension packages) are code and instructions the agent
runs with. Kurahi pins the exact content an operator approved and checks it again every time the
loader is about to read it. An unpinned or changed skill or plugin is refused (enforce) or loaded
with a warning (warn).
The lock
Section titled “The lock”<KOLO_HOME>/integrity/supply-chain.lock.json holds one entry per approved artifact, keyed by
skill:<name> or plugin:<id>:
{ "kind": "skill", "name": "weather", "path": "/home/me/.kolo/workspace/skills/weather", "sha256": "4bacdd590d00…", "files": 3, "bytes": 2210, "pinnedAt": "2026-10-22T14:00:00.000Z", "scan": { "findings": ["pipe-to-shell"], "forced": true }}- Digest (
src/integrity/supply-chain-hash.ts): SHA-256 over one line per regular file, sorted by relative path:<relPath>\0<sha256 of the file>\n.node_modulesand.gitare skipped at any depth. Symlinks are followed. A one-file plugin digests as that file; a package plugin as its package folder. File modes, timestamps and empty folders are not included. - Signature: the lock is signed with the local owner key (
<KOLO_HOME>/secrets/policy-owner.json, the same Ed25519 key andpolicy/owners.jsonas the policy attestation) under its own signing domain, intosupply-chain.lock.json.sig. A lock signature can never pass as a policy signature, and the reverse is also true. - Trust: signed by a pinned owner: trusted. No owner pinned at all: trusted, but reported as
unsigned. Owners are pinned but the signature is missing, invalid or from another key: untrusted. Enforce then refuses every non-bundled skill and plugin until the operator re-pins with--all. - Write protection: the lock is under
<KOLO_HOME>/integrity, which the integrity write policy already denies to agent tools.kurahi skills pinandkurahi plugins pinare refused when an agent runs them throughexec(src/integrity/write-policy.ts).
integrity.supplyChain.mode:
| Mode | Unpinned, modified, unreadable, or lock untrusted |
|---|---|
enforce (new installs) |
not loaded; gateway log warning; audit supply-chain.refused (denied) |
warn (fallback, unset) |
loaded; log warning once per process; a modified artifact or untrusted lock also audits supply-chain.unverified |
off |
no check |
Where the check runs:
- Skills: in the skill loader (
src/agents/skills/workspace.ts→src/agents/skills/integrity.ts), on the merged set (workspace, managed,skills.load.extraDirs, plugin-provided skill folders) before anySKILL.mdreaches a prompt, a snapshot or the sandbox copy. A refused skill is dropped. It does not fall back to a lower-precedence skill with the same name. - Plugins: in
loadPluginCandidate(src/plugins/loader.ts), after the enable checks and before the module is imported. A refused plugin is quarantined with the reason, like a plugin that fails to load. The same check runs on hot reload.
Exempt: bundled skills and bundled plugins, which ship inside the runtime package and have the same trust as its code. A byte-identical copy of a bundled skill is also exempt (the sandbox workspace holds copies).
Pinning (review and approve)
Section titled “Pinning (review and approve)”kurahi skills verify # status of every skill and plugin against the lockkurahi skills pin weather # scan, then pin one skillkurahi skills pin --all # every non-bundled skill; drops entries that no longer existkurahi plugins pin my-pluginkurahi plugins pin --allkurahi skills pin risky --force # pin even though the scan flagged it (recorded in the lock)--agent <id> reads that agent’s workspace instead of the default agent’s workspace. --json
prints machine-readable output. Every pin writes a supply-chain.pin audit row. After
kurahi plugins install, the plugin is not approved yet: pin it after you have reviewed it.
Pin-time static scan
Section titled “Pin-time static scan”Before pinning, src/integrity/supply-chain-scan.ts checks every text file (up to 1 MB each,
binary files are skipped) for:
| Rule | Looks for |
|---|---|
pipe-to-shell |
curl/wget piped into sh, bash, zsh, … |
base64-exec |
base64 -d … | sh, eval(atob(…)), exec(b64decode(…)) |
shell-rc-write |
>>, > or tee into .bashrc, .zshrc, .profile, … |
credential-path |
~/.ssh, ~/.aws, .aws/credentials, ~/.gnupg, ~/.kube, id_rsa, … |
reverse-shell |
/dev/tcp/, nc -e |
A finding blocks the pin and shows the file, line and text. Pass --force after reading it if
the content is intended. This is a tripwire for obvious patterns, not a malware scanner.
Obfuscated, split or binary payloads, prompt-injection text and malicious logic that looks
ordinary all pass it. A clean scan proves nothing. The review is still the operator’s job.
Existing installations
Section titled “Existing installations”A config without integrity.supplyChain.mode behaves as warn, and it is reported by
kurahi doctor (Secure defaults), kurahi doctor --json (secure-defaults.supply-chain,
supply-chain.lock) and the gateway startup log. To move to enforce:
kurahi skills verifykurahi skills pin --allkurahi plugins pin --allkurahi config set integrity.supplyChain.mode enforceWhat it does not cover
Section titled “What it does not cover”- Bundled code is not hashed. Its integrity is the integrity of the installed package.
- Plugin dependencies in
node_modulesare not in the digest. A one-file plugin’s digest covers only that file, not the sibling files it imports. - Same-user attacker: anyone who can write
<KOLO_HOME>as the operator (including the owner key insecrets/) can re-pin. The lock defends against content changing under a pinned name (agent writes to workspace skills, a package update, a swapped folder), not against the operator’s own account being compromised. - One namespace: entries are keyed by name. Two agents with different workspace skills of the same name cannot both be pinned.
- Time-of-check: the check runs when the loader reads the skill or imports the plugin. A file changed after that is caught on the next load or reload, not mid-run. The digest cache uses size, mtime, ctime and inode, and never caches a tree changed in the last 3 seconds.
- Not a provenance check: there is no publisher signature or registry attestation. A pin records “the operator approved these bytes”, not who wrote them.