- Generating a publisher keypair.
- Signing a manifest with
feral app sign. - Installing it via the API or CLI (
--allow-unsignedfor local dev). - Verifying a signed manifest from the command line.
Trust model in one paragraph
Every install path goes throughferal_core.agents.app_registry.install_app.
That method requires a manifest.signed.json envelope sitting next to
manifest.json. The envelope is an
Ed25519 signature over the
canonical JSON of the manifest, plus the publisher’s public key and a
key id that the host pins to a row in the
publisher_keys namespace of the local vault. If the manifest is
unsigned, tampered, or signed by a key the host doesn’t trust,
installation raises UnverifiedManifestError (HTTP 422 from
POST /api/apps/preview, which is where the check now runs; the install
call spends a token the preview minted and cannot be reached without
one).
The only escape hatch is the explicit allow_unsigned=True flag (CLI
--allow-unsigned, API unsigned: true). Choosing it writes an
audit_log entry tagged unsigned_install so the supervisor can
trace which device admitted which untrusted bundle.
1. Generate a keypair
BlindVault.put_namespace("publisher_keys", key_id, public_key_b64)
internally; admins can also add keys through the API once that ships.
2. Sign your manifest
--private-key flag: signing uses your publisher key at
~/.feral/publisher.key, created on demand, which is the same keypair
the registry authenticates publishes with. One keypair per publisher,
not one per signing surface.
The CLI calls manifest_signing.sign(manifest_dict, private_key, key_id=...)
under the hood and emits a SignedManifest envelope:
3. Install
Installing is two calls. An app may declareskill_dependencies, and a
skill runs Python inside the brain, so the install has to say what code
it brings before it brings it. See
Marketplace → Installing a GenUI app.
Production / signed
AppRegistry._verify_source through inspect_app: it
finds manifest.signed.json, calls verify, and applies the permissions
policy. install_app runs the same method, so the two cannot disagree.
On failure you get a 422 with an error envelope of:
install_token is refused with 403
preview_required, whatever the source. The token is single-use, bound
to the staged bytes and to the source that produced them, and expires
after five minutes.
Local dev / unsigned (escape hatch)
unsigned: true goes on the preview, because that is where
verification happens. The token it mints carries the decision forward.
unsigned_install to the audit log when the install
that follows is performed.
4. Verify a manifest
feral app verify immediately after
feral app sign so that a fat-fingered key id never escapes the build.
High-trust permissions
If your manifest requestspermissions.network = ["*"], install will
refuse it unless all of the following hold:
- The manifest is signed and verified (i.e.
allow_unsigned=False). - The manifest carries a non-empty
permissions.justificationstring. - The installer passes
user_high_trust=True(CLI flag / API field).
feral_core.genui.permissions_policy.enforce_install_policy.