Trust and security
PTWM refuses to load an extension unless its signature chains to a key the user has explicitly trusted. This page covers the trust state machine, the policy controls, and the threat model.
The bundled-then-pinned model
On a fresh install, PTWM ships with no trusted keys at all. Running:
ptwm trust add --bundled
imports the curated bundled keyring (a snapshot of "official"
contribution authors) and pins its blake3 hash to
$XDG_CONFIG_HOME/ptwm/trust/bundled.lock. From that moment on,
opening a .ptwm file referencing one of those contributions
succeeds.
If a subsequent PTWM upgrade ships a different bundled keyring — a new author added, a compromised key revoked — the on-disk lock no longer matches. The reader treats the bundled keyring as inactive until the user explicitly accepts the new state:
ptwm trust update --bundled
update --bundled shows you the diff (added / removed keys) before
applying it. This means a PTWM upgrade can't silently grant a new
publisher the ability to ship code into your loader — that step always
requires consent.
Three kinds of trust entry
Per ptwm trust list, your keyring may hold:
author_key— trust everything signed by this Ed25519 public key. The most common entry. Use for "I trust everything this author ships".contribution_hash— pin trust to a single canonical id (a specific contribution at a specific version). Use for one-off approvals where you don't want a key to grant transitive trust.key_with_capability_constraints— trust this author only for contributions whose declared capabilities are a subset of an allowed list. Use for "trust author X but only their pure codecs, not anything that declareshost_imports".
Add entries with ptwm trust add; remove with ptwm trust remove <identifier>; inspect with ptwm trust show.
Capabilities
Every contribution declares its requirements in a capability map that's signed alongside the manifest. The v1 baseline keys:
| Key | Meaning |
|---|---|
determinism | Promises byte-identical output across runs / flavors. |
native_deps | Lists <library>=<version-constraint> requirements. |
host_imports | WASM host functions the module needs imported. |
hardware_class | cpu / cuda / rocm / mps. |
sandbox_class | pure_decode / read_only_filesystem / network. |
mem_factor | Multiplier for the default 256 MiB WASM memory budget. |
fuel_factor | Multiplier for the default 100M fuel budget. |
A capability the host doesn't know is denied at verify time, not silently accepted. This means a future PTWM upgrade introducing a new capability key can't be exploited by replaying an old extension that declared the new key — the contribution authors must re-sign.
Policy files
Capabilities + canonical IDs feed into the policy file that
ptwm bench compress --variant consumes. The TOML schema is the one
parsed by PolicyFile::from_toml
and resolved against the active keyring by
ResolvedPolicy.
Example:
[allow]
extra = [
"blake3:5c3a4cd0c46825ae6dbb471459e63347ea6e1d62c8a0dbfdf5d532a124525f09",
]
[ignore]
extensions = [
"blake3:da39a3ee5e6b4b0d3255bfef95601890afd80709…",
]
[per_role.scale]
deny = ["blake3:<canonical-id>"]
[capabilities]
allow_host_imports = [] # WASM modules with host_imports declared → rejected
deny_native_deps_unverified = true
The resolved policy enters the encoder via
CompressionConfig.from_resolved_policy(rp) and constrains the
dispatcher's trial-encode menu per-plane. An empty intersection (the
policy excludes every codec a plane can use) is a hard InvalidContainer
error — silent identity fallback would falsify ablation numbers.
ContributionUntrusted at decode
When ContainerReader::open encounters an extension-table entry
referring to a non-builtin canonical id, it:
- Looks up the matching installed bundle on disk
(
$PTWM_EXTENSION_PATHor$XDG_DATA_HOME/ptwm/extensions/). - Recomputes
blake3(manifest ‖ binaries)from the on-disk files. - Verifies the bundle's
signature.binagainst the author key from the manifest. - Confirms the author key is in the active keyring (or that the
canonical id is pinned via
contribution_hash).
Failure at any step surfaces as a ContributionUntrusted error with a
specific reason. The reader does not silently downgrade; tampered
binaries fail loudly. (Verified by the
open_verifies_signed_bundle_end_to_end test in
crates/ptwm-core/src/container.rs.)
If you genuinely want to inspect a container that references an
untrusted extension (e.g. listing tensor names without executing
codec code), pass skip_missing=True to Decompressor. Decoding any
tensor that needs the untrusted extension still fails.
Threat model in two paragraphs
Assets: the user's signing keys, the on-disk extensions directory
($XDG_DATA_HOME/ptwm/extensions/), and the integrity of decompressed
tensor bytes flowing into a training/inference loop.
Mitigations: WASM extensions execute in a Wasmtime sandbox with no
host imports granted by default, finite fuel, finite memory, and no
filesystem or network access. Native cdylib extensions have no
sandbox — their security boundary is the signing key, which is why
the verifier always checks the on-disk binary hash, not just the
header signature. The trust state is never updated silently:
every change requires an explicit ptwm trust add/update/remove
invocation. The bundled keyring is treated as untrusted on hash
mismatch, so a malicious PTWM upgrade can't graft new publishers
into your loader.
Out of scope: confidentiality of compressed weights (use
filesystem-level encryption), denial-of-service via deliberately slow
extensions (use fuel_factor to bound), and side-channel attacks
against the signing key (use a hardware token).
Recommended workflow
For researchers: keep ptwm trust add --bundled plus a single
local --key for your own development. Re-sign on every commit; the
v1 CLI doesn't ship a gen-key subcommand, so generate the 32-byte
raw seed yourself (openssl rand 32 > key.priv) and re-feed it to
ptwm ext sign --key key.priv. Key rotation is "generate a new
seed, add the new public key with ptwm trust add --key, then
remove the old entry with ptwm trust remove <label>".
For operators shipping models with custom codecs: don't ship the
private key with the model. Sign at release time, distribute the
public key out-of-band (e.g. in the model card), and let downstream
users explicitly ptwm trust add --key …. The bundled keyring
update flow makes this audit-trail-friendly.