Secrets and environment
Per-repo encrypted secrets, environment variables, and the network allowlist.
Runs execute in a sandbox with explicit inputs: the secrets you granted, the environment you set, and the network you allowed. Nothing else leaks in.
Add a secret
Secrets live in repository settings, encrypted at rest. Anything that looks like a secret's value is redacted from run logs, so a stray echo does not publish your deploy token to everyone with read access.
Read secrets in a run
import { defineWorkflow, on, sh, secret } from "@plainalpha/ci";
defineWorkflow("deploy", {
on: [on.push({ branches: ["main"] })],
run: async () => {
if (!secret.has("DEPLOY_TOKEN")) throw new Error("DEPLOY_TOKEN not configured");
await sh("./scripts/deploy.sh", {
env: { DEPLOY_TOKEN: secret.get("DEPLOY_TOKEN") },
});
},
});
secret.get returns the decrypted value inside the run only. Because access is a function call rather than string interpolation in YAML, "which workflows touch this secret" is a grep.
Environment variables
env.get(name) reads the run's base environment. Per-command environment goes through the env option on sh, as above, which keeps a variable scoped to the one command that needs it.
Network policy
By default a run can reach package registries and nothing else. Workflows that need more declare it:
defineWorkflow("e2e", {
on: [on.pullRequest()],
network: { allow: ["api.staging.example.com"] },
run: async () => {
/* ... */
},
});
The allowlist is part of the workflow file, so a dependency that starts phoning home fails loudly in CI instead of succeeding quietly, and network access shows up in code review like any other change.