feat(credential-store-migration): T4 wasi-shim secret_get host function
新增 secret_get(ref) host function:讀 CF Workers per-script Secrets 的值, host 端實作 = env[ref] 動態字串索引(T1.5 spike ② 已證可行,零網路呼叫)。 - ArcrunHostEnv 加 index signature 支援任意 secret_ref 動態取值 - WasiHostFunctions 加 secret_get,u6u WASI imports 比照 kv_get 同款 pointer/memory-write 機制 wiring - 安全邊界:只允許 CRED_ 前綴(拒絕讀 ENCRYPTION_KEY/CF_SECRETS_API_TOKEN 等非 credential 機密),比照既有 routedKvGet 前綴檢查精神 驗證:tsc --noEmit exit 0;vitest 18/18 通過(新增 5 案例)。 撞牆記錄:JSPI Suspending 物件不可在單元測試直接呼叫(既有架構環境限制, kv_get 同樣受影響),已移除不可行的 2 個測試並記錄於測試檔註解。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018D6QoC5waFkcjc2N7csJBB
This commit is contained in:
@@ -16,6 +16,13 @@ export interface ArcrunHostEnv {
|
||||
CREDENTIALS_KV: KVNamespace;
|
||||
RECIPES: KVNamespace;
|
||||
ENCRYPTION_KEY: string;
|
||||
/**
|
||||
* credential-store-migration T4(§2.5/§5):CF Workers per-script Secrets 以 env var 形式
|
||||
* 注入 worker,值只能靠字串動態索引取得(`env[ref]`,T1.5 spike ② 已證可行)。
|
||||
* 用 index signature 讓 `secret_get` host function 能對任意 secret_ref 字串取值,
|
||||
* 不需要像 ENCRYPTION_KEY 那樣逐一宣告固定屬性名。
|
||||
*/
|
||||
[secretRef: string]: unknown;
|
||||
}
|
||||
|
||||
const WASI_ESUCCESS = 0;
|
||||
@@ -56,6 +63,13 @@ export interface WasiHostFunctions {
|
||||
http_request?: (url: string, method: string, headers: string, body: string) => Promise<string>;
|
||||
/** KV 讀取:key 前綴由 Worker 路由到對應 binding,並做越權檢查 */
|
||||
kv_get?: (key: string) => Promise<string | null>;
|
||||
/**
|
||||
* credential-store-migration T4(§2.5/§5):讀取 CF Workers per-script Secrets 的值。
|
||||
* ref = D1 credentials 表的 secret_ref(env var 名,如 `CRED_TELEGRAM_BOT_TOKEN_A1B2C3D4`)。
|
||||
* host 端實作=`env[ref]` 動態字串索引(零網路呼叫,值已由 CF runtime 以 env var 注入 worker,
|
||||
* T1.5 spike ② 已證可行)。找不到該 ref → 回傳 null(與 kv_get 的「找不到」語意一致)。
|
||||
*/
|
||||
secret_get?: (ref: string) => Promise<string | null>;
|
||||
/** KV 寫入:用於快取 access_token 等短效值,ttlSeconds=0 表示不設 TTL */
|
||||
kv_put?: (key: string, value: string, ttlSeconds: number) => Promise<void>;
|
||||
/** AES-GCM 解密:encryption key 由 Worker 保管,不暴露給 WASM */
|
||||
@@ -375,6 +389,23 @@ export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFuncti
|
||||
})
|
||||
: () => 1,
|
||||
|
||||
// secret_get(refPtr, refLen, outPtr, outLenPtr) → 0 成功;1 錯誤;2 找不到 ref
|
||||
// 與 kv_get 同款 pointer/memory-write 機制;差別只在 host 端實作來源(env[ref] 而非 KV.get)。
|
||||
secret_get: hostFunctions?.secret_get
|
||||
? hostWrap(async (refPtr: number, refLen: number, outPtr: number, outLenPtr: number): Promise<number> => {
|
||||
if (!memory) return 1;
|
||||
const ref = new TextDecoder().decode(new Uint8Array(memory.buffer, refPtr, refLen));
|
||||
try {
|
||||
const result = await hostFunctions!.secret_get!(ref);
|
||||
if (result === null) return 2;
|
||||
const encoded = new TextEncoder().encode(result);
|
||||
return writeOut(memory.buffer, outPtr, outLenPtr, encoded);
|
||||
} catch {
|
||||
return 1;
|
||||
}
|
||||
})
|
||||
: () => 1,
|
||||
|
||||
// kv_put(keyPtr, keyLen, valPtr, valLen, ttlSeconds) → 0 成功;1 錯誤
|
||||
kv_put: hostFunctions?.kv_put
|
||||
? hostWrap(async (keyPtr: number, keyLen: number, valPtr: number, valLen: number, ttlSeconds: number): Promise<number> => {
|
||||
@@ -694,7 +725,22 @@ async function rsaPkcs1Sha256Sign(data: Uint8Array, pkcs8: Uint8Array): Promise<
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立 arcrun host function 組合(kv_get / crypto_decrypt / crypto_sign_rs256)。
|
||||
* 讀取 CF Workers per-script Secrets 的值:`env[ref]` 動態字串索引
|
||||
* (credential-store-migration T4,§2.5/§5;T1.5 spike ② 已證此場景成立,零網路呼叫)。
|
||||
*
|
||||
* 安全邊界(比照 `routedKvGet` 的前綴檢查精神;design.md §2.3「secret_ref 命名需以 CRED_
|
||||
* 前綴隔離命名空間」):只允許讀 `CRED_` 開頭的 ref。WASM 不該、也不需要讀到 worker 本身的
|
||||
* 其他機密(`ENCRYPTION_KEY` / `CF_SECRETS_API_TOKEN` 等非 credential 用途的 env var)。
|
||||
* 不符前綴或值非字串 → 回傳 null(與 `kv_get` 的「拒絕/找不到」語意一致)。
|
||||
*/
|
||||
function secretGet(env: ArcrunHostEnv, ref: string): string | null {
|
||||
if (!/^CRED_/.test(ref)) return null;
|
||||
const value = env[ref];
|
||||
return typeof value === 'string' ? value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 建立 arcrun host function 組合(kv_get / crypto_decrypt / crypto_sign_rs256 / secret_get)。
|
||||
* 由 WASM runner(component-loader 的 WASM 路徑)呼叫,與 api_key 綁定以做越權檢查。
|
||||
*
|
||||
* http_request 不由本 factory 提供 — auth primitive WASM 與 API WASM 零件若需要
|
||||
@@ -706,6 +752,7 @@ export function createArcrunHostFunctions(env: ArcrunHostEnv, apiKey: string): W
|
||||
kv_put: (key: string, value: string, ttlSeconds: number) => routedKvPut(env, apiKey, key, value, ttlSeconds),
|
||||
crypto_decrypt: (encB64: string, ivB64: string) => aesGcmDecrypt(env, encB64, ivB64),
|
||||
crypto_sign_rs256: (data: Uint8Array, pkcs8: Uint8Array) => rsaPkcs1Sha256Sign(data, pkcs8),
|
||||
secret_get: async (ref: string) => secretGet(env, ref),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user