Chapter 8: Secrets Management

Learning Objectives

Pre-Reading Check — Where Secrets Live

1. Why is "all-or-nothing access" a particularly dangerous property of environment variables for an assistant that runs untrusted third-party skills?

Environment variables can only hold one secret at a time, so skills compete for access
Any code loaded in the process can read the entire environment, so one malicious skill can read every key you hold
Environment variables are encrypted, so skills cannot decrypt them without a shared password
Skills must request permission from the operating system before reading any variable

2. Which capability does a secret manager (vault) provide that a plain environment variable fundamentally cannot?

A read-level audit trail that records who read a secret and when
Faster startup because secrets load instantly at process launch
Guaranteed immunity from all prompt-injection attacks
The ability to store secrets in plain text with zero setup effort

3. Why is baking a secret into a container image with ENV OPENAI_API_KEY=sk-... considered the worst place to put it?

Container images cannot read environment variables at runtime
The secret ships wherever the image ships and persists in image layers and git history, and cannot be rotated without a rebuild
Docker automatically publishes all ENV values to a public registry
Environment variables set in a Dockerfile are ignored by the running process

4. Why is a mounted "secret file" (tmpfs volume) a middle ground between an env var and a vault?

It is encrypted at rest by default and rotates itself automatically
It avoids some env-var leakage paths (not in /proc/environ, not inherited by every child) but still lacks encryption, rotation, and audit unless a manager provides them
It provides a full read-level audit trail like a vault but with no setup
It is identical to an environment variable in every security property

5. What is the distilled operating principle for handling real credentials with runtime injection?

Store everywhere, fetch never, scope broadly, rotate manually, log nothing
Store once, fetch just in time, scope narrowly, rotate automatically, log every access
Copy secrets into every artifact so any process can find them quickly
Keep all secrets in a single plain-text file for easy backup

Where Secrets Live

Key Points

A personal AI assistant is a credential magnet. To do anything useful it needs an OpenAI or Anthropic API key, a Slack or Telegram bot token, an email password or OAuth refresh token, a GitHub personal access token, maybe a Stripe key and a database connection string. Each of these is a secret — a credential that grants access or carries real risk if it leaks — and each one lives inside a process that reads untrusted inbound messages and runs third-party skills, exactly the kind of process most likely to be tricked into leaking or misusing what it holds. Think of the difference between a house key that opens every door forever and a hotel keycard that stops working at checkout: both open the same door today, but only one is safe to lose.

Environment Variables: Benefits, Risks, and Leakage Paths

The most common place a developer puts a secret is an environment variable, usually loaded from a .env file (for example OPENAI_API_KEY=sk-proj-abc123... or DATABASE_URL=postgres://agent:hunter2@db:5432/assistant). Env vars are popular because they are trivial: every language reads them in one line and no library is required. That convenience is the whole appeal — and also the whole problem. Environment variables are excellent at injection but they are not a storage or lifecycle system, and using them as one is where security falls apart.

The specific deficiencies each map to a way a personal assistant gets compromised:

None of this means env vars are forbidden. Harmless configuration — a log level, a feature flag, a region name — is fine in a plain env var; anything that grants access or carries risk if leaked belongs somewhere with encryption, scoping, audit, and rotation.

Figure 8.1: Leakage paths that expose environment-variable secrets from a single host

flowchart LR ENV["Process environment
(secrets in plain text)"] ENV --> PROC["/proc/PID/environ"] ENV --> CHILD["Inherited by every
child process"] ENV --> DBG["Debugger / memory dump"] ENV --> INSPECT["docker inspect /
kubectl exec"] ENV --> CI["CI / build logs"] ENV --> CRASH["Crash reporters
(Sentry, Datadog)"] ENV --> SKILL["Untrusted skill
reads whole env"] PROC --> LEAK(["Secret exposed"]) CHILD --> LEAK DBG --> LEAK INSPECT --> LEAK CI --> LEAK CRASH --> LEAK SKILL --> LEAK

Visual animation — coming soon

Secret Managers and Vaults

A secret manager (also called a vault) is a dedicated system whose job is to be the single source of truth for credentials — HashiCorp Vault, AWS Secrets Manager, Google Secret Manager, Azure Key Vault, CyberArk Conjur, and hosted products like Doppler and Infisical. Instead of the secret sitting in a file next to your code, the application authenticates to the manager and fetches the secret at runtime.

What a vault adds over a bare env var:

The trade-off is engineering effort: you fetch secrets through an SDK or CLI, define access policies, and update pipelines. For a hobby bot on a single machine that may be overkill on day one; for an assistant that reads strangers' messages and runs third-party skills, it is exactly the isolation you want. Note that "secret files" — mounted files, tmpfs volumes, or .env files — are a middle ground: a mounted file is not in /proc/environ and is not inherited by every child, but it still lacks encryption, rotation, and audit unless a manager provides them.

The comparison below summarizes the three storage models:

Property Environment variable Secret file (mounted/tmpfs) Secret manager / vault
Encryption at restNo (plain text)No, unless encrypted volumeYes, often HSM-backed
Per-secret access scopingNo — whole env readablePer-file permissions possibleYes, policy per identity
Read-level audit trailNoNoYes, every read logged
Automated rotationNo (restart needed)No, unless managedYes, no restart
Inherited by child processesYesNoNo
Visible via /proc/environ, docker inspectYesNoNo
Setup effortTrivialLowHigher (SDK, policies)
Good forNon-sensitive configInterim deliveryReal credentials, source of truth

Runtime Injection vs. Baked-In Secrets

The single worst place to put a secret is inside the deployment artifact — hardcoded in source, or baked into a container image via ENV OPENAI_API_KEY=sk-... or a copied .env. A baked-in secret ships wherever the image ships: to a registry, to every developer who pulls it, into every layer of the image history (deleting it in a later layer does not remove it from earlier layers), and into your git history if the Dockerfile is committed. It cannot be rotated without rebuilding and redeploying.

The alternative is runtime injection: the artifact contains no secrets, and the credential is delivered into the process only when it starts or, better, fetched on demand from the vault. The CNCF recommends that secrets be "injected at runtime through non-persistent mechanisms that are immune to leaks via logs, audit, or system dumps." In practice a sidecar or agent (for example, HashiCorp's Vault Agent) authenticates to the vault, retrieves current secrets, and writes them to a tmpfs file or the process environment at launch — with nothing sensitive persisted to the image or the repo. The distilled operating principle: store once, fetch just in time, scope narrowly, rotate automatically, and log every access.

Figure 8.2: Baked-in secrets ship everywhere vs. runtime injection keeps the artifact clean

flowchart LR subgraph BAKED["Baked-in (anti-pattern)"] SRC["Secret in source /
Dockerfile ENV"] --> IMG["Container image
(all layers)"] IMG --> REG["Registry"] IMG --> DEV["Every developer
who pulls"] IMG --> GIT["Git history"] end subgraph INJECT["Runtime injection (recommended)"] VAULT["Secret manager /
vault"] --> AGENT["Sidecar / Vault Agent
fetches on demand"] AGENT --> TMPFS["tmpfs file or env
at launch"] TMPFS --> PROC["Running process"] CLEAN["Clean artifact
(no secrets)"] --> PROC end

Visual animation — coming soon

Post-Reading Check — Where Secrets Live

1. Why is "all-or-nothing access" a particularly dangerous property of environment variables for an assistant that runs untrusted third-party skills?

Environment variables can only hold one secret at a time, so skills compete for access
Any code loaded in the process can read the entire environment, so one malicious skill can read every key you hold
Environment variables are encrypted, so skills cannot decrypt them without a shared password
Skills must request permission from the operating system before reading any variable

2. Which capability does a secret manager (vault) provide that a plain environment variable fundamentally cannot?

A read-level audit trail that records who read a secret and when
Faster startup because secrets load instantly at process launch
Guaranteed immunity from all prompt-injection attacks
The ability to store secrets in plain text with zero setup effort

3. Why is baking a secret into a container image with ENV OPENAI_API_KEY=sk-... considered the worst place to put it?

Container images cannot read environment variables at runtime
The secret ships wherever the image ships and persists in image layers and git history, and cannot be rotated without a rebuild
Docker automatically publishes all ENV values to a public registry
Environment variables set in a Dockerfile are ignored by the running process

4. Why is a mounted "secret file" (tmpfs volume) a middle ground between an env var and a vault?

It is encrypted at rest by default and rotates itself automatically
It avoids some env-var leakage paths (not in /proc/environ, not inherited by every child) but still lacks encryption, rotation, and audit unless a manager provides them
It provides a full read-level audit trail like a vault but with no setup
It is identical to an environment variable in every security property

5. What is the distilled operating principle for handling real credentials with runtime injection?

Store everywhere, fetch never, scope broadly, rotate manually, log nothing
Store once, fetch just in time, scope narrowly, rotate automatically, log every access
Copy secrets into every artifact so any process can find them quickly
Keep all secrets in a single plain-text file for easy backup
Pre-Reading Check — Never Commit API Keys

1. Why does deleting a committed secret in a later commit fail to fix the leak?

Git compresses old commits, making the secret unreadable after deletion
Git history is permanent, so the secret remains discoverable in earlier commits, and anyone who cloned or forked already has it
Deleting a file automatically triggers key rotation with the provider
Making the repository private removes the secret from all existing clones

2. In the layered defense against committed secrets, what makes GitHub push protection the "hard gate" compared with a pre-commit hook?

Push protection runs on the developer's laptop before the commit exists
A pre-commit hook can be bypassed with --no-verify, whereas push protection blocks the push at the remote and is hard to bypass
Push protection only scans the .gitignore file, so it is faster
Pre-commit hooks scan the entire history while push protection scans nothing

3. When a secret leaks, why is revoking it with the provider the first and most important step — before cleaning git history?

Revoking the key neutralizes it regardless of git history, so even a copy in an attacker's hands stops working
Cleaning git history un-exposes the secret and makes revocation unnecessary
Providers refuse to revoke a key until the git history has been rewritten
Rewriting history is faster than revoking, so it should always come first

4. Why does the speed of your response matter so much when a key leaks to a public repository?

GitHub deletes public repositories automatically after an hour
Automated bots scan the public commit stream and can collect and exploit fresh credentials within minutes
Keys expire on their own within a few minutes of being committed
The provider charges a penalty fee for every minute a key stays exposed

5. Which value does GitHub secret scanning add beyond simply detecting a hardcoded key?

It rewrites your git history automatically to remove the secret
Partner notification, where GitHub tells the provider directly so they can often auto-revoke the credential
It encrypts every committed secret so it can no longer be read
It prevents developers from ever using the --no-verify flag again

Never Commit API Keys

Key Points

How Keys Leak Into Git History, Logs, and Error Messages

The most common credential leak is embarrassingly simple: a developer hardcodes a key while testing, forgets it, and commits it — frequently to a public repository. Human error, not exotic attacks, remains the dominant cause of secret exposure. The trap that catches people is the belief that deleting the secret fixes it. It does not. Git permanently records every commit, so a secret committed once remains discoverable in the repository's history indefinitely — even after you delete the file in a later commit, make the repo private, or delete and recreate it. Anyone who cloned or forked before you noticed still has the full history. As GitGuardian puts it, leaking a secret and deleting it is "just like accidentally posting an embarrassing tweet, deleting it and just hoping no one saw it."

Worse, exploitation is fast and automated. GitHub exposes a public API that streams every commit in real time, and bots continuously scan that stream for fresh credentials. Leaked credentials from a public repo can be collected and exploited within minutes, and are typically abused within hours. For an unattended personal assistant, the window between "oops" and "someone is spending your OpenAI budget or reading your email" can be shorter than your next coffee break. Git commits are not the only leak path: the same key surfaces in logs (an assistant that logs its full outbound HTTP request logs the Authorization header), in error messages (a stack trace that prints the connection string), and in the many env-var channels above.

gitignore, Pre-Commit Scanning, and Push Protection

Defense against committed secrets is layered, moving detection as early ("left") as possible so the secret never reaches the remote:

LayerTool / controlWhen it actsBypassable?
Ignore.gitignoreAt git addYes (add -f); no help for inline keys
Pre-commitGitleaks, TruffleHogBefore commit existsYes (--no-verify)
PushGitHub push protectionBefore push reaches remoteHard to bypass
Post-hoc detectionGitHub secret scanning, GitGuardianAfter push / continuouslyN/A (detection, not prevention)

What To Do When a Secret Is Exposed

When a secret leaks, the instinct is to scrub the git history first. That instinct is wrong and dangerous, because rewriting history does nothing to stop an attacker who already copied the key. Any leaked secret must be treated as immediately compromised. Suppose your assistant's OpenAI key appears in a public commit at 2:14 PM. Following GitHub's recommended remediation flow — a revoke-first runbook:

  1. Identify the secret: it is the OPENAI_API_KEY, hardcoded in agent/config.py, owned by your assistant's OpenAI project.
  2. Assess risk: a live production key with billing attached and full model access. High risk.
  3. Strategize: high risk means act immediately, not after a coordinated cleanup.
  4. Revoke the secret with the provider. This is the single most important step — revoking the key neutralizes it regardless of git history, so even a copy in an attacker's hands stops working. Do this first.
  5. Update affected services with a freshly issued key everywhere the old one was used — the assistant process, CI, any sidecar.
  6. Check for unauthorized access by reviewing access and audit logs (provider usage dashboard, AWS CloudTrail/CloudWatch, Slack audit logs) to see whether the key was already abused.
  7. Clean repository history (optional). Rewriting history is destructive, usually requires a force-push, and does not un-expose an already-discovered secret — weigh compliance needs against disruption.
  8. Resolve the alert and document the incident.
  9. Prevent future leaks by enabling secret scanning plus push protection and adopting the pre-commit habits above.

The order is the lesson: revoke first, clean up later. Rotation and revocation neutralize the secret; history rewriting only tidies the evidence. This is where the earlier investment in rotation pays off — if revoking and reissuing a key is a one-command operation you have practiced, step 4 takes seconds instead of an afternoon.

Figure 8.3: Revoke-first remediation runbook for a leaked secret

flowchart TD LEAK(["Secret exposed
(treat as compromised)"]) --> ID["1. Identify the secret
and where it lives"] ID --> ASSESS["2. Assess risk
(live? billing? scope?)"] ASSESS --> STRAT["3. Strategize
(high risk = act now)"] STRAT --> REVOKE["4. REVOKE with provider
(neutralizes key regardless
of git history)"] REVOKE --> UPDATE["5. Update affected services
with freshly issued key"] UPDATE --> CHECK["6. Check access / audit logs
for unauthorized use"] CHECK --> CLEAN["7. Clean git history
(optional, destructive)"] CLEAN --> RESOLVE["8. Resolve alert,
document incident"] RESOLVE --> PREVENT["9. Enable scanning +
push protection"]

Visual animation — coming soon

Post-Reading Check — Never Commit API Keys

1. Why does deleting a committed secret in a later commit fail to fix the leak?

Git compresses old commits, making the secret unreadable after deletion
Git history is permanent, so the secret remains discoverable in earlier commits, and anyone who cloned or forked already has it
Deleting a file automatically triggers key rotation with the provider
Making the repository private removes the secret from all existing clones

2. In the layered defense against committed secrets, what makes GitHub push protection the "hard gate" compared with a pre-commit hook?

Push protection runs on the developer's laptop before the commit exists
A pre-commit hook can be bypassed with --no-verify, whereas push protection blocks the push at the remote and is hard to bypass
Push protection only scans the .gitignore file, so it is faster
Pre-commit hooks scan the entire history while push protection scans nothing

3. When a secret leaks, why is revoking it with the provider the first and most important step — before cleaning git history?

Revoking the key neutralizes it regardless of git history, so even a copy in an attacker's hands stops working
Cleaning git history un-exposes the secret and makes revocation unnecessary
Providers refuse to revoke a key until the git history has been rewritten
Rewriting history is faster than revoking, so it should always come first

4. Why does the speed of your response matter so much when a key leaks to a public repository?

GitHub deletes public repositories automatically after an hour
Automated bots scan the public commit stream and can collect and exploit fresh credentials within minutes
Keys expire on their own within a few minutes of being committed
The provider charges a penalty fee for every minute a key stays exposed

5. Which value does GitHub secret scanning add beyond simply detecting a hardcoded key?

It rewrites your git history automatically to remove the secret
Partner notification, where GitHub tells the provider directly so they can often auto-revoke the credential
It encrypts every committed secret so it can no longer be read
It prevents developers from ever using the --no-verify flag again
Pre-Reading Check — Credential Rotation

1. Using the hotel-keycard analogy, why do short-lived credentials shift security from prevention to rapid containment?

They make keys physically impossible to copy, like a metal house key
They expire on their own after minutes or hours, so a stolen copy becomes worthless before it matters — you assume it may leak rather than hope it never does
They require a human to approve every single use of the credential
They store the secret in encrypted form so theft is impossible

2. What defines a dynamic secret, such as one from Vault's database secrets engine?

A static value stored once and rotated manually every quarter
A credential generated on demand with a TTL and lease, automatically revoked when it expires
A password shared across every service so it only needs one rotation
An API key hardcoded into the container image at build time

3. How does OIDC / workload identity federation end the "secret zero" bootstrapping problem?

It stores the bootstrap key in an extra-secure plain-text file
The workload proves its identity with a short-lived, provider-issued token instead of presenting a stored static secret
It disables authentication to the vault entirely
It emails a new master password to the operator every hour

4. Why is automated renewal (e.g., Vault Agent) what makes short TTLs practical?

It permanently disables expiration so credentials never need renewing
It refreshes short-lived credentials before they expire, so legitimate work is never interrupted and no custom renewal code is needed
It converts every short-lived credential into a static long-lived key
It requires the operator to manually re-enter the secret each hour

5. How do short, per-request dynamic credentials reduce blast radius compared with one shared static credential?

They make audit logging unnecessary because nothing can ever be stolen
They compress the attacker's usable window to minutes and let you revoke one compromised credential individually instead of forcing a global rotation
They guarantee an attacker can never intercept a credential in transit
They eliminate the need to ever revoke a credential at all

Credential Rotation

Key Points

Why Rotation Limits the Value of a Stolen Secret

Long-lived static credentials are among the most significant vulnerabilities in modern infrastructure: once stolen, they stay valid for weeks, months, or years, giving an attacker an extended window to exploit them. Rotation attacks this directly by capping how long any single credential is usable. Return to the hotel keycard analogy. A traditional metal house key is a static credential: copy it once and you have access until the lock is physically changed. A hotel keycard is a rotating credential: it works today and is dead at checkout, so a copy made yesterday is worthless tomorrow. Short-lived (ephemeral) credentials apply the keycard model to software — they automatically expire after a brief, predefined period, typically minutes or hours. This shifts your posture from prevention (never let the key leak) to rapid containment (assume it might leak, and make sure it expires before it matters).

Automated Rotation, Short-Lived Tokens, and Dynamic Secrets

A dynamic secret is a credential generated on demand and revoked automatically when it expires, rather than a static value stored and manually rotated. HashiCorp Vault's database secrets engine is the canonical example, and its lifecycle illustrates the pattern:

  1. Generation — Vault creates fresh credentials with a defined time-to-live (TTL).
  2. Issuance — the credentials come with a lease ID that tracks their lifetime.
  3. Usage — the client uses the temporary credentials for the lease period, renewing if needed.
  4. Revocation — at lease expiration (or manual trigger), Vault automatically disables or deletes the credentials.

Figure 8.4: Lifecycle of a dynamic secret (generation to automatic revocation)

stateDiagram-v2 [*] --> Generated: Vault mints credential
with TTL Generated --> Issued: Attach lease ID
tracking lifetime Issued --> InUse: Client uses temporary
credentials InUse --> InUse: Renew before
lease expires InUse --> Revoked: Lease expires
or manual trigger Revoked --> [*]: Credential disabled /
deleted automatically

Rather than your assistant holding one permanent database password, it asks Vault for credentials when it needs them; Vault mints a unique username/password valid for, say, one hour, and tears them down afterward. A short-lived token is the same idea for API access: an OAuth access token with a short expiry plus a refresh token, or IAM temporary credentials, so no long-lived key is ever stored.

Where does the "secret zero" chain end — how does the process authenticate to Vault without yet another static key? The modern answer is OIDC / workload identity federation. In GitHub Actions, the official Vault Action authenticates via OIDC, so the pipeline proves its identity to Vault using a short-lived, GitHub-issued token instead of a stored static secret. This eliminates the last long-lived key. Similarly, Vault's AWS secrets engine dynamically generates IAM access keys scoped to a policy and rotates them automatically, and the PKI/SSH engines issue short-lived certificates instead of persistent keys.

Figure 8.5: OIDC workload-identity federation ends the "secret zero" chain

sequenceDiagram participant Pipeline as GitHub Actions job participant GitHub as GitHub OIDC issuer participant Vault participant Provider as Credential provider (DB/AWS) Pipeline->>GitHub: Request short-lived OIDC token GitHub-->>Pipeline: Signed identity token (no stored secret) Pipeline->>Vault: Authenticate with OIDC token Vault->>Vault: Verify token, match role/policy Vault->>Provider: Generate dynamic credential (TTL) Provider-->>Vault: Fresh scoped credential Vault-->>Pipeline: Short-lived secret + lease Note over Pipeline,Provider: Lease expires; Vault revokes automatically

Automation is what makes short TTLs practical. If credentials expired mid-task with no renewal, legitimate work would break. Tools like Vault Agent automatically authenticate to Vault and renew short-lived secrets, writing them to a local file or environment variable, so expiration never interrupts a running workflow and you write no custom token-renewal code.

Credential typeLifetimeOn theftExample
Static long-livedMonths–yearsValid until manually revokedHardcoded API key
Manually rotatedDays–weeksValid until next rotationPassword rotated quarterly
Short-lived / dynamicMinutes–hoursExpires on its ownVault DB credential, 1h TTL
OIDC / workload identityPer-requestNo stored secret to stealGitHub Actions → Vault via OIDC

Visual animation — coming soon

Rotation Runbooks and Blast-Radius Reduction

Rotation's payoff is blast-radius reduction — shrinking how much damage a single compromised credential can do. Short TTLs compress the attacker's usable window from calendar durations to minutes: a one-hour TTL on production database credentials means an intercepted credential is useless within sixty minutes. Netflix's "Bless" SSH-certificate system is the canonical illustration — employees get briefly-valid certificates, so any intercepted credential expires almost immediately. And because dynamic credentials are unique per request, a compromised application's credentials can be revoked individually rather than forcing a global rotation across every service.

Rotation best practices worth codifying for an assistant:

A rotation runbook turns all of this into a rehearsed, boring procedure so that the leaked-secret response is fast under pressure. A minimal runbook entry for a personal assistant:

StepAction
TriggerLeak detected, scheduled rotation, or personnel change
1. Issue newMint the replacement credential (ideally automated / dynamic)
2. DeployPush the new credential to every consumer via the vault
3. VerifyConfirm the assistant works on the new credential
4. Revoke oldDisable the old credential with the provider
5. AuditReview access logs for use of the old credential after issuance
6. RecordLog the rotation in the audit trail (Chapter 11)

Adopting short-lived credentials does not have to be a big-bang migration. A practical path is to start small with one low-risk pipeline, inventory existing static credentials using the secret-scanning tools from earlier, pilot a dynamic secrets engine against a database or PKI, integrate with CI/CD using OIDC-based authentication, and only then add monitoring and scale. Together, these turn the inevitable secret exposure from "how bad is the breach" into "the key had already expired."

Post-Reading Check — Credential Rotation

1. Using the hotel-keycard analogy, why do short-lived credentials shift security from prevention to rapid containment?

They make keys physically impossible to copy, like a metal house key
They expire on their own after minutes or hours, so a stolen copy becomes worthless before it matters — you assume it may leak rather than hope it never does
They require a human to approve every single use of the credential
They store the secret in encrypted form so theft is impossible

2. What defines a dynamic secret, such as one from Vault's database secrets engine?

A static value stored once and rotated manually every quarter
A credential generated on demand with a TTL and lease, automatically revoked when it expires
A password shared across every service so it only needs one rotation
An API key hardcoded into the container image at build time

3. How does OIDC / workload identity federation end the "secret zero" bootstrapping problem?

It stores the bootstrap key in an extra-secure plain-text file
The workload proves its identity with a short-lived, provider-issued token instead of presenting a stored static secret
It disables authentication to the vault entirely
It emails a new master password to the operator every hour

4. Why is automated renewal (e.g., Vault Agent) what makes short TTLs practical?

It permanently disables expiration so credentials never need renewing
It refreshes short-lived credentials before they expire, so legitimate work is never interrupted and no custom renewal code is needed
It converts every short-lived credential into a static long-lived key
It requires the operator to manually re-enter the secret each hour

5. How do short, per-request dynamic credentials reduce blast radius compared with one shared static credential?

They make audit logging unnecessary because nothing can ever be stolen
They compress the attacker's usable window to minutes and let you revoke one compromised credential individually instead of forcing a global rotation
They guarantee an attacker can never intercept a credential in transit
They eliminate the need to ever revoke a credential at all

Your Progress

Answer Explanations