Token Security skill for Codex

Keys you can detect, scope, rotate and kill. Codex designs API keys, access tokens and webhook secrets so that a leak is detectable and short-lived: prefixed high-entropy formats that scanners recognise, digests instead of raw tokens in the database, resource-level scopes, expiry and last-used tracking. It adds dual-secret rotation for zero-downtime changes and refresh token reuse detection, and moves tokens out of URLs and localStorage.

  • Security
  • $token-security
  • 157 lines
  • 2 files
  • ~1,800 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- token-security

Installs to ~/.agents/skills/token-security/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $token-security, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs and reviews API keys, personal access tokens, refresh tokens, webhook secrets and service credentials - prefixed high-entropy formats that secret scanners can detect, hashed storage, least-privilege scopes, expiry, rotation with overlap, revocation, last-used tracking and safe transport and browser storage. Use when building an API key system, issuing tokens to customers or services, reviewing how tokens are stored or compared, planning key rotation, or deciding between cookies and localStorage for tokens.

Use it when

  • Building API keys or personal access tokens for customers
  • Reviewing how tokens are generated, stored, compared or logged
  • Planning rotation for webhook secrets or service credentials
  • Choosing token storage for a browser or mobile client

Use something else when

  • You are validating or issuing JWTs specifically: use jwt-auth
  • A secret was committed to git and must be found or purged: use secret-scanning
  • You manage secrets for CI pipelines: use secrets-in-ci

How to check the result

Tests reject revoked, expired, wrong-scope and wrong-environment keys; the database holds only digests and last-4 characters; a staging rotation drill shows no failed requests.

The mistake it prevents

Storing raw API keys in the database or logging the Authorization header, so one read-only leak exposes every customer's credentials.

~/.agents/skills/token-security/SKILL.md

--- name: token-security description: Designs and reviews API keys, personal access tokens, refresh tokens, webhook secrets and service credentials - prefixed high-entropy formats that… license: MIT ---

Done well means: a leaked token is detectable (recognisable format), useless after a short time or a single revoke, limited to the smallest scope that works, and never recoverable from your database; and rotating any secret is a routine, zero-downtime operation rather than an outage.

Inspect first

rg -n -i "api_?key|access_?token|refresh_?token|personal_?access|secret|bearer|x-api-key|webhook_secret|signing_secret" -g '!node_modules' -g '!*.lock' | head -80
rg -n "localStorage\.(set|get)Item\(.*(token|jwt|key)" -i -g '!node_modules'
rg -n "==\s*(api_?key|token)|token\s*===|\.equals\(.*[Tt]oken" -g '!node_modules'

Inventory every credential type: who issues it, to whom, what it can do, how long it lives, where it is stored (server and client), how it is revoked.

Choose the token type

NeedTokenLifetime
Customer or developer calling your APIOpaque API key / personal access token, scopedExpiry chosen at creation (default 90 days, max 1 year), revocable
Browser session for your own web appSession cookie (opaque ID)Idle 30 min - 24 h, absolute 7-30 days
SPA or mobile app via OAuthShort access token + rotating refresh tokenAccess 5-15 min; refresh 1-30 days with reuse detection
Service to service inside your platformWorkload identity (cloud IAM, SPIFFE, OIDC federation)Minutes, auto-rotated
CI to cloudOIDC federation, no stored keyPer job
Webhook signingPer-endpoint HMAC secretRotate on demand with dual secrets

Opaque tokens checked against a store are revocable instantly. Self-contained JWTs are not, unless you add a denylist; keep them short-lived. JWT validation specifics belong to $jwt-auth.

API key format

<prefix>_<random>_<checksum>
acme_live_4fQ9tY...Zk2 (prefix + env, 32 bytes base62 random, CRC32 checksum)
  • Prefix (acme_live_, acme_test_) lets secret scanners, log filters and support identify the key and its environment. Register the pattern with GitHub secret scanning partner program if you are a public API provider.
  • At least 128 bits (prefer 256) from a CSPRNG: secrets.token_urlsafe(32), crypto.randomBytes(32), crypto/rand.
  • Checksum lets you reject typos and scanner false positives offline without a DB hit.
  • Show the full key exactly once at creation. Afterwards show only the prefix and last 4 characters.

Storage and comparison

High-entropy random tokens do not need slow password hashing. Store SHA-256(token) (or HMAC-SHA256 with a server key) and look up by the hash:

import hashlib, hmac, secrets

def issue_key(env: str) -> tuple[str, str]:
    raw = f"acme_{env}_{secrets.token_urlsafe(32)}"
    return raw, hashlib.sha256(raw.encode()).hexdigest()     # give raw to user, store digest

def authenticate(presented: str):
    digest = hashlib.sha256(presented.encode()).hexdigest()
    row = db.api_keys.find_one(digest=digest, revoked_at=None)
    if row is None or row.expires_at < now():
        return None
    db.api_keys.touch_last_used(row.id)                        # async/batched in practice
    return row

When you must compare secrets directly (webhook signatures, static shared secrets), use constant-time comparison: hmac.compare_digest, crypto.timingSafeEqual (equal-length buffers), subtle.ConstantTimeCompare, MessageDigest.isEqual.

Passwords chosen by humans are different: use $password-storage.

Scopes and least privilege

  • Scopes are resource + action (orders:read, orders:write, billing:admin), checked on every endpoint, defaulting to deny.
  • Restrict keys further where the product allows: allowed IP ranges, specific projects or tenants, read-only by default.
  • Separate test and live keys with different prefixes and backends; a test key must never work against live data.
  • Admin and billing scopes require shorter expiry and step-up re-authentication to create.

Rotation without downtime

- [ ] Support two valid secrets per credential at once (current + next)
- [ ] Issue the new secret; deploy it to consumers
- [ ] Watch last_used_at: traffic moves from old to new
- [ ] Revoke the old secret (confirm with the user before revoking production credentials)
- [ ] Record rotation date; schedule the next one or alert on age

For webhook signing, send signatures for both secrets during the overlap (for example v1=<sig_old>,v1=<sig_new>) so receivers can switch at their own pace.

Transport and client storage

RuleReason
Tokens in Authorization header, never in URLsURLs land in logs, history, Referer headers and analytics
HTTPS only; reject plain HTTP requests carrying keys, then revoke that keyIt has already crossed the network in clear text
Browser apps: prefer HttpOnly; Secure; SameSite cookies or a backend-for-frontendlocalStorage is readable by any XSS
If tokens must live in JS, keep them in memory, short-lived, with refresh via HttpOnly cookieLimits theft window
Mobile: Keychain / Android Keystore, never shared preferences in plain text
Redact tokens in logs, error trackers and APM (match your prefix)Logs are the most common leak path

Refresh token rotation

  • Each refresh returns a new refresh token and invalidates the old one.
  • Reuse of an already-used refresh token means theft: revoke the whole token family and force re-login.
  • Bind refresh tokens to the client (DPoP or mTLS for high-value APIs) where supported.

Leak response

When a token leaks (commit, log, screenshot, support ticket): revoke first, then investigate usage via last_used_at and access logs, then notify the owner. Purging it from git history comes after revocation; see $secret-scanning. Revoking a customer’s production key affects their service: confirm with the user and notify the customer.

Verification

  • Unit tests: a revoked key, an expired key, a key with the wrong scope and a test-env key on live all return 401/403.
  • rg for the token prefix in log output from a test run returns nothing.
  • Database inspection shows only digests, prefixes and last-4, no raw tokens.
  • A rotation drill in staging: issue new, switch consumer, revoke old, no failed requests.

Output template

## Token review: <system>
| Credential | Format | Storage | Scope | Expiry | Rotation | Issues |
|---|---|---|---|---|---|---|
| Customer API key | acme_live_ + 32B | sha256 digest | per-scope | none -> 90 d | manual -> dual-key | raw key logged in access log |
Leak detection: <prefix registered with scanners? log redaction?>
Next actions: <ordered list>

Gotchas

  • Logging the full Authorization header in request logs or error reports is the most common way keys leak; scrub at the logger, not at each call site.
  • crypto.timingSafeEqual throws on unequal lengths; hash both sides first or check lengths in a way that does not leak the secret’s length.
  • Rate-limit authentication failures per key prefix and per IP to slow online guessing.
  • Long-lived keys with no last_used_at cannot be safely cleaned up; add tracking first.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Token Security"
  short_description: "Keys you can detect, scope, rotate and kill"
  default_prompt: "$token-security Design an API key system for our public API with scopes and rotation"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem