fix(clients): remove embedded API key + URL defaults from palace-mode#12
fix(clients): remove embedded API key + URL defaults from palace-mode#12jphein wants to merge 1 commit intorboarescu:mainfrom
Conversation
…u#12 (palace-mode security)
There was a problem hiding this comment.
Pull request overview
Removes user-specific embedded defaults (daemon URL + API key) from clients/palace-mode so the script no longer ships with an operator’s private config and avoids writing unintended values into a user’s ~/.claude/settings.local.json.
Changes:
- Drop the hardcoded default URL and API key; read defaults from environment variables instead.
- Add fail-fast validation in
remotemode to avoid writing empty values intosettings.local.json. - Update in-script guidance/comments around how to configure remote mode.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if [ -z "$DEFAULT_KEY" ]; then | ||
| echo "error: PALACE_API_KEY not set — set it in your shell or in ~/.config/palace-daemon/env" >&2 | ||
| echo " the daemon's auth check rejects requests without a matching key." >&2 | ||
| exit 2 | ||
| fi |
There was a problem hiding this comment.
Addressed — set_mode_remote no longer hard-fails on missing PALACE_API_KEY. Lines 56-60 now warn and proceed: if [ -z "$DEFAULT_KEY" ]; then echo "warning: PALACE_API_KEY not set — proceeding without auth header"; echo " if the daemon at $url enforces auth, the client will get 401s."; fi. The header comment at lines 22-26 documents the contract: PALACE_API_KEY is only required when the target daemon enforces auth (its own server-side PALACE_API_KEY is set).
| # Defaults are read from the environment so this script ships without any | ||
| # user-specific values. Override via the env vars below or pass an explicit | ||
| # URL on the `remote` subcommand. PALACE_API_KEY is required when switching | ||
| # to remote mode — set it in your shell or in ~/.config/palace-daemon/env | ||
| # (sourced by your shell rc). |
There was a problem hiding this comment.
Reworded — header comment at lines 20-26 now reads: "Defaults are read from the environment so this script ships without any user-specific values. Override via the env vars below or pass an explicit URL on the `remote` subcommand… Export the env vars in your shell environment." No longer claims ~/.config/palace-daemon/env is sourced by the user's rc; readers can place defaults wherever their shell config is wired up.
| set_mode_remote() { | ||
| url="${1:-$DEFAULT_URL}" | ||
| if [ -z "$url" ]; then | ||
| echo "error: no daemon URL — pass one as the first arg or set PALACE_DAEMON_URL[_DEFAULT]" >&2 |
There was a problem hiding this comment.
Done — error text at line 53 now names both variables explicitly: error: no daemon URL — pass one as the first arg or set PALACE_DAEMON_URL_DEFAULT or PALACE_DAEMON_URL in env. No more [_DEFAULT] ambiguity.
5b0b8b0 to
84172ac
Compare
`clients/palace-mode` shipped with two embedded user-specific defaults
on lines 19-20: a `DEFAULT_URL` pointing at my homelab daemon and a
real hex `DEFAULT_KEY`. Both are leftovers from when the script was
extracted from my own install — the URL is my homelab address, and
the hex string is a real API key I authored (it has since been
rotated, so it doesn't authenticate against my daemon anymore — but
the literal value is still in upstream main's source).
## Fix
Read both from environment, no in-source defaults:
```bash
DEFAULT_URL="${PALACE_DAEMON_URL_DEFAULT:-${PALACE_DAEMON_URL:-}}"
DEFAULT_KEY="${PALACE_API_KEY:-}"
```
Plus fail-fast guards in `set_mode_remote()` so silent empty-string
writes don't end up in `~/.claude/settings.local.json`. If either URL
or key is unset, the script prints a clear error pointing at the right
env vars and exits 2 instead of writing broken config.
## Why now
Three reasons even though the embedded key is no longer live:
1. Trains contributors that hex defaults are normal — anyone reading
the source might assume embedding their own key is fine.
2. Default `palace-mode remote` (no args) writes my homelab URL into
a fresh user's `settings.local.json`, sending their hooks to a
daemon they have no auth for.
3. Reviewable provenance — removing the literal makes future audits
cleaner.
## Scope
+11 / -1. Single file. No new dependencies. No behavior change for
operators who set `PALACE_DAEMON_URL` and `PALACE_API_KEY` in their
shell rc. Operators who relied on the embedded defaults get a clear
error pointing at what they need to set.
Originally part of fork commit `d450cef` ("address upstream review on
PR #4"); the portable-paths half of that commit shipped as PR rboarescu#10.
Sending the embedded-defaults half standalone.
84172ac to
9a957d8
Compare
…boarescu#10/rboarescu#12/rboarescu#13 to main The amendments to those open upstream PRs caught real bugs that exist on fork main too — production at disks runs fork main, so these were shipping live until now. ## main.py — /graph endpoint (from PR rboarescu#13) - `_read_wings_rooms_direct()`: build `rooms` from union of `wings.keys()` + `rooms_by_wing.keys()` rather than just wings. Otherwise a partial schema-drift (one query OperationalError-ed but the other succeeded) silently drops the half that worked. Real data-loss bug for the SME / /viz consumers. - `/graph` handler: gate the direct-sqlite reads on `_read_sem` via a `_direct_under_sem` helper. Coordinates with `_exclusive_palace()` used by `/repair mode=rebuild` (so /graph yields rather than racing delete-then-create on chroma.sqlite3) and rate-limits unbounded `asyncio.to_thread` reads under load (was 2 threads/request × N concurrent requests). - `_canonical_topic()` (from PR rboarescu#8): drop the `topic: str` annotation and add a non-string isinstance guard at the top, coerce `None`/numbers/lists to `CHECKPOINT_TOPIC` with a warning. A malformed `{"topic": null}` JSON payload no longer leaks `None` through to `tool_diary_write`. ## scripts/verify-routes.sh (from PR rboarescu#9) - `set -euo pipefail` (was `set -e`) — pipeline failures and unset vars now surface instead of being masked. - `curl -fsS` instead of `-sS` on every probe so HTTP non-2xx (e.g. `/health` 503 when palace is degraded) fails curl directly. Without this, a 503 body with `"daemon":"palace-daemon"` slips through the body-grep as a pass. - Limit-check rewrite: split curl exit code from JSON parse so a connection error or non-JSON response `?`-flags rather than fails-with-traceback (the previous `2>&1` redirect captured Python tracebacks into `$COUNT`, which then triggered the fail path with a confusing message). ## clients/palace-mcp-dispatch.sh (from PR rboarescu#10) - Replace `readlink -f` (GNU-specific, fails on macOS BSD readlink) with `cd "$(dirname …)" && pwd -P`. POSIX-portable; covers the actual common case (parent directory is a symlink under the Claude Code plugin cache). - Add explicit existence check on `$MCP_CLIENT` before exec, with a clear error message instead of a generic Python "can't open file" trace. ## clients/palace-mode (from PR rboarescu#12) - Make `PALACE_API_KEY` optional in `set_mode_remote`. Server-side `_check_auth` only enforces auth when its own `PALACE_API_KEY` is set, so a daemon running without auth accepts unauthenticated clients. The previous fail-fast was overly strict for that case. Warn-but-proceed instead. All five fixes are amendments on the corresponding upstream PR branches (force-pushed earlier today). No behavior change for the healthy path; the changes harden failure modes that were caught in review.
Why this is a separate PR rather than a quiet follow-up to PR #4
Two embedded user-specific defaults shipped in
clients/palace-modelines 19-20: aDEFAULT_URLpointing at my homelab daemon, and a real hexDEFAULT_KEY.Both are leftovers from when the script was extracted from my own install. The URL is
http://disks.jphe.in:8085(my homelab). The key was a real value I authored.The key has since been rotated so it no longer authenticates against my daemon — but I want to flag this on the record because:
palace-mode remotewith no args writes my homelab URL into a fresh user's~/.claude/settings.local.json, pointing their hooks at a daemon they have no auth for.Fix
Read both from environment, no in-source defaults:
Plus fail-fast guards in
set_mode_remote(): if either URL or key is unset, print a clear error pointing at the right env var and exit 2, instead of silently writing empty strings intosettings.local.json.Test plan
palace-mode remote(no env, no arg): exits 2 with URL errorpalace-mode remote http://host:8085(no key): exits 2 with key errorpalace-mode remote http://host:8085withPALACE_API_KEY=...exported: writes both into settings, prints successpalace-mode local: unchangedpalace-mode(no subcommand): unchangedScope
+11 / -1. Single file. No new dependencies.Originally part of fork commit
d450cef("address upstream review on PR #4"); the portable-paths half of that commit shipped as PR #10. Sending the embedded-defaults half here standalone.