Skills injected into actor context are a prompt injection vector. mallcop's trust web cryptographically verifies that skills come from authors you've explicitly trusted before loading them. No trust setup needed for built-in skills — the root key ships with mallcop.
Skills are markdown injected into LLM context. A malicious skill — one crafted to redirect an investigation actor, suppress findings, or exfiltrate information through tool calls — looks identical to a legitimate one until the model acts on it.
Two incidents illustrate the risk:
mallcop's response: any skill loaded into actor context must be traceable to a key you've designated as trusted. If no trust path exists, the skill is rejected.
A trust anchor is a public key you've declared as the root of trust. mallcop ships with one anchor pre-loaded: the mallcop root key, which covers all built-in skills. Built-in skills just work — no configuration needed.
To add your own anchor (for org-signed skills or third-party skill authors):
mallcop trust add-anchor security-team@example.com ./security-team.pub
Anchors are stored in .mallcop/trust/anchors in your deployment repo.
Each line is: identity keytype base64. Anchors are the only keys that
don't need to be endorsed by something else — you add them directly, which means
you are making an explicit trust decision.
To add a key to the keyring without making it an anchor (i.e., it needs to be endorsed before its skills are trusted):
mallcop trust add-key alice@example.com ./alice.pub
An endorsement says: "I (the endorser) vouch for this identity, for skills matching this scope, at this trust level, until this date."
# Endorse alice for all AWS skills until end of year mallcop trust endorse alice@example.com \ --scope "aws-*" \ --level author \ --expires 2026-12-31 \ --reason "Alice owns AWS security runbooks" \ --key ~/.ssh/id_ed25519 # Endorse the security-team identity to re-delegate trust (full level) mallcop trust endorse security-team@example.com \ --scope "*" \ --level full \ --expires 2026-12-31 \ --key ~/.ssh/id_ed25519
| Level | Can sign skills | Can endorse others |
|---|---|---|
author | Yes | No — terminal node in the chain |
full | Yes | Yes — can act as intermediary, endorsing other identities |
Scope is a glob pattern matched against skill names. It constrains what a given endorsement covers, regardless of what the endorsed identity signs.
| Pattern | Matches |
|---|---|
* | All skills |
aws-* | aws-iam, aws-networking, etc. |
privilege-analysis | Exactly that skill name |
Scope narrows at each hop in the chain — an intermediary with aws-* scope
cannot endorse someone for skills outside that scope.
Every endorsement has a required expiry date (YYYY-MM-DD). Expired
endorsements are silently excluded from chain traversal. A chain that was valid yesterday
may not be valid today if an intermediate endorsement expired. Re-endorse before expiry
to maintain the chain.
When mallcop needs to load a skill authored by a given identity, it performs a BFS from all anchors through the endorsement graph to find a valid path to that identity.
Chain rules:
trust_level=fullfull or author
Example chain: mallcop-root → security-team@example.com → alice@example.com
full level with scope=*author level with scope=aws-*aws-iam skill — the chain validatesgcp-iam — rejected: her endorsement scope is aws-*To inspect the chain for a given identity and skill:
mallcop trust chain alice@example.com --skill aws-iam
skills.lock is a YAML file recording the SHA-256 hash of every installed
skill's content. At startup, mallcop verifies each skill against its recorded hash.
If the hash doesn't match — skill was modified, file was added or removed — mallcop
refuses to load it.
This is the fail-closed property: a skill not in the lockfile is not loaded. A skill whose content changed since the lockfile was generated is not loaded. There is no "trust but verify" mode — verification failure means rejection.
# After adding or updating skills, regenerate the lockfile mallcop skill lock # Commit it to your deployment repo git add skills.lock && git commit -m "update skills.lock"
The lockfile format:
version: 1
skills:
aws-iam:
author: mallcop@mallcop.app
expires: null
sha256: a3f9c2...
source: builtin
trust_chain: null
verified_at: "2026-03-14T10:00:00+00:00"
privilege-analysis:
author: mallcop@mallcop.app
...
How to set up trust for a team writing custom skills. Takes about ten minutes.
ssh-keygen -t ed25519 -C "security-team@example.com" -f ./security-team-key # Produces: security-team-key (private) + security-team-key.pub (public)
mallcop trust add-anchor security-team@example.com ./security-team-key.pub
If contributors will sign their own skills, add their public keys and endorse them from the anchor:
mallcop trust add-key alice@example.com ./alice.pub
mallcop trust endorse alice@example.com \
--scope "aws-*" \
--level author \
--expires 2026-12-31 \
--key ./security-team-key
Skip this step if contributors submit skills for the security-team key to sign.
# Sign with the anchor key (or the contributor's key if they're endorsed) mallcop skill sign skills/aws-networking --key ./security-team-key
mallcop skill lock
git add .mallcop/trust/ skills/ skills.lock
git commit -m "add trust setup and sign custom skills"
If you don't configure any trust anchors, mallcop loads only built-in skills (signed with the mallcop root key, which is pre-loaded). No custom skills, no third-party skills. This is the safe default — the system works, and you haven't opened any trust surface you haven't explicitly approved.
Trust setup is only needed when you want to load skills beyond the built-in set.
mallcop trust add-anchor IDENTITY PUBKEY_FILE
Adds IDENTITY as a trust anchor. PUBKEY_FILE is an SSH public key file.
Anchors are written to .mallcop/trust/anchors. No-ops if IDENTITY
is already present.
mallcop trust add-key IDENTITY PUBKEY_FILE
Adds IDENTITY to the keyring without making them an anchor. They still need to be endorsed before their skills are trusted.
mallcop trust endorse IDENTITY \
--scope GLOB \
--level [full|author] \
--expires YYYY-MM-DD \
--key PRIVATE_KEY_FILE \
[--reason TEXT] \
[--identity ENDORSER_IDENTITY]
Creates a signed .endorse + .endorse.sig pair in
.mallcop/trust/endorsements/. Endorser identity defaults to
the comment field of the corresponding .pub file.
mallcop trust chain IDENTITY [--skill SKILL_NAME]
Shows the trust path from any anchor to IDENTITY for a given skill name
(default: *). Exits non-zero if no path exists.
{"status": "ok", "path": ["mallcop@mallcop.app", "security-team@example.com", "alice@example.com"], "skill": "aws-iam"}
mallcop trust list
Prints the full trust state: all anchors, keyring entries, and endorsements with their scope, level, and expiry. Useful for auditing the current trust configuration.
mallcop trust --trust-dir /path/to/trust SUBCOMMAND
All mallcop trust subcommands accept --trust-dir to override
the default trust directory (.mallcop/trust in the current working directory).