Security
Verum's type system and verification pipeline eliminate entire classes of vulnerabilities. This guide surveys the tools and the residual risks.
The recommendations below are the right ones to design against. Three of them cannot be executed by this build, and a security guide that does not say so is worse than none — a reader follows it and ships code that stops at the first call.
Checked, both tiers:
| Recommended here | State in this build |
|---|---|
| ML-DSA signatures, ML-KEM-768 exchange | ❌ verum.pq.* has no implementation — traps on both tiers |
| X25519 key exchange | ❌ verum.x25519.scalar_mult likewise; generate_* also needs the missing CSPRNG |
| HKDF, HMAC, SHA-2 | ⚠️ correct under the interpreter, fault under AOT (A147) |
| AES-128 / AES-256 block cipher | ✅ verified against the FIPS-197 vectors at both tiers |
| ChaCha20, Poly1305, the AEADs | ⚠️ correct under the interpreter, fault under AOT |
So the AEAD advice stands in design and, today, runs only under
verum run. Check security/overview
before planning against any row here — it carries the per-primitive
measurement and is updated as the defects close.
What Verum prevents by construction
| Class | Mechanism |
|---|---|
| Use-after-free | CBGR generation checks |
| Double-free | CBGR tracks the current generation |
| Null dereference | No null; Maybe<T> forces handling |
| Buffer overflow | Bounds-checked indexing; refinement types proving in-range |
| Integer overflow | Explicit checked_* / saturating_* / wrapping_* APIs |
| Uninitialised-memory read | MaybeUninit<T> + escape check |
| Type confusion | Nominal types; explicit conversions |
| Data races | Send / Sync enforce thread-safety at compile time |
| SQL/HTML/shell injection | Tagged literals (sql#, html#, sh#) auto-escape |
| Deserialisation mismatch | Typed @derive(Deserialize) with refinements |
| Unvalidated user input | Refinement types force validation at boundaries |
What still requires discipline
- Cryptographic correctness — use the vetted stdlib crypto
primitives under
core.security:aeadfor authenticated encryption,eccfor X25519,pqfor ML-KEM / ML-DSA,kdffor HKDF. Don't roll your own primitives — picking the wrong mode or truncating tags catastrophically weakens security. - Constant-time code — the compiler doesn't guarantee side-
channel resistance by default. The stdlib crypto primitives are
constant-time by construction, and
core.subtle.constant_time_eqis mandatory for any secret-byte comparison. Don't branch on secret bytes in your own code. - Supply-chain trust — see the trust model for cog signatures and advisories.
- Resource exhaustion — DoS via memory, open FDs, task queues.
Use bounded channels, semaphores,
nurserylimits. - Business-logic bugs — verification proves what you wrote; if the spec is wrong, the proof doesn't help. Pair verified code with good tests.
Practical checklist
Inputs
- All external inputs parsed via typed
Deserializewith refinement types or, for arbitrary shapes,Data+ explicit validation. - Regex in tagged form (
rx#"...") — compile-time-checked. - SQL in tagged form (
sql#"...") — injection-safe. - Shell commands via
Command.new(...).arg(...)— never string- concatenate. - HTML output via
html#"""..."""— escape-by-default.
Authentication / authorisation
-
Sensitive operations require
using [Auth]— no global auth. -
Use capability-restricted types:
Database with [Read]for views that must not write. -
security.labelssupplies runtime classification tags and explicit comparisons. Apply the result of a label comparison in your application policy; wrapping a value inLabeled<T>does not automatically restrict where it can flow. -
Workload identity (K8s / multi-cluster / multi-cloud) —
security.spiffegives you SPIFFE-ID-based mTLS with automatic rotation.
Secrets
- Keep secrets out of logs and public responses. Runtime
Labeled<T>tags do not make the logger refuse sensitive data. - For types holding key material, implement
Dropto zero the buffer before release — seesecurity.util.zeroise(stable wrapper planned; use theverum.mem.zeroiseintrinsic until then). - Pull secrets from a dedicated store, not env vars: HashiCorp
Vault, AWS Secrets Manager, or GCP Secret Manager via
security.secrets. - Environment variables over on-disk plaintext; container secrets over env vars where a store is not available.
Crypto
- Authenticated encryption — use the AEAD layer: AES-128/256-GCM or ChaCha20-Poly1305. Never raw AES / raw ChaCha20 — those lack authentication.
- Signatures — ML-DSA (post-quantum) for new work; Ed25519 (planned P1) when it lands; ECDSA-P256 (planned P1) for legacy interop.
- Key exchange — X25519 paired with ML-KEM-768 for PQ-hybrid is the TLS 1.3 / QUIC default.
- KDFs — HKDF-SHA-256 / -SHA-384 / -SHA-512 for key derivation from high-entropy IKM. Argon2id for password hashing (different module, planned).
- MACs — HMAC-SHA-256 is the right
default for cookie signing / token MACs. Always verify tags with
constant_time_eq, never==. - Never
unsafe-cast between[Byte; N]and other types without zeroisation first.
Network
- TLS 1.3 only. Verum's pure-Verum TLS stack at
core.net.tls13is 1.3-only by design; there is notls12_fallbackflag. Clients detect RFC 8446 §4.1.3 downgrade sentinels ("DOWNGRD\x01"/"DOWNGRD\x00") inServerHello.randomand abort withProtocolVersion. - Load the system trust store explicitly:
ClientOptions.with_system_trust()(H3) orQuicClientOptions.with_system_trust()— both wrapTrustStore.system()with a deterministic fallback to an empty store on macOS / Windows backends that fail. - Certificate pinning: construct a
TrustStorefrom your pinned DER chains viaTrustStore.from_der(&[...])and pass it asopts.trust. - Hostname verification — on by default
(
opts.verify_hostname = true) via RFC 6125 §6.4 SAN matching. CN fallback is not supported (deprecated). - QUIC is anti-amplification limited to
3× received bytes (§21.1) before path validation completes; no
configuration required — the limit is enforced in
path.vrand verified by V7. - Rate-limit inbound:
Semaphorefor concurrent connections, leaky bucket for per-IP throughput.
Concurrency
- Use
nurserywithon_error: cancel_allto bound task trees. - Bounded channels (
bounded(N)) to limit queue depth. - Avoid mutable shared state; prefer message-passing.
@verify(static)catchesSend/Syncviolations at compile time.
FFI
- Every
extern "C"block comes with a boundary contract (requires,ensures,memory_effects,thread_safe,errors_via,@ownership). - Input pointers: validate non-null, bound-check length.
- Output pointers: zero before return if returning on error.
- Strings crossing the boundary:
Text.from_utf8— returnsResult<Text, Utf8Error>, so invalid input is a value you handle.
Verification
- Safety-critical code:
@verify(thorough). - Security-critical lemmas:
@verify(certified)with a proof term. @verify(certified)in CI for any module touching crypto, auth, or raw memory.
Auditing
verum audit # scan deps against advisory DB
verum analyze --context # capability context surface per function
verum analyze --escape # reference-tier distribution
verum analyze --refinement # refinement / verification coverage
verum analyze --all # everything in one pass
verum smt-stats # solver-routing stats from the last build
Reporting vulnerabilities
Security issues in Verum itself: email security@verum-lang.org
with a PoC. The team follows a 90-day coordinated-disclosure policy.
See also
- security stdlib — labels, regions.
- Tooling → cog packages → trust — supply-chain trust.
- Verification → thorough strategy — solver cross-validation.
- FFI — boundary contracts.