ALLOW_ANY_ORIGIN, ALLOW_ANY_ORIGIN_MUSIC, and everything they gated. The flag defaulted to ON, so none of it ran on a real install — what comes out is documented defence in depth that was already switched off. The file said so itself: "Both flags and their call sites come out once the tailnet is the perimeter." Origin was never authentication here in any case. An app's `officer://<hex>` origin is chosen by the client, forgeable outside a browser, and extractable from a shipped binary. Gone: the two flags, isOriginAllowed, isOriginCheckDisabled, isMusicOriginExempt, originValidationMiddleware, ORIGIN_RULES and the whole OFFICER_<APP>_ORIGIN scheme, PUBLIC_URL's origin/host derivation, and origin-validation.test.ts, which existed only to pin them. CORS now echoes whatever Origin it is given, which is what every install already did. What SURVIVES is the reason this needed care. origin-validation.ts held two unrelated things, and the second was the global authorization gate — a valid non-owner token reaches only what its role grants, deliberately NOT under the flag because it is account-based rather than origin-based. Its own comment called it "the airtight half". Deleting the file wholesale would have deleted authorization. So it moves to _middlewares/capability-gate.ts as capabilityGateMiddleware, with the name matching what it does: nothing in it reads an Origin header any more. hono.ts mounts it in the same position, ahead of every router. origin-middleware.ts stays and is untouched — it extracts the Origin for six auth handlers that log it, and for passkeys. Extraction, not validation. Also updates every claim that rested on the old model: CLAUDE.md's security section and repo map, docs/secret-store.md, docs/mobile-api-keys.md, and five messages in machine-setup's Tailscale section which told the owner to set ALLOW_ANY_ORIGIN=false when declining a tailnet. That advice is now impossible to follow, and the honest version is different: with no tailnet the token is the whole lock, so put a proxy in front and restrict who can reach it. Not typechecked (empty node_modules, frozen installs). Every changed file parses; the setup section was run and writes four variables now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
82 lines
2.7 KiB
Bash
82 lines
2.7 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# officer-setup — the environment file
|
|
# =============================================================================
|
|
#
|
|
# Definitions only.
|
|
#
|
|
# ── What is NOT here ──
|
|
#
|
|
# JWT_SECRET and VAULT_STORE_KEY are not written. They are moving into the SQLite
|
|
# key store (docs/secret-store.md), and writing them here in the meantime would
|
|
# mean generating a value that the store then has to be reconciled with — two
|
|
# origins for one secret, which is the failure the store exists to end.
|
|
#
|
|
# The consequence is honest and deliberate: jwt.ts throws at module load without
|
|
# JWT_SECRET, so an install made by this script does not boot until the store
|
|
# lands. That sequencing was chosen rather than stumbled into.
|
|
#
|
|
# ── Derived, not asked ──
|
|
#
|
|
# DATA_PATH, OFFICER_ITEMS_DIR and HOME_DIR are gone too, and this time nothing
|
|
# replaces them. The platform derives the install root as the parent of its own
|
|
# working directory, so data/, capabilities/ and dockers/ follow from the layout
|
|
# on disk, and the owner's home comes from the OS. They were three environment
|
|
# variables that had to agree with each other and with the directory tree.
|
|
|
|
[[ -n "${OFFICER_SETUP_ENV_LOADED:-}" ]] && return 0
|
|
OFFICER_SETUP_ENV_LOADED=1
|
|
|
|
env_file() { echo "$(platform_dir)/.env"; }
|
|
|
|
env_exists() { [[ -f "$(env_file)" ]]; }
|
|
|
|
# One value out of an existing .env, without sourcing it — the file holds
|
|
# secrets and arbitrary shell would run as root.
|
|
env_get() {
|
|
[[ -r "$(env_file)" ]] || return 0
|
|
awk -F= -v k="$1" '
|
|
$1 == k {
|
|
v = substr($0, index($0, "=") + 1)
|
|
gsub(/^"|"$/, "", v)
|
|
print v
|
|
exit
|
|
}' "$(env_file)"
|
|
}
|
|
|
|
write_env() {
|
|
local dest
|
|
dest="$(env_file)"
|
|
|
|
[[ -f "$dest" ]] && cp -a "$dest" "${dest}.before-officer-setup"
|
|
|
|
# Restrictive from the moment it exists rather than chmod'd afterwards, so the
|
|
# secrets are never briefly world-readable. Restored straight after: umask is
|
|
# not scoped to a function, and leaving it at 077 would quietly make every file
|
|
# a later section creates owner-only.
|
|
local prior_umask
|
|
prior_umask="$(umask)"
|
|
umask 077
|
|
cat >"$dest" <<ENVF
|
|
# Written by officer-setup.
|
|
#
|
|
# Everything Officer reads at runtime. Kept at 0600 and owned by ${USERNAME}: it
|
|
# holds the token-signing secret and the database credential.
|
|
|
|
PORT="${ENV_PORT}"
|
|
|
|
# The browser relay listens on its own port, separate from the app.
|
|
BROWSER_RELAY_PORT="${ENV_BROWSER_RELAY_PORT}"
|
|
|
|
POSTGRES_URL="${POSTGRES_URL}"
|
|
|
|
DISCORD_BUG_REPORT_WEBHOOK="${ENV_DISCORD_WEBHOOK}"
|
|
ENVF
|
|
|
|
umask "$prior_umask"
|
|
|
|
chown "${USERNAME}:$(user_group)" "$dest"
|
|
chmod 600 "$dest"
|
|
return 0
|
|
}
|