with one click
bouncer
// Execute AWS CLI commands with Telegram approval. Safe commands auto-execute, dangerous commands require human approval via Telegram. Supports trust sessions, batch uploads, and grant sessions.
// Execute AWS CLI commands with Telegram approval. Safe commands auto-execute, dangerous commands require human approval via Telegram. Supports trust sessions, batch uploads, and grant sessions.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | bouncer |
| description | Execute AWS CLI commands with Telegram approval. Safe commands auto-execute, dangerous commands require human approval via Telegram. Supports trust sessions, batch uploads, and grant sessions. |
| metadata | {"openclaw":{"emoji":"🔐","requires":{"bins":["mcporter"]}}} |
# 輸出 clean(無 JSON wrapper),自動 poll 等審批
bash ~/.openclaw/workspace/skills/bouncer-exec/scripts/bouncer_exec.sh aws s3 ls
bash ~/.openclaw/workspace/skills/bouncer-exec/scripts/bouncer_exec.sh aws sts get-caller-identity
✅ v3.10.0 特殊字元支援(#49 #51):
含空格、pipe (|)、括號等特殊字元的參數現在自動用雙引號包裹,aws_cli_split 解析正確。
# pipe 字元現在正確處理
bash bouncer_exec.sh aws s3 ls --query "Contents[?Size > \`1000\`]"
✅ v3.16.0 --json-args 模式(#85):
當命令含有 |、$、> 等被 OS/shell 截斷的字元時,用 --json-args 傳入 pre-built JSON 繞過 shell pipe 截斷問題。
# 一般模式(適合大多數命令)
bash bouncer_exec.sh aws s3 ls
# --json-args 模式(含特殊字元 / DynamoDB expression 等)
bash bouncer_exec.sh --json-args '{"command":"aws dynamodb query ...","reason":"查詢 items","source":"Private Bot (db check)","trust_scope":"private-bot-main"}'
只有以下 MCP-only tools 才用 mcporter 直接呼叫:
bouncer_deploy, bouncer_deploy_frontend, bouncer_upload, bouncer_upload_batch, bouncer_request_grant, bouncer_trust_status 等
Use mcporter to execute AWS CLI commands through the Bouncer approval system.
API: https://n8s3f1mus6.execute-api.us-east-1.amazonaws.com/prod/
GitHub: https://github.com/qwer2003tw/bouncer
MCP Source: /home/ec2-user/projects/bouncer/bouncer_mcp.py
所有需要審批的操作預設異步返回,避免 API Gateway 29 秒超時:
# 1. 發送請求(立即返回 request_id)
mcporter call bouncer bouncer_execute_native --args '{
"aws": {"service": "s3", "operation": "create_bucket", "params": {"Bucket": "test"}},
"bouncer": {"reason": "建桶", "source": "Private Bot (task)", "trust_scope": "private-bot-main"}
}'
# 返回: {"status": "pending_approval", "request_id": "abc123", ...}
# 2. 輪詢結果(必須!不會自動通知!)
mcporter call bouncer bouncer_status request_id="abc123"
# 返回: {"status": "approved", "result": "..."} 或 {"status": "pending_approval"}
收到 pending_approval 後,你必須主動輪詢 bouncer_status,Bouncer 不會主動通知你結果:
1. 等 10 秒後第一次查 bouncer_status
2. 如果還是 pending,每 10-15 秒查一次
3. 最多輪詢 5 分鐘
4. 超過 5 分鐘仍 pending → 回報「等待審批中,request_id: xxx」
trust_scope 是穩定的呼叫者識別符,用於信任匹配。bouncer_execute_native 必須帶此參數。
缺少 trust_scope 時的錯誤訊息(v3.9.0 改善):
Missing required parameter: trust_scope
trust_scope is a stable caller identifier used for trust session matching.
Examples:
- "private-bot-main" (for general usage)
- "private-bot-deploy" (for deployment tasks)
- "private-bot-kubectl" (for kubectl operations)
source 是顯示用的來源描述,出現在 Telegram 通知中。
格式:{Bot名稱} ({專案/任務})
source="Private Bot (Bouncer 部署)"source="Private Bot"(太模糊)⚠️ 注意:
bouncer_execute已於 v3.70 移除(v3.65 標記為 deprecated)。請使用bouncer_execute_native。
執行 AWS API call(boto3 native,不依賴 awscli)。
v3.66+:
bouncer_mcp.pylocal proxy 已支援此 tool,可透過mcporter call bouncer bouncer_execute_native直接呼叫。與bouncer_execute走相同的 Bouncer 審批 pipeline,但不需要 CLI string,直接用 boto3 參數格式。
使用時機:
aws eks create-cluster --version 1.32(awscli v1 全域 flag 衝突)→ 改用 nativemcporter call bouncer bouncer_execute_native --args '{
"aws": {
"service": "eks",
"operation": "create_cluster",
"region": "us-east-1",
"account": "992382394211",
"params": {
"name": "my-cluster",
"version": "1.32",
"roleArn": "arn:aws:iam::992382394211:role/eksClusterRole",
"resourcesVpcConfig": {
"subnetIds": ["subnet-xxx", "subnet-yyy"],
"securityGroupIds": ["sg-xxx"]
}
}
},
"bouncer": {
"reason": "建立 EKS cluster",
"source": "Private Bot (EKS)",
"trust_scope": "private-bot-eks"
}
}'
Parameters(aws 物件):
| 參數 | 必填 | 說明 |
|---|---|---|
service | ✅ | boto3 service name(eks, s3, ec2, iam...) |
operation | ✅ | boto3 method name(snake_case,如 create_cluster) |
params | ✅ | boto3 kwargs dict |
region | ❌ | AWS region(預設 us-east-1) |
account | ❌ | 目標 AWS 帳號 ID(用於 cross-account assume role) |
Parameters(bouncer 物件):
| 參數 | 必填 | 說明 |
|---|---|---|
reason | ✅ | 執行原因 |
source | ✅ | 來源標識 |
trust_scope | ✅ | 穩定呼叫者 ID |
approval_timeout | ❌ | 審批等待秒數(預設 600) |
與 bouncer_execute 的差異:
bouncer_execute:CLI string(aws eks create-cluster --version 1.32 ...)bouncer_execute_native:boto3 kwargs({"service": "eks", "operation": "create_cluster", "params": {"version": "1.32", ...}})生成 kubectl EKS 認證 token(k8s-aws-v1.* 格式)。等同於 aws eks get-token,但不需要 awscli binary(v3.67+ 新增)。
mcporter call bouncer bouncer_eks_get_token --args '{"cluster_name": "ztp-eks-v2", "region": "us-east-1", "account": "992382394211"}'
專用日誌查詢工具。一步到位查 CloudWatch Logs,不走審批 pipeline。
mcporter call bouncer bouncer_query_logs --args '{
"log_group": "/aws/lambda/bouncer-prod-function",
"filter_pattern": "deploy_frontend_callback",
"start_time": "-1h",
"end_time": "now",
"limit": 50,
"account": "190825685292"
}'
管理日誌查詢允許名單。
# 列出
mcporter call bouncer bouncer_logs_allowlist --args '{"action": "list", "account": "190825685292"}'
# 加入
mcporter call bouncer bouncer_logs_allowlist --args '{"action": "add", "log_group": "/aws/lambda/my-function", "account": "190825685292"}'
# 批量加入
mcporter call bouncer bouncer_logs_allowlist --args '{"action": "add_batch", "log_groups": ["/aws/lambda/a", "/aws/lambda/b"], "account": "190825685292"}'
# 移除
mcporter call bouncer bouncer_logs_allowlist --args '{"action": "remove", "log_group": "/aws/lambda/my-function", "account": "190825685292"}'
查詢審批請求狀態。
mcporter call bouncer bouncer_status request_id="abc123"
列出待審批的請求。
mcporter call bouncer bouncer_list_pending source="Private Bot"
上傳單一檔案到 S3。
CONTENT=$(base64 -w0 config.json)
mcporter call bouncer bouncer_upload \
filename="config.json" \
content="$CONTENT" \
content_type="application/json" \
reason="上傳設定檔" \
source="Private Bot (config)" \
trust_scope="private-bot-main"
Parameters:
| 參數 | 必填 | 說明 |
|---|---|---|
filename | ✅ | 檔案名稱 |
content | ✅ | 檔案內容(base64 encoded) |
reason | ✅ | 上傳原因 |
source | ✅ | 來源標識 |
content_type | ❌ | MIME type(預設 application/octet-stream) |
trust_scope | ❌ | 信任範圍 ID(帶了才能走信任上傳) |
account | ❌ | 目標帳號 |
信任上傳(Trust Upload):
.sh .exe .py .jar .zip .tar.gz .7z .bat .ps1 .rb .war .bin .bash批量上傳多個檔案,一次審批。
mcporter call bouncer bouncer_upload_batch \
files='[
{"filename":"index.html","content":"'$(base64 -w0 index.html)'"},
{"filename":"style.css","content":"'$(base64 -w0 style.css)'"},
{"filename":"app.js","content":"'$(base64 -w0 app.js)'"}
]' \
reason="前端部署" \
source="Private Bot (ZTP Files deploy)" \
trust_scope="private-bot-main"
Parameters:
| 參數 | 必填 | 說明 |
|---|---|---|
files | ✅ | JSON array: [{filename, content, content_type?}] |
reason | ✅ | 上傳原因 |
source | ✅ | 來源標識 |
trust_scope | ❌ | 信任範圍 ID |
account | ❌ | 目標帳號 |
Limits:
⚠️ 大檔案早期驗證(v3.9.0): payload 超過 3.5MB base64(約 2.5MB raw)時,在 decode 前立即返回明確錯誤:
{
"status": "validation_error",
"message": "Total payload too large: ... bytes (limit 3500000). Use bouncer_request_presigned_batch for large files.",
"suggestion": "bouncer_request_presigned_batch"
}
→ 改用 bouncer_request_presigned_batch 避免 Lambda 靜默失敗。
⚠️ base64 截斷偵測(v3.9.0):
透過 CLI args 傳入的 base64 content 若被 OS 截斷(len % 4 != 0),立即返回錯誤:
{
"status": "validation_error",
"message": "Invalid base64 content: likely truncated by OS argument length limit. Use HTTP API or bouncer_request_presigned."
}
審批按鈕:
[📁 批准上傳] — 只批准這批[🔓 批准 + 信任10分鐘] — 批准 + 開信任(含 5 次上傳 quota)[❌ 拒絕]信任 batch: 如果有 active trust session + 足夠 quota → 全部自動執行
大檔案直傳:生成 S3 presigned PUT URL,client 直接 PUT,不過 Lambda(解除 500KB 限制)。
# Step 1: 取得 presigned URL
result=$(mcporter call bouncer bouncer_request_presigned \
--args '{
"filename": "assets/pdf.worker.min.mjs",
"content_type": "application/javascript",
"reason": "ZTP Files 前端部署",
"source": "Private Bot (ZTP Files deploy)"
}')
# Step 2: 直接 PUT(不過 Lambda)
presigned_url=$(echo $result | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('presigned_url',''))")
curl -X PUT \
-H "Content-Type: application/javascript" \
--data-binary @pdf.worker.min.mjs \
"$presigned_url"
Parameters:
| 參數 | 必填 | 說明 |
|---|---|---|
filename | ✅ | 目標檔名(含路徑,如 assets/foo.js) |
content_type | ✅ | MIME type |
reason | ✅ | 上傳原因 |
source | ✅ | 來源標識 |
account | ❌ | 目標帳號(預設主帳號) |
expires_in | ❌ | URL 有效期秒數(預設 900,min 60,max 3600) |
Response:
{
"status": "ready",
"presigned_url": "https://...",
"s3_key": "2026-02-25/{request_id}/assets/foo.js",
"s3_uri": "s3://bouncer-uploads-190825685292/...",
"request_id": "abc123",
"expires_at": "2026-02-25T06:00:00Z",
"method": "PUT",
"headers": {"Content-Type": "application/javascript"}
}
特性:
bouncer-uploads-{DEFAULT_ACCOUNT_ID})bouncer_execute s3 cp(那步才審批)action=presigned_upload, status=url_issued)assets/foo.js 完整保留)批量大檔案直傳:一次呼叫取得 N 個 presigned PUT URL,client 各自直接 PUT,不過 Lambda。解決前端部署 10+ 檔案有大有小的問題。
# Step 1: 一次取得所有 presigned URL
result=$(mcporter call bouncer bouncer_request_presigned_batch \
--args '{
"files": [
{"filename": "index.html", "content_type": "text/html"},
{"filename": "assets/index-xxx.js", "content_type": "application/javascript"},
{"filename": "assets/pdf.worker.min.mjs", "content_type": "application/javascript"}
],
"reason": "ZTP Files 前端部署",
"source": "Private Bot (ZTP Files deploy)"
}')
# Step 2: 各自 PUT(可並行)
echo $result | python3 -c "
import sys, json, subprocess
data = json.load(sys.stdin)
for f in data['files']:
subprocess.run(['curl', '-s', '-X', 'PUT',
'-H', f'Content-Type: {f[\"headers\"][\"Content-Type\"]}',
'--data-binary', f'@{f[\"filename\"]}',
f['presigned_url']])
print(f'Uploaded: {f[\"filename\"]} -> {f[\"s3_uri\"]}')
"
Parameters:
| 參數 | 必填 | 說明 |
|---|---|---|
files | ✅ | [{filename, content_type}],最多 50 個 |
reason | ✅ | 上傳原因 |
source | ✅ | 來源標識 |
account | ❌ | 目標帳號(預設主帳號) |
expires_in | ❌ | URL 有效期秒數(預設 900,min 60,max 3600) |
Response:
{
"status": "ready",
"batch_id": "batch-abc123",
"file_count": 3,
"files": [
{
"filename": "index.html",
"presigned_url": "https://...",
"s3_key": "2026-02-25/batch-abc123/index.html",
"s3_uri": "s3://bouncer-uploads-190825685292/...",
"method": "PUT",
"headers": {"Content-Type": "text/html"}
}
],
"expires_at": "2026-02-25T07:00:00Z",
"bucket": "bouncer-uploads-190825685292"
}
特性:
batch_id prefix,方便後續 s3 cp 批量搬到正式 bucket_1, _2, ...)驗證 presigned batch 上傳結果:在 PUT 後確認所有檔案已成功上傳到 staging bucket,避免後續 s3 cp 時遇到 404。
result=$(mcporter call bouncer bouncer_confirm_upload \
--args '{
"batch_id": "batch-db31d35b7c1e",
"files": [
{"s3_key": "2026-02-25/batch-db31d35b7c1e/index.html"},
{"s3_key": "2026-02-25/batch-db31d35b7c1e/assets/main.js"}
]
}')
# 回傳 verified=true 才繼續後續 s3 cp
Parameters:
| 參數 | 必填 | 說明 |
|---|---|---|
batch_id | ✅ | batch ID(格式:batch-{12 hex chars}) |
files | ✅ | [{s3_key}],最多 50 個 |
Response(成功):
{
"batch_id": "batch-db31d35b7c1e",
"verified": true,
"results": [
{"s3_key": "2026-02-25/batch-db31d35b7c1e/index.html", "exists": true},
{"s3_key": "2026-02-25/batch-db31d35b7c1e/assets/main.js", "exists": true}
],
"missing": []
}
Response(有缺失):
{
"batch_id": "batch-db31d35b7c1e",
"verified": false,
"results": [...],
"missing": ["2026-02-25/batch-db31d35b7c1e/assets/main.js"]
}
特性:
list_objects_v2 批量驗證(比 N 次 HeadObject 省 API call)verified=false 時列出所有缺失檔案建議的前端部署流程:
presigned_batch → PUT 上傳 → confirm_upload 驗證 → (verified=true) → grant s3 cp
審批時選「🔓 信任10分鐘」,期間同 trust_scope 的操作自動執行。
Trust session revoke 或自動到期時,Bouncer 會自動發送 Telegram 摘要:
🔓 信任時段結束(手動撤銷)
⏱ 時長:8 分 32 秒
📋 執行了 5 個命令:
1. ✅ aws s3 ls s3://bucket
2. ✅ aws ec2 describe-instances
3. ❌ aws s3 cp /nonexistent ...
...
✅ 4 成功 / ❌ 1 失敗
需要執行多個命令時,不要一個一個等審批,應該:
✅ 正確流程:
1. 把所有命令一次全部發出(全部帶同一 trust_scope)
→ 全部進入 pending 狀態
2. Steven 看到第一個請求,按「🔓 信任10分鐘」
3. Bouncer 建立 trust session,並自動執行所有同 trust_scope 的 pending 命令
4. 後續新命令帶同一 trust_scope → 繼續自動執行
❌ 錯誤流程:
1. 發第一個命令 → 等審批 → 審批後才發第二個
→ 信任時段已建立,但第二個命令是新的 pending,不在 trust 建立當下
→ 第二個命令可能需要再次審批(trust 不一定能 match)
關鍵原則:先批量發出所有 pending,再等 Steven 一次批准信任。
trust_scope + account_id(不是 source)/silent_list、/silent_revokemcporter call bouncer bouncer_trust_status
mcporter call bouncer bouncer_trust_status source="Private Bot"
mcporter call bouncer bouncer_trust_revoke trust_id="trust-xxx-yyy"
預先申請一組命令的執行權限,審批後可在 TTL 內重複或一次性執行。
mcporter call bouncer bouncer_request_grant \
commands='["aws ec2 describe-instances", "aws s3 ls"]' \
reason="基礎設施檢查" \
source="Private Bot (infra)" \
trust_scope="private-bot-main" \
ttl_minutes=30 \
allow_repeat=true
Parameters:
| 參數 | 必填 | 說明 |
|---|---|---|
commands | ✅ | JSON array of AWS CLI commands |
reason | ✅ | 授權原因 |
source | ✅ | 來源標識 |
trust_scope | ✅ | 呼叫者 ID |
ttl_minutes | ❌ | 授權時長(1-60 分鐘,預設 30) |
allow_repeat | ❌ | 可重複執行(預設 true) |
account | ❌ | 目標帳號 |
在已批准的 grant 內執行命令(精確匹配)。
mcporter call bouncer bouncer_grant_execute \
grant_id="grant-abc123" \
command="aws ec2 describe-instances" \
trust_scope="private-bot-main"
mcporter call bouncer bouncer_grant_status grant_id="grant-abc123"
| 維度 | Grant Session | Trust Session |
|---|---|---|
| 模式 | 白名單(精確命令) | 黑名單(排除高危) |
| 觸發 | Agent 主動申請 | 審批者選擇信任 |
| 匹配 | 命令精確匹配 | trust_scope + account |
| 適用 | 可預測的命令清單 | 互動式探索 |
| 上傳 | 不支援 | 支援(quota 限制) |
一鍵前端部署:staging → 一次審批 → S3 copy + CloudFront invalidation,取代原本 3-5 次審批流程。
實作說明(v3.11.1): 審批通過後,Bouncer Lambda 先用自己的 role 從暫存 bucket 讀取檔案到記憶體,再 assume 各專案的
deploy_role_arnrole,用 boto3 直接 put_object 到前端 bucket + CloudFront invalidation。deploy role 不需要暫存 bucket 的任何讀取權限。每個檔案上傳都有審計 log(含 user_id)。
mcporter call bouncer bouncer_deploy_frontend \
project="ztp-files" \
files='[
{"filename":"index.html","content":"'$(base64 -w0 index.html)'","content_type":"text/html"},
{"filename":"assets/app.js","content":"'$(base64 -w0 assets/app.js)'","content_type":"application/javascript"}
]' \
reason="前端部署" \
source="Private Bot (ZTP Files deploy)" \
trust_scope="private-bot-deploy"
Parameters:
| 參數 | 必填 | 說明 |
|---|---|---|
project | ✅ | 專案名稱(目前支援:ztp-files) |
files | ✅ | JSON array: [{filename, content(base64), content_type?}] |
reason | ✅ | 部署原因 |
source | ✅ | 來源標識 |
trust_scope | ✅ | 穩定呼叫者 ID |
Cache-Control 自動設定:
index.html → no-cache, no-store, must-revalidateassets/* → max-age=31536000, immutableno-cacheLimits:
index.html.sh .exe .py .jar .zip .tar .gz .7z .bat .ps1 .rb .war .bin .bash../)審批按鈕:
[✅ 批准部署] — 自動 S3 copy + CloudFront invalidation[❌ 拒絕]已設定專案:
| project | frontend_bucket | distribution_id | deploy_role_arn |
|---|---|---|---|
ztp-files | ztp-files-dev-frontendbucket-nvvimv31xp3v | E176PW0SA5JF29 | arn:aws:iam::190825685292:role/ztp-files-dev-frontend-deploy-role |
✅ v3.18.0:PROJECT_CONFIGS 改為純 DynamoDB(hardcoded fallback 已移除) 新增前端專案不需要 redeploy Bouncer!設定存在 DynamoDB
bouncer-projectstable,Lambda 直接查詢。 ⚠️ hardcoded fallback dict_PROJECT_CONFIG已在 Sprint 18 移除。未在 DDB 的專案會收到清楚的 "Unknown project" 錯誤。Adding a New Frontend Project(新增前端專案)
Step 1: 在
scripts/seed_frontend_configs.py的FRONTEND_CONFIGSdict 加入新專案:FRONTEND_CONFIGS = { 'my-new-project': { 'frontend_bucket': 'my-new-project-bucket-name', 'frontend_distribution_id': 'EXXXXXXXXX', 'frontend_region': 'us-east-1', 'frontend_deploy_role_arn': 'arn:aws:iam::190825685292:role/my-deploy-role', }, # ... existing entries }Step 2: 執行 seed script(透過 Bouncer grant):
python3 scripts/seed_frontend_configs.py --dry-run # 先確認 python3 scripts/seed_frontend_configs.py # 實際寫入Step 3: 驗證 DDB 有記錄:
aws dynamodb get-item --table-name bouncer-projects \ --key '{"project_id": {"S": "my-new-project"}}' --region us-east-1Step 4: 在
test_project_configs_ddb_s12_001.py加入新專案的 smoke test。DynamoDB bouncer-projects schema(frontend 欄位):
欄位 說明 project_id(PK)專案 ID(如 ztp-files)frontend_bucketS3 前端 bucket 名稱 frontend_distribution_idCloudFront distribution ID frontend_region部署 region(default: us-east-1)frontend_deploy_role_arnIAM role for S3 put + CF invalidation
mcporter call bouncer bouncer_deploy \
project="bouncer" \
reason="更新功能" \
source="Private Bot (Bouncer deploy)"
Response 包含:
commit_sha — 完整 commit hashcommit_short — 7 字元短 hash(🔖 abc1234 — commit message)commit_message — commit 標題衝突(已有部署在跑)時回傳:
{
"status": "conflict",
"running_deploy_id": "deploy-xxx",
"started_at": "2026-02-27T03:00:00Z",
"estimated_remaining": "2 minutes",
"hint": "Use bouncer_deploy_cancel to cancel the running deploy"
}
✅ v3.13.0 GitHub PAT check(#57):
sam-deployer/github-pat)mcporter call bouncer bouncer_deploy_status deploy_id="deploy-xxx"
mcporter call bouncer bouncer_deploy_cancel deploy_id="deploy-xxx"
mcporter call bouncer bouncer_deploy_history project="bouncer" limit=5
mcporter call bouncer bouncer_project_list
⚠️ Deploy 狀態 Poll 規則(重要):
bouncer_deploy_status 查部署進度 — 直接查 DDB,不發 Telegram 通知status 欄位(pending/RUNNING/SUCCESS/FAILED/expired/not_found)phase 欄位 — 整個 deploy 過程一直顯示 INITIALIZING,不準確(bug #53)bouncer_execute + aws stepfunctions describe-execution — 每次執行都發一則自動通知,造成通知洗版✅ v3.10.0 deploy_status 行為改善(#47):
deploy_id 不存在時回傳 {status: "pending"} 而非 error(避免 race condition)elapsed_seconds;SUCCESS/FAILED 時回傳 duration_seconds✅ v3.11.0 deploy_status 新增欄位(#53 #56):
progress_hint — 人類可讀的目前階段描述(e.g. "正在初始化" / "build 執行中" / "CloudFormation 更新中")sfn_status — Step Functions execution 狀態(RUNNING/SUCCEEDED/FAILED),與 CodeBuild 的 build_status 分開顯示progress_hint 取代舊的 phase 欄位(phase 一直顯示 INITIALIZING,不準確)✅ v3.12.0 deploy_status 新增 status(#69):
expired — deploy record 存在但 TTL 已過期(部署可能已完成但記錄過舊)not_found — deploy record 完全不存在(可能是錯誤的 deploy_id)pending 混用表示「找不到」,狀態語意更精確✅ v3.14.0 deploy_status phase 欄位 deprecated(#53):
phase 欄位已標記為 deprecated,回傳中仍存在但絕對不要使用progress_hint 取代(人類可讀的階段描述,準確反映實際進度)phase_note 欄位說明棄用原因:「phase 欄位已 deprecated,請改用 progress_hint」✅ v3.16.0 deploy_status 新增 FAILED 詳情(#55):
failed_resources — CloudFormation events 中失敗的資源列表error_summary — 人類可讀的失敗摘要(取自 CloudFormation status reason)mcporter call bouncer bouncer_list_accounts
mcporter call bouncer bouncer_add_account account_id="111111111111" name="Production" role_arn="arn:aws:iam::111111111111:role/BouncerRole" source="Bot"
mcporter call bouncer bouncer_remove_account account_id="111111111111" source="Bot"
| 帳號 | ID | 說明 |
|---|---|---|
| 2nd (主帳號) | 190825685292 | 直接使用 Lambda execution role |
| Dev | 992382394211 | 透過 assume role BouncerExecutionRole |
| 1st | 841882238387 | 透過 assume role BouncerExecutionRole |
命令執行失敗(exit code != 0)時,Bouncer 自動記錄到 DynamoDB,並在 MCP response 加上 exit_code 欄位。
DDB 新增欄位(失敗時):
| 欄位 | 說明 |
|---|---|
status | executed_error |
exit_code | AWS CLI 退出碼(e.g. 255) |
error_output | 錯誤輸出前 2000 字元 |
executed_at | 執行完成時間(Unix timestamp) |
MCP response 範例(失敗時):
{
"status": "auto_approved",
"result": "❌ usage: aws [options] ...",
"exit_code": 255
}
當命令輸出超過 3500 字元自動分頁,用此 tool 取後續頁面。
mcporter call bouncer bouncer_get_page page_id="abc123:page:2"
列出命令分類規則。
| Tool | 說明 | 審批 |
|---|---|---|
bouncer_execute | 執行 AWS CLI 命令 | 視命令而定 |
bouncer_status | 查詢審批請求狀態 | 自動 |
bouncer_list_pending | 列出待審批請求 | 自動 |
bouncer_list_accounts | 列出 AWS 帳號 | 自動 |
bouncer_add_account | 新增 AWS 帳號 | 需審批 |
bouncer_remove_account | 移除 AWS 帳號 | 需審批 |
bouncer_upload | 上傳單一檔案到 S3 | 需審批(信任可自動) |
bouncer_upload_batch | 批量上傳多個檔案 | 需審批(信任可自動) |
bouncer_request_presigned | 取得單檔 presigned PUT URL | 自動 |
bouncer_request_presigned_batch | 取得批量 presigned PUT URL | 自動 |
bouncer_confirm_upload | 驗證 presigned batch 上傳結果,確認 S3 files 存在 | 自動 |
bouncer_deploy_frontend | 前端一鍵部署(staging→S3→CloudFront) | 需審批 |
bouncer_deploy | 部署 SAM 專案 | 需審批 |
bouncer_deploy_status | 查詢部署狀態 | 自動 |
bouncer_deploy_cancel | 取消部署 | 自動 |
bouncer_deploy_history | 查看部署歷史 | 自動 |
bouncer_project_list | 列出可部署專案 | 自動 |
bouncer_request_grant | 申請批次命令授權 | 需審批 |
bouncer_grant_execute | 在授權內執行命令 | 自動 |
bouncer_grant_status | 查詢授權狀態 | 自動 |
bouncer_trust_status | 查詢信任時段 | 自動 |
bouncer_trust_revoke | 撤銷信任時段 | 自動 |
bouncer_get_page | 取分頁輸出 | 自動 |
bouncer_help | 查詢命令說明 | 自動 |
bouncer_list_safelist | 列出命令分類規則 | 自動 |
在 Telegram 中可直接對 Bouncer bot 發送的指令:
| 指令 | 說明 |
|---|---|
/start | 顯示歡迎訊息與基本說明 |
/help | 顯示完整指令列表 |
/stats [hours] | 查看 N 小時統計(預設 24h)。顯示:總請求數、各狀態分布、top sources/commands、approval rate、avg execution time |
/pending | 列出待審批請求 |
/stats 範例/stats → 顯示過去 24 小時統計
/stats 1 → 顯示過去 1 小時統計
/stats 168 → 顯示過去 7 天統計
回傳欄位:
total — 總請求數by_status — 各狀態分布(approved / denied / pending / auto_approved)approval_rate — 人工審批通過率(%)avg_execution_time_seconds — 平均執行時間(已審批命令)top_sources — Top 5 來源top_commands — Top 5 命令類型Bouncer 用 aws_cli_split 解析命令,不走 bash,所以 shell substitution $(...) 和變數 $VAR 不會被展開。
# ❌ 不行 — $(date +%s) 會被當成 literal 字串
aws cloudwatch get-metric-statistics --start-time $(date -u +%Y-%m-%dT%H:%M:%SZ)
# ✅ Agent 先算好值再 inline 進命令字串
import datetime
start = (datetime.datetime.utcnow() - datetime.timedelta(hours=1)).strftime('%Y-%m-%dT%H:%M:%SZ')
command = f"aws cloudwatch get-metric-statistics --start-time {start} ..."
直接在 terminal 跑 AWS CLI 時,bash 會先展開 $(...);透過 Bouncer 沒有這一層展開。
| Type | Behavior | Examples |
|---|---|---|
| BLOCKED | 永遠拒絕(含原因 + 建議) | iam create-*, sts assume-role |
| DANGEROUS | 特殊審批(⚠️ 高危警告) | delete-bucket, terminate-instances |
| SAFELIST | 自動執行 | describe-*, list-*, get-* |
| APPROVAL | 需要 Telegram 審批 | start-*, stop-*, create-* |
使用 presigned_batch → confirm_upload → trust → grant 達成多檔案部署,最小化審批次數。
| 步驟 | 工具 | 說明 |
|---|---|---|
| 1 | bouncer_presigned_batch | 取得多個 S3 presigned URL(無需審批) |
| 2 | 直接 PUT(curl/SDK) | 用 presigned URL 上傳檔案到暫存 bucket |
| 3 | bouncer_confirm_upload | 確認上傳完成,建立 DynamoDB 請求記錄 |
| 4 | bouncer_request_grant | 申請批次 grant(列出所有部署命令,一次審批) |
| 5 | bouncer_grant_execute | 在 grant 內逐一執行命令(無需再次審批) |
# ─── Step 1: 取得 presigned URLs ───────────────────────────────────────────
BATCH=$(mcporter call bouncer bouncer_presigned_batch \
files='[
{"filename":"app.zip","content_type":"application/zip"},
{"filename":"index.html","content_type":"text/html"}
]' \
reason="部署 app v2.0" \
source="Private Bot (batch-deploy)")
BATCH_ID=$(echo "$BATCH" | jq -r '.batch_id')
echo "batch_id: $BATCH_ID"
# ─── Step 2: 用 presigned URL 上傳(curl)──────────────────────────────────
APP_URL=$(echo "$BATCH" | jq -r '.presigned_urls[] | select(.filename=="app.zip") | .url')
curl -s -X PUT \
-H "Content-Type: application/zip" \
--data-binary @app.zip \
"$APP_URL"
HTML_URL=$(echo "$BATCH" | jq -r '.presigned_urls[] | select(.filename=="index.html") | .url')
curl -s -X PUT \
-H "Content-Type: text/html" \
--data-binary @index.html \
"$HTML_URL"
# ─── Step 3: 確認上傳完成 ──────────────────────────────────────────────────
mcporter call bouncer bouncer_confirm_upload \
batch_id="$BATCH_ID" \
source="Private Bot (batch-deploy)"
# ─── Step 4: 申請 grant session(一次審批所有命令)────────────────────────
GRANT=$(mcporter call bouncer bouncer_request_grant \
commands='[
"aws s3 cp s3://bouncer-uploads-190825685292/pending/app.zip s3://my-deploy-bucket/app.zip",
"aws lambda update-function-code --function-name MyApp --s3-bucket my-deploy-bucket --s3-key app.zip",
"aws cloudfront create-invalidation --distribution-id EXXXXX --paths /index.html"
]' \
reason="部署 app v2.0" \
source="Private Bot (batch-deploy)" \
account_id="190825685292" \
ttl_minutes=30)
GRANT_ID=$(echo "$GRANT" | jq -r '.grant_id')
echo "grant_id: $GRANT_ID"
# → Telegram 會收到審批請求,等待 Steven 批准
# ─── Step 5: grant 批准後,逐一執行(無需再審批)─────────────────────────
mcporter call bouncer bouncer_grant_execute \
grant_id="$GRANT_ID" \
command="aws s3 cp s3://bouncer-uploads-190825685292/pending/app.zip s3://my-deploy-bucket/app.zip"
mcporter call bouncer bouncer_grant_execute \
grant_id="$GRANT_ID" \
command="aws lambda update-function-code --function-name MyApp --s3-bucket my-deploy-bucket --s3-key app.zip"
mcporter call bouncer bouncer_grant_execute \
grant_id="$GRANT_ID" \
command="aws cloudfront create-invalidation --distribution-id EXXXXX --paths /index.html"
mcporter call bouncer bouncer_help command="batch-deploy"
clawdbot-bouncer - 主要 Bouncerbouncer-deployer - SAM Deployer 基礎建設TRUST_IP_BINDING_MODE 기본값 warn → strict. _check_trust_session()에서 caller_ip 전달됨src/callbacks_command.py 생성: handle_command_callback + 관련 함수들 이동 (callbacks.py Phase 2 리팩토링)PENDING_REMINDER_MINUTES(預設 10 分鐘)未審批,自動 re-notify
scheduler_service.py: create_pending_reminder_schedule() / delete_reminder_schedule()app.py: action == "pending_reminder" handler(查 DDB status,仍 pending 才發通知)constants.py: PENDING_REMINDER_MINUTES = 10TRUST_RATE_LIMIT_PER_MINUTE(預設 5)自動拒絕
trust.py: TrustRateExceeded exception;increment_trust_command_count() 加 velocity check(rate_window_start / rate_window_count 欄位)mcp_execute.py, callbacks_command.py, mcp_deploy_frontend.py: catch TrustRateExceeded,回傳 errorTrustRateExceededconstants.py: TRUST_RATE_LIMIT_PER_MINUTE = 5, TRUST_RATE_LIMIT_ENABLED = True