Documentation
¶
Overview ¶
Package secrets generates and validates prefix-id-secret style credentials used across agentserver. Centralizes the `rand.Read + encode + sha256 + hex` pattern that was previously duplicated across workspace_api_keys, codex_token_format, proxy_tokens, and a few smaller call sites.
Format on the wire: <prefix>_<id>_<secret><crc32>
- prefix is a short ASCII tag (e.g. "ask", "ast") with a trailing _ in the Spec (so the full wire token reads "ask_...").
- id is a public, indexable handle (DB primary key); base62 encoded
- secret is the high-entropy payload; base62 encoded
- crc32 is a 6-char base62-encoded CRC32/IEEE checksum of the preceding <prefix>_<id>_<secret> portion (no separator). Same scheme as GitHub.
- hash = HMAC-SHA256(pepper, <full token>) or SHA-256 if no pepper — the value persisted in the database.
Encoding is base62 (0-9, a-z, A-Z) for ~5.95 bits/character. CRC32/IEEE (32-bit) encodes to ceil(32/log2(62)) = 6 chars with leading- zero padding. SHA-256 is used for hashing because secrets are CSPRNG- generated (>= ~140 bits of entropy at SecretLen=24+); bcrypt's KDF overhead adds nothing against random secrets and just slows validation.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( APIKeySpec = Spec{Prefix: "ask_", IDLen: 16, SecretLen: 48} AgentserverTokenSpec = Spec{Prefix: "ast_", IDLen: 16, SecretLen: 48} )
APIKeySpec is the format for per-workspace developer API keys ("Agentserver Secret Key" — `ask_` prefix). Minted via POST /api/workspaces/{wid}/api-keys; returned to the user as Token.Full once.
AgentserverTokenSpec is the format for the workspace-scoped codex remote token ("Agentserver Token" — `ast_` prefix) that users export as AGENTSERVER_TOKEN and pass to `codex --remote --remote-auth-token-env`. Same algorithm, different prefix so leaked-token scanners can distinguish.
Sizing rationale (shared by both):
- IDLen=16 chars of base62 = ~95 bits. Globally collision-free for any plausible total key count (birthday bound ~ 2^47 keys for 50% odds).
- SecretLen=48 chars of base62 = ~286 bits. Well past any cryptographic threshold; oversized vs strictly necessary to leave headroom.
- Total wire length: 4 + 16 + 1 + 48 + 6 = 75 chars including CRC32.
var ErrInvalidFormat = fmt.Errorf("secrets: invalid token format")
ErrInvalidFormat is returned by Parse when the presented token doesn't match the Spec's wire shape.
Functions ¶
func ConstantTimeMatch ¶
ConstantTimeMatch hashes presented and constant-time compares to storedHash. Returns false on any mismatch including length difference. Use this in auth paths to avoid timing leaks on prefix collisions.
func Hash ¶
Hash returns the storage hash for full. When a pepper is configured (production), it's HMAC-SHA256(pepper, full); otherwise plain SHA-256. Both produce 64-char lowercase hex.
func Parse ¶
Parse validates wire shape and splits a presented full token into (id, secret) without doing any crypto beyond the CRC32 integrity check. Returns ErrInvalidFormat on any structural mismatch (wrong prefix, wrong segment lengths, missing underscore separator, non-base62 character, or CRC32 mismatch).
Used by middleware to extract the indexable id before the DB lookup, so a malformed token never hits the DB.
func RandomHex ¶
RandomHex returns 2*nBytes hex chars from crypto/rand. For sites that just need an opaque bare-hex token (session cookies, internal proxy tokens) and don't want the prefix-id-secret structure. Equivalent to `rand.Read(b); hex.EncodeToString(b)` but centralizes the pattern.
func SetPepper ¶
func SetPepper(b []byte)
SetPepper installs the server-side HMAC key. Call once at startup; safe to call concurrently with Hash() but the typical flow is startup-only. Idempotent — same value reset is a no-op; a different value panics (we'd rather crash than silently break all stored hashes).
Types ¶
type Spec ¶
type Spec struct {
// Prefix is the human-readable type tag. MUST end in "_".
// Examples: "ask_" (workspace API secret key), "ast_" (agentserver
// remote token used as AGENTSERVER_TOKEN by the codex CLI).
Prefix string
// IDLen is the number of base62 chars in the public id segment.
// 8 is the project default; gives 47 bits of entropy in the id alone,
// enough to make collisions vanishingly unlikely across the lifetime
// of any one workspace.
IDLen int
// SecretLen is the number of base62 chars in the secret segment.
// 40 chars = ~238 bits, well above any cryptographic threshold.
SecretLen int
}
Spec defines the shape of a credential type. One Spec per kind.
type Token ¶
type Token struct {
// Full is what the user receives: "<prefix>_<id>_<secret><crc32>".
// Caller MUST present this back as `Authorization: Bearer <Full>`.
Full string
// ID is "<prefix>_<id>" — the public, indexable handle.
// Use as DB primary key for the row.
ID string
// Secret is the bare secret segment (no prefix, no id, no crc32).
// Most callers don't need to store this directly — store Hash instead.
Secret string
// Hash is the storage hash of Full (HMAC-SHA256 with pepper if set,
// otherwise plain SHA-256). Persist this; never persist Full or Secret.
Hash string
}
Token is a freshly minted credential. Only `Full` ever leaves the server (returned to the user once, never stored). `Hash` is what gets persisted; presented secrets are compared via ConstantTimeMatch.