From 70fde3c14fd13270fd69c80838ab053a82fd64d3 Mon Sep 17 00:00:00 2001 From: Kumario <93065676+Kumario1@users.noreply.github.com> Date: Sun, 7 Jun 2026 00:25:45 -0500 Subject: [PATCH] fix(skills): keep curl credentials out of argv (#2175) * fix(skills): avoid curl credential argv leaks * test(ci): guard secret curl examples --- docs/ja-JP/skills/jira-integration/SKILL.md | 21 +++-- docs/zh-CN/skills/jira-integration/SKILL.md | 21 +++-- skills/jira-integration/SKILL.md | 21 +++-- skills/social-publisher/SKILL.md | 3 +- tests/ci/secret-curl-flags.test.js | 96 +++++++++++++++++++++ 5 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 tests/ci/secret-curl-flags.test.js diff --git a/docs/ja-JP/skills/jira-integration/SKILL.md b/docs/ja-JP/skills/jira-integration/SKILL.md index 6f1a5add..ceb02558 100644 --- a/docs/ja-JP/skills/jira-integration/SKILL.md +++ b/docs/ja-JP/skills/jira-integration/SKILL.md @@ -65,6 +65,15 @@ MCP が利用できない場合は、`curl` またはヘルパースクリプト シェル環境変数、シークレットマネージャー、またはリポジトリにコミットしないローカル環境ファイルに保存してください。 +直接 `curl` 例では、Jira ユーザー設定を標準入力で渡し、認証情報がコマンドライン引数に出ないようにします。 + +```bash +jira_curl() { + printf 'user = "%s:%s"\n' "$JIRA_EMAIL" "$JIRA_API_TOKEN" | + curl -s -K - "$@" +} +``` + ## MCP ツールリファレンス `mcp-atlassian` MCP サーバーが設定されている場合、以下のツールが利用可能です。 @@ -88,7 +97,7 @@ MCP が利用できない場合は、`curl` またはヘルパースクリプト ### チケットの取得 ```bash -curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl \ -H "Content-Type: application/json" \ "$JIRA_URL/rest/api/3/issue/PROJ-1234" | jq '{ key: .key, @@ -105,7 +114,7 @@ curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ### コメントの取得 ```bash -curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl \ -H "Content-Type: application/json" \ "$JIRA_URL/rest/api/3/issue/PROJ-1234?fields=comment" | jq '.fields.comment.comments[] | { author: .author.displayName, @@ -117,7 +126,7 @@ curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ### コメントの追加 ```bash -curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl -X POST \ -H "Content-Type: application/json" \ -d '{ "body": { @@ -136,11 +145,11 @@ curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ```bash # 1. 利用可能なトランジションを取得 -curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl \ "$JIRA_URL/rest/api/3/issue/PROJ-1234/transitions" | jq '.transitions[] | {id, name: .name}' # 2. トランジションを実行(TRANSITION_ID を置き換える) -curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl -X POST \ -H "Content-Type: application/json" \ -d '{"transition": {"id": "TRANSITION_ID"}}' \ "$JIRA_URL/rest/api/3/issue/PROJ-1234/transitions" @@ -149,7 +158,7 @@ curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ### JQL での検索 ```bash -curl -s -G -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl -G \ --data-urlencode "jql=project = PROJ AND status = 'In Progress'" \ "$JIRA_URL/rest/api/3/search" ``` diff --git a/docs/zh-CN/skills/jira-integration/SKILL.md b/docs/zh-CN/skills/jira-integration/SKILL.md index 1b707219..62089fd3 100644 --- a/docs/zh-CN/skills/jira-integration/SKILL.md +++ b/docs/zh-CN/skills/jira-integration/SKILL.md @@ -67,6 +67,15 @@ origin: ECC 将这些存储在您的 shell 环境、密钥管理器或未跟踪的本地环境文件中。不要将其提交到仓库。 +对于直接 `curl` 示例,请通过标准输入传递 Jira 用户配置,避免凭据出现在命令行参数中。 + +```bash +jira_curl() { + printf 'user = "%s:%s"\n' "$JIRA_EMAIL" "$JIRA_API_TOKEN" | + curl -s -K - "$@" +} +``` + ## MCP 工具参考 当配置了 `mcp-atlassian` MCP 服务器时,以下工具可用: @@ -90,7 +99,7 @@ origin: ECC ### 获取工单 ```bash -curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl \ -H "Content-Type: application/json" \ "$JIRA_URL/rest/api/3/issue/PROJ-1234" | jq '{ key: .key, @@ -107,7 +116,7 @@ curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ### 获取评论 ```bash -curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl \ -H "Content-Type: application/json" \ "$JIRA_URL/rest/api/3/issue/PROJ-1234?fields=comment" | jq '.fields.comment.comments[] | { author: .author.displayName, @@ -119,7 +128,7 @@ curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ### 添加评论 ```bash -curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl -X POST \ -H "Content-Type: application/json" \ -d '{ "body": { @@ -138,11 +147,11 @@ curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ```bash # 1. Get available transitions -curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl \ "$JIRA_URL/rest/api/3/issue/PROJ-1234/transitions" | jq '.transitions[] | {id, name: .name}' # 2. Execute transition (replace TRANSITION_ID) -curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl -X POST \ -H "Content-Type: application/json" \ -d '{"transition": {"id": "TRANSITION_ID"}}' \ "$JIRA_URL/rest/api/3/issue/PROJ-1234/transitions" @@ -151,7 +160,7 @@ curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ### 使用 JQL 搜索 ```bash -curl -s -G -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl -G \ --data-urlencode "jql=project = PROJ AND status = 'In Progress'" \ "$JIRA_URL/rest/api/3/search" ``` diff --git a/skills/jira-integration/SKILL.md b/skills/jira-integration/SKILL.md index 564c28bd..f4ea12be 100644 --- a/skills/jira-integration/SKILL.md +++ b/skills/jira-integration/SKILL.md @@ -65,6 +65,15 @@ If MCP is not available, use the Jira REST API v3 directly via `curl` or a helpe Store these in your shell environment, secrets manager, or an untracked local env file. Do not commit them to the repo. +For direct `curl` examples, keep credentials out of command-line arguments by passing the Jira user config on stdin: + +```bash +jira_curl() { + printf 'user = "%s:%s"\n' "$JIRA_EMAIL" "$JIRA_API_TOKEN" | + curl -s -K - "$@" +} +``` + ## MCP Tools Reference When the `mcp-atlassian` MCP server is configured, these tools are available: @@ -88,7 +97,7 @@ When the `mcp-atlassian` MCP server is configured, these tools are available: ### Fetch a Ticket ```bash -curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl \ -H "Content-Type: application/json" \ "$JIRA_URL/rest/api/3/issue/PROJ-1234" | jq '{ key: .key, @@ -105,7 +114,7 @@ curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ### Fetch Comments ```bash -curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl \ -H "Content-Type: application/json" \ "$JIRA_URL/rest/api/3/issue/PROJ-1234?fields=comment" | jq '.fields.comment.comments[] | { author: .author.displayName, @@ -117,7 +126,7 @@ curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ### Add a Comment ```bash -curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl -X POST \ -H "Content-Type: application/json" \ -d '{ "body": { @@ -136,11 +145,11 @@ curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ```bash # 1. Get available transitions -curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl \ "$JIRA_URL/rest/api/3/issue/PROJ-1234/transitions" | jq '.transitions[] | {id, name: .name}' # 2. Execute transition (replace TRANSITION_ID) -curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl -X POST \ -H "Content-Type: application/json" \ -d '{"transition": {"id": "TRANSITION_ID"}}' \ "$JIRA_URL/rest/api/3/issue/PROJ-1234/transitions" @@ -149,7 +158,7 @@ curl -s -X POST -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ ### Search with JQL ```bash -curl -s -G -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \ +jira_curl -G \ --data-urlencode "jql=project = PROJ AND status = 'In Progress'" \ "$JIRA_URL/rest/api/3/search" ``` diff --git a/skills/social-publisher/SKILL.md b/skills/social-publisher/SKILL.md index 536e573e..02771bf3 100644 --- a/skills/social-publisher/SKILL.md +++ b/skills/social-publisher/SKILL.md @@ -23,7 +23,8 @@ Connects Claude Code to [SocialClaw](https://getsocialclaw.com) for agent-driven export SC_API_KEY="" # Verify access -curl -sS -H "Authorization: Bearer $SC_API_KEY" https://getsocialclaw.com/v1/keys/validate +printf 'header = "Authorization: Bearer %s"\n' "$SC_API_KEY" | + curl -sS -K - https://getsocialclaw.com/v1/keys/validate # Install CLI (optional but recommended) npm install -g socialclaw@0.1.12 diff --git a/tests/ci/secret-curl-flags.test.js b/tests/ci/secret-curl-flags.test.js new file mode 100644 index 00000000..c411f432 --- /dev/null +++ b/tests/ci/secret-curl-flags.test.js @@ -0,0 +1,96 @@ +#!/usr/bin/env node +/** + * Guard agent-facing curl examples from exposing credentials in argv. + */ + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const repoRoot = path.resolve(__dirname, '..', '..'); + +const jiraDocs = [ + 'skills/jira-integration/SKILL.md', + 'docs/ja-JP/skills/jira-integration/SKILL.md', + 'docs/zh-CN/skills/jira-integration/SKILL.md', +]; + +const socialDocs = [ + 'skills/social-publisher/SKILL.md', +]; + +function test(name, fn) { + try { + fn(); + console.log(` ✓ ${name}`); + return true; + } catch (error) { + console.log(` ✗ ${name}`); + console.log(` Error: ${error.message}`); + return false; + } +} + +function read(relativePath) { + return fs.readFileSync(path.join(repoRoot, relativePath), 'utf8'); +} + +function shellExamples(source) { + const examples = []; + const fencePattern = /```(?:bash|sh|shell)\r?\n([\s\S]*?)```/g; + let match; + + while ((match = fencePattern.exec(source)) !== null) { + examples.push(match[1].replace(/\\\r?\n\s*/g, ' ')); + } + + return examples.join('\n'); +} + +function run() { + console.log('\n=== Testing secret-safe curl examples ===\n'); + + let passed = 0; + let failed = 0; + + for (const relativePath of jiraDocs) { + if (test(`${relativePath} keeps Jira credentials out of curl argv`, () => { + const source = read(relativePath); + const shell = shellExamples(source); + + assert.match(shell, /jira_curl\(\)/, 'Expected a Jira curl wrapper'); + assert.match(shell, /\bcurl -s -K - "\$@"/, 'Expected curl config stdin in Jira wrapper'); + assert.doesNotMatch( + shell, + /\bcurl\b[^\n]*(?:-u|--user)(?:=|\s+)(?:"|')?\$JIRA_EMAIL:\$JIRA_API_TOKEN/, + 'Jira credentials must not be passed with curl -u/--user', + ); + })) passed++; else failed++; + } + + for (const relativePath of socialDocs) { + if (test(`${relativePath} keeps SocialClaw bearer token out of curl argv`, () => { + const source = read(relativePath); + const shell = shellExamples(source); + + assert.match( + shell, + /printf 'header = "Authorization: Bearer %s"\\n' "\$SC_API_KEY" \|/, + 'Expected SocialClaw bearer header to be passed via curl config stdin', + ); + assert.match(shell, /\bcurl -sS -K - https:\/\/getsocialclaw\.com\/v1\/keys\/validate/, 'Expected curl -K - validation call'); + assert.doesNotMatch( + shell, + /\bcurl\b[^\n]*-H\s+(?:"|')Authorization:\s*Bearer\s+\$SC_API_KEY(?:"|')/, + 'SocialClaw bearer token must not be passed with curl -H', + ); + })) passed++; else failed++; + } + + console.log(`\nPassed: ${passed}`); + console.log(`Failed: ${failed}`); + + process.exit(failed > 0 ? 1 : 0); +} + +run();