Skip to main content

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 an api.* request's endpoint URL, header values, or body. Throws SECRET_USE_DENIED if this execution may not use the secret.

  • Name
    secrets.verifyHmacSha256
    Type
    (name: string, message: string, signatureHex: string) => boolean
    Description

    Computes the HMAC-SHA256 of message with the secret as the key, and compares it to signatureHex in constant time. Returns true when they match. Throws SECRET_USE_DENIED if 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 from header and claims, signed with HS256 using the secret's value as the key. Throws SECRET_USE_DENIED if 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 an Authorization: Basic header (see below).

  • Name
    username
    Type
    string, optional
    Description

    With encoding: "basic", the username to pair with the secret. Ignored for "raw".

With encoding: "basic":

CallWhat'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, and api.delete, in the endpoint URL, header values, and body.
  • Anywhere else it stays as the handle text: in console.log output, in response.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
);
  • message must be exactly what the sender signed, usually the raw request body. Re-serialized JSON won't match.
  • signatureHex is the hex digest. Strip any prefix such as sha256= first.
  • Each call adds a USE event to the secret's audit log with the host hmac.

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 alg is set to HS256 for you.
  • The token is an ordinary string. Unlike a secrets.ref handle, it works anywhere, so you can send it to any host. Treat it like a password until it expires, and give it a short exp.
  • The same use rules apply: if the secret is bound to tools, only those tools can sign with it.
  • Each call adds a USE event to the secret's audit log with the host jwt.

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.