Skip to content

Security

This section documents the controls that protect a distribution (a signed, released build) and the trust decisions dk0 makes when it imports or builds one. The underlying model is in the Specification "Attestations" and "OpenBSD signify keys" sections; the value-store protections are in SECURITY.md.

ControlEntry pointEnforced on consumption
GitHub SLSA Level 2 attestationimport github-l2, inspect github-l2, restore github-l2Yes. The release values.json is verified with gh attestation verify against the Sigstore trusted root, scoped to -R OWNER/REPO; a failed verification is fatal.
OpenBSD signify signing of a distributionprepare-version (key generation); distribute and combine (signing); every import (verification)Yes. distribute signs the canonical build payload (ThunkDist.canonical_build_payload_id) when the build key is the distribution producer key (the dk-distribute CI action passes it with --keys-env), and combine re-signs the combined distribution the same way. On import github-l2, import local, restore github-l2 and remote-result import, the signed continuations must verify against the producer.openbsd_signify public key, and the build.attestation.openbsd_signify signature (when present) must verify over the canonical build payload. A present-but-invalid signature is fatal (SecConsumerTrust).
Key rotation via signed continuations, monotonic per MAJOR.MINORprepare-version, distribute (author); every import (consumer)Yes. SecPackageRegistry.characterize runs on both sides. A producer key is imported once and never overwritten (no key may reclaim an established MAJOR.MINOR); a new MAJOR.MINOR must carry the continuation key a trusted prior release signed; a release below the latest imported release is rejected (restore falls back to a cold build).
Vendor-key trust root and deny-by-default acceptanceevery importYes. Trust anchors, in order: the built-in dk signify key for CommonsBase_Std, locally prepared keys in etc/dk/d, previously imported releases (the import directory etc/dk/i plus the local trust records in etc/dk/t), and the documented --trust-local-package escape hatch. Any other producer key gets an interactive accept/deny prompt that defaults to deny and denies at end of input, so CI fails closed. Transitive distributions recovered from a directly imported release are verified (signatures and rotation consistency) before their content-pinned acceptance, and never anchor a directly imported release's rotation.
Value-store integrity of Marshal-ed ASTsbuild / get-object pathYes. A SHA-256 prefix guards each Marshal-ed AST and is signify-signed with a per-workspace build key (SECURITY.md).
Rule-capability consent for spawn / capture / writefile, keyed by signify provenancerequest.ui.* (BuildRequestUi, SecUiCapability); trust list/grant/revokeYes. Deny-by-default prompt before a rule first exercises a capability — run (running a program: request.ui.spawn and request.ui.capture, gated identically so capture cannot bypass a spawn denial) or write (request.ui.writefile); fails closed with no TTY, naming dk0 trust grant. The prompt identifies the rule by its signify provenance: the producer key fingerprint and package@version for a rule from an imported, signature-verified distribution, or the values-file SHA-256 for a host script or local rule. Answering [a]lways (or dk0 trust grant) persists the grant in etc/dk/t/capabilities.json, keyed by the full producer public key (never the fingerprint, which is the attacker-choosable keynum) or by content hash; [y]es allows once. A producer-key grant is honored only for values content whose SHA-256 is recorded in the etc/dk/t/imports.json ledger against that producer — the producer signs each exported script module's content hash into build_to_sign.build_script_module_sha256s, and import records it after verifying the signature — so content tampered after import (or a local squatter) falls back to the content-hash prompt. Key acceptance at import never confers a capability. The process-wide --dangerously-trust-all is a separate command-line escape hatch; the isolated dk0 remote path additionally allows the single, producer-trusted orchestration rule it runs without a prompt. The prompt warns when a program is a bare name resolved through PATH.
Windows executable-search hardeningdk0 process startup (Shell.ml)Yes. On Windows dk0 sets NoDefaultCurrentDirectoryInExePath, removing the current directory from the executable search for every program it spawns — rule spawns, precommands, function commands and subshells — so a program named by a bare name is found only through PATH, never from an executable dropped into the working directory.
Build-state exclusion from globsrequest.ui.glob (BuildRequestUi)Yes. The signify keys directory (holding build.sec), the data directory and the cache directory are never enumerated, so a rule cannot route build.sec or other build state into a content-addressed bundle.
signify primitive (keygen / sign / verify / checksum lists)signify -G / -S / -V / -CThe OpenBSD signify implementation (MlFront_Signify), including -C verification of a signed SHA256/SHA512 checksum list against its files.
  • Attestation is required. dk0 rejects assets and objects produced without a trusted attestation. Two sources are recognized: a human OpenBSD signify signature, or GitHub Actions SLSA Level 2/3.
  • import github-l2 verifies two anchors. The release must carry a valid GitHub/Sigstore attestation for the OWNER/REPO on the command line, and its producer signify key must anchor to the built-in dk vendor root, a locally prepared key, a trusted continuation chain, --trust-local-package, or an interactive acceptance (deny by default).
  • import local has no transport attestation by design (the user names a local file); the signify signature, rotation, and acceptance controls above are its distribution-integrity control.
  • inspect runs the same verification as import (SLSA Level 2 for github-l2, then the signify / rotation / deny-by-default acceptance controls) but never imports the package payload: it extracts the exported script modules (for audit) and the sealed distribution scripts (for verified manifest rendering) from the small distmeta only. It is the verified-release source for catalog and manifest rendering.
  • Rule capabilities are deny-by-default and keyed by signify provenance. A rule action that runs a program (request.ui.spawn, request.ui.capture — one run capability, so capture cannot bypass a spawn denial) or writes a file (request.ui.writefile, the write capability) is prompted the first time; answering [a]lways persists a workspace grant (etc/dk/t/capabilities.json) for the rule's producer signify key — bound to the full public key — or, for unsigned local content, for the values-file SHA-256 (editing the content asks again). A rule from an imported distribution is attributed to the producer key when its values.lua content SHA-256 is in the import ledger (etc/dk/t/imports.json) against that key: the producer signs each exported script module's content SHA into the distribution (build_to_sign.build_script_module_sha256s), import records it after verifying the signature, and the run-time content is bound by matching that hash. Content tampered after import (or a local file squatting on the module id) is not in the ledger and safely falls back to the content-hash prompt. dk0 trust grant|revoke|list manages the grants non-interactively (CI). Accepting a producer key at import time never grants a capability: authenticity and authorization are separate decisions. A program given as a bare name is flagged as PATH-resolved (with the current directory excluded from the search on Windows).
  • dk0 remote composes producer trust into capability. The isolated remote orchestration path imports and verifies the explicitly named producer, then (only for the single orchestration rule it runs, in the ephemeral isolated workspace) allows that producer key's rule to run its own capture/spawn/writefile without a per-action prompt — so dk0 remote GitHub@X no longer needs --dangerously-trust-all. The allowance is scoped to the named producer's key and the isolated run; it is empty in every other process.

dk0 embeds the OpenBSD signify public key for exactly one package: CommonsBase_Std, in SecConsumerTrust.builtin_root_keys (the current 2.6 line key, fingerprint f012f39422d61ed2). This implements the Specification's trust store, which names the dk signify key for the CommonsBase_Std packages as the only trusted entity by default.

The keys are embedded because deny-by-default enforcement needs a starting anchor. Nearly every dk workspace bootstraps by importing CommonsBase_Std (coreutils, 7zip, toybox, ...), and a fresh consumer - a first-time user or a CI runner - has no previously imported releases, no locally prepared keys, and no terminal to answer an accept/deny prompt. Without the embedded root, every first import would fail closed or teach users to reach for --trust-local-package, hollowing out the deny-by-default model. Only this one package's keys are embedded - not every Commons* key - to keep the curated, code-reviewed trust surface minimal: every other producer anchors through the signed continuation chains of imported releases, an explicit --trust-local-package, or an interactive acceptance.

Newer CommonsBase_Std lines chain from the embedded keys through the signed continuations of imported releases (2.5 signs the 2.6 and 3.0 keys, and so on), so rotation within the reachable chain needs no code change. A release only carries the chain it links transitively, though, so a fresh import of the latest release can outrun an old root (this is how the 2.6 key earned its entry). The network-gated trust-root.t test imports the latest dkpkg/CommonsBase_Std release into an anchorless workspace; when it fails, builtin_root_keys must gain the current line's key, taken from the signed continuation of an attested prior release.

The consumer-side gaps that the controls above close were found by Opus 4.8 with Claude Code on 2026-07-11, and were closed the same day. Still tracked:

  1. GitHub SLSA Level 3 is not verified. A release carrying a non-empty github_slsa_v1_l3 attestation document is explicitly rejected on import instead of being accepted unverified.
  2. query manifest performs no verification by design: it reads the local working tree for authoring and preview. A consumer that renders its output (for example a package catalog) should render from verified release data instead: inspect github-l2 extracts the sealed distribution scripts, and query manifest -f <outdir>/LIBRARY.VERSION.dist-scripts/dk.u renders them. Releases produced before dk0 sealed manifest inputs cannot be rendered this way until re-released.
  3. A release produced without the distribution key in the build environment (for example a local distribute with the auto-generated workspace key) publishes an empty build.attestation.openbsd_signify anchor; consumers accept the absence and rely on the other controls. Requiring the signature is a possible future hardening once every producer signs.