Developer documentation
Marketplace
Last reviewed 31 August 2026
Marketplace and extensions
cain marketplace search redis cain marketplace inspect acme/redis-connector cain marketplace install acme/redis-connector
An extension is third-party code or configuration installed into the boundary
that decides whether your agent may act. That makes the install path the highest-
risk surface in the CLI, and it is built accordingly.
Read this before you install anything
CAIN does not sandbox extension code. A framework adapter or connector loaded
into your Python process runs with your process's privileges. No manifest
declaration changes that, and this page is not going to imply otherwise.
What is real: signature verification, permission disclosure, refusing scopes that
can never be safe, compatibility checks, and a lockfile that records exactly what
you accepted and why. Those are about *knowing what you ran and being able to
prove it later*. Isolating extension code is on the roadmap and is listed as
not-yet-done.
What install checks, in order
1. fetch metadata — nothing on disk yet
2. validate the manifest
3. check compatibility with your CAIN version and deployment capabilities
4. verify the signature — before any bytes are written
5. show permissions and require a grant
6. resolve dependencies, checked the same way
7. write the lockfile
Nothing touches your filesystem before step 4. Verifying after unpacking means
untrusted contents have already landed, which is where path traversal lives.
Refused outright, no flag overrides
- a malformed manifest
- an incompatible extension
- a scope that can never be granted (
credentials:read,enforcement:disable,
quota:override, tenant:switch, admin, credentials:write)
- a yanked version
- an unrecognised scope — refused rather than ignored, because an ignored scope
request is a permission nobody reviewed
Requires an explicit flag
| situation | flag | why |
| signature not verified | --allow-unsigned | recorded in the lockfile as a waiver |
| update adds a dangerous scope | --accept-new-scopes | the supply-chain case |
| permissions, non-interactive | --grant | CI must not accept permissions by default |
| paid extension | --accept-price |
Permissions
Sixteen coarse scopes, each with a one-line meaning. Deliberately coarse: a
fifty-scope model is one nobody reads, and an unread prompt is not consent.
Dangerous scopes are shown in full and separately. The combination worth
questioning is any read scope plus network:outbound — that is an exfiltration
path by construction. Legitimate for an evidence integration; a red flag for a
policy bundle.
$ cain marketplace inspect acme/example
Permissions requested:
! network:outbound Make outbound network requests. Combined with any read
scope, this is an exfiltration path.
policy:read Read your policy and its version.
Code versus data
requires_execution is derived from the extension type, never read from the
manifest — "is this code or data" is exactly the question a malicious publisher
would lie about.
| declarative (data) | executes (code) |
policy, security-rules | everything else |
Signing
A signature covers both the manifest digest and the artifact digest. That
binding matters:
- it cannot be lifted onto a manifest whose scopes were widened after review
- it cannot be reused for different code under a reviewed manifest
All three tamper vectors are covered by tests in tests/test_marketplace.py.
Publishers: register a public key on your handle and sign releases. Unsigned
releases can be published, but every installer must pass --allow-unsigned and
carry the waiver in their lockfile — so signing is how you get adopted.
cain marketplace publish --signature "$SIG" --artifact-sha256 "$SHA"
The lockfile
cain-extensions.lock. Commit it. It records, per extension: version, manifest
digest, artifact digest, whether the signature verified, **whether an unsigned
install was waived**, which scopes were granted, and when.
That last set is deliberately the awkward part. A convenient implementation would
omit installed_unsigned; a year later, "why does this project trust this
extension" needs an answer.
A corrupt lockfile raises rather than being replaced — overwriting it would erase
the record of what the project had accepted.
Versions are immutable
Republishing a version returns 409. A mutable version is a supply-chain attack
with extra steps: anyone who verified it yesterday has no way to know it changed
today.
Yanking is supported and does not delete. A yanked version stays in the
registry so existing lockfiles remain auditable, and install treats it as a
critical finding.
Publishing
cain marketplace init --type policy # edit cain-extension.yaml cain marketplace publish --dry-run # validate, publish nothing cain marketplace publish
A publisher handle is claimed once and bound to your tenant. Nobody else can
publish under it — without that, provenance means nothing. Handles like cain
and official are reserved.
Ask for the fewest scopes you can do the job with. A long list is a reason to be
refused.
Two independent security screens
The registry scans on publish; the CLI scans again on the machine about to run the
code. Both results are shown. A registry-side scan is a claim the registry makes,
and the installer should not have to take it on faith.
Both are static screens over the manifest. Neither reads or executes the
extension's code, and neither certifies safety.
Extension types
mcp-integration · framework-adapter · policy · connector · tool ·
actionproof-module · security-rules · evidence-integration ·
deployment-plugin · sdk-extension · enterprise-integration
Revenue
Free and paid listings are both supported. Platform share 20%, publisher 80%,
applied at charge time via a Stripe destination charge.
Payouts are not enabled today. They ride the platform's Stripe Connect
integration, and Connect is not switched on for the CAIN account — an account
setting, not missing code. GET /marketplace/payouts/status reports this, and
paid listings are marked payouts_available: false.
Publish a paid extension if you want; do not price work on revenue that cannot
reach you yet.
Ratings
One rating per tenant per extension, and you cannot rate your own. Both
constraints are needed for the number to mean anything. install_count counts
distinct tenants, not repeat installs — a counter a CI pipeline can inflate is a
vanity metric, and installers use it to judge whether they are early.
Commands
cain marketplace search [query] [--type T] [--free] [--limit N]
cain marketplace inspect <publisher/name> [--version V]
cain marketplace install <publisher/name> [--grant] [--allow-unsigned]
[--accept-new-scopes] [--accept-price]
cain marketplace update [name] [--check] [--allow-unsigned] [--accept-new-scopes]
cain marketplace publish [--dry-run] [--signature S] [--artifact-sha256 H]
cain marketplace init [--type T]
cain marketplace list
cain marketplace uninstall <publisher/name>
All accept --json.