Skip to content

fix: BuildKit secret のプレーンテキストトークンを JSON に変換 - #633

Merged
keito4 merged 1 commit into
mainfrom
fix/buildkit-secret-plugin-install
Mar 23, 2026
Merged

fix: BuildKit secret のプレーンテキストトークンを JSON に変換#633
keito4 merged 1 commit into
mainfrom
fix/buildkit-secret-plugin-install

Conversation

@keito4

@keito4 keito4 commented Mar 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • install-claude-plugins.sh で BuildKit secret の内容が JSON かプレーンテキストかを判定し、プレーンテキストの場合は credentials JSON に変換するロジックを追加
  • これにより docker-image.yml から CLAUDE_CODE_OAUTH_TOKEN を BuildKit secret として渡した場合にプラグインが正しくインストールされる

Why

docker-image.ymlsecrets: claude_credentials=${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} でトークン文字列を渡すが、スクリプト側はそれを JSON ファイルとして cp していたため、プラグインインストールが失敗していた。

How

BuildKit secret ファイルの内容を python3 -c "import sys,json; json.load(sys.stdin)" で JSON 判定し:

  • JSON → そのままコピー
  • プレーンテキスト → claudeAiOauth JSON に変換

セキュリティ: secret はレイヤーに残らず、.credentials.json はスクリプト末尾で削除される。

Test plan

  • workflow_dispatch でイメージビルドを実行し、プラグインがインストールされることを確認
  • ビルドログに [INFO] トークン文字列を credentials JSON に変換中... が表示されること

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved credentials validation during plugin installation with automatic recovery from malformed credentials.

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>
@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The 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 claudeAiOauth.accessToken is generated and written to .credentials.json with restricted permissions.

Changes

Cohort / File(s) Summary
Credential Validation
script/install-claude-plugins.sh
Enhanced BuildKit secret handling with JSON validation; attempts to parse secret as JSON, falls back to token wrapping if invalid, and sets file permissions to 600.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

size/XS

Poem

🐰 A secret arrives, we wonder if true,
JSON we parse—is it valid or new?
If it's well-formed, we keep what we've got,
If not, we shall wrap it in all that we've thought.
With permissions now tightened, we rest easy tonight! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: converting BuildKit secret plain text tokens to JSON format.
Description check ✅ Passed The description covers all required sections: Summary, Why, How, and includes a test plan with specific steps and acceptance criteria.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/buildkit-secret-plugin-install

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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}",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@keito4 keito4 self-assigned this Mar 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Same JSON injection risk exists in fallback branches.

The CLAUDE_CODE_OAUTH_TOKEN and ANTHROPIC_API_KEY branches (lines 75-82 and 85-92) have the same vulnerability where special characters in the token value could produce malformed JSON. Consider applying the same jq-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

📥 Commits

Reviewing files that changed from the base of the PR and between 0f8e527 and dce5e98.

📒 Files selected for processing (1)
  • script/install-claude-plugins.sh

Comment on lines +63 to +70
cat > "${CLAUDE_DIR}/.credentials.json" << CRED_EOF
{
"claudeAiOauth": {
"accessToken": "${SECRET_CONTENT}",
"expiresAt": 9999999999999
}
}
CRED_EOF

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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"
fi

Repository: 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 -20

Repository: 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 -20

Repository: 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"
fi

Repository: 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 -20

Repository: 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"
fi

Repository: 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.

@claude

claude Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

コードレビュー

概要

BuildKit secret がプレーンテキストの場合に JSON 変換する仕組みは理にかなっています。-s チェックの追加も防御的で適切です。


指摘事項

重要: JSON インジェクション / 不正な JSON 生成リスク

heredoc 内で ${SECRET_CONTENT} を直接展開しているため、トークンに "\ が含まれると JSON が壊れます。また cat "$CREDENTIALS_SECRET" の末尾改行がそのまま accessToken の値に含まれ、無効な JSON になる可能性があります。

修正案: Python で JSON を正しく構築する

json.dumps を使えば特殊文字エスケープと末尾改行の除去が自動で行われます。SECRET_CONTENT 変数も不要になります。

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"

軽微: 既存コードとの重複

CLAUDE_CODE_OAUTH_TOKEN の elif ブランチも同様の heredoc でトークンを JSON に変換しており、同じリスクを持っています。本 PR のスコープ外ですが、別 Issue として対応を検計してください。


良い点

  • -s チェックの追加で空ファイルの誤処理を防いでいる
  • Python を使った JSON 判定ロジックは明快
  • セキュリティ上、認証情報はレイヤーに残らず rm -f で削除される

まとめ

JSON 構築を json.dumps ベースに変えることで、トークン内の特殊文字や末尾改行の問題を安全に回避できます。それ以外のロジックは問題ありません。

@keito4
keito4 merged commit 7996432 into main Mar 23, 2026
22 checks passed
@keito4
keito4 deleted the fix/buildkit-secret-plugin-install branch March 23, 2026 05:33
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.106.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released リリース済み

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant