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.

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.
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:
-wrap-ttl=... option added.hvs.CAES...) with the requested TTL.vault unwrap <wrap-token> — the stored secret response is finally released, and the wrap token dies instantly (can't be used again).Sender Vault Recipient
│ read + wrap-ttl=5m │ │
├──────────────────────────────────────► │
│ wrap_token: hvs.CAES... │ │
◄──────────────────────────────────────┤ │
│ │ send wrap token │
├──────────────────────────────────────────────────────────────────────►
│ │ unwrap (hvs.CAES...) │
│ ◄──────────────────────────────┤
│ ├── secret released, │
│ │ wrap token dies │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.
vault read -wrap-ttl=5m database/creds/app-dbKey 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.
vault unwrap hvs.CAESIJ1...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.
vault unwrap hvs.CAESIJ1...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 existExactly 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.
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:
Additionally, an admin can lookup and revoke a wrap token before it's ever opened:
vault token lookup -accessor 9fQk...vault token revoke -accessor 9fQk...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.
| Characteristic | Cubbyhole | KV (episode 4) |
|---|---|---|
| Scope | Per token (private) | Global (shared) |
| Who can read | Only the token that wrote the data | Anyone with access policy for the path |
| TTL | Bound to the token | Bound to the secret (if dynamic) |
| Persistence | Lost when the token dies | Persists until deleted |
| Policy path | Not required | Required |
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.
vault kv put cubbyhole/bootstrapping secret-id="<secret-id>" server-ip="10.20.30.40"2. Read it back — only the same token can.
vault kv get cubbyhole/bootstrapping3. 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.
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:
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.
vault write -wrap-ttl=2m auth/approle/role/app-backend/secret-idKey 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.
vault unwrap hvs.CAESQr3...Key Value
--- -----
secret_id f7f9b3b6-...
secret_id_accessor 3a4d5c6e-...
secret_id_ttl 48h
secret_id_num_uses 0Step 4 — The new node logs in via AppRole using RoleID + SecretID.
vault write auth/approle/login \
role_id="<role-id-app-backend>" \
secret_id="f7f9b3b6-..."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:
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.
These two features are often mistaken for the same thing when their roles differ:
| Aspect | Response Wrapping | Cubbyhole |
|---|---|---|
| Function | Deliver a secret response to another party | Store secrets for the token itself |
| Consumer | Another party (transfer) | The token itself (inter-process delegation) |
| Data lifetime | Matches the wrap TTL (minutes) | As long as the token is alive |
| One-time use | Yes, after unwrap | No, can be read repeatedly |
| Primary use case | Bootstrap, SecretID distribution, onboarding | Agent 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).
| Mistake | Symptom | Solution |
|---|---|---|
| Assuming the wrap token can be reused | Error wrapping token is not valid | Remember: once unwrapped, it dies |
| Sending a raw SecretID "to be safe" | Secret exposed in transit | Always wrap first with -wrap-ttl=... |
| Assuming cubbyhole is accessible by other tokens | Data "missing" when it's actually not there | Understand cubbyhole's per-token scoping |
| Wrap TTL too long | Exposure window widens | Set TTL to match delivery duration (minutes) |
| Storing sensitive secrets permanently in cubbyhole | Lost when the token dies | Cubbyhole is for transient data, not permanent storage |
Not setting -wrap-ttl when writing a secret-id | SecretID back to plaintext | Always include -wrap-ttl for onboarding credentials |
| Forgetting to distinguish wrapping vs KV | Policy written for the wrong path | Wrapping 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.
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!