Response wrapping is how Vault delivers secrets that you only get to use once, preventing them from lingering in etcd or other backends longer than necessary.
Let’s see it in action. Imagine you need a temporary database credential.
vault write auth/approle/login role_id=your-role-id secret_id=your-secret-id
This command logs you in and gets you a token. But what if you want that token to be short-lived and only usable for a single API call? That’s where wrapping comes in.
You can request a wrapped token like this:
vault write -wrap-ttl=10m auth/approle/login role_id=your-role-id secret_id=your-secret-id
The output will look something like this:
{
"request_id": "...",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": null,
"wrap_info": {
"token": "s.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"accessor": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"token_policies": [
"default",
"my-app-policy"
],
"token_explicit_max_ttl": 0,
"token_binding_hash": "",
"token_creation_time": "2023-10-27T10:00:00.123456789Z",
"token_duration": 600000000000,
"token_ttl": 600000000000,
"token_is_orphan": false,
"token_type": "service",
"wrapped_token": "s.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"wrapped_accessor": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
},
"warnings": null,
"auth": null
}
Notice the wrap_info block. The key here is wrapped_token. This is your one-time-use token. You then take this wrapped_token and use it immediately to unwrap the actual secret.
vault unwrap s.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
The output of the unwrap command will contain the actual data you were trying to retrieve, in this case, your AppRole login details, and importantly, it will be the only time you can retrieve them. The wrapped token is consumed in the process.
The primary problem response wrapping solves is the security risk of long-lived, potentially exposed secrets. If a token is used to retrieve a secret and that token is later compromised, the secret is already out in the open. With wrapping, the actual secret isn’t directly returned by the initial login or secret retrieval command. Instead, you get a temporary token that only allows you to retrieve the secret once. This significantly reduces the attack surface.
Internally, when you request a wrapped response, Vault doesn’t immediately fetch and return the data. Instead, it generates a special, short-lived token (the wrapped_token) that is bound to the secret data. This wrapped_token is then encrypted and stored within Vault’s backend, associated with the wrap_info structure. The wrap-ttl parameter dictates how long this encrypted blob and its associated wrapped_token will persist before being garbage collected. When you call vault unwrap with the wrapped_token, Vault decrypts the blob, returns the original secret data, and immediately invalidates the wrapped_token.
The wrap-ttl parameter on the write command is crucial. It specifies the maximum lifetime of the wrapped secret before it is unwrapped. If you don’t unwrap it within this duration, the wrapped token will expire, and the secret will be lost forever. This is a feature, not a bug, designed to enforce immediate consumption.
What most people don’t realize is that the wrap-ttl applies to the wrapped token itself, not the token used to request the wrapped token. The token you use for the initial vault write -wrap-ttl=... command can have its own, potentially longer, lease duration. The wrap-ttl is purely about the lifespan of the encrypted secret payload and its associated unwrapping token.
After successfully unwrapping a secret, you’ll often find that the token you used to perform the unwrap operation itself is now expired or has fewer capabilities. This is because the unwrap operation consumes the capabilities of the token used to perform it, effectively "spending" its power on retrieving the wrapped secret.