Skip to content

Commit 9cee345

Browse files
yasinBursaliclaude
andcommitted
fix(dream-cli): belt-and-suspenders masking in 'config show'
The schema-authoritative check in the previous commit leaks keys that are present in .env.schema.json with secret: false, which today miscovers five real upstream-provider credentials (TARGET_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY, TOGETHER_API_KEY, LIVEKIT_API_KEY). A malformed schema (valid JSON but empty jq output) has the same silent-leak failure mode. After a schema-miss under _schema_loaded=1, fall through to the keyword substring match instead of returning "not secret" immediately. Schema still defines what IS definitely a secret; the keyword pass adds defense in depth for schema gaps. Over-masking of LANGFUSE_PROJECT_PUBLIC_KEY, TOKEN_SPY_PORT, and TOKEN_SPY_URL is acceptable — 'show' should default to over-masking, and raw values remain available via cat .env. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ce37c11 commit 9cee345

2 files changed

Lines changed: 47 additions & 33 deletions

File tree

dream-server/.env.schema.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@
7979
},
8080
"N8N_USER": {
8181
"type": "string",
82-
"description": "n8n initial admin email address"
82+
"description": "n8n initial admin email address",
83+
"secret": true
8384
},
8485
"N8N_PASS": {
8586
"type": "string",
@@ -502,7 +503,8 @@
502503
"LANGFUSE_INIT_USER_EMAIL": {
503504
"type": "string",
504505
"description": "Langfuse initial admin user email",
505-
"default": "admin@dreamserver.local"
506+
"default": "admin@dreamserver.local",
507+
"secret": true
506508
},
507509
"LANGFUSE_INIT_USER_PASSWORD": {
508510
"type": "string",

dream-server/dream-cli

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,42 @@ cmd_shell() {
11741174
fi
11751175
}
11761176

1177+
# File-scope helpers used by `dream config show` and `dream preset diff` to
1178+
# mask secret values. `.env.schema.json` is the authoritative source; keys
1179+
# marked `"secret": true` are treated as secrets. A keyword fallback covers
1180+
# schema gaps (e.g. ANTHROPIC_API_KEY is currently secret:false) and the case
1181+
# where the schema file or jq is unavailable. Callers must invoke
1182+
# _cmd_config_load_secret_schema once (after check_install, so INSTALL_DIR is
1183+
# set) before calling _cmd_config_is_secret.
1184+
_cmd_config_secret_keys=()
1185+
_cmd_config_schema_loaded=0
1186+
1187+
_cmd_config_load_secret_schema() {
1188+
local _schema_path="$INSTALL_DIR/.env.schema.json"
1189+
_cmd_config_secret_keys=()
1190+
_cmd_config_schema_loaded=0
1191+
if [[ -f "$_schema_path" ]] && command -v jq >/dev/null 2>&1; then
1192+
mapfile -t _cmd_config_secret_keys < <(jq -r '.properties | to_entries[] | select(.value.secret == true) | .key' "$_schema_path" 2>/dev/null)
1193+
_cmd_config_schema_loaded=1
1194+
fi
1195+
}
1196+
1197+
_cmd_config_is_secret() {
1198+
local _k="$1" _s _kl
1199+
if (( _cmd_config_schema_loaded == 1 )); then
1200+
for _s in "${_cmd_config_secret_keys[@]}"; do
1201+
[[ "$_k" == "$_s" ]] && return 0
1202+
done
1203+
# Fall through to keyword match — defense in depth against schema gaps
1204+
# and against malformed schemas where jq returns zero secret keys.
1205+
fi
1206+
_kl="${_k,,}"
1207+
case "$_kl" in
1208+
*secret*|*password*|*pass*|*token*|*key*|*salt*|*bearer*) return 0 ;;
1209+
esac
1210+
return 1
1211+
}
1212+
11771213
cmd_config() {
11781214
check_install
11791215

@@ -1186,36 +1222,7 @@ cmd_config() {
11861222
echo ""
11871223
echo -e "${CYAN}.env contents:${NC}"
11881224

1189-
# Mask secrets using .env.schema.json as the authoritative source:
1190-
# keys marked `"secret": true` in the schema are hidden. If the
1191-
# schema file or jq is unavailable, fall back to a case-insensitive
1192-
# substring match on common secret-ish keywords. The narrow regex
1193-
# that previously lived here missed suffixes like _PASSWORD and
1194-
# _SALT (see fix notes in commit message).
1195-
local _schema_path="$INSTALL_DIR/.env.schema.json"
1196-
local -a _secret_keys=()
1197-
local _schema_loaded=0
1198-
if [[ -f "$_schema_path" ]] && command -v jq >/dev/null 2>&1; then
1199-
mapfile -t _secret_keys < <(jq -r '.properties | to_entries[] | select(.value.secret == true) | .key' "$_schema_path" 2>/dev/null)
1200-
_schema_loaded=1
1201-
fi
1202-
1203-
_cmd_config_is_secret() {
1204-
local _k="$1" _s _kl
1205-
if (( _schema_loaded == 1 )); then
1206-
for _s in "${_secret_keys[@]}"; do
1207-
[[ "$_k" == "$_s" ]] && return 0
1208-
done
1209-
return 1
1210-
fi
1211-
# Fallback (schema or jq unavailable): case-insensitive
1212-
# substring match against common secret-ish keywords.
1213-
_kl="${_k,,}"
1214-
case "$_kl" in
1215-
*secret*|*password*|*pass*|*token*|*key*|*salt*|*bearer*) return 0 ;;
1216-
esac
1217-
return 1
1218-
}
1225+
_cmd_config_load_secret_schema
12191226

12201227
local _line _key
12211228
while IFS= read -r _line; do
@@ -2121,6 +2128,11 @@ META
21212128
# Compare environment variables
21222129
echo -e "${CYAN}━━━ Environment Variables ━━━${NC}"
21232130
if [[ -f "$dir1/env" ]] && [[ -f "$dir2/env" ]]; then
2131+
# Load secret schema so `_cmd_config_is_secret` below can consult
2132+
# .env.schema.json instead of the narrow regex the old version
2133+
# used (which missed _PASS, _SALT, email admin fields, etc.).
2134+
_cmd_config_load_secret_schema
2135+
21242136
# Parse both env files
21252137
declare -A env1 env2
21262138
while IFS='=' read -r key value; do
@@ -2152,7 +2164,7 @@ META
21522164
has_diff=true
21532165

21542166
# Mask sensitive values for display
2155-
if [[ "$key" =~ (PASSWORD|SECRET|KEY|TOKEN|API) ]]; then
2167+
if _cmd_config_is_secret "$key"; then
21562168
[[ -n "$val1" ]] && val1="***"
21572169
[[ -n "$val2" ]] && val2="***"
21582170
fi

0 commit comments

Comments
 (0)