Documentation
¶
Overview ¶
Package manifest defines the typed schema for a pilot app manifest.
The manifest is the *only* source of truth for an app's grants — the runtime never infers permissions from anywhere else. Pinned at install time and re-verified on every launch.
See ../docs/architecture/graph.json (node id: "manifest") for the canonical description; this file is the Go embodiment of that node.
Index ¶
Constants ¶
const SideloadMarkerName = ".sideloaded"
SideloadMarkerName is the sentinel file whose presence in an install directory flips an app into sideloaded mode. The pilotctl install command writes this when invoked with --local; absence is the default (catalogue install). Keeping it as a file (not a manifest field) means a tampered manifest can never claim "I'm a catalogue app" — the file's existence is the trust signal, and only the install command writes it.
const SideloadOSSandboxTODO = "TODO(sandbox): OS-level isolation not yet wired; sideload safety is manifest-gate only"
SideloadOSSandboxTODO is a marker constant referenced from places that need to remember sideload safety today is manifest-gate-only. When OS sandboxing lands (linux landlock + seccomp + net unshare, macOS sandbox-exec), search-and-remove this constant and update the CLI warning printed at install time.
Variables ¶
var KnownCaps = map[string]bool{ "fs.read": true, "fs.write": true, "fs.append": true, "fs.delete": true, "net.dial": true, "net.call": true, "ipc.call": true, "key.sign": true, "audit.log": true, "proc.exec": true, }
Known capability vocabulary. The runtime extends this list; manifests declaring unknown caps are rejected because the daemon wouldn't know how to broker them.
var KnownConditionKinds = map[string]bool{ "rate": true, "cap": true, "allowlist": true, "denylist": true, "time_window": true, "requires_user_consent": true, "requires_foreground": true, "signed_by": true, }
Known condition kinds.
KnownFlagTypes for Extension.AddsFlags.
Known protection levels.
var KnownRuntimes = map[string]bool{ "go": true, "bun": true, "node": true, "python": true, }
Known binary runtimes.
var SideloadAllowedCaps = map[string]struct{}{
"audit.log": {},
"fs.read": {},
"fs.write": {},
}
SideloadAllowedCaps is the closed set of capability strings a sideloaded manifest may declare. Order doesn't matter; membership is exact-match.
var TrustedPublishers []string
TrustedPublishers is the compile-time-embedded list of publisher ed25519 public keys ("ed25519:<base64>" or raw base64) that are trusted to sign manifests. Empty list = fail-closed (no publisher passes the trust-anchor check). Production builds MUST populate this list with the known-good publisher keys.
Functions ¶
func EnforceSideloadPolicy ¶ added in v1.0.1
EnforceSideloadPolicy checks a parsed manifest against the sideload allow-list. Returns nil iff every grant, extension, affiliate, and protection setting fits the policy. The error message lists the FIRST violation; callers should fix-and-retry rather than expecting a complete enumeration in one pass.
The check is strict by design: anything unrecognised is denied, so adding a new cap kind to the manifest schema doesn't silently widen the sideload surface. Allowing a new cap for sideloads is an intentional, reviewable change to SideloadAllowedCaps.
Types ¶
type Affiliate ¶
type Affiliate struct {
Pubkey string `json:"pubkey"`
Role string `json:"role"`
Purpose string `json:"purpose"`
}
Affiliate: a pilot-network endpoint that's co-trusted with this app (e.g. wallet's settlement notary). Calls between the app and an affiliate pass through without per-call user consent.
type Binary ¶
type Binary struct {
// Runtime: "go" | "bun" | "node" | "python"
Runtime string `json:"runtime"`
Path string `json:"path"`
// SHA256 is the lowercase-hex sha256 of the binary at Path.
SHA256 string `json:"sha256"`
}
Binary identifies the executable and what it must hash to.
type Condition ¶
type Condition struct {
// Leaf form:
// Kind: "rate" | "cap" | "allowlist" | "denylist" | "time_window" |
// "requires_user_consent" | "requires_foreground" | "signed_by"
Kind string `json:"kind,omitempty"`
Params map[string]interface{} `json:"params,omitempty"`
// Composite form:
Op string `json:"op,omitempty"` // "and" | "or" | "not"
Compose []Condition `json:"compose,omitempty"`
}
Condition is a predicate the daemon evaluates per request. Either Kind+Params (a leaf condition) OR Compose+Op (composite). Not both.
type Dependency ¶
Dependency: the calling app declares which methods of which other apps it expects to invoke. User reviews these at install time.
type Extension ¶
type Extension struct {
// Primitive names the hook point, e.g. "send-message.pre" or "recv.post".
Primitive string `json:"primitive"`
// Method is the IPC method name the daemon dispatches to when this
// hook fires. Must be present in the app's `exposes` list.
Method string `json:"method"`
// AddsFlags are CLI flags this hook contributes to pilotctl when the
// app is installed. Optional.
AddsFlags []FlagSpec `json:"adds_flags,omitempty"`
// Order determines chain position when multiple apps hook the same
// primitive. Lower runs earlier. Default 0.
Order int `json:"order,omitempty"`
}
Extension is one hook the app registers with the daemon's extend.Registry. The Primitive must be one of the runtime's known hook points (see pkg/extend KnownHookPoints — duplicated here as a small known-string set so pkg/manifest stays dep-free).
type FlagSpec ¶
type FlagSpec struct {
Name string `json:"name"` // "--paywall" — must start with "--"
Type string `json:"type"` // "string" | "bool" | "int"
Help string `json:"help,omitempty"`
}
FlagSpec describes one CLI flag an Extension contributes.
type Grant ¶
type Grant struct {
// Cap: "fs.read" | "fs.write" | "net.dial" | "net.call" | "ipc.call" |
// "key.sign" | "audit.log" | "proc.exec" | ...
Cap string `json:"cap"`
// Target: path pattern, host pattern, "<app>.<method>", sign-purpose, or
// (for proc.exec) the single executable the app may spawn — an absolute path
// or a bare command name.
Target string `json:"target"`
// Condition is optional. If absent, the grant is unconditional.
Condition *Condition `json:"if,omitempty"`
}
Grant is a (capability, target, condition?) triple. The runtime brokers every privileged op and grants are the only thing that authorizes them.
type Manifest ¶
type Manifest struct {
// Unique app identifier, reverse-DNS form (e.g. "io.pilot.wallet").
ID string `json:"id"`
// AppVersion is the publisher's semver — bumps freely on bug fixes and
// feature work, applied as silent binary swaps.
AppVersion string `json:"app_version"`
// ManifestVersion is a monotonic int that increments ONLY when the grants
// list, affiliates, or any other security-affecting field changes.
// Same ManifestVersion ⇒ silent update; bumped ⇒ explicit re-consent.
ManifestVersion int `json:"manifest_version"`
Binary Binary `json:"binary"`
Exposes []string `json:"exposes,omitempty"`
Grants []Grant `json:"grants"`
// Protection: "shareable" (default) or "guarded" (encrypted volume +
// restricted process namespace).
Protection string `json:"protection,omitempty"`
Store Store `json:"store"`
Affiliates []Affiliate `json:"affiliates,omitempty"`
Depends []Dependency `json:"depends,omitempty"`
// Extends is the set of daemon-primitive hook points this app
// participates in. Each entry says: at this primitive (e.g.
// "send-message.pre"), call my Method via IPC; the daemon's
// extend.Registry threads HookArgs through the chain. AddsFlags
// contribute to pilotctl's CLI surface for that primitive when
// this app is installed.
Extends []Extension `json:"extends,omitempty"`
// DynamicExtends is the set of hook points this app may register
// against at runtime via the daemon's extend.register IPC. Empty
// (or absent) means no runtime registration is allowed. This is
// the user-visible bound on dynamic behavior — the user reviews
// this list at install/upgrade, same as Grants.
DynamicExtends []string `json:"dynamic_extends,omitempty"`
}
Manifest is the signed declaration of what an app is and what it's allowed to do.
func Parse ¶
Parse decodes a manifest from JSON bytes. Does not validate; call Validate after Parse for the policy-level checks.
func (*Manifest) ExposesMethod ¶ added in v1.0.1
ExposesMethod reports whether method appears in the manifest's Exposes list. Exposes is the app's entire broker surface: the daemon's app-store broker dispatches a method into an app only if the app explicitly exposes it. An empty Exposes list therefore means "no broker-callable methods" — fail-closed.
func (*Manifest) HasGrant ¶ added in v1.0.1
HasGrant reports whether the manifest declares a grant whose capability equals capName and whose target matches the requested target. Used by the broker to authorize cross-app ipc.call dispatch: the calling app must declare an `ipc.call` grant targeting the specific "<app>.<method>" it wants to reach.
Target matching supports three forms, in order of generality:
- "*" matches any target (blanket grant)
- "<prefix>.*" matches any target sharing the prefix (e.g. "io.pilot.wallet.*" matches "io.pilot.wallet.pay")
- exact the grant target equals the requested target verbatim
Conditions on the grant are NOT evaluated here — HasGrant answers only "is this capability+target declared". Per-request condition evaluation (rate, consent, time-window, …) is a separate, later gate.
func (*Manifest) Marshal ¶
Marshal serializes the manifest as deterministic JSON (sorted keys). Use this for signing inputs — the signature must be over a canonical form.
func (*Manifest) Validate ¶
Validate checks a manifest against the schema rules. Returns ALL errors found (not just the first), so callers can fix them in one pass.
func (*Manifest) VerifySignature ¶
VerifySignature checks that Store.Signature is a valid ed25519 signature over the signing payload, verified against the Store.Publisher key embedded in the manifest. This provides cryptographic integrity — tampering with any manifest field that feeds the signing payload (Publisher, ID, ManifestVersion, Binary.SHA256, Grants) will cause verification to fail.
IMPORTANT: This does NOT check that Store.Publisher is a trusted key. Callers MUST also call VerifyTrustAnchor() after VerifySignature() to confirm the publisher is on the TrustedPublishers list.
func (*Manifest) VerifyTrustAnchor ¶ added in v1.0.1
VerifyTrustAnchor checks that Store.Publisher is on the trusted publishers list. Without this check, VerifySignature only confirms the manifest was signed by whoever claims to be the publisher; VerifyTrustAnchor confirms the publisher itself is known and trusted.
Returns nil if Store.Publisher is in TrustedPublishers. Returns an error if TrustedPublishers is empty (fail-closed) or if the publisher is not found.