Skip to content

Commit 93af788

Browse files
committed
refactor(auth): replace credential backup with overlay isolation
- Add ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL env var support - Replace mv/restore credential backup with overlay file mounting - Filter sensitive files (.credentials.json, auth.json) from home mounts - Extract should_mount_home_item() for cleaner mount logic
1 parent ac74d82 commit 93af788

3 files changed

Lines changed: 108 additions & 61 deletions

File tree

agents/claude.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ setup_claude_auth() {
8080
DOCKER_ARGS+=("-e" "CLAUDE_CODE_OAUTH_TOKEN=$CLAUDE_CODE_OAUTH_TOKEN")
8181
AUTH_DETAILS="oauth-token (CLAUDE_CODE_OAUTH_TOKEN)"
8282
echo "Using OAuth token from CLAUDE_CODE_OAUTH_TOKEN" >&2
83+
elif [ -n "${ANTHROPIC_AUTH_TOKEN:-}" ]; then
84+
DOCKER_ARGS+=("-e" "ANTHROPIC_AUTH_TOKEN=$ANTHROPIC_AUTH_TOKEN")
85+
AUTH_DETAILS="auth-token (ANTHROPIC_AUTH_TOKEN)"
86+
echo "Using auth token from ANTHROPIC_AUTH_TOKEN" >&2
8387
elif [ -n "${ANTHROPIC_API_KEY:-}" ] && is_oauth_token_pattern "$ANTHROPIC_API_KEY"; then
8488
DOCKER_ARGS+=("-e" "CLAUDE_CODE_OAUTH_TOKEN=$ANTHROPIC_API_KEY")
8589
AUTH_DETAILS="oauth-token (auto-detected from ANTHROPIC_API_KEY)"
@@ -89,7 +93,10 @@ setup_claude_auth() {
8993
AUTH_DETAILS="api-key (ANTHROPIC_API_KEY)"
9094
else
9195
auth_error "No API key found for --auth-with api-key" \
92-
"Set: export ANTHROPIC_API_KEY=sk-ant-... or export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-..."
96+
"Set: export ANTHROPIC_API_KEY=sk-ant-..., export ANTHROPIC_AUTH_TOKEN=token, or export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-..."
97+
fi
98+
if [ -n "${ANTHROPIC_BASE_URL:-}" ]; then
99+
DOCKER_ARGS+=("-e" "ANTHROPIC_BASE_URL=$ANTHROPIC_BASE_URL")
93100
fi
94101
;;
95102
copilot)
@@ -122,6 +129,9 @@ setup_claude_auth() {
122129
fi
123130
AUTH_DETAILS="oauth-token (CLAUDE_CODE_OAUTH_TOKEN)"
124131
DOCKER_ARGS+=("-e" "CLAUDE_CODE_OAUTH_TOKEN=$CLAUDE_CODE_OAUTH_TOKEN")
132+
if [ -n "${ANTHROPIC_BASE_URL:-}" ]; then
133+
DOCKER_ARGS+=("-e" "ANTHROPIC_BASE_URL=$ANTHROPIC_BASE_URL")
134+
fi
125135
;;
126136
bedrock)
127137
AUTH_DETAILS="aws-bedrock (region: ${AWS_REGION:-default})"

agents/shared_auth.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ validate_github_token() {
4343
}
4444

4545
validate_anthropic_key() {
46-
[ -n "${ANTHROPIC_API_KEY:-}" ] || [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]
46+
[ -n "${ANTHROPIC_API_KEY:-}" ] || [ -n "${ANTHROPIC_AUTH_TOKEN:-}" ] || [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]
4747
}
4848

4949
validate_openai_key() {

deva.sh

Lines changed: 96 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -784,28 +784,28 @@ should_skip_env_for_auth() {
784784
case "${AUTH_METHOD:-claude}" in
785785
claude)
786786
case "$name" in
787-
ANTHROPIC_API_KEY | ANTHROPIC_BASE_URL | CLAUDE_CODE_OAUTH_TOKEN | OPENAI_API_KEY | OPENAI_BASE_URL | openai_base_url)
787+
ANTHROPIC_API_KEY | ANTHROPIC_AUTH_TOKEN | ANTHROPIC_BASE_URL | CLAUDE_CODE_OAUTH_TOKEN | OPENAI_API_KEY | OPENAI_BASE_URL | openai_base_url)
788788
return 0
789789
;;
790790
esac
791791
;;
792792
api-key | oat)
793793
case "$name" in
794-
ANTHROPIC_API_KEY | ANTHROPIC_BASE_URL | CLAUDE_CODE_OAUTH_TOKEN | OPENAI_API_KEY | OPENAI_BASE_URL | openai_base_url)
794+
ANTHROPIC_API_KEY | ANTHROPIC_AUTH_TOKEN | ANTHROPIC_BASE_URL | CLAUDE_CODE_OAUTH_TOKEN | OPENAI_API_KEY | OPENAI_BASE_URL | openai_base_url)
795795
return 0
796796
;;
797797
esac
798798
;;
799799
copilot)
800800
case "$name" in
801-
ANTHROPIC_API_KEY | ANTHROPIC_BASE_URL | CLAUDE_CODE_OAUTH_TOKEN)
801+
ANTHROPIC_API_KEY | ANTHROPIC_AUTH_TOKEN | ANTHROPIC_BASE_URL | CLAUDE_CODE_OAUTH_TOKEN)
802802
return 0
803803
;;
804804
esac
805805
;;
806806
bedrock | vertex | credentials-file)
807807
case "$name" in
808-
ANTHROPIC_API_KEY | ANTHROPIC_BASE_URL | CLAUDE_CODE_OAUTH_TOKEN | OPENAI_API_KEY | OPENAI_BASE_URL | openai_base_url)
808+
ANTHROPIC_API_KEY | ANTHROPIC_AUTH_TOKEN | ANTHROPIC_BASE_URL | CLAUDE_CODE_OAUTH_TOKEN | OPENAI_API_KEY | OPENAI_BASE_URL | openai_base_url)
809809
return 0
810810
;;
811811
esac
@@ -938,20 +938,36 @@ mount_config_home() {
938938
[ -e "$item" ] || continue
939939
local name
940940
name="$(basename "$item")"
941-
if [ "$name" = "." ] || [ "$name" = ".." ]; then
941+
if ! should_mount_home_item "$item" "$name"; then
942942
continue
943943
fi
944944
DOCKER_ARGS+=(-v "$item:/home/deva/$name")
945945
done
946946
}
947947
948-
# Credential backup system.
949-
# - .claude.json: always cp'd to XDG_STATE (outside mount tree, corruption protection).
950-
# - Credential files: mv'd to tmpdir when auth override is detected.
951-
# - Auth override = non-default --auth-with OR auth env vars reaching container.
952-
# Entries are "orig_path:backup_path" pairs for restore.
953-
BACKED_UP_CREDS=()
954-
CRED_BACKUP_TMPDIR=""
948+
should_mount_home_item() {
949+
local item="$1"
950+
local name="$2"
951+
952+
case "$name" in
953+
. | .. | .DS_Store | .git | .gitignore)
954+
return 1
955+
;;
956+
.claude.json.backup | .claude.json.backup.* | .claude.json.bak.after-corrupted.*)
957+
return 1
958+
;;
959+
*.credentials.json | auth.json | mcp-oauth-tokens-v2.json)
960+
# Loose credential files should only enter the container through explicit auth mounts.
961+
[ -f "$item" ] && return 1
962+
;;
963+
esac
964+
965+
if [ -n "${CUSTOM_CREDENTIALS_FILE:-}" ] && [ "$item" = "$CUSTOM_CREDENTIALS_FILE" ]; then
966+
return 1
967+
fi
968+
969+
return 0
970+
}
955971
956972
# Effective config base: where agent config dirs (.claude/, .codex/, .gemini/) live.
957973
resolve_config_base() {
@@ -1004,18 +1020,10 @@ has_auth_override() {
10041020
return 1
10051021
}
10061022
1007-
backup_credentials() {
1023+
backup_claude_json() {
10081024
local config_base
10091025
config_base=$(resolve_config_base)
10101026
1011-
local agent_dir cred_file
1012-
case "$ACTIVE_AGENT" in
1013-
claude) agent_dir=".claude"; cred_file=".credentials.json" ;;
1014-
codex) agent_dir=".codex"; cred_file="auth.json" ;;
1015-
gemini) agent_dir=".gemini"; cred_file="mcp-oauth-tokens-v2.json" ;;
1016-
*) return 0 ;;
1017-
esac
1018-
10191027
# .claude.json corruption backup: persistent, outside container mount tree.
10201028
if [ "$ACTIVE_AGENT" = "claude" ]; then
10211029
local state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/deva/backups"
@@ -1026,31 +1034,66 @@ backup_credentials() {
10261034
fi
10271035
fi
10281036
1029-
# Credential backup: tmpdir outside all mount trees.
1030-
if has_auth_override; then
1031-
local cred_path="$config_base/$agent_dir/$cred_file"
1032-
if [ -f "$cred_path" ]; then
1033-
CRED_BACKUP_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/deva-cred.XXXXXX")
1034-
mv "$cred_path" "$CRED_BACKUP_TMPDIR/$cred_file"
1035-
BACKED_UP_CREDS+=("$cred_path:$CRED_BACKUP_TMPDIR/$cred_file")
1036-
echo "info: backed up $cred_file -> tmpdir (auth override active)" >&2
1037-
fi
1037+
}
1038+
1039+
default_credential_target_path() {
1040+
case "$ACTIVE_AGENT" in
1041+
claude)
1042+
printf '%s' "/home/deva/.claude/.credentials.json"
1043+
;;
1044+
codex)
1045+
printf '%s' "/home/deva/.codex/auth.json"
1046+
;;
1047+
gemini)
1048+
printf '%s' "/home/deva/.gemini/mcp-oauth-tokens-v2.json"
1049+
;;
1050+
*)
1051+
return 1
1052+
;;
1053+
esac
1054+
}
1055+
1056+
append_auth_credential_overlay() {
1057+
if ! has_auth_override; then
1058+
return
1059+
fi
1060+
1061+
case "$ACTIVE_AGENT:$AUTH_METHOD" in
1062+
claude:credentials-file | codex:credentials-file)
1063+
# Explicit file mount already overlays the default auth file path.
1064+
return
1065+
;;
1066+
esac
1067+
1068+
local target_path
1069+
if ! target_path=$(default_credential_target_path); then
1070+
return
1071+
fi
1072+
1073+
local state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/deva/auth-overlays/$ACTIVE_AGENT"
1074+
local overlay_key="${AUTH_METHOD:-default}"
1075+
case "$ACTIVE_AGENT:$AUTH_METHOD" in
1076+
claude:claude | codex:chatgpt | gemini:oauth | gemini:gemini-app-oauth)
1077+
overlay_key="env"
1078+
;;
1079+
esac
1080+
local overlay_file
1081+
overlay_file="$state_dir/$(workspace_hash).${overlay_key}.blank"
1082+
if [ "$DRY_RUN" != true ]; then
1083+
mkdir -p "$state_dir"
1084+
printf '{}\n' > "$overlay_file"
10381085
fi
1086+
DOCKER_ARGS+=("-v" "$overlay_file:$target_path")
10391087
}
10401088
1041-
restore_backed_up_credentials() {
1042-
local entry
1043-
for entry in "${BACKED_UP_CREDS[@]+"${BACKED_UP_CREDS[@]}"}"; do
1044-
local orig="${entry%%:*}"
1045-
local backup="${entry##*:}"
1046-
if [ -f "$backup" ]; then
1047-
mv "$backup" "$orig"
1048-
echo "info: restored $(basename "$orig")" >&2
1049-
fi
1050-
done
1051-
if [ -n "$CRED_BACKUP_TMPDIR" ] && [ -d "$CRED_BACKUP_TMPDIR" ]; then
1052-
rm -rf "$CRED_BACKUP_TMPDIR"
1089+
mount_loose_home_item() {
1090+
local item="$1"
1091+
local name
1092+
name="$(basename "$item")"
1093+
if ! should_mount_home_item "$item" "$name"; then
1094+
return
10531095
fi
1096+
DOCKER_ARGS+=(-v "$item:/home/deva/$name")
10541097
}
10551098
10561099
mount_dir_contents_into_home() {
@@ -1059,12 +1102,7 @@ mount_dir_contents_into_home() {
10591102
local item
10601103
for item in "$base"/.* "$base"/*; do
10611104
[ -e "$item" ] || continue
1062-
local name
1063-
name="$(basename "$item")"
1064-
if [ "$name" = "." ] || [ "$name" = ".." ]; then
1065-
continue
1066-
fi
1067-
DOCKER_ARGS+=(-v "$item:/home/deva/$name")
1105+
mount_loose_home_item "$item"
10681106
done
10691107
}
10701108
@@ -2035,6 +2073,7 @@ fi
20352073
20362074
autolink_legacy_into_deva_root() {
20372075
[ "$AUTOLINK" = true ] || return 0
2076+
[ "$DRY_RUN" != true ] || return 0
20382077
[ "$CONFIG_HOME_FROM_CLI" = false ] || return 0
20392078
[ -n "${CONFIG_ROOT:-}" ] || return 0
20402079
[ -d "$CONFIG_ROOT" ] || mkdir -p "$CONFIG_ROOT"
@@ -2259,10 +2298,9 @@ DOCKER_ARGS+=(-e "DEVA_CONTAINER_NAME=${CONTAINER_NAME}")
22592298
DOCKER_ARGS+=(-e "DEVA_WORKSPACE=$(pwd)")
22602299
DOCKER_ARGS+=(-e "DEVA_EPHEMERAL=${EPHEMERAL_MODE}")
22612300
2262-
# Credential backup: move credential files to tmpdir before mounting.
2263-
# Skipped during --dry-run to avoid side effects (mkdir, cp, mv).
2301+
# Back up .claude.json before mounting, without touching live credential files.
22642302
if [ "$QUICK_MODE" != true ] && [ "$DRY_RUN" != true ]; then
2265-
backup_credentials
2303+
backup_claude_json
22662304
fi
22672305
22682306
# Centralized mounting logic.
@@ -2286,6 +2324,12 @@ else
22862324
fi
22872325
fi
22882326
2327+
# Hide default OAuth credential files for non-default auth modes.
2328+
# For credentials-file auth on Claude/Codex, the agent-specific file mount already overlays the path.
2329+
if [ "$QUICK_MODE" = false ]; then
2330+
append_auth_credential_overlay
2331+
fi
2332+
22892333
# Set statusline log paths via env vars (XDG-compliant)
22902334
DOCKER_ARGS+=("-e" "CLAUDE_DATA_DIR=/home/deva/.config/deva/claude")
22912335
DOCKER_ARGS+=("-e" "CLAUDE_CACHE_DIR=/home/deva/.cache/deva/claude/sessions")
@@ -2335,7 +2379,7 @@ mask_secrets_in_args() {
23352379
local name="${BASH_REMATCH[1]}"
23362380
local value="${BASH_REMATCH[2]}"
23372381
case "$name" in
2338-
*TOKEN*|*KEY*|*SECRET*|*PASSWORD*|*CREDENTIALS*|*BARK_KEY*)
2382+
*TOKEN*|*KEY*|*SECRET*|*PASSWORD*|*CREDENTIALS*)
23392383
printf '%s=<redacted> ' "$name"
23402384
;;
23412385
*)
@@ -2364,11 +2408,6 @@ if [ "$DEBUG_MODE" = true ]; then
23642408
echo "" >&2
23652409
fi
23662410
2367-
# Restore backed-up credential files on exit (covers crashes, signals, normal exit)
2368-
if [ ${#BACKED_UP_CREDS[@]} -gt 0 ]; then
2369-
trap restore_backed_up_credentials EXIT
2370-
fi
2371-
23722411
if [ "$DRY_RUN" = true ]; then
23732412
exit 0
23742413
fi
@@ -2429,8 +2468,6 @@ if [ "$EPHEMERAL_MODE" = false ]; then
24292468
update_session_file
24302469
fi
24312470
2432-
# Restore credentials before exec (exec replaces the process, trap won't fire)
2433-
restore_backed_up_credentials
24342471
exec docker exec -it "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}"
24352472
else
24362473
echo "Launching ${ACTIVE_AGENT} (ephemeral mode) via ${DEVA_DOCKER_IMAGE}:${DEVA_DOCKER_TAG}"

0 commit comments

Comments
 (0)