Provision LOCALSTACK_AUTH_TOKEN for every repository

LocalStack is a common enough dependency that declaring it per repository is
busywork, and the token is already in the host environment when it is needed at
all. It is now provisioned host-wide, through the same custom-secret path as
the per-repository declarations, so the value stays out of the sandbox and the
proxy substitutes it on requests to localstack.cloud.

Provisioning is conditional on the sandbox having Docker. LocalStack runs as a
container, so on a sandbox created from a non-docker template the credential
would be dead weight, and the entry is skipped with a message rather than
stored. An unset variable is skipped silently, which costs nothing on a machine
that never uses LocalStack.

The provisioning body moves into provision_secret so the host-wide and
per-repository paths cannot drift apart.
This commit is contained in:
2026-07-31 12:09:56 -05:00
parent 93dc61a024
commit d2b7a5ef63
3 changed files with 116 additions and 36 deletions
+22
View File
@@ -437,6 +437,28 @@ still provision.
Placeholders are derived from the repository and variable name, so re-running `setup` Placeholders are derived from the repository and variable name, so re-running `setup`
does not invalidate a value already exported inside a running sandbox. does not invalidate a value already exported inside a running sandbox.
### Host-wide credentials
Some credentials are worth provisioning everywhere rather than declaring per
repository. These are taken from your host environment automatically:
| Variable | Hosts | Provisioned when |
| --- | --- | --- |
| `LOCALSTACK_AUTH_TOKEN` | `localstack.cloud`, `*.localstack.cloud` | the variable is set **and** the sandbox has Docker |
The Docker condition matters: LocalStack runs as a container, so on a sandbox created
from a non-`-docker` template the token would be dead weight. It is skipped there with
a message rather than stored.
Nothing happens if the variable is unset, so this costs nothing on a machine that
does not use LocalStack.
> **Unverified.** LocalStack runs as a *nested* container inside the sandbox's own
> Docker daemon. Whether its outbound activation request traverses the `sbx` proxy —
> and therefore gets the placeholder substituted — has not been tested. If activation
> fails reporting an invalid token, the placeholder is reaching LocalStack literally,
> and the token needs exporting as a real value instead.
## AWS profile naming ## AWS profile naming
Only a trailing `-readonly` is removed. Everything else passes through: Only a trailing `-readonly` is removed. Everything else passes through:
+78 -36
View File
@@ -8,6 +8,13 @@ DEFAULT_MODE="${AI_SBX_MODE:-clone}"
DEFAULT_TOKEN_DAYS="${AI_SBX_TOKEN_DAYS:-30}" DEFAULT_TOKEN_DAYS="${AI_SBX_TOKEN_DAYS:-30}"
DEFAULT_TEMPLATE="${AI_SBX_TEMPLATE:-}" DEFAULT_TEMPLATE="${AI_SBX_TEMPLATE:-}"
DEFAULT_TOOLS="${AI_SBX_TOOLS:-bun}" DEFAULT_TOOLS="${AI_SBX_TOOLS:-bun}"
# VAR|host[,host...]|requirement. Provisioned for every repository when the
# variable is present in the host environment. "docker" skips the entry on a
# sandbox that cannot run containers, where the credential would be useless.
HOST_WIDE_SECRETS=(
"LOCALSTACK_AUTH_TOKEN|localstack.cloud,*.localstack.cloud|docker"
)
CLAUDE_HOME="${CLAUDE_HOME:-$HOME/.claude}" CLAUDE_HOME="${CLAUDE_HOME:-$HOME/.claude}"
# Only these leave the host. ~/.claude also holds OAuth credentials, shell # Only these leave the host. ~/.claude also holds OAuth credentials, shell
@@ -76,6 +83,8 @@ Registry credentials are declared per repository in the user's config
directory as a "secrets" file, one line of VAR|host|command each. The command directory as a "secrets" file, one line of VAR|host|command each. The command
runs on the host and its output becomes an sbx custom secret, so the sandbox runs on the host and its output becomes an sbx custom secret, so the sandbox
sees a placeholder and the proxy substitutes the real value. sees a placeholder and the proxy substitutes the real value.
LOCALSTACK_AUTH_TOKEN is provisioned for every repository when it is set on
the host and the sandbox has Docker to make use of it.
AI_SBX_TOOLS lists mise tools installed globally in the sandbox, defaulting AI_SBX_TOOLS lists mise tools installed globally in the sandbox, defaulting
to bun because several Claude plugins run their hooks under it. Set it to an to bun because several Claude plugins run their hooks under it. Set it to an
@@ -780,14 +789,80 @@ secret_placeholder() {
printf 'sbx-cs-%s' "$digest" printf 'sbx-cs-%s' "$digest"
} }
sandbox_has_docker() {
sbx exec "$SANDBOX_NAME" \
bash -lc 'command -v docker >/dev/null' \
</dev/null >/dev/null 2>&1
}
provision_secret() {
local var="$1" hosts="$2" value="$3"
local placeholder
placeholder="$(secret_placeholder "$var")"
local -a host_args=()
local host
for host in ${hosts//,/ }; do
host_args+=(--host "$host")
done
# Piped rather than --value: the secret would otherwise be visible in the
# process list to anything running as this user.
printf '%s' "$value" |
sbx secret set-custom "$SANDBOX_NAME" \
"${host_args[@]}" \
--env "$var" \
--placeholder "$placeholder" >/dev/null 2>&1 ||
{
printf 'Could not store %s in the sandbox.\n' "$var" >&2
return 1
}
# A sandbox that already exists keeps whatever environment it was created
# with, so the placeholder is exported explicitly.
sbx exec "$SANDBOX_NAME" bash -c "
persistent=/etc/sandbox-persistent.sh
marker=$(printf '%q' "# ai-sbx secret $var")
grep -Fq \"\$marker\" \"\$persistent\" 2>/dev/null && exit 0
printf '%s\nexport %s=%s\n' \
\"\$marker\" $(printf '%q' "$var") $(printf '%q' "$placeholder") \
>>\"\$persistent\"
" </dev/null >/dev/null 2>&1 || true
printf 'Provisioned %s for %s\n' "$var" "${hosts//,/, }"
}
# Credentials worth provisioning for every repository rather than declaring per
# repository, taken straight from the host environment. LocalStack only matters
# when the agent can run containers, so it is skipped where Docker is absent.
install_host_wide_secrets() {
local entry var hosts requirement
for entry in "${HOST_WIDE_SECRETS[@]}"; do
IFS='|' read -r var hosts requirement <<<"$entry"
[[ -n "${!var:-}" ]] || continue
if [[ "$requirement" == docker ]] && ! sandbox_has_docker; then
printf 'Skipping %s: the sandbox has no Docker to run it.\n' "$var" >&2
continue
fi
provision_secret "$var" "$hosts" "${!var}" || true
done
}
install_sandbox_secrets() { install_sandbox_secrets() {
install_host_wide_secrets
local declarations local declarations
declarations="$(read_secret_declarations)" || return 0 declarations="$(read_secret_declarations)" || return 0
[[ -n "$declarations" ]] || return 0 [[ -n "$declarations" ]] || return 0
local var hosts command value placeholder local var hosts command value
local -a host_args
while IFS='|' read -r var hosts command; do while IFS='|' read -r var hosts command; do
[[ -n "$var" ]] || continue [[ -n "$var" ]] || continue
@@ -804,40 +879,7 @@ install_sandbox_secrets() {
continue continue
} }
placeholder="$(secret_placeholder "$var")" provision_secret "$var" "$hosts" "$value" || true
host_args=()
local host
for host in ${hosts//,/ }; do
host_args+=(--host "$host")
done
# Piped rather than --value: the secret would otherwise be visible in
# the process list to anything running as this user.
if printf '%s' "$value" |
sbx secret set-custom "$SANDBOX_NAME" \
"${host_args[@]}" \
--env "$var" \
--placeholder "$placeholder" >/dev/null 2>&1; then
printf 'Provisioned %s for %s\n' "$var" "${hosts//,/, }"
else
printf 'Could not store %s in the sandbox.\n' "$var" >&2
continue
fi
# A sandbox that already exists keeps whatever environment it was
# created with, so the placeholder is exported explicitly.
sbx exec "$SANDBOX_NAME" bash -c "
persistent=/etc/sandbox-persistent.sh
marker=$(printf '%q' "# ai-sbx secret $var")
grep -Fq \"\$marker\" \"\$persistent\" 2>/dev/null && exit 0
printf '%s\nexport %s=%s\n' \
\"\$marker\" $(printf '%q' "$var") $(printf '%q' "$placeholder") \
>>\"\$persistent\"
" </dev/null >/dev/null 2>&1 || true
unset value unset value
done <<<"$declarations" done <<<"$declarations"
+16
View File
@@ -69,6 +69,22 @@ REPOSITORY="other/repo"
[[ "$(secret_placeholder FONTAWESOME_API_KEY)" != "$first" ]] || [[ "$(secret_placeholder FONTAWESOME_API_KEY)" != "$first" ]] ||
fail "placeholder does not vary by repository" fail "placeholder does not vary by repository"
for entry in "${HOST_WIDE_SECRETS[@]}"; do
IFS='|' read -r var hosts requirement <<<"$entry"
[[ -n "$var" ]] || fail "host-wide entry has no variable: $entry"
[[ -n "$hosts" ]] || fail "host-wide entry $var has no hosts"
[[ "$requirement" == docker || "$requirement" == "-" ]] ||
fail "host-wide entry $var has an unknown requirement: '$requirement'"
done
printf '%s\n' "${HOST_WIDE_SECRETS[@]}" | grep -q '^LOCALSTACK_AUTH_TOKEN|' ||
fail "LOCALSTACK_AUTH_TOKEN should be provisioned host-wide"
printf '%s\n' "${HOST_WIDE_SECRETS[@]}" | grep '^LOCALSTACK_AUTH_TOKEN|' |
grep -q 'localstack\.cloud' ||
fail "LOCALSTACK_AUTH_TOKEN must target localstack.cloud"
if ((failures)); then if ((failures)); then
printf '%d assertion(s) failed\n' "$failures" >&2 printf '%d assertion(s) failed\n' "$failures" >&2
exit 1 exit 1