secrets

package
v0.64.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2026 License: MIT Imports: 10 Imported by: 0

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

View Source
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.
View Source
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

func ConstantTimeMatch(presented, storedHash string) bool

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

func Hash(full string) string

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

func Parse(spec Spec, full string) (id, secret string, err error)

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

func RandomHex(nBytes int) (string, error)

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.

func Mint

func Mint(spec Spec) (Token, error)

Mint generates a new credential matching spec. Reads from crypto/rand and rejects bad specs (empty/no-underscore prefix, zero lengths) so misconfigured callers fail loud at startup rather than at the first mint.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL