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`
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
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_TEMPLATE="${AI_SBX_TEMPLATE:-}"
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}"
# 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
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.
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
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"
}
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_host_wide_secrets
local declarations
declarations="$(read_secret_declarations)" || return 0
[[ -n "$declarations" ]] || return 0
local var hosts command value placeholder
local -a host_args
local var hosts command value
while IFS='|' read -r var hosts command; do
[[ -n "$var" ]] || continue
@@ -804,40 +879,7 @@ install_sandbox_secrets() {
continue
}
placeholder="$(secret_placeholder "$var")"
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
provision_secret "$var" "$hosts" "$value" || true
unset value
done <<<"$declarations"
+16
View File
@@ -69,6 +69,22 @@ REPOSITORY="other/repo"
[[ "$(secret_placeholder FONTAWESOME_API_KEY)" != "$first" ]] ||
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
printf '%d assertion(s) failed\n' "$failures" >&2
exit 1