Claude Code Action 是 Anthropic 官方推出的 GitHub Action,讓 Claude 能直接在 PR 和 Issue 中回答問題、審查程式碼、實作修改。這個專案值得深入閱讀的原因不在於「如何使用」,而在於它展示了一套精巧的設計:如何在 GitHub Actions 的 composite action 框架下,建構出一個具備智慧模式偵測、多層安全防護、MCP 工具整合的 AI 自動化系統。讀完你能學到:雙階段執行架構的分離策略、Mode 系統的 Registry Pattern 設計、以 MCP Server 作為工具邊界的整合模式、以及針對 prompt injection 的多層防禦機制。
Claude Code Action GitHub Repository — 專案原始碼,包含本文解讀的所有設計實作
專案概覽
Claude Code Action 是一個 GitHub composite action,以 TypeScript 撰寫,使用 Bun 作為 runtime。專案支援多種認證方式(Anthropic API、Amazon Bedrock、Google Vertex AI、Microsoft Foundry),能回應 @claude 提及、Issue 指派、Label 觸發,或透過 prompt 直接驅動自動化工作流。
技術棧:TypeScript + Bun、GitHub Actions composite action、MCP(Model Context Protocol)、Claude Agent SDK、Octokit(GitHub REST/GraphQL)、Zod schema validation。
架構總覽
claude-code-action/
├── action.yml # Composite action 定義,串接所有步驟
├── src/
│ ├── entrypoints/ # Action 進入點(Phase 1)
│ │ ├── prepare.ts # 主要準備邏輯:認證、驗證、觸發偵測
│ │ ├── update-comment-link.ts # 執行後更新 comment
│ │ ├── format-turns.ts # 對話格式化
│ │ ├── cleanup-ssh-signing.ts # SSH 金鑰清理
│ │ └── collect-inputs.ts # 收集 action inputs
│ ├── modes/ # 模式系統
│ │ ├── detector.ts # 自動模式偵測
│ │ ├── registry.ts # 模式註冊表
│ │ ├── types.ts # Mode interface 定義
│ │ ├── tag/index.ts # Tag 模式實作
│ │ └── agent/index.ts # Agent 模式實作
│ ├── github/ # GitHub 整合層
│ │ ├── context.ts # 統一的事件上下文解析
│ │ ├── token.ts # OIDC token 交換與認證
│ │ ├── api/ # REST/GraphQL client
│ │ ├── data/ # 資料擷取與格式化
│ │ ├── validation/ # 權限與觸發驗證
│ │ ├── operations/ # 分支、留言、Git 操作
│ │ └── utils/ # 清潔、圖片下載
│ ├── mcp/ # MCP Server 實作
│ │ ├── install-mcp-server.ts # MCP 配置組裝
│ │ ├── github-comment-server.ts # 留言操作
│ │ ├── github-file-ops-server.ts # 檔案操作(commit signing)
│ │ ├── github-actions-server.ts # CI/CD 存取
│ │ └── github-inline-comment-server.ts # PR inline comment
│ ├── create-prompt/ # Prompt 生成
│ └── prepare/ # 準備階段編排
├── base-action/ # Phase 2:Claude Code 執行核心
│ └── src/
│ ├── index.ts # base-action 進入點
│ ├── run-claude-sdk.ts # Claude Agent SDK 呼叫
│ ├── run-claude.ts # CLI 執行邏輯
│ ├── prepare-prompt.ts # Prompt 檔案處理
│ └── setup-claude-code-settings.ts # 設定管理
└── test/ # 測試
Code language: PHP (php)整體架構分為兩個明確的階段:Phase 1(Preparation) 由 src/ 負責,處理認證、驗證、模式偵測、上下文準備、MCP 配置;Phase 2(Execution) 由 base-action/ 負責,實際執行 Claude Code SDK 呼叫。這種分離讓 base-action 可以獨立作為 @anthropic-ai/claude-code-base-action 發布。
核心設計解析
設計一:雙階段執行架構——Preparation 與 Execution 的解耦
整個 Action 的執行透過 action.yml 串接,但核心邏輯被切分為兩個完全獨立的階段。
action.yml — Composite action 定義,定義了所有步驟的串接順序與環境變數傳遞
在 action.yml 中可以看到關鍵步驟:
steps:
- name: Prepare action
id: prepare
shell: bash
run: bun run ${GITHUB_ACTION_PATH}/src/entrypoints/prepare.ts
- name: Run Claude Code
id: claude-code
if: steps.prepare.outputs.contains_trigger == 'true'
shell: bash
run: bun run ${GITHUB_ACTION_PATH}/base-action/src/index.ts
Code language: JavaScript (javascript)Phase 1 的 prepare.ts 負責所有前置工作——如果觸發條件不成立,Phase 2 根本不會執行。兩個階段之間的溝通完全透過 GitHub Actions 的 outputs 機制和環境變數,沒有任何 in-process 的狀態共享。
src/entrypoints/prepare.ts — Phase 1 進入點,編排認證、驗證、模式偵測的完整流程
async function run() {
collectActionInputsPresence();
const context = parseGitHubContext();
const mode = getMode(context);
const githubToken = await setupGitHubToken();
const octokit = createOctokit(githubToken);
if (isEntityContext(context)) {
const hasWritePermissions = await checkWritePermissions(
octokit.rest, context, context.inputs.allowedNonWriteUsers, githubTokenProvided,
);
if (!hasWritePermissions) {
throw new Error("Actor does not have write permissions to the repository");
}
}
const containsTrigger = mode.shouldTrigger(context);
core.setOutput("contains_trigger", containsTrigger.toString());
if (!containsTrigger) {
console.log("No trigger found, skipping remaining steps");
return;
}
const result = await prepare({ context, octokit, mode, githubToken });
}
Code language: JavaScript (javascript)這個設計的好處是:Phase 1 可以快速 fail-fast(不符合觸發條件就結束),Phase 2 的 base-action 可以被其他 action 獨立使用,而且兩個階段的測試可以完全獨立進行。
設計二:Mode 系統——Registry Pattern 與策略模式
專案定義了一個清晰的 Mode interface,每個模式自行決定觸發條件、prompt 生成、工具配置等行為。
src/modes/types.ts — Mode interface 定義,規範了每個模式必須實作的方法契約
export type Mode = {
name: ModeName;
description: string;
shouldTrigger(context: GitHubContext): boolean;
prepareContext(context: GitHubContext, data?: ModeData): ModeContext;
getAllowedTools(): string[];
getDisallowedTools(): string[];
shouldCreateTrackingComment(): boolean;
generatePrompt(context: PreparedContext, githubData: FetchDataResult, useCommitSigning: boolean): string;
prepare(options: ModeOptions): Promise<ModeResult>;
getSystemPrompt?(context: ModeContext): string | undefined;
};
Code language: JavaScript (javascript)這個 interface 的設計把「是否觸發」、「如何準備環境」、「生成什麼 prompt」、「允許什麼工具」全部封裝在模式內部,外部代碼只需要拿到 mode 物件就能執行完整流程。
src/modes/registry.ts — 模式註冊表,負責根據 context 自動選擇正確的模式
const modes = {
tag: tagMode,
agent: agentMode,
} as const satisfies Record<AutoDetectedMode, Mode>;
export function getMode(context: GitHubContext): Mode {
const modeName = detectMode(context);
const mode = modes[modeName];
if (!mode) {
throw new Error(`Mode '${modeName}' not found.`);
}
return mode;
}
Code language: JavaScript (javascript)Registry 使用 as const satisfies Record<AutoDetectedMode, Mode> 確保型別安全——每個 AutoDetectedMode 都必須有對應的 Mode 實作,編譯器會強制檢查。
設計三:智慧模式偵測——從 GitHub 事件推導執行策略
detector.ts 實作了一套基於 GitHub 事件類型和使用者配置的自動偵測邏輯,使用者不需要手動指定模式。
src/modes/detector.ts — 模式偵測器,根據事件類型和配置自動選擇 tag 或 agent 模式
export function detectMode(context: GitHubContext): AutoDetectedMode {
if (context.inputs.trackProgress) {
validateTrackProgressEvent(context);
}
// 如果設定了 track_progress,強制使用 tag 模式
if (context.inputs.trackProgress && isEntityContext(context)) {
if (isPullRequestEvent(context) || isIssuesEvent(context) || ...) {
return "tag";
}
}
// Comment 事件:有 prompt 就用 agent,有 @claude 就用 tag
if (isEntityContext(context)) {
if (isIssueCommentEvent(context) || isPullRequestReviewCommentEvent(context) || ...) {
if (context.inputs.prompt) return "agent";
if (checkContainsTrigger(context)) return "tag";
}
}
// 預設回到 agent 模式
return "agent";
}
Code language: JavaScript (javascript)偵測邏輯的核心思路是:有明確 prompt 就用 agent(自動化),有 @claude 提及就用 tag(互動式)。這種「convention over configuration」的設計讓使用者幾乎不需要理解模式系統就能正確使用。
設計四:Discriminated Union 型別系統——統一的 GitHub 事件處理
context.ts 使用 TypeScript 的 discriminated union 把所有 GitHub 事件類型統一為一個 GitHubContext 型別。
src/github/context.ts — GitHub 上下文解析,將不同事件類型統一為型別安全的資料結構
type EntityEventName = "issues" | "issue_comment" | "pull_request"
| "pull_request_review" | "pull_request_review_comment";
type AutomationEventName = "workflow_dispatch" | "repository_dispatch"
| "schedule" | "workflow_run";
export type ParsedGitHubContext = BaseContext & {
eventName: EntityEventName;
payload: IssuesEvent | IssueCommentEvent | PullRequestEvent | ...;
entityNumber: number;
isPR: boolean;
};
export type AutomationContext = BaseContext & {
eventName: AutomationEventName;
payload: WorkflowDispatchEvent | RepositoryDispatchEvent | ...;
};
export type GitHubContext = ParsedGitHubContext | AutomationContext;
Code language: JavaScript (javascript)搭配一系列 type guard 函式:
export function isEntityContext(context: GitHubContext): context is ParsedGitHubContext {
return ENTITY_EVENT_NAMES.includes(context.eventName as EntityEventName);
}
export function isIssueCommentEvent(context: GitHubContext):
context is ParsedGitHubContext & { payload: IssueCommentEvent } {
return context.eventName === "issue_comment";
}
Code language: JavaScript (javascript)這個設計讓 TypeScript 編譯器能在 type guard 後自動收窄型別,所有對 payload 的存取都是型別安全的。不需要 as 強制轉型,消除了 runtime 型別錯誤的可能性。
設計五:MCP Server 作為工具邊界——按需組裝的能力配置
專案沒有給 Claude 無限制的工具存取權,而是透過多個 MCP Server 精確控制每個能力邊界。
src/mcp/install-mcp-server.ts — MCP 配置組裝器,根據模式和權限動態決定啟用哪些 MCP Server
export async function prepareMcpConfig(params: PrepareConfigParams): Promise<string> {
const baseMcpConfig: { mcpServers: Record<string, unknown> } = { mcpServers: {} };
// Comment server:tag 模式必備,agent 模式只在明確要求時啟用
const shouldIncludeCommentServer = !isAgentMode || hasGitHubCommentTools;
if (shouldIncludeCommentServer) {
baseMcpConfig.mcpServers.github_comment = { /* ... */ };
}
// File ops server:只有啟用 commit signing 時才啟用
if (context.inputs.useCommitSigning) {
baseMcpConfig.mcpServers.github_file_ops = { /* ... */ };
}
// Inline comment server:只對 PR 且明確要求時啟用
if (isEntityContext(context) && context.isPR && (hasGitHubMcpTools || hasInlineCommentTools)) {
baseMcpConfig.mcpServers.github_inline_comment = { /* ... */ };
}
// CI server:需要額外權限檢查
if (shouldIncludeCIServer) {
const actuallyHasPermission = await checkActionsReadPermission(
process.env.DEFAULT_WORKFLOW_TOKEN || "", owner, repo
);
if (actuallyHasPermission) {
baseMcpConfig.mcpServers.github_ci = { /* ... */ };
}
}
return JSON.stringify(baseMcpConfig, null, 2);
}
Code language: JavaScript (javascript)每個 MCP Server 都是獨立的 process(透過 command: "bun" 啟動),有自己的環境變數和權限範圍。這種設計讓安全邊界非常清晰:Claude 只能透過 MCP 工具存取被授權的 GitHub 操作。
設計六:多層安全防護——從 Actor 到 Content 的全方位防禦
安全設計貫穿整個 codebase,從觸發者身份到內容清潔,形成多層防禦。
第一層:Actor 驗證
src/github/validation/actor.ts — 確保觸發者是人類,防止 bot 觸發無限迴圈
export async function checkHumanActor(octokit: Octokit, githubContext: GitHubContext) {
const { data: userData } = await octokit.users.getByUsername({
username: githubContext.actor,
});
if (actorType !== "User") {
const allowedBots = githubContext.inputs.allowedBots;
if (allowedBots.trim() === "*") return; // 允許所有 bot
// 檢查特定 bot 白名單...
throw new Error(`Workflow initiated by non-human actor: ${botName}`);
}
}
Code language: JavaScript (javascript)第二層:權限驗證
src/github/validation/permissions.ts — 確保觸發者有倉庫寫入權限
export async function checkWritePermissions(
octokit: Octokit, context: ParsedGitHubContext,
allowedNonWriteUsers?: string, githubTokenProvided?: boolean
): Promise<boolean> {
// 如果設定了 allowedNonWriteUsers 且提供了自訂 token,可以繞過
if (allowedNonWriteUsers && githubTokenProvided) {
core.warning(`SECURITY WARNING: Bypassing write permission check...`);
return true;
}
const response = await octokit.repos.getCollaboratorPermissionLevel({
owner: repository.owner, repo: repository.repo, username: actor
});
return permissionLevel === "admin" || permissionLevel === "write";
}
Code language: JavaScript (javascript)第三層:時間窗口防護
src/github/data/fetcher.ts — TOCTOU 攻擊防禦,過濾觸發時間點之後被修改的內容
export function filterCommentsToTriggerTime<
T extends { createdAt: string; updatedAt?: string; lastEditedAt?: string }
>(comments: T[], triggerTime: string | undefined): T[] {
if (!triggerTime) return comments;
const triggerTimestamp = new Date(triggerTime).getTime();
return comments.filter((comment) => {
const createdTimestamp = new Date(comment.createdAt).getTime();
if (createdTimestamp >= triggerTimestamp) return false;
const lastEditTime = comment.lastEditedAt || comment.updatedAt;
if (lastEditTime) {
const lastEditTimestamp = new Date(lastEditTime).getTime();
if (lastEditTimestamp >= triggerTimestamp) return false;
}
return true;
});
}
Code language: JavaScript (javascript)這是一個精巧的 TOCTOU(Time-of-Check-Time-of-Use)防禦:如果攻擊者在觸發 Claude 之後修改了留言或 Issue body,這些修改過的內容會被排除,防止注入惡意指令。
第四層:內容清潔
src/github/utils/sanitizer.ts — 多層內容清潔,防止隱形字元和 prompt injection
export function sanitizeContent(content: string): string {
content = stripHtmlComments(content); // 移除 HTML 註解(常見注入向量)
content = stripInvisibleCharacters(content); // 移除零寬字元
content = stripMarkdownImageAltText(content); // 清除圖片 alt text
content = stripMarkdownLinkTitles(content); // 清除連結 title
content = stripHiddenAttributes(content); // 清除 data-* 等隱藏屬性
content = normalizeHtmlEntities(content); // 正規化 HTML entities
content = redactGitHubTokens(content); // 遮蔽 GitHub tokens
return content;
}
Code language: JavaScript (javascript)第五層:分支名稱驗證
src/github/operations/branch.ts — 嚴格的分支名稱白名單驗證,防止 command injection
export function validateBranchName(branchName: string): void {
if (branchName.startsWith("-")) {
throw new Error(`Branch names cannot start with a dash.`); // 防止 option injection
}
if (/[\x00-\x1F\x7F ~^:?*[\]\\]/.test(branchName)) {
throw new Error(`Branch names cannot contain control characters...`);
}
const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9/_.-]*$/;
if (!validPattern.test(branchName)) {
throw new Error(`Branch names must start with alphanumeric...`);
}
// 還有更多檢查:..、.lock、@{、連續 //...
}
// 使用 execFileSync 而非 shell 執行 git 命令
function execGit(args: string[]): void {
execFileSync("git", args, { stdio: "inherit" });
}
Code language: JavaScript (javascript)注意 execFileSync 的使用——它直接將參數傳給 git binary,不經過 shell 解析,從根本上防止了 command injection。
設計七:Tag 模式 vs Agent 模式——兩種截然不同的執行策略
兩種模式在行為上有根本差異,體現在工具配置、prompt 生成、分支管理等多個面向。
src/modes/tag/index.ts — Tag 模式實作,完整的互動式工作流,包含 tracking comment、分支管理、精確的工具白名單
Tag 模式的工具配置是白名單制:
const tagModeTools = [
"Edit", "MultiEdit", "Glob", "Grep", "LS", "Read", "Write",
"mcp__github_comment__update_claude_comment",
"mcp__github_ci__get_ci_status",
"mcp__github_ci__get_workflow_run_details",
"mcp__github_ci__download_job_log",
...userAllowedMCPTools,
];
// 根據 commit signing 策略加入不同工具
if (!useApiCommitSigning) {
tagModeTools.push(
"Bash(git add:*)", "Bash(git commit:*)", "Bash(git push:*)",
"Bash(git status:*)", "Bash(git diff:*)", "Bash(git log:*)", "Bash(git rm:*)",
);
} else {
tagModeTools.push(
"mcp__github_file_ops__commit_files",
"mcp__github_file_ops__delete_files",
);
}
Code language: JavaScript (javascript)值得注意的是 Bash(git add:*) 這種語法——它只允許 Claude 執行特定的 git 子命令,而非任意 Bash 命令。
src/modes/agent/index.ts — Agent 模式實作,簡潔的自動化模式,直接使用使用者的 prompt
Agent 模式則更為簡潔:
export const agentMode: Mode = {
name: "agent",
shouldTrigger(context) {
return !!context.inputs?.prompt; // 只在有明確 prompt 時觸發
},
shouldCreateTrackingComment() {
return false; // 不建立 tracking comment
},
async prepare({ context, octokit, githubToken }) {
// 直接寫入使用者的 prompt 到檔案
const promptContent = context.inputs.prompt ||
`Repository: ${context.repository.owner}/${context.repository.repo}`;
await writeFile(`${RUNNER_TEMP}/claude-prompts/claude-prompt.txt`, promptContent);
// 不需要 fetchGitHubData、不需要建立分支...
},
};
Code language: JavaScript (javascript)兩種模式的對比:Tag 模式是「全功能互動」(fetching PR data、creating tracking comments、branch management),Agent 模式是「最小化自動化」(直接使用 prompt,不做多餘動作)。
設計八:OIDC Token Exchange——零 Secret 的認證方案
src/github/token.ts — Token 管理,支援 OIDC token exchange 和自訂 GitHub App 認證
export async function setupGitHubToken(): Promise<string> {
// 如果提供了自訂 token,直接使用
const providedToken = process.env.OVERRIDE_GITHUB_TOKEN;
if (providedToken) {
core.setOutput("GITHUB_TOKEN", providedToken);
return providedToken;
}
// 否則走 OIDC 流程
const oidcToken = await retryWithBackoff(() => getOidcToken());
const permissions = parseAdditionalPermissions();
const appToken = await retryWithBackoff(() => exchangeForAppToken(oidcToken, permissions));
return appToken;
}
Code language: JavaScript (javascript)OIDC 流程避免了在 repository secrets 中儲存長期有效的 token。GitHub Actions 提供的 OIDC token 是短期的,透過 Anthropic 的 API 交換為 GitHub App token,這個 token 在 action 結束後會被撤銷(見 action.yml 最後的 Revoke app token 步驟)。
特別注意 parseAdditionalPermissions() 的設計:
const DEFAULT_PERMISSIONS: Record<string, string> = {
contents: "write",
pull_requests: "write",
issues: "write",
};
Code language: JavaScript (javascript)預設只要求最小必要權限,使用者可以透過 additional_permissions input 擴展,但無法縮小基礎權限。
設計九:Prompt 生成——結構化上下文注入
src/create-prompt/index.ts — Prompt 生成器,將 GitHub 資料轉換為結構化的 Claude 指令
Prompt 生成使用 XML-like tags 將不同類型的上下文分隔:
let promptContent = `You are Claude, an AI assistant...
<formatted_context>
${formattedContext}
</formatted_context>
<pr_or_issue_body>
${formattedBody}
</pr_or_issue_body>
<comments>
${formattedComments || "No comments"}
</comments>
<event_type>${eventType}</event_type>
<trigger_comment>
${sanitizeContent(eventData.commentBody)}
</trigger_comment>
`;
Code language: HTML, XML (xml)這種結構化設計讓 Claude 能明確區分「系統指令」和「使用者提供的內容」,降低 prompt injection 風險。同時,所有使用者提供的內容(comment body、PR body)都經過 sanitizeContent() 清潔。
另一個精巧的設計是「自訂 prompt 注入」:
generatePrompt(context, githubData, useCommitSigning): string {
const defaultPrompt = generateDefaultPrompt(context, githubData, useCommitSigning);
if (context.githubContext?.inputs?.prompt) {
return defaultPrompt + `\n\n<custom_instructions>\n${context.githubContext.inputs.prompt}\n</custom_instructions>`;
}
return defaultPrompt;
}
Code language: JavaScript (javascript)使用者的自訂 prompt 被包裹在 <custom_instructions> tag 中,附加在系統 prompt 之後,而非取代它。這確保了核心行為指令不會被覆蓋。
設計十:Commit Signing 雙軌機制——Git CLI vs GitHub API
專案支援兩種完全不同的 commit 方式,各有優缺點。
Git CLI 模式(預設):直接在 runner 上使用 git 命令:
// Tag 模式中的工具配置
if (!useApiCommitSigning) {
tagModeTools.push(
"Bash(git add:*)", "Bash(git commit:*)", "Bash(git push:*)", ...
);
}
Code language: JavaScript (javascript)GitHub API 模式(commit signing):透過 MCP Server 使用 GitHub API 建立 commit:
src/mcp/github-file-ops-server.ts — File Operations MCP Server,透過 GitHub Git Data API 實現 verified commit
server.tool("commit_files", "Commit one or more files...", {
files: z.array(z.string()),
message: z.string(),
}, async ({ files, message }) => {
// 1. 取得或建立分支 reference
const baseSha = await getOrCreateBranchRef(owner, repo, branch, githubToken);
// 2. 取得 base commit
// 3. 建立 tree entries(支援 binary 和 text 檔案)
// 4. 建立新 tree
// 5. 建立新 commit
// 6. 更新 reference(含 retry 機制)
await retryWithBackoff(async () => {
const updateRefResponse = await fetch(updateRefUrl, { /* ... */ });
if (updateRefResponse.status === 403) {
throw new Error(`Permission denied: Unable to push commits to branch...`);
}
}, { maxAttempts: 3, initialDelayMs: 1000, backoffFactor: 2 });
});
Code language: JavaScript (javascript)GitHub API 模式的好處是 commit 會帶有 GitHub 的 verified 標記(因為是透過 GitHub App 身份建立的),但需要更多 API 呼叫。值得注意的是 retryWithBackoff 的使用——GitHub API 有時會出現暫時性的 403 錯誤,retry 機制確保了可靠性。
設計理念萃取
- 雙階段解耦勝過單一流程 — Preparation 和 Execution 的分離讓每個階段可以獨立測試、獨立部署(base-action 可以單獨使用),也讓 fail-fast 成為可能。這種設計特別適合需要前置驗證的系統。
- 型別系統是最好的文件 — Discriminated union + type guard 的組合,讓 TypeScript 編譯器取代了大量 runtime 檢查和文件說明。當你看到
isEntityContext(context)後面的代碼,不需要額外文件就知道context.entityNumber一定存在。 - 安全防護要多層且各自獨立 — 不依賴任何單一防線:Actor 驗證防 bot 觸發、權限驗證防無權限操作、時間窗口防 TOCTOU 攻擊、內容清潔防 prompt injection、分支名稱驗證防 command injection、
execFileSync防 shell injection。每一層都假設其他層可能失效。 - MCP Server 是天然的能力邊界 — 把 GitHub 操作封裝為獨立 MCP Server,而非直接給 Claude 工具存取,創造了明確的安全邊界。每個 Server 只暴露特定操作,有自己的 token 和環境變數,按需啟動。
- Convention over Configuration 降低使用門檻 — Mode detector 根據 GitHub 事件類型自動選擇模式,使用者不需要理解 tag/agent 的區別。有 prompt 就走自動化,有 @claude 就走互動式,設計意圖隱含在使用方式中。
延伸思考
這個專案展示了如何在「給 AI 足夠能力」和「維持安全控制」之間取得平衡。如果你正在建構自己的 AI 自動化系統,可以考慮:
- 工具存取的白名單設計:不要給 AI 「所有 Bash 命令」的存取權,而是像
Bash(git add:*)這樣精確控制允許的子命令。 - MCP 作為能力封裝:如果你的 AI agent 需要存取外部服務,考慮將每個服務封裝為獨立的 MCP Server,而非直接在 agent 中呼叫 API。這讓權限控制和測試都更容易。
- 多層 sanitization:處理使用者輸入時,不要只做一種清潔。HTML 註解、隱形字元、image alt text、link title 都可能成為 prompt injection 的向量。
- TOCTOU 防護:如果你的系統在「觸發時」和「處理時」之間有時間差,要考慮這段時間內資料可能被修改。記錄觸發時間戳,過濾掉之後修改的內容。
- Discriminated union 統一事件處理:當你的系統需要處理多種事件類型時,用 TypeScript 的 discriminated union 搭配 type guard,比起
switch-case+as轉型更安全、更具可維護性。
Vibe Coding:把觀念變成程式碼
以下是你可以直接給 AI 的 prompt 指令,將本文介紹的設計觀念應用到你的專案中:
雙階段執行架構(Preparation / Execution 解耦)
場景:你正在建構一個需要「前置驗證」再「執行核心邏輯」的系統,例如 CI/CD pipeline、API Gateway、或任何有 pre-check 流程的自動化工具。
給 AI 的指令:
我想重構目前的 [系統名稱] 流程,採用雙階段執行架構(Preparation / Execution 解耦)。目前所有邏輯都在同一個函式中,我希望拆成兩個獨立階段:Phase 1 負責認證、驗證、條件檢查,如果不符合條件就 fail-fast 直接結束;Phase 2 負責核心邏輯執行。兩個階段之間只透過明確定義的資料結構傳遞狀態,不共享任何 in-process 狀態。請幫我:(1) 定義 Phase 1 的輸出資料結構(包含 Phase 2 所需的所有資訊);(2) 將目前的 [函式名稱] 拆分為 prepare() 和 execute() 兩個函式;(3) 確保 Phase 2 可以獨立被其他模組呼叫。
效果:AI 會分析你現有的程式碼,提取前置檢查邏輯到 prepare 階段,定義兩階段之間的介面型別,並將核心邏輯封裝為可獨立使用的 execute 函式。
Registry Pattern 與策略模式(Mode 系統)
場景:你的系統需要根據不同條件執行不同的策略,例如不同的事件類型需要不同處理流程、不同使用者等級對應不同功能集、或不同環境(dev/staging/prod)有不同的執行行為。
給 AI 的指令:
我的 [系統名稱] 目前使用 if-else 或 switch-case 來處理不同的 [事件/策略/模式],我想重構為 Registry Pattern 搭配策略模式。請參考以下設計:(1) 定義一個 Mode interface,包含 shouldTrigger()、prepare()、execute() 等方法,每個模式自行決定觸發條件和執行邏輯;(2) 建立一個 registry(使用
as const satisfies Record<ModeName, Mode>確保型別安全),讓每個模式名稱都有對應的實作;(3) 實作一個 detectMode() 函式,根據 [你的判斷條件] 自動選擇正確的模式。目前我有以下幾種模式:[列出你的模式]。請幫我建立完整的型別定義、registry、和偵測邏輯。
效果:AI 會建立完整的 Mode interface、各模式的實作類別、型別安全的 registry 物件,以及自動偵測邏輯。新增模式時只需實作 interface 並加入 registry,不需要修改任何 if-else。
Discriminated Union 統一事件處理
場景:你的系統需要處理多種不同結構的事件或資料類型(例如 webhook 事件、API 回應、使用者操作),目前使用 any 或大量 as 型別斷言來處理。
給 AI 的指令:
我正在用 TypeScript 處理多種 [事件/資料] 類型,目前使用 any 和 as 轉型,容易出現 runtime 錯誤。請幫我重構為 discriminated union 搭配 type guard 的模式。我的事件類型包括:[列出你的事件類型和各自的 payload 結構]。請幫我:(1) 定義一個 discriminated union type,用 eventName 作為 discriminant;(2) 為每種事件寫 type guard 函式(例如 isXxxEvent()),讓 TypeScript 在 type guard 後自動收窄型別;(3) 示範如何在不使用 as 強制轉型的情況下安全存取各事件的特有欄位。
效果:AI 會產出完整的 discriminated union 型別定義、一組 type guard 函式,以及使用範例。重構後所有 payload 存取都是型別安全的,編譯器會在你漏掉某種事件處理時發出警告。
MCP Server 作為工具邊界
場景:你正在建構 AI agent 系統,需要讓 AI 存取外部服務(資料庫、API、檔案系統等),但想要精確控制 AI 能使用的操作範圍。
給 AI 的指令:
我正在建構一個 AI agent,需要存取 [外部服務名稱]。我不想直接給 agent 完整的 API 存取權,而是想用 MCP Server 作為工具邊界,精確控制允許的操作。請幫我設計一個 MCP Server,參考 Claude Code Action 的設計模式:(1) 只暴露特定的操作(例如 [列出允許的操作]),不暴露危險操作(例如 [列出禁止的操作]);(2) 每個工具都用 Zod schema 驗證輸入參數;(3) 根據不同的模式(例如唯讀 vs 讀寫)動態決定啟用哪些 MCP Server;(4) 加入權限檢查,確認呼叫者有足夠權限才啟用對應的 Server。
效果:AI 會設計出以安全邊界為核心的 MCP Server 架構,包含工具定義、輸入驗證、權限檢查邏輯、以及動態配置組裝函式。每個 Server 都是獨立 process,互不影響。
多層安全防護(Defense in Depth)
場景:你的系統接受外部輸入(使用者留言、webhook payload、API 請求等),需要防止惡意輸入造成安全問題,特別是在 AI agent 場景中要防止 prompt injection。
給 AI 的指令:
我的 [系統名稱] 接受來自 [輸入來源] 的內容,這些內容會被送給 AI 處理。請幫我實作多層安全防護(Defense in Depth),參考 Claude Code Action 的五層防禦設計:(1) Actor 驗證層:確認觸發者身份,防止 bot 觸發無限迴圈;(2) 權限驗證層:確認操作者有足夠權限;(3) 時間窗口防護(TOCTOU 防禦):記錄觸發時間戳,過濾掉觸發後被修改的內容;(4) 內容清潔層:實作 sanitizeContent() 函式,依序移除 HTML 註解、零寬隱形字元、隱藏屬性、正規化 HTML entities;(5) 命令注入防護:使用 execFileSync 取代 shell 執行,對所有動態參數做白名單驗證。請針對我的 [輸入來源] 設計完整的防護 pipeline。
效果:AI 會產出一個完整的安全防護 pipeline,每一層都是獨立的函式,可以單獨測試。每一層都假設其他層可能失效,實現真正的縱深防禦。
白名單工具控制與結構化 Prompt
場景:你正在配置 AI agent 的工具權限和 prompt,想要既給 AI 足夠的能力完成任務,又防止 AI 執行危險操作或被 prompt injection 影響。
給 AI 的指令:
我正在配置一個 AI agent 的工具權限和系統 prompt。請幫我設計白名單工具控制和結構化 prompt,參考 Claude Code Action 的做法:(1) 工具白名單:只允許特定工具和子命令(例如
Bash(git add:*)只允許 git add,不允許任意 Bash),列出我的 agent 需要的操作:[你的操作清單];(2) 結構化 prompt:使用 XML-like tags 將不同類型的上下文分隔(system instructions、user content、custom instructions),讓 AI 能明確區分系統指令和使用者提供的內容;(3) 使用者自訂指令要包裹在專用 tag 中(如<custom_instructions>),附加在系統 prompt 之後而非取代它。請產出完整的工具配置和 prompt template。
效果:AI 會產出精確的工具白名單配置、結構化的 prompt template(含 XML tags 分隔),以及自訂指令的安全注入機制。這確保了 AI 只能使用被授權的工具,且系統指令不會被使用者輸入覆蓋。
延伸閱讀
- Claude Code Action GitHub Repository — 專案完整原始碼,包含本文解讀的所有設計實作,是最直接的參考資源
- Claude Code GitHub Actions 官方文件 — Anthropic 官方的 GitHub Actions 整合指南,涵蓋設定、認證、進階用法
- Claude Code: Best practices for agentic coding — Anthropic 工程團隊分享的 agentic coding 最佳實踐,理解 Claude Code 的設計哲學
- GitHub Action Structure | DeepWiki — DeepWiki 對 Claude Code Action 架構的視覺化解析,包含雙層 composite action 結構圖和執行流程圖
- PromptInjectionPreventionCheat_Sheet.html”>LLM Prompt Injection Prevention – OWASP Cheat Sheet — OWASP 的 LLM Prompt Injection 防禦指南,系統性整理了本文提到的多層防禦策略