Security Model
Boruna is a local engine and CLI. Since v3.0.0 there is no HTTP server, coordinator, worker or dashboard, so there is no network-facing service to secure. This document covers what the engine itself enforces. For the evidence-specific threat model see concepts/threat-model.md.
Capability System
All side effects in Boruna are declared and enforced. Functions annotate their capabilities:
fn fetch(url: String) -> String !{net.fetch} { ... }
The compiler records each function’s declared capabilities in the bytecode. At runtime the VM rejects a capability call from a function that did not declare it (CapabilityDenied), and the CapabilityGateway checks every remaining call against the active Policy.
Built-in Capabilities
net.fetch— HTTP requestsdb.query— Database queriesfs.read,fs.write— File system accessllm.call— LLM model invocationtime.now— Current timerandom— Randomnessui.render— Framework view outputactor.spawn,actor.send— Actor system operationsstep.input— Read a workflow step’s resolved inputs
Isolation
Process Isolation
Each workflow step compiles to bytecode and runs in a fresh VM instance. Steps cannot share memory or state except through the explicit data flow system.
Filesystem Isolation
PatchBundlerejects.., absolute, drive-letter and rooted paths on every platformcanonicalize()defense-in-depth prevents path traversal- Evidence bundle outputs are written to controlled directories
Data Validation
- LLM cache keys are hex-only validated
- Context store hashes are hex-only validated
- Package content uses SHA-256 integrity verification
Policy Enforcement
Policies can restrict:
- Which capabilities may be used, with a per-capability call budget
- Which network domains and HTTP methods are reachable, response size, timeout and redirects (
net_policy)
A denied call is a runtime error (CapabilityDenied, or CapabilityBudgetExceeded when the budget is used up). Check a policy file with boruna policy validate / boruna policy show (see reference/policy-schema.md).
The boruna-effect crate also has an LLM policy (allowed models, total token budget). It is a library API; the CLI policy file does not set it.
Secrets Management (Gap)
There is no dedicated secrets management system. The bundle encryption key (--bundle-encryption-key / BORUNA_BUNDLE_KEK) and the ed25519 signing seed (--signing-key / BORUNA_BUNDLE_SIGNING_KEY) are passed as flags or environment variables. Recommended approach:
- Use external secret managers (Vault, AWS Secrets Manager) and inject keys at invocation time
- Pass secrets via capability handlers at runtime
- Never embed secrets in workflow definitions or source files
Threat Model
In Scope
- Malicious workflow steps: Capability gating prevents unauthorized IO
- Tampered evidence: Hash-chained audit log, per-file checksums and
bundle_hashdetect modification - Path traversal: Validated at multiple layers
- Non-determinism: BTreeMap ordering,
time.nowandrandomgated as capabilities, replay verification
Out of Scope (current)
- Host OS compromise
- Supply chain attacks on the Boruna binary itself (release archives are checked against
SHA256SUMSby the installers, but binaries are not signed) - Multi-tenant isolation (single-tenant model)
- A stolen signing key: a bundle signed with it verifies (see concepts/threat-model.md)
Audit Trail
Workflow runs recorded with --record produce an evidence bundle with a hash-chained audit log. It records workflow start and completion, step completion and failure, approval decisions, external triggers and confidence-gate decisions. Individual capability calls are recorded in the VM event log (boruna run --record <file>), not in the bundle’s audit log.
The chain is verifiable:
content_sha256 = SHA-256(event_json);entry_hash = SHA-256(sequence || prev_hash || content_sha256)(bundle format 1.1; 1.0 bundles hash the event directly)- Tampering with any entry breaks the chain from that point forward
boruna evidence verify <dir>checks the chain, file checksums, required files, and the signature and encryption when present
Digital Signatures
A manifest can carry an ed25519 signature over bundle_hash. Bundles are signed through the library API (EvidenceBundleBuilder::with_signing_key); boruna workflow run has no signing flag. boruna evidence verify --verify-key <hex> pins the trusted public key, and --require-signature rejects unsigned bundles. boruna evidence attest emits an in-toto + DSSE attestation signed with the same ed25519 seed.
Encryption at Rest
boruna workflow run --encrypt-bundle encrypts bundle files with AES-256-GCM envelope encryption under a key-encryption key (KEK). boruna evidence verify --bundle-encryption-key decrypts in memory to verify, and boruna evidence rotate-kek re-wraps the data key under a new KEK without re-encrypting the files.
Redaction
boruna evidence redact <dir> --event <N> [--field <name>] removes content (for example PII) from a sealed plaintext bundle. The entry keeps its content_sha256, so the chain and audit_log_hash stay valid and evidence verify still passes and reports the redacted entries. A prior manifest signature is dropped; re-sign or re-anchor afterward. Confidence-gate decisions cannot be redacted. See orchestrator/docs/verifiable-redaction.md.
Transparency-Log Anchoring
boruna evidence anchor <dir> records a signed bundle’s bundle_hash in a Sigstore Rekor log, which adds an external witness and timestamp. --rekor-url accepts a private Rekor instance, --offline emits the entry for out-of-band submission, and --verify checks a stored inclusion proof without network access. Live submission requires a build with the rekor cargo feature.