Secrets and safety guardrails
Business view
A GitHub PR description is plain, visible text — anyone who can see the repo can read it, and it stays in the PR's history forever. That makes it a fine place to write "turn on WhatsApp for this sandbox," but a bad place to paste an API key. This PR lets a PR author point at a shared secret by name (secret:MY_TEST_KEY) instead of ever typing its value, and it lets the team store a default secret once — in the sandbox GitHub Environment — instead of every PR having to reference it individually. Two guardrails exist so this can't be turned into a way to leak something sensitive: the one credential that would let a sandbox impersonate the CI system itself (github_token) is flatly refused, and referencing a secret that doesn't exist fails the deploy loudly instead of silently deploying with a missing value.
Technical view
Source: .github/workflows/sandbox.yml:98-188.
Where the secret values come from
env:
SANDBOX_VARS: ${{ toJSON(vars) }}
SANDBOX_SECRETS: ${{ toJSON(secrets) }}
.github/workflows/sandbox.yml:109-110
Because the deploy job now declares environment: sandbox (sandbox.yml:96), vars and secrets here include both the repo/org-level values and the sandbox environment's own variables and secrets — previously, without that declaration, the environment's values were invisible to this expression entirely. Both are handed to the step as JSON strings.
Resolving a secret:<NAME> reference
for k, v in (pr.get("env") or {}).items():
if isinstance(v, str) and v.startswith("secret:"):
n = v[7:]
if n.lower() == "github_token":
raise SystemExit("github_token cannot be injected into a sandbox")
if n not in secrets_:
raise SystemExit("sandbox secret not found: " + n)
env[k] = secrets_[n]
else:
env[k] = v
.github/workflows/sandbox.yml:164-173
A PR-body env: entry whose value is the literal string secret:FOO is resolved against the same secrets_ JSON dict the layering logic already loaded — the real value only ever exists inside this Python process and the generated settings file, never in the PR description itself. This logic carries over unchanged from before this PR; what's new is that the same secrets dict can now also supply base-layer values directly (via SANDBOX_ALL_*/SANDBOX_<branch>_* secrets), not only PR-referenced ones.
Two guardrails, both fail the whole step
github_tokenis unconditionally rejected, case-insensitively
(n.lower() == "github_token", line 167). It isn't prefixed with SANDBOX_ anywhere, so it can never enter through a base layer either — the only remaining path was a PR writing secret:github_token, and that path is explicitly closed. This matters because the auto-injected workflow token is what the deploy step separately uses to clone/fetch (sandbox.yml:196); letting a sandbox inject it would hand container access to a credential scoped to the whole workflow run.
- An unresolvable name fails loudly.
raise SystemExit(...)inside the
heredoc makes the Python process exit non-zero; combined with set -eo pipefail (line 113) and the redirect python3 - > "$sf" capturing only stdout, a failed resolution aborts the step before any settings file is written — the deploy never proceeds with a half-resolved secret.
What reaches the logs
print("toggles:", {k: v for k, v in d.items() if k != "env"})
print("env keys:", sorted((d.get("env") or {}).keys()))
.github/workflows/sandbox.yml:182-186
The step's own log output is deliberately limited to toggle values (which aren't secret) and env key names only — never values, so a secret that got resolved into env never appears in the Actions log even by its own key's value. This is a second, independent line of defense on top of GitHub's automatic masking of any string that matches a known secret value wherever it appears in a log line (noted in the step's own comment, sandbox.yml:106-107).
Residual risk, called out in the code
injected values are readable by anyone with this sandbox's access cred — reference test/dev creds, not production secrets (sandbox.yml:107-108)
Nothing in this PR changes that boundary. A secret injected into a sandbox's env is, by design, retrievable by anyone who can reach that sandbox's containers (e.g. via the sandbox's shared "Terminal" access shown in the deploy comment). The guardrails here stop github_token and unresolvable references specifically — they are not a general claim that sandbox secrets are as protected as production secrets.