Secrets Helper
The secrets helper lets a script use a secret without ever holding its value. secrets.ref returns an opaque handle that the API helper swaps for the real value only inside an outgoing HTTPS request to one of the secret's allowed hosts. secrets.verifyHmacSha256 checks a signature against a stored signing key and returns only true or false. secrets.signJwtHs256 signs a JWT with a stored key and returns the token, not the key.
Methods
- Name
secrets.ref- Type
- (name: string, opts?: object) => string
- Description
Returns a handle for the secret called
name. Pass the handle anywhere in anapi.*request's endpoint URL, header values, or body. ThrowsSECRET_USE_DENIEDif this execution may not use the secret.
- Name
secrets.verifyHmacSha256- Type
- (name: string, message: string, signatureHex: string) => boolean
- Description
Computes the HMAC-SHA256 of
messagewith the secret as the key, and compares it tosignatureHexin constant time. Returnstruewhen they match. ThrowsSECRET_USE_DENIEDif this execution may not use the secret.
- Name
secrets.signJwtHs256- Type
- (name: string, header: object, claims: object) => string
- Description
Returns a signed JWT (compact form,
header.payload.signature) made fromheaderandclaims, signed with HS256 using the secret's value as the key. ThrowsSECRET_USE_DENIEDif this execution may not use the secret.
secrets.ref
const key = secrets.ref("acme-api-key");
const res = JSON.parse(api.get(JSON.stringify({
endpoint: "https://api.acme.com/v1/me",
headers: { "Authorization": "Bearer " + key }
})));
Options
- Name
encoding- Type
- string, optional
- Description
"raw"(the default) fills in the value as stored."basic"fills in a Base64 value ready for anAuthorization: Basicheader (see below).
- Name
username- Type
- string, optional
- Description
With
encoding: "basic", the username to pair with the secret. Ignored for"raw".
With encoding: "basic":
| Call | What's sent |
|---|---|
secrets.ref("acme-api-key", { encoding: "basic", username: "orders-bot" }) | Base64 of orders-bot:<value> |
secrets.ref("stripe-live", { encoding: "basic" }) | Base64 of <value>: (the value as the username, with an empty password) |
const auth = secrets.ref("acme-api-key", { encoding: "basic", username: "orders-bot" });
api.get(JSON.stringify({
endpoint: "https://api.acme.com/v1/orders",
headers: { "Authorization": "Basic " + auth }
}));
The handle
The return value is a string of the form [[autessa-secret:…]]. It is only a placeholder:
- It works only during the execution that created it. A handle saved and used later, or passed to another execution, does nothing.
- It is filled in only by
api.get,api.post,api.put,api.patch, andapi.delete, in the endpoint URL, header values, and body. - Anywhere else it stays as the handle text: in
console.logoutput, inresponse.setResponse, in AutessaDB writes, in agent prompts, and in tool responses.
When the value is filled in
api.* fills in a handle only when both are true:
- the endpoint uses
https://, and - the endpoint's hostname exactly matches one of that secret's allowed hosts.
A handle used in the hostname part of the URL is always refused.
Otherwise the request isn't sent, and the call returns the API helper's error shape:
{
"error": true,
"errorCode": "SECRET_HOST_NOT_ALLOWED",
"statusCode": -1,
"body": "",
"headers": {}
}
A request that uses several secrets is sent only if every one of them allows the host. In a JSON body, the value is inserted JSON-escaped, so quotes and backslashes in it can't break the body. The normal checks on outgoing requests still apply first.
Responses are redacted
Before the response reaches your script, every occurrence in the response body and headers of a secret used in this execution is replaced with [redacted secret]: the value itself, its standard and URL-safe Base64 forms, and its basic form. A service that echoes your credentials back can't leak them into your script. Values shorter than 4 characters aren't redacted, because they would match ordinary text.
Who may use a secret
The check happens when you call secrets.ref or secrets.verifyHmacSha256:
- If the secret is bound to tools, the tool that is running must be one of them. Who started the run doesn't matter.
- If the secret isn't bound to any tool, whoever the execution runs as needs at least Use access to the secret. A run that doesn't act for any person or key can't use it.
A denied check throws SECRET_USE_DENIED and adds a USE_DENIED event to the secret's audit log. Catch it if your tool should return a friendly error:
let key;
try {
key = secrets.ref("acme-api-key");
} catch (e) {
response.setResponse(JSON.stringify({ error: "This tool isn't allowed to use the Acme key." }));
}
Each request that fills in a secret adds a USE event to the secret's audit log, once per execution for each host.
secrets.verifyHmacSha256
const valid = secrets.verifyHmacSha256(
"acme-webhook-signing", // the secret holding the signing key
variables.get("rawBody"), // the exact bytes the sender signed
variables.get("signature") // the hex signature they sent
);
messagemust be exactly what the sender signed, usually the raw request body. Re-serialized JSON won't match.signatureHexis the hex digest. Strip any prefix such assha256=first.- Each call adds a
USEevent to the secret's audit log with the hosthmac.
secrets.signJwtHs256
Some APIs authenticate each request with a short-lived JWT signed with a key they gave you, instead of a fixed API key. secrets.signJwtHs256 signs the token for you, so the key stays in the secret and never enters your script.
const now = Math.floor(Date.now() / 1000);
const token = secrets.signJwtHs256(
"acme-signing-key", // the secret holding the shared key
{ typ: "JWT" }, // header; alg is always HS256
{ // claims
iss: "my-company",
aud: "https://api.acme.com",
iat: now,
exp: now + 300 // valid for 5 minutes
}
);
const res = JSON.parse(api.post(JSON.stringify({
endpoint: "https://api.acme.com/v1/payments",
headers: { "Authorization": "Bearer " + token },
body: { amount: 2500 }
})));
- The token is signed with HS256 using the secret's value, exactly as stored, as the key. The header's
algis set toHS256for you. - The token is an ordinary string. Unlike a
secrets.refhandle, it works anywhere, so you can send it to any host. Treat it like a password until it expires, and give it a shortexp. - The same use rules apply: if the secret is bound to tools, only those tools can sign with it.
- Each call adds a
USEevent to the secret's audit log with the hostjwt.
To sign with a key your script already has, rather than a stored secret, build the token yourself with cryptoUtils.hmac(key, message, "SHA256", "BASE64URL"). See the cryptoUtils reference.
What the helper can't do
There is no method that returns a secret's value. Scripts can't reveal a secret, even with an approved reveal request; revealing is only for signed-in people in the builder or the CLI. See Reveal requests.
Related
- Secrets
- Use a third-party API key from a tool
- Protect Sensitive Data, for the
vaulthelper - API helper
- Bindings
- Limits and errors