manifest

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 23, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

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

View Source
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.

View Source
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

View Source
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.

View Source
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.

View Source
var KnownFlagTypes = map[string]bool{
	"string": true,
	"bool":   true,
	"int":    true,
}

KnownFlagTypes for Extension.AddsFlags.

View Source
var KnownProtections = map[string]bool{
	"":          true,
	"shareable": true,
	"guarded":   true,
}

Known protection levels.

View Source
var KnownRuntimes = map[string]bool{
	"go":     true,
	"bun":    true,
	"node":   true,
	"python": true,
}

Known binary runtimes.

View Source
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.

View Source
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

func EnforceSideloadPolicy(m *Manifest) error

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

type Dependency struct {
	App     string   `json:"app"`
	Methods []string `json:"methods"`
}

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

func Parse(data []byte) (*Manifest, error)

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

func (m *Manifest) ExposesMethod(method string) bool

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

func (m *Manifest) HasGrant(capName, target string) bool

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

func (m *Manifest) Marshal() ([]byte, error)

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

func (m *Manifest) Validate() []error

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

func (m *Manifest) VerifySignature() error

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

func (m *Manifest) VerifyTrustAnchor() error

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.

type Store

type Store struct {
	// Publisher pubkey, base64 ed25519 ("ed25519:<base64>").
	Publisher string `json:"publisher"`
	// Signature: store's signature over (id || manifest_version || binary.sha256 || grants-hash).
	Signature string `json:"signature"`
}

Store: the signature-chain anchor.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL