Synsemadocsv0.6.xENES

Capabilities & Security

Secrets

A secret is an opaque, redacted value: you can use it but never read its plaintext from the language. It is LLM-proof by design — it never appears in print, logs, error bodies, JSON responses, the blackboard, or an LLM prompt.

env() vs secret() §

Both are deny-by-default: declare the capability or the read fails.

secret-bearer.syn
-- Doc example (UNIVERSAL — one source, shared by every language).
-- Comments are English on purpose: the code is the same in /en, /es, /pt, ...
-- Doctest gate: `synsema test docs/0.5.x/examples/` must be green to publish.
intent: "doc example: a secret stays redacted everywhere except the socket"
require secret("API_KEY")

print("a secret prints as → " + text(secret("API_KEY", "sk-demo-123")))    -- redacted

test "a secret is redacted in every string surface"
    let k be secret("API_KEY", "sk-demo-123")
    assert_eq(text(k), "secret(API_KEY)")                       -- never the real value
    assert_eq(type_of(k), "secret")
    -- GOTCHA: concatenating text + secret yields a SECRET, and it redacts WHOLE —
    -- the "Authorization: " prefix is absorbed, you do NOT get "Authorization: secret(...)".
    assert_eq(type_of("Authorization: " + k), "secret")
    assert_eq(text("Authorization: " + k), "secret(API_KEY)")   -- prefix absorbed (taint)

test "bearer() builds a tainted Authorization value"
    let b be bearer(secret("API_KEY", "sk-demo-123"))
    assert_eq(type_of(b), "secret")                             -- still a secret
    assert_eq(text(b), "secret(API_KEY)")                       -- redacted, even as a Bearer value

test "a raw secret goes in ANY header, not just Bearer"
    -- materialization to the real value happens ONLY at the socket; here we just show
    -- it stays a redacted secret in your program's value space (see /docs/.../secrets).
    let headers be {"x-api-key": secret("API_KEY", "sk-demo-123")}
    assert_eq(text(headers["x-api-key"]), "secret(API_KEY)")

test "as_secret seals a value that arrives at runtime"
    let sealed be as_secret("rk-from-user", "user_key")         -- e.g. a tenant's key from a header
    assert_eq(text(sealed), "secret(user_key)")
    assert_eq(type_of(sealed), "secret")

Where the values come from §

secret("NAME") resolves process environ > .env > default, and with none of the three it errors — never a silent empty value. synsema init writes a .env.example whose last section is exactly this: your own secrets, with the ones the auth features expect already named (JWT_KEY, CAPTOKEN_ROOT_KEY, AGENT_SIGNING_KEY — see Agent identity). The name you pick is also the capability scope: require secret("JWT_KEY") grants that one and no other.

Credentials go in any header — not just Bearer §

A secret placed as a header value is materialized to its real value only at the socket, regardless of the header name. So you are not limited to Authorization:

In query params and the body a secret is redacted (fail-closed) — a credential only survives over the wire inside a header.

as_secret() — seal a value that arrives at runtime §

secret("NAME") reads from config. For a value that arrives at runtime and is not in .env (a tenant's key in a request header, a token from another HTTP call), seal it at the edge with as_secret(value, label?). It is pure (no require), idempotent on a secret, and accepts text or bytes.

reveal() — the audited last resort §

reveal(secret) returns the plaintext. It requires require reveal("NAME") scoped to the secret's name/label, writes an append-only audit entry for every attempt, and warns on the bare (unscoped) form. Prefer bearer/hmac_sha256/verify_hmac — they consume the secret without exposing it.

The audit directory ($SYNSEMA_AUDIT_DIR, default ~/.synsema/audit) holds one append-only log per gated family, all with the same guarantees — every attempt recorded (granted or denied), never the sensitive value, fail-loud on the granted path (if the entry cannot be written, the operation refuses): reveal.log, sign.log (signatures; a denied_by=ceiling entry marks a SYNSEMA_SIGN_CEILING cut), wallet.log (custody creation) and spend.log (the money ledger: canonical decimal amount, unit, quoted reason, file:line, program; denied_by=ceiling marks a SYNSEMA_SPEND_CEILING cut — see Capabilities).