202a5ab8d6
新 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>
93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
// G1 假零件偵測(component-gatekeeping SDD,R2)
|
||
//
|
||
// 判準(DECISIONS §1):零件若滿足任一,是假零件,該降級成 recipe / 工作流:
|
||
// (a) contract 或 wasm binary 出現具體外部服務 URL / domain
|
||
// (b) 宣告能力是 http_request 子集(打某固定 endpoint)
|
||
//
|
||
// Q2 決議(richblack 2026-05-29):兩者都「硬擋」(不是只 warn)。
|
||
// 理由:零件不該連外,連外即 recipe。這兩個 pattern 都是「該是 recipe 的東西偽裝成零件」。
|
||
//
|
||
// 排除:auth_* primitive(credential 後端,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
|
||
// - 裸 domain(api.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 recipe(http_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」
|
||
// heuristic:description 描述「打/呼叫 ... 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;
|
||
}
|