Dentolize · Layered Sandbox Settings Walkthrough
On this pageBusiness viewTechnical view

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_token is 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.