Skip to content

Secrets and outbound calls

A secret is a credential for someone else’s API, such as a Stripe key, that the platform keeps for your app. Your code calls the API without it, and the platform’s egress proxy adds it as a header to requests for the one host you name, so the key never enters your app, its logs or its versions.

Terminal window
jiayang secret set acme/hello STRIPE_KEY --host api.stripe.com --header Authorization --format "Bearer {value}"
Paste the value, then press Enter. It won't be shown:
Stored STRIPE_KEY. It's added as Authorization on requests to api.stripe.com.

At a terminal, the value is read at a prompt that doesn’t echo it. Anything else is read from stdin, less a trailing newline, which suits CI:

Terminal window
printf '%s' "$STRIPE_KEY" | jiayang secret set acme/hello STRIPE_KEY --host api.stripe.com --header Authorization --format "Bearer {value}"

--value takes it on the command line instead, where it lands in your shell history.

Option What it does
--host The one host that gets the header, matched exactly: api.stripe.com is not stripe.com and not files.stripe.com.
--header The header that carries it, such as Authorization or X-Api-Key.
--format How the header’s value is built, with {value} exactly once. The default is the value on its own.

Setting a secret that already exists replaces its value, host, header and format. The app’s Secrets tab in the dashboard does the same.

Leave the header out. The platform adds it on the way out:

const res = await fetch("https://api.stripe.com/v1/customers", {
method: "POST",
body: new URLSearchParams({ email: "someone@example.com" }),
});

This works the same in a Worker and in a container. A container’s HTTPS goes through the proxy too, and the variables it needs to trust it are set for you.

Every outbound request from your app goes through the egress proxy. For each request it:

  • adds the secrets for that request’s host, each in its header, replacing a header of the same name your code set;
  • adds them only over https, so a request to http://api.stripe.com goes out without them;
  • doesn’t let a secret follow a redirect to another host;
  • refuses requests to the platform’s own hosts, with a 403 and egress to platform hosts is blocked.

Several secrets can go to one host, each in its own header.

The proxy keeps what it looked up for 10 seconds, so a change to a secret reaches your app’s requests within that time.

Secret values are encrypted where they’re stored. No command, API call or dashboard page shows a value again once it’s set.

The platform keeps the value out of your app, but the host you named gets it on every request, and what that host sends back reaches your code. If it echoes request headers in a response, as a debugging endpoint does, or quotes the key in an error message, your code can read the value there. Anyone who can deploy the app can write code that calls such an endpoint.

So name only a host you trust with the key, and treat everyone who can deploy the app as able to use it and, through a host like that, to read it. If you think a key has been read, rotate it with the provider and set the new value.

Terminal window
jiayang secret list acme/hello
SENDGRID_KEY Authorization → api.sendgrid.com
STRIPE_KEY Authorization → api.stripe.com

The list shows each secret’s name, header and host, never its value.

Terminal window
jiayang secret rm acme/hello SENDGRID_KEY
Removed SENDGRID_KEY.

Within 10 seconds, requests to that host go out without the header.

Setting, listing and removing secrets takes an editor or owner of the app, or a workspace admin or owner. Each change goes in the audit log with the secret’s name and host, never its value.

Name Uppercase letters, digits and underscores, starting with a letter, at most 64 characters.
Value 1 to 8,192 bytes, with no control characters.
Host A hostname with at least one dot. It’s stored in lowercase.
Header Letters, digits and dashes, at most 64 characters. Not Host, Content-Length, Transfer-Encoding, Connection, Upgrade, Cookie, TE, Trailer, Keep-Alive, Proxy-Authorization, or any X-Jiayang-* or CF-* header.
Format At most 128 characters, with {value} exactly once.

What each refusal says:

error: Secret names are uppercase letters, digits and underscores, like STRIPE_KEY.
error: Secret values are 1 to 8192 bytes with no control characters.
error: inject_host must be a hostname like api.stripe.com.
error: That header can't carry a secret.
error: inject_format must contain {value} exactly once, like "Bearer {value}".

Environment variables are plain text that your code reads and anyone who can deploy the app can list. jiayang env set refuses values that are clearly credentials for that reason, and points you here.

  • Database for the data your app stores.
  • Security for how the platform keeps credentials out of your code.