profile

package
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Jul 8, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package profile defines the declarative profile schema and its loader. A profile is a named security contract for a task, resolved fresh on every invocation and never written back to disk. Profiles may be authored in TOML, YAML, or JSON; the struct tags carry identical toml and json keys so field spellings match across formats.

Index

Constants

View Source
const (
	// SeverityError marks a problem that should fail validation. In strict
	// runtime terms these are fail-closed: the profile will not behave as its
	// author intends (or will refuse to run) if shipped as-is.
	SeverityError = "error"
	// SeverityWarn marks a likely footgun that still lets the profile run.
	SeverityWarn = "warn"
)

Severity levels a Finding can carry.

View Source
const SignatureFormat = "runeward.profile.sig.v1"

SignatureFormat is the schema tag written into detached signature files so a reader can tell a runeward profile signature apart from other JSON.

Variables

View Source
var ErrNotFound = errors.New("profile not found")

ErrNotFound is returned when a profile name cannot be resolved.

Functions

func List

func List(opts Options) ([]string, error)

List returns the names of all profiles on the search path, de-duplicated with earlier tiers shadowing later ones.

func Print

func Print(w io.Writer, p *Profile)

Print renders a human-readable view of the resolved profile. Secret env values are never shown, only their source kind.

func Sign

func Sign(content []byte, priv ed25519.PrivateKey) []byte

Sign returns the raw ed25519 signature over the exact profile bytes.

func Verify

func Verify(content, sig []byte, pub ed25519.PublicKey) error

Verify returns nil when sig is a valid ed25519 signature over content for pub, and an error otherwise.

Types

type Audit

type Audit struct {
	// Sink is a path or URI for the append-only ledger.
	Sink string `toml:"sink" json:"sink"`
	// Redact stores hashes instead of sensitive payloads. Defaults to true.
	Redact *bool `toml:"redact" json:"redact"`
	// ScrubPatterns appends operator-defined regexes to built-in secret scrubbing.
	ScrubPatterns []string `toml:"scrub_patterns" json:"scrub_patterns"`
}

Audit configures the tamper-evident ledger sink and redaction policy.

func (Audit) RedactEnabled

func (a Audit) RedactEnabled() bool

RedactEnabled reports the effective redaction setting (defaults to true).

type CELRule

type CELRule struct {
	Expr    string  `toml:"expr" json:"expr"`
	Verdict Verdict `toml:"verdict" json:"verdict"`
	Reason  string  `toml:"reason" json:"reason"`
}

CELRule is one rule of a CEL policy. Expr is a boolean expression over `tool` and `arg` (both strings); the first true Expr renders its Verdict.

type EnvVar

type EnvVar struct {
	Name string `toml:"name" json:"name"`
	// Value is a literal.
	Value string `toml:"value" json:"value"`
	// File reads the value from a path on the operator's machine.
	File string `toml:"file" json:"file"`
	// Op is a secret-manager scheme reference resolved at sandbox creation.
	// Supported: env://NAME, vault://<mount>/<path>#<field>, and op://... (the
	// latter is not built in and always fails closed). The TOML key stays "op"
	// for backward compatibility.
	Op string `toml:"op" json:"op"`
	// SecretFlag marks a literal Value as sensitive so it is redacted from the
	// ledger and API output. File/Op sources are always treated as secret.
	SecretFlag bool `toml:"secret" json:"secret"`
}

EnvVar is a single environment value resolved fresh per invocation. Value, File, and Op are mutually exclusive; set exactly one.

func (EnvVar) Secret

func (e EnvVar) Secret() bool

Secret reports whether this env value carries a sensitive source or is explicitly flagged secret.

type File

type File struct {
	Path string `toml:"path" json:"path"`
	Mode string `toml:"mode" json:"mode"`
	// File is the source path on the operator's machine; Content is the inline
	// alternative.
	File    string `toml:"file" json:"file"`
	Content string `toml:"content" json:"content"`
}

File is projected into the sandbox, owned root:root at Mode, streamed in without touching host disk.

type Finding

type Finding struct {
	Severity string
	Field    string
	Message  string
}

Finding is a single lint result. Field is a dotted/indexed path into the profile (e.g. "host.image", "env[2].op", "policy[1]") so the operator can locate the offending stanza; Message explains the problem.

func Lint

func Lint(p *Profile) []Finding

Lint statically inspects a resolved profile and returns findings in a stable, deterministic order (top-level fields first, then env, network, and policy stanzas in declaration order). It performs no network I/O; the only side effect is stat-ing local paths referenced by the profile (env files and host.copy_from) to warn about ones that are missing.

Lint is intentionally decoupled from Profile.Validate: Validate enforces the hard invariants that make a profile parseable, while Lint surfaces the softer "this probably isn't what you meant" problems on top of an already-valid profile. Because Lint operates on the struct directly it can also be run on profiles built in memory (e.g. tests) that never touched disk.

type Fleet

type Fleet struct {
	Replicas int `toml:"replicas" json:"replicas"`
	// TaskBoard optionally seeds task identifiers to distribute.
	TaskBoard []string `toml:"task_board" json:"task_board"`
}

Fleet spawns N sandboxes from the same contract sharing a task board and artifact volume.

type Host

type Host struct {
	Type    HostType `toml:"type" json:"type"`
	Name    string   `toml:"name" json:"name"`
	Image   string   `toml:"image" json:"image"`
	User    string   `toml:"user" json:"user"`
	Workdir string   `toml:"workdir" json:"workdir"`
	// CopyFrom is a local directory copied into the workspace at creation.
	// One-time copy, never a mount; supports "~/". The image must have tar.
	CopyFrom string `toml:"copy_from" json:"copy_from"`
	// Runtime is a backend hint ("docker", "podman").
	Runtime string `toml:"runtime" json:"runtime"`
	// RuntimeClass selects a sandboxed runtime for VM-grade isolation. On k8s
	// it maps to runtimeClassName (e.g. "gvisor", "kata"); on the docker
	// backend it maps to `docker run --runtime` (e.g. "runsc", "kata-runtime").
	// The runtime must be registered with the engine or creation fails.
	RuntimeClass string `toml:"runtime_class" json:"runtime_class"`
	// ReadOnly mounts the container root filesystem read-only (with a writable
	// tmpfs at /tmp and the writable workspace). Hardens against tampering with
	// the image; workloads that write outside /workspace or /tmp must opt out.
	ReadOnly bool `toml:"read_only" json:"read_only"`
	// Seccomp selects a seccomp profile. On Docker it is a path passed to
	// `--security-opt seccomp=<path>`; on Kubernetes a path makes the pod use a
	// Localhost profile, otherwise the pod defaults to RuntimeDefault.
	Seccomp string `toml:"seccomp" json:"seccomp"`
	// AppArmor selects an AppArmor profile name (Docker: `--security-opt
	// apparmor=<name>`; Kubernetes: the container AppArmor profile).
	AppArmor string `toml:"apparmor" json:"apparmor"`
}

Host declares where and how a session runs.

type HostType

type HostType string

HostType selects the execution backend a profile runs on.

const (
	// HostContainer runs the sandbox as a Docker/Podman container (the default).
	HostContainer HostType = "container"
	// HostK8s runs the sandbox as a Kubernetes Sandbox custom resource.
	HostK8s HostType = "k8s"
)

type Limits

type Limits struct {
	// WallClock is a duration string, e.g. "30m".
	WallClock string `toml:"wall_clock" json:"wall_clock"`
	MaxExecs  int    `toml:"max_execs" json:"max_execs"`
	// Memory caps container memory, e.g. "512m" or "2g" (empty = backend default).
	Memory string `toml:"memory" json:"memory"`
	// CPUs caps CPU cores, e.g. 1.5 (zero = backend default).
	CPUs float64 `toml:"cpus" json:"cpus"`
	// EgressRequests caps outbound requests through the proxy.
	EgressRequests int `toml:"egress_requests" json:"egress_requests"`
	// LoopWindow/LoopThreshold kill a session that repeats >= LoopThreshold
	// near-identical failing actions within LoopWindow.
	LoopWindow    string `toml:"loop_window" json:"loop_window"`
	LoopThreshold int    `toml:"loop_threshold" json:"loop_threshold"`
	// MaxTokens caps the cumulative model tokens a sandbox may spend (as
	// reported via the usage API). Zero means unlimited. Once exceeded, further
	// governed tool calls are denied.
	MaxTokens int64 `toml:"max_tokens" json:"max_tokens"`
	// MaxCostUSD caps cumulative reported spend in US dollars. Zero means
	// unlimited. Once exceeded, further governed tool calls are denied.
	MaxCostUSD float64 `toml:"max_cost_usd" json:"max_cost_usd"`
}

Limits declares the cost and loop guardrails for a session. Zero/empty values mean unlimited (subject to the backend's secure-by-default ceilings).

type Network

type Network struct {
	Default string        `toml:"default" json:"default"`
	Rules   []NetworkRule `toml:"rule" json:"rule"`
	// Enforce: "" uses the cooperative HTTP(S)_PROXY env (bypassable);
	// "strict" (alias "l3") adds kernel-level iptables redirection on the k8s
	// backend. Ignored by the docker backend.
	Enforce string `toml:"enforce" json:"enforce"`
}

Network is the declarative egress/ingress policy. An empty [network] block means fully open; Default = "deny" enables the allowlist.

func (Network) DenyByDefault

func (n Network) DenyByDefault() bool

DenyByDefault reports whether unmatched egress should be blocked.

func (Network) StrictEgress

func (n Network) StrictEgress() bool

StrictEgress reports whether L3 (kernel-level) egress enforcement is requested.

type NetworkRule

type NetworkRule struct {
	Verdict  string `toml:"verdict" json:"verdict"`
	Hostname string `toml:"hostname" json:"hostname"`
	CIDR     string `toml:"cidr" json:"cidr"`
}

NetworkRule allows or denies traffic to a hostname (supports *.wildcards).

type Options

type Options struct {
	// ConfigDir, when set, pins the search to a single directory.
	ConfigDir string
	// WorkingDir resolves project-local profiles; defaults to the process cwd.
	WorkingDir string
}

Options controls where profiles are resolved from.

type PolicyBundle

type PolicyBundle struct {
	Ref       string `toml:"ref" json:"ref"`               // oci://registry/repo:tag or repo@sha256:...
	VerifyKey string `toml:"verify_key" json:"verify_key"` // base64 ed25519 public key; signature verification requires this
	PlainHTTP bool   `toml:"plain_http" json:"plain_http"` // allow http registries (local testing)
	// Insecure explicitly accepts an unsigned/unverified bundle. Without it,
	// a bundle with no verify_key is rejected (fail-closed) rather than trusted.
	Insecure bool `toml:"insecure_skip_verify" json:"insecure_skip_verify"`
}

PolicyBundle references a signed policy bundle stored as an OCI artifact. When VerifyKey is set the ed25519 signature is verified before the policy is loaded.

type PolicyRule

type PolicyRule struct {
	// Tool matches the action surface: shell|python|node|file.read|file.write|
	// file.edit|net (supports "*").
	Tool string `toml:"tool" json:"tool"`
	// Match is a glob applied to the action's primary argument (command,
	// path, or hostname depending on Tool).
	Match string `toml:"match" json:"match"`
	// MatchArgv, when set, is a glob applied to the command's argv[0] (the
	// executable). It closes the evasion where `deny "rm*"` on the joined
	// command line is sidestepped via `sh -c 'rm ...'` or `/bin/rm`: a rule with
	// match_argv = "*/rm" or "rm" matches regardless of wrapper or path.
	MatchArgv string  `toml:"match_argv" json:"match_argv"`
	Verdict   Verdict `toml:"verdict" json:"verdict"`
	// Reason is shown to the approver and recorded in the ledger.
	Reason string `toml:"reason" json:"reason"`
}

PolicyRule maps an action to a verdict evaluated before the action executes.

type Profile

type Profile struct {
	// Name comes from the filename, not the file body.
	Name string `toml:"-" json:"-"`
	// Source is the absolute path the profile was resolved from.
	Source string `toml:"-" json:"-"`

	Host    Host         `toml:"host" json:"host"`
	Prompt  Prompt       `toml:"prompt" json:"prompt"`
	Env     []EnvVar     `toml:"env" json:"env"`
	Files   []File       `toml:"file" json:"file"`
	Network Network      `toml:"network" json:"network"`
	Policy  []PolicyRule `toml:"policy" json:"policy"`
	// PolicyDefault controls the fallback verdict when no policy rule matches.
	// Empty keeps the historical default ("allow"); set to "deny" to fail closed.
	PolicyDefault Verdict `toml:"policy_default" json:"policy_default"`
	// PolicyEngine is "" or "builtin" (the [[policy]] rules), "cel", or "rego".
	// Selecting cel or rego makes the [[policy]] rules ignored.
	PolicyEngine string     `toml:"policy_engine" json:"policy_engine"`
	CEL          []CELRule  `toml:"cel" json:"cel"`
	Rego         RegoPolicy `toml:"rego" json:"rego"`
	// PolicyBundle, when set, supersedes the inline policy fields with a signed
	// bundle pulled from an OCI registry.
	PolicyBundle *PolicyBundle `toml:"policy_bundle" json:"policy_bundle"`
	Limits       Limits        `toml:"limits" json:"limits"`
	Fleet        *Fleet        `toml:"fleet" json:"fleet"`
	Audit        Audit         `toml:"audit" json:"audit"`
	// Packages are installed only with --provision, never on the run path.
	Packages []string `toml:"packages" json:"packages"`
}

Profile is the top-level resolved contract read from a <name>.{toml,yaml,json} file.

func Load

func Load(name string, opts Options) (*Profile, error)

Load resolves and parses the named profile; the file extension selects the parser.

Resolution order (first match wins):

  1. <workingdir>/.runeward/<name>.{toml,yaml,yml,json}
  2. $XDG_CONFIG_HOME/runeward/... or ~/.config/runeward/...

Within a directory, extensions are tried in profileExts order, so <name>.toml shadows <name>.yaml. If Options.ConfigDir is set, only that directory is consulted.

func (*Profile) UsesCEL

func (p *Profile) UsesCEL() bool

UsesCEL reports whether the profile selects the CEL authority engine.

func (*Profile) UsesPolicyBundle

func (p *Profile) UsesPolicyBundle() bool

UsesPolicyBundle reports whether the profile sources its policy from an OCI policy bundle.

func (*Profile) UsesRego

func (p *Profile) UsesRego() bool

UsesRego reports whether the profile selects the Rego authority engine.

func (*Profile) Validate

func (p *Profile) Validate() error

Validate checks cross-field invariants after parsing.

type Prompt

type Prompt struct {
	Inline string `toml:"inline" json:"inline"`
	File   string `toml:"file" json:"file"`
}

Prompt is an optional system prompt, inline or sourced from a file.

type RegoPolicy

type RegoPolicy struct {
	Module string `toml:"module" json:"module"`
	File   string `toml:"file" json:"file"`
	Query  string `toml:"query" json:"query"`
}

RegoPolicy configures the Rego (OPA) engine. Set exactly one of Module (inline) or File. Query defaults to "data.runeward.decision".

type Signature

type Signature struct {
	// Format is the envelope schema tag; see SignatureFormat.
	Format string `json:"format"`
	// KeyID is the short fingerprint of the signing public key
	// (policybundle.KeyID).
	KeyID string `json:"key_id"`
	// Sig is the base64 (std) ed25519 signature over the profile bytes.
	Sig string `json:"sig"`
}

Signature is the JSON envelope written to a detached ".sig" file next to a signed profile. It carries the base64 ed25519 signature over the exact profile file bytes on disk plus the signer's key id, so verification can report which key was used and reject an obviously mismatched key early.

The signed message is the raw profile bytes verbatim: signing the exact on-disk bytes keeps the scheme canonical (no re-serialization) and mirrors how policybundle covers the raw policy layer.

func NewSignature

func NewSignature(content []byte, priv ed25519.PrivateKey) Signature

NewSignature signs content and wraps the result in a detached Signature envelope tagged with the signer's key id.

func ParseSignature

func ParseSignature(b []byte) (Signature, error)

ParseSignature decodes a detached signature file produced by Signature.Marshal.

func (Signature) Marshal

func (s Signature) Marshal() ([]byte, error)

Marshal encodes the signature envelope as indented JSON (with a trailing newline) suitable for writing to a detached ".sig" file.

func (Signature) Verify

func (s Signature) Verify(content []byte, pub ed25519.PublicKey) (string, error)

Verify checks the detached envelope against content and pub. It fails closed when the envelope key id does not match pub, then verifies the signature, returning the verified key id on success.

type Verdict

type Verdict string

Verdict is the decision a policy rule renders for a matched action.

const (
	VerdictAllow          Verdict = "allow"
	VerdictDeny           Verdict = "deny"
	VerdictRequireApprove Verdict = "require-approval"
)

Jump to

Keyboard shortcuts

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