Secrets and outbound calls
A secret is a credential for a third-party API, such as a Stripe key, that we store for your app. Your code calls the API without the key. Our egress proxy adds it as a header to requests for the host you name, so the key stays out of your app, its logs and its versions.
Store a secret
Section titled “Store a secret”jiayang secret set acme/hello STRIPE_KEY --host api.stripe.com --header Authorization --format "Bearer {value}"Paste the value and press Enter. It stays hidden:Read 1 line, 107 characters.Stored STRIPE_KEY. We'll send it as Authorization on requests to api.stripe.com.At a terminal, you paste the value at a prompt that doesn’t echo it, and it says how much it read, so you can tell a paste that came up short.
When the value was typed rather than pasted, runs over more than one line, or comes from a Windows terminal, which sends a paste as typing, it asks before storing it:
Paste the value and press Enter. It stays hidden:Read 1 line, 107 characters.If that's less than you pasted, it got cut short. Answer n and pipe the value in instead, which reads all of it: jiayang secret set ... < value.txtStore it? [y/N]Answer y to store it. The default is no, so pressing Enter alone stores nothing.
Otherwise the value is read from stdin, without a trailing newline, which works in CI:
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, but then it ends up 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. You can do the same in the app’s Secrets tab in the dashboard.
Call the API without the key
Section titled “Call the API without the key”Leave the header out of your request. We add it:
const res = await fetch("https://api.stripe.com/v1/customers", { method: "POST", body: new URLSearchParams({ email: "someone@example.com" }),});import requests
res = requests.post("https://api.stripe.com/v1/customers", data={"email": "someone@example.com"})form := url.Values{"email": {"someone@example.com"}}res, err := http.PostForm("https://api.stripe.com/v1/customers", form)let res = reqwest::Client::new() .post("https://api.stripe.com/v1/customers") .form(&[("email", "someone@example.com")]) .send() .await?;This works the same in a Worker and in a container. A container’s HTTPS requests also go through the proxy, and the variables it needs to trust the proxy are set for you.
How the header is added
Section titled “How the header is added”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 tohttp://api.stripe.comgoes out without them; - doesn’t let a secret follow a redirect to another host;
- refuses requests to our own hosts, with a 403 and
Refused: apps can't send requests to Jiayang Cloud itself.; - adds the header
X-Jiayang-Outbound: 1, which tells a custom domain on Jiayang Cloud to refuse the request. The API you call sees it too; - refuses every request, over
httporhttps, while the app isn’t being served because its workspace is suspended or over a limit that stops it. The answer is a 403 withRefused: this app is stopped, so it can't send requests.
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, or the app stopping or starting again, reaches your app’s requests within that time.
Secret values are encrypted at rest. Once a value is set, nothing in the CLI, API or dashboard shows it again.
What the host sends back
Section titled “What the host sends back”We keep the value out of your app, but the host you named receives it on every request, and your code reads that host’s responses. If the host echoes request headers, as a debugging endpoint does, or quotes the key in an error message, your code can read the key from the response. Anyone who can deploy the app can write code that calls such an endpoint.
Name only a host you trust with the key. Assume everyone who can deploy the app can use the key, and, through a host like that, read it. If you think a key has been read, rotate it with the provider and set the new value.
List and remove secrets
Section titled “List and remove secrets”jiayang secret list acme/helloSENDGRID_KEY Authorization → api.sendgrid.comSTRIPE_KEY Authorization → api.stripe.comThe list shows each secret’s name, header and host, without its value.
jiayang secret remove acme/hello SENDGRID_KEYRemoved 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 is recorded in the audit log with the secret’s name and host, without its value.
Rules and errors
Section titled “Rules and errors”| 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, and not one of ours or a name under .internal, .local or .localhost, which the proxy never sends to. 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. |
jiayang secret set checks the name and the flags before it asks for the value, so you don’t paste a value only to have it refused. The errors for each:
error: Secret names are uppercase letters, digits and underscores, like STRIPE_KEY.error: Secret values must be 1 to 8,192 bytes, with no control characters.error: --host takes a hostname like api.stripe.com, with no scheme, port or path: --host api.stripe.com.error: --header X Token isn't a header name: letters, digits and dashes, like Authorization.error: --header Host can't carry a secret: HTTP or the platform sets it.error: --format must contain {value} exactly once, like "Bearer {value}".When --host is a URL, or has a port or a path, the error ends with its hostname, to use instead. With --json, the errors for the name and the flags have the code invalid_argument. The dashboard, the API and the agent tools say the same things in their own words.
Secrets and environment variables
Section titled “Secrets and environment variables”Environment variables are plain text that your code reads and anyone who can deploy the app can list. For that reason, jiayang env set refuses values that are clearly credentials and points you here.