Developer documentation
Marketplace
Last reviewed 31 August 2026
All docs
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.