fix(skills): keep curl credentials out of argv (#2175)

* fix(skills): avoid curl credential argv leaks

* test(ci): guard secret curl examples
This commit is contained in:
Kumario 2026-06-07 00:25:45 -05:00 committed by GitHub
parent 40673a89fa
commit 70fde3c14f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 143 additions and 19 deletions

View File

@ -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"
```

View File

@ -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"
```

View File

@ -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"
```

View File

@ -23,7 +23,8 @@ Connects Claude Code to [SocialClaw](https://getsocialclaw.com) for agent-driven
export SC_API_KEY="<workspace-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

View File

@ -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();