Files
Arcrun/registry/src/actions/detectFakeComponent.ts
T
Leo 202a5ab8d6 feat(registry): Phase 3 零件投稿靜態把關 + component-gatekeeping SDD
新 SDD .agents/specs/component-gatekeeping/(richblack 確認,含 venue 修訂 + 信任模型)。

registry 端靜態把關(CF Worker 可跑,不執行 wasm):
- G1 detectFakeComponent: 外部 URL/domain + http_request 子集偵測,硬擋退稿指回 recipe
- G3 wasmImports: 解析 wasm import section,只准 wasi_snapshot_preview1 + u6u 白名單
- G5/G6: unimplemented_steps 明列 gherkin/cold_start/runtime_compat,不假綠(§3c/§7)
- gherkin_evidence 一致性驗證(投稿者本地跑,registry 不重跑——CF 禁 runtime 編譯 wasm)

把關範圍:公共庫 + self-hosted 私人庫同一套(design §0.0)。
信任模型(design §4.5):Gherkin 全綠≠安全;純 WASI 沙箱框死能力才是發佈底氣;
第一期 evidence 可造假(誠實標明),平台重跑列未來。

hook: pre-write-guard 白名單加 component-gatekeeping / component-registry-canon SDD 目錄。

測試: sandboxAcceptance.test.ts 4 綠(含 G1 假零件被擋)。

待續(同 SDD): G4 CLI 投稿指令本地跑 Gherkin、G0 人類閘門、R5 白名單+本機 hook。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 17:53:03 +08:00

93 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// G1 假零件偵測(component-gatekeeping SDDR2
//
// 判準(DECISIONS §1):零件若滿足任一,是假零件,該降級成 recipe / 工作流:
// (a) contract 或 wasm binary 出現具體外部服務 URL / domain
// (b) 宣告能力是 http_request 子集(打某固定 endpoint
//
// Q2 決議(richblack 2026-05-29):兩者都「硬擋」(不是只 warn)。
// 理由:零件不該連外,連外即 recipe。這兩個 pattern 都是「該是 recipe 的東西偽裝成零件」。
//
// 排除:auth_* primitivecredential 後端,DECISIONS §3b 不適用假零件判準)、http_request 自己。
import type { ComponentContract } from '../types';
// auth primitive 與 http_request 不適用假零件判準
const EXEMPT_IDS = new Set([
'http_request',
'auth_static_key',
'auth_oauth2',
'auth_service_account',
'auth_mtls',
]);
// 外部 URL / domain pattern。
// - 明確的 scheme://host
// - 裸 domainapi.foo.com / foo.googleapis.com 之類)
const URL_SCHEME_RE = /\bhttps?:\/\/[^\s"'`)]+/i;
// 裸 domain:至少 host.tld,排除過於通用的誤判(如 stdin.json)——要求含常見 TLD 或 .xxx.yyy 多段
const BARE_DOMAIN_RE = /\b[a-z0-9][a-z0-9-]*(\.[a-z0-9-]+)*\.(com|org|net|dev|io|me|click|app|co|googleapis\.com|telegram\.org)\b/i;
/**
* 把 contract 的文字欄位攤平成一個字串,供 URL 掃描。
* 掃 description / display_name / input_schema / output_schema / tags / aliases。
*/
function flattenContractText(contract: ComponentContract): string {
const parts: string[] = [
contract.display_name ?? '',
contract.description ?? '',
JSON.stringify(contract.input_schema ?? {}),
JSON.stringify(contract.output_schema ?? {}),
(contract.tags ?? []).join(' '),
(contract.aliases ?? []).join(' '),
];
return parts.join('\n');
}
/**
* 偵測投稿零件是否為假零件。
* 回 null = 通過;回字串 = 退稿原因(已含指回正路的訊息)。
*/
export function detectFakeComponent(
contract: ComponentContract,
wasmBytes: Uint8Array,
): string | null {
if (EXEMPT_IDS.has(contract.canonical_id)) {
return null;
}
const pointToRecipe =
'。這該是 API recipehttp_request + 固定設定)或工作流,不是零件。' +
'零件 = 封閉邏輯(流程控制 / 資料處理),不連外部服務。見 DECISIONS §1。';
// (a) contract 文字含外部 URL / domain
const contractText = flattenContractText(contract);
const schemeHit = contractText.match(URL_SCHEME_RE);
if (schemeHit) {
return `偵測到 contract 含外部 URL${schemeHit[0].slice(0, 80)}${pointToRecipe}`;
}
const domainHit = contractText.match(BARE_DOMAIN_RE);
if (domainHit) {
return `偵測到 contract 含外部 domain${domainHit[0]}${pointToRecipe}`;
}
// (a') wasm binary 文字含外部 URL / domain(零件原碼把 endpoint 編進去)
// 只掃可印 ASCII 區段以降低誤判;wasm 字串以 UTF-8 存放。
const wasmText = new TextDecoder('utf-8').decode(wasmBytes);
const wasmSchemeHit = wasmText.match(URL_SCHEME_RE);
if (wasmSchemeHit) {
return `偵測到 wasm 內嵌外部 URL${wasmSchemeHit[0].slice(0, 80)}${pointToRecipe}`;
}
// (b) http_request 子集:零件宣告自己只是「打某 API/endpoint」
// heuristicdescription 描述「打/呼叫 ... API/endpoint」+ input 有 url-like 欄位
const desc = (contract.description ?? '') + ' ' + contract.display_name;
const inputKeys = Object.keys(contract.input_schema ?? {}).join(' ').toLowerCase();
const hasUrlField = /\b(url|endpoint|api_url|base_url|host)\b/.test(inputKeys);
const describesHttpCall = /(打|呼叫|call|fetch|request|POST|GET|PUT|DELETE)\s*.*(api|endpoint|url|https?)/i.test(desc);
if (hasUrlField && describesHttpCall) {
return `偵測到疑似 http_request 子集(input 有 url 欄位 + 描述為打 API)${pointToRecipe}`;
}
return null;
}