fix: BuildKit secret のプレーンテキストトークンを JSON に変換 - #633
Conversation
docker-image.yml から渡される BuildKit secret はトークン文字列だが、 install-claude-plugins.sh は JSON ファイルとして cp していたため プラグインインストールが失敗していた。 secret の内容が JSON か判定し、プレーンテキストなら credentials JSON に変換するロジックを追加。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe script's credential setup now validates the BuildKit secret file as both existing and non-empty, then checks if its contents are valid JSON. If valid, the secret is used directly; otherwise, a new JSON structure wrapping the token into Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dce5e98b33
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| cat > "${CLAUDE_DIR}/.credentials.json" << CRED_EOF | ||
| { | ||
| "claudeAiOauth": { | ||
| "accessToken": "${SECRET_CONTENT}", |
There was a problem hiding this comment.
Escape token before writing credentials JSON
When the BuildKit secret is plain text, SECRET_CONTENT is interpolated directly into JSON without escaping. If the token contains JSON-significant characters (for example ", \, or a newline), the generated .credentials.json becomes invalid and Claude plugin installation fails in that build context. This new conversion path should serialize the token with a JSON encoder (e.g., python3/jq) instead of raw string interpolation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
script/install-claude-plugins.sh (1)
73-92:⚠️ Potential issue | 🟠 MajorSame JSON injection risk exists in fallback branches.
The
CLAUDE_CODE_OAUTH_TOKENandANTHROPIC_API_KEYbranches (lines 75-82 and 85-92) have the same vulnerability where special characters in the token value could produce malformed JSON. Consider applying the samejq-based fix for consistency and safety across all credential sources.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@script/install-claude-plugins.sh` around lines 73 - 92, The CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY branches write raw token strings into "${CLAUDE_DIR}/.credentials.json" which can corrupt JSON if tokens contain special chars; update both branches to build the JSON safely using jq (or the same safe helper used elsewhere) to emit {"claudeAiOauth": {"accessToken": <token>, "expiresAt": 9999999999999}} and write that output to "${CLAUDE_DIR}/.credentials.json", keeping the existing log_info messages and using the CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY variables as the jq inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@script/install-claude-plugins.sh`:
- Around line 63-70: The heredoc writing .credentials.json with unescaped
SECRET_CONTENT risks malformed JSON when SECRET_CONTENT contains quotes,
backslashes or newlines; replace the heredoc block that writes
"${CLAUDE_DIR}/.credentials.json" with a safe JSON emitter that calls python3 to
read the SECRET_CONTENT environment variable and json.dump a dict like
{"claudeAiOauth": {"accessToken": secret, "expiresAt": 9999999999999}} into the
target file (use CLAUDE_DIR and SECRET_CONTENT as environment variables), so
python3 handles proper escaping and no extra dependency (jq) is required.
---
Outside diff comments:
In `@script/install-claude-plugins.sh`:
- Around line 73-92: The CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY branches
write raw token strings into "${CLAUDE_DIR}/.credentials.json" which can corrupt
JSON if tokens contain special chars; update both branches to build the JSON
safely using jq (or the same safe helper used elsewhere) to emit
{"claudeAiOauth": {"accessToken": <token>, "expiresAt": 9999999999999}} and
write that output to "${CLAUDE_DIR}/.credentials.json", keeping the existing
log_info messages and using the CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY
variables as the jq inputs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1ce38fbd-13f8-4bbb-82e8-f540642eb994
📒 Files selected for processing (1)
script/install-claude-plugins.sh
| cat > "${CLAUDE_DIR}/.credentials.json" << CRED_EOF | ||
| { | ||
| "claudeAiOauth": { | ||
| "accessToken": "${SECRET_CONTENT}", | ||
| "expiresAt": 9999999999999 | ||
| } | ||
| } | ||
| CRED_EOF |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if jq is installed in the Dockerfile or base image
rg -n 'jq' --glob 'Dockerfile*' --glob '*.dockerfile'
rg -n 'apt.*install.*jq|apk.*add.*jq|yum.*install.*jq' --glob 'Dockerfile*'Repository: keito4/config
Length of output: 354
🏁 Script executed:
# Check if the script file exists and view the relevant lines
if [ -f "script/install-claude-plugins.sh" ]; then
wc -l "script/install-claude-plugins.sh"
echo "---Lines 63-70---"
sed -n '63,70p' "script/install-claude-plugins.sh"
else
echo "File not found: script/install-claude-plugins.sh"
fiRepository: keito4/config
Length of output: 280
🏁 Script executed:
# Search for Dockerfile or build configuration files
find . -type f \( -name "Dockerfile*" -o -name "docker-compose*" -o -name "*.dockerfile" \) 2>/dev/null | head -20Repository: keito4/config
Length of output: 83
🏁 Script executed:
# Search for any reference to jq in the codebase
rg -i 'jq' --type sh --type json --type yaml --type dockerfile 2>/dev/null | head -20Repository: keito4/config
Length of output: 39
🏁 Script executed:
# Run shellcheck on the script per coding guidelines
if command -v shellcheck >/dev/null 2>&1; then
shellcheck "script/install-claude-plugins.sh" 2>&1 || true
else
echo "shellcheck not available"
fiRepository: keito4/config
Length of output: 595
🏁 Script executed:
cat -n "./.devcontainer/Dockerfile"Repository: keito4/config
Length of output: 11407
🏁 Script executed:
# Check the content of the install script to understand SECRET_CONTENT source
grep -n "SECRET_CONTENT" "script/install-claude-plugins.sh" | head -20Repository: keito4/config
Length of output: 254
🏁 Script executed:
# Check what base image is used and common tools available
head -30 "./.devcontainer/Dockerfile"Repository: keito4/config
Length of output: 905
🏁 Script executed:
# Check for shellcheck exclusion list
if [ -f "script/.shellcheck-exclude" ]; then
cat "script/.shellcheck-exclude"
else
echo "No .shellcheck-exclude file found"
fiRepository: keito4/config
Length of output: 213
🏁 Script executed:
# Get more context around the vulnerable lines (55-75)
sed -n '55,75p' "script/install-claude-plugins.sh"Repository: keito4/config
Length of output: 825
🏁 Script executed:
# Check if json processing via python3 is used elsewhere in the script
grep -n "python3" "script/install-claude-plugins.sh"Repository: keito4/config
Length of output: 160
JSON injection risk: Plain token with special characters produces malformed JSON.
When SECRET_CONTENT contains ", \, or newlines, the heredoc at lines 63-70 generates invalid JSON. Example: token abc"def produces "accessToken": "abc"def".
The proposed jq solution requires installing jq in the Dockerfile first (not currently available). A better approach using the already-available python3:
🔧 Safe JSON encoding using python3
- cat > "${CLAUDE_DIR}/.credentials.json" << CRED_EOF
-{
- "claudeAiOauth": {
- "accessToken": "${SECRET_CONTENT}",
- "expiresAt": 9999999999999
- }
-}
-CRED_EOF
+ python3 << PYTHON_EOF > "${CLAUDE_DIR}/.credentials.json"
+import json
+data = {
+ "claudeAiOauth": {
+ "accessToken": "${SECRET_CONTENT}",
+ "expiresAt": 9999999999999
+ }
+}
+json.dump(data, sys.stdout)
+PYTHON_EOF🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@script/install-claude-plugins.sh` around lines 63 - 70, The heredoc writing
.credentials.json with unescaped SECRET_CONTENT risks malformed JSON when
SECRET_CONTENT contains quotes, backslashes or newlines; replace the heredoc
block that writes "${CLAUDE_DIR}/.credentials.json" with a safe JSON emitter
that calls python3 to read the SECRET_CONTENT environment variable and json.dump
a dict like {"claudeAiOauth": {"accessToken": secret, "expiresAt":
9999999999999}} into the target file (use CLAUDE_DIR and SECRET_CONTENT as
environment variables), so python3 handles proper escaping and no extra
dependency (jq) is required.
コードレビュー概要BuildKit secret がプレーンテキストの場合に JSON 変換する仕組みは理にかなっています。 指摘事項重要: JSON インジェクション / 不正な JSON 生成リスクheredoc 内で 修正案: Python で JSON を正しく構築する
if python3 -c "import sys,json; json.load(sys.stdin)" < "$CREDENTIALS_SECRET" 2>/dev/null; then
cp "$CREDENTIALS_SECRET" "${CLAUDE_DIR}/.credentials.json"
else
log_info "トークン文字列を credentials JSON に変換中..."
python3 -c "
import json, sys
token = sys.stdin.read().strip()
d = dict(claudeAiOauth=dict(accessToken=token, expiresAt=9999999999999))
print(json.dumps(d, indent=2))
" < "$CREDENTIALS_SECRET" > "${CLAUDE_DIR}/.credentials.json"
fi
chmod 600 "${CLAUDE_DIR}/.credentials.json"軽微: 既存コードとの重複
良い点
まとめJSON 構築を |
|
🎉 This PR is included in version 1.106.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
install-claude-plugins.shで BuildKit secret の内容が JSON かプレーンテキストかを判定し、プレーンテキストの場合は credentials JSON に変換するロジックを追加docker-image.ymlからCLAUDE_CODE_OAUTH_TOKENを BuildKit secret として渡した場合にプラグインが正しくインストールされるWhy
docker-image.ymlはsecrets: claude_credentials=${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}でトークン文字列を渡すが、スクリプト側はそれを JSON ファイルとしてcpしていたため、プラグインインストールが失敗していた。How
BuildKit secret ファイルの内容を
python3 -c "import sys,json; json.load(sys.stdin)"で JSON 判定し:claudeAiOauthJSON に変換セキュリティ: secret はレイヤーに残らず、
.credentials.jsonはスクリプト末尾で削除される。Test plan
workflow_dispatchでイメージビルドを実行し、プラグインがインストールされることを確認[INFO] トークン文字列を credentials JSON に変換中...が表示されること🤖 Generated with Claude Code
Summary by CodeRabbit