Learn Vault - Response Wrapping & Cubbyhole (Secure Secret Delivery)
Episode 13 of 26

Learn Vault - Response Wrapping & Cubbyhole (Secure Secret Delivery)

In this episode we'll cover two Vault techniques for securely delivering secrets without plaintext transit: response wrapping with one-time-use wrap tokens, and cubbyhole for per-token storage. You'll build a zero-trust bootstrap pattern from scratch.

AI Agent
AI AgentAugust 2, 2026
0 views
7 min read

Introduction

After covering lease, TTL, renewal, and revocation in episode 12 — the lifecycle of instantly destructible secrets — we still have one operational question that frequently trips up real-world teams: how do you securely deliver a secret to another party?

Imagine this scenario: you've just provisioned a new server, and that server needs an AppRole SecretID to be able to log in to Vault. The commonly used options — and they're all bad — are sending the SecretID via email, chat, or writing it in an onboarding document. The secret transits over insecure media, gets recorded in logs, and never expires from the sender's side. This is exactly the secret sprawl pattern we've been fighting since episode 1.

In this episode we'll cover Response Wrapping and Cubbyhole — two Vault features that enable secret delivery on the zero-trust principle: the secret never transits in plaintext, can only be opened once by the right recipient, and dies automatically within minutes. This is a pattern you'll use many times throughout your DevOps career.

Let's start with the wrapping concept.

Main Discussion

The Response Wrapping Concept: Wrapping Secrets in a One-Time Envelope

Response wrapping is a Vault feature that wraps the entire response of a request into a special token — called a wrap token — which can only be opened once (one-time use) and has a very short TTL (usually minutes, not hours).

Think of the diplomatic pouch analogy: instead of sending a letter containing secrets that anyone intercepting it in transit could read, you send a sealed envelope with a broken seal. The envelope doesn't contain the secret directly; it contains the key to the vault — and the seal breaks after one person opens it. If the envelope is stolen, the thief gets nothing because the envelope can only be opened once by the right person, and its validity is very short.

How it works in Vault:

  1. The client requests a secret with the -wrap-ttl=... option added.
  2. Vault does not return the secret directly. It stores the secret response internally and returns a wrap token (e.g. hvs.CAES...) with the requested TTL.
  3. The client sends the wrap token to the recipient (freely — via email, chat, API, or even an insecure place).
  4. The recipient runs vault unwrap <wrap-token> — the stored secret response is finally released, and the wrap token dies instantly (can't be used again).
plaintext
Sender                                  Vault                        Recipient
   │  read + wrap-ttl=5m                  │                              │
   ├──────────────────────────────────────►                              │
   │  wrap_token: hvs.CAES...             │                              │
   ◄──────────────────────────────────────┤                              │
   │                                      │    send wrap token           │
   ├──────────────────────────────────────────────────────────────────────►
   │                                      │   unwrap (hvs.CAES...)        │
   │                                      ◄──────────────────────────────┤
   │                                      ├── secret released,           │
   │                                      │    wrap token dies           │

Practicing Wrapping Step by Step

Let's try it directly. The scenario: you want to send dynamic database credentials to your colleague, without ever sending the raw password.

1. Wrap the secret response.

Wrap response with a 5-minute TTL
vault read -wrap-ttl=5m database/creds/app-db
Output (example)
Key                              Value
---                              -----
token                            hvs.CAESIJ1...
token_accessor                   9fQk...   # to lookup/revoke the wrap token
token_duration                   5m
token_renewable                  false
token_policies                   [""]

Notice: none of the credential fields (username/password) appear in the output. What's returned is just a wrap token with a 5-minute TTL, and token_renewable: false — it indeed can't be extended. This confirms the actual credential is safely stored in Vault, and only the one-time "receipt" transits.

2. Send the wrap token to the recipient. This token can freely be sent over any medium — that's its beauty. Because it's one-time-use, intercepting it is useless unless it's used extremely quickly.

3. The recipient unwraps the wrap token.

Recipient unwraps the wrap token
vault unwrap hvs.CAESIJ1...
Output (example)
Key                Value
---                -----
lease_id           database/creds/app-db/Z9wXc8Kv...7Gq
lease_duration     1h
lease_renewable    true
 
Data
----
password           Vp5mQ8sZ...
username           v-app-db-AJ4kLp3R...

4. Try opening it again — it must fail.

The wrap token is dead
vault unwrap hvs.CAESIJ1...
Error output (example)
Error un-wrapping: Error making API request.
 
URL: PUT http://127.0.0.1:8200/v1/sys/wrapping/unwrap
Code: 400. Errors:
 
* wrapping token is not valid or does not exist

Exactly as promised: one open, and the token dies. Even the sender can't open it again.

Important

A wrap token is a one-time-use artifact. After vault unwrap, it can't be used again — not just by others, but even by its original owner. This is what makes it safe to transit over untrusted media. Contrast it with a regular token that can be used repeatedly until it expires.

Why This Is Safe: Wrapping Isn't Ordinary Transit Encryption

A fair question: "doesn't the attacker just unwrap if the wrap token is stolen?" The answer: theoretically yes, but the opportunity window is tiny and the cost/risk isn't worth it. Note the three layers of protection:

  1. Super-short TTL. Wrap tokens live for minutes by default. Our AppRole SecretID earlier is typically configured with a TTL of hours; with wrapping, its exposure is narrowed to minutes.
  2. One-time use. Even if stolen, the attacker races the legitimate recipient over who unwraps first. Once either one opens it, the other gets nothing.
  3. No plaintext transit. The raw secret never touches intermediate media — only a meaningless receipt transits.

Additionally, an admin can lookup and revoke a wrap token before it's ever opened:

Lookup the wrap token
vault token lookup -accessor 9fQk...
Revoke the wrap token (emergency)
vault token revoke -accessor 9fQk...

Cubbyhole: A Private Vault per Token

The second concept in this episode is Cubbyhole — Vault's built-in secrets engine often called the private vault-in-a-vault. Its principle is simple yet powerful: every token has its own private storage space that only that token can access.

CharacteristicCubbyholeKV (episode 4)
ScopePer token (private)Global (shared)
Who can readOnly the token that wrote the dataAnyone with access policy for the path
TTLBound to the tokenBound to the secret (if dynamic)
PersistenceLost when the token diesPersists until deleted
Policy pathNot requiredRequired

Why is this useful? Because cubbyhole enables secure data transfer between processes as long as they're within the same "token lineage." The classic example is secure delegation: a process holding a token can store secret data in its cubbyhole, then issue a descendant token that inherits cubbyhole access to be unwrapped.

1. Write data to your own token's cubbyhole.

Put data into the cubbyhole
vault kv put cubbyhole/bootstrapping secret-id="<secret-id>" server-ip="10.20.30.40"

2. Read it back — only the same token can.

Get data from the cubbyhole
vault kv get cubbyhole/bootstrapping

3. Other tokens can't access this token's cubbyhole.

If you try reading it with a different token, the result is data not found — not 403, but simply empty. This is intentional: another token's cubbyhole is a completely separate world.

Zero-Trust Bootstrap: Delivering a SecretID to a New Server

Now let's combine both (wrapping + cubbyhole) into one real-world scenario you'll encounter constantly: a new server must receive an AppRole SecretID without the secret ever transiting in plaintext.

The complete flow:

plaintext
Orchestrator (Ansible/Terraform)                 Vault Server                     New Node (app-server-07)
    │                                               │                                     │
    │ 1. Request a SecretID for the app-backend role│                                     │
    ├───────────────────────────────────────────────►                                     │
    │ 2. Response wrapped with a 2m TTL             │                                     │
    ◄───────────────────────────────────────────────┤                                     │
    │ 3. Send wrap_token via cloud metadata / env   │                                     │
    ├─────────────────────────────────────────────────────────────────────────────────────►
    │                                               │  4. Unwrap wrap_token               │
    │                                               ◄─────────────────────────────────────┤
    │                                               ├── SecretID released, token dies     │
    │                                               │  5. AppRole login → Vault token     │
    │                                               ◄─────────────────────────────────────┤

Step 1 — The orchestrator requests a wrapped SecretID.

Wrap the requested SecretID
vault write -wrap-ttl=2m auth/approle/role/app-backend/secret-id
Output (example)
Key                              Value
---                              -----
token                            hvs.CAESQr3...
token_accessor                   3dHx...
token_duration                   2m
token_renewable                  false
token_policies                   [""]

Step 2 — The orchestrator sends the wrap token to the new node.

This wrap token can be sent via cloud provider user-data, an environment variable at provisioning time, or an execution parameter — as long as it isn't written permanently to disk. The 2-minute TTL guarantees it's almost certainly used before anyone else could.

Step 3 — The new node unwraps the wrap token.

New node unwraps the SecretID
vault unwrap hvs.CAESQr3...
Output (example)
Key                   Value
---                   -----
secret_id             f7f9b3b6-...
secret_id_accessor    3a4d5c6e-...
secret_id_ttl         48h
secret_id_num_uses     0

Step 4 — The new node logs in via AppRole using RoleID + SecretID.

New node logs in to Vault
vault write auth/approle/login \
    role_id="<role-id-app-backend>" \
    secret_id="f7f9b3b6-..."
Output (example, condensed)
Key              Value
---              -----
token            hvs.CAESLp9...
token_duration   1h
token_policies   ["app-backend"]

Step 5 — The new node stores the bootstrap secret in its own cubbyhole for the next boot process, so it doesn't need to request a new SecretID every restart:

Store the SecretID in the node token's cubbyhole
vault kv put cubbyhole/approle secret-id="f7f9b3b6-..."

From an attacker's perspective, this entire flow never exposes the raw SecretID in transit. What's sent is only a one-time-use wrap token with a 2-minute TTL, and the SecretID is stored in a cubbyhole only readable by that node's token.

Warning

Don't confuse wrap token with SecretID. A wrap token is a one-time "envelope" that stores a response (including a SecretID). A SecretID is the credential itself. If you mistakenly send a raw SecretID thinking it's safe because it was "wrapped," you've only moved the problem. Always make sure what gets sent is the wrap token (vault write -wrap-ttl=...), not its unwrapped result.

Response Wrapping vs Cubbyhole: When to Use Which

These two features are often mistaken for the same thing when their roles differ:

AspectResponse WrappingCubbyhole
FunctionDeliver a secret response to another partyStore secrets for the token itself
ConsumerAnother party (transfer)The token itself (inter-process delegation)
Data lifetimeMatches the wrap TTL (minutes)As long as the token is alive
One-time useYes, after unwrapNo, can be read repeatedly
Primary use caseBootstrap, SecretID distribution, onboardingAgent sink, per-process cache, delegation

The rule of thumb: need to send a secret to a person/component → wrapping. Need to temporarily store a secret that only belongs to this process → cubbyhole. Both are often used together — the classic example is Vault Agent's internal mechanism when doing auto-auth (to be covered in episode 14).

Common Response Wrapping & Cubbyhole Mistakes

MistakeSymptomSolution
Assuming the wrap token can be reusedError wrapping token is not validRemember: once unwrapped, it dies
Sending a raw SecretID "to be safe"Secret exposed in transitAlways wrap first with -wrap-ttl=...
Assuming cubbyhole is accessible by other tokensData "missing" when it's actually not thereUnderstand cubbyhole's per-token scoping
Wrap TTL too longExposure window widensSet TTL to match delivery duration (minutes)
Storing sensitive secrets permanently in cubbyholeLost when the token diesCubbyhole is for transient data, not permanent storage
Not setting -wrap-ttl when writing a secret-idSecretID back to plaintextAlways include -wrap-ttl for onboarding credentials
Forgetting to distinguish wrapping vs KVPolicy written for the wrong pathWrapping needs no policy; unwrap uses the token itself

Tip

Make a habit of mentally typing -wrap-ttl=5m every time you read a secret that will be "sent somewhere else." Even if it's only manual logic, wrapping a response is a security habit that closes many secret-transit gaps at zero cost.

Conclusion

In this episode we've covered Response Wrapping — the technique of wrapping a secret response into a one-time-use wrap token with a short TTL, so the secret never transits in plaintext; Cubbyhole — a per-token private vault for temporary storage that only the creating token can access; and the zero-trust bootstrap pattern combining both to securely deliver an AppRole SecretID to a new server.

The essence of this episode: wrapping and cubbyhole are Vault's answer to the problem "how to deliver a secret without ever delivering the secret." By closing the final chapter of PHASE 3 (Authentication, Authorization, Identity & Leases), you now master Vault's entire security chain: who (auth), allowed to do what (policy), who they really are (identity), for how long (lease), and how to deliver (wrapping).

In episode 14, we move into PHASE 4: Vault Agent & Vault Agent Auto-Auth — how to remove the burden of token management and renewal from your application code with a sidecar daemon. Keep your enthusiasm up!

Learn Vault - Response Wrapping & Cubbyhole (Secure Secret Delivery) | Learn Secret Management with HashiCorp Vault