From 18f04448cef4748cf3b58d045c167653fd2be302 Mon Sep 17 00:00:00 2001 From: richblack Date: Mon, 20 Apr 2026 16:54:18 +0800 Subject: [PATCH] feat(auth): auth_static_key WASM primitive + host functions - wasi-shim gains kv_get / crypto_decrypt / crypto_sign_rs256 host functions with strict boundary (ENCRYPTION_KEY never exits Worker). - registry/components/auth_static_key: TinyGo impl for API-key / Bearer / Basic Auth recipes (80% of supported services). - .component-builds/auth_static_key: independent Worker at auth-static-key.arcrun.dev, imports wasi-shim cross-directory. - cypher-executor/auth-dispatcher routes static_key recipes to the new Worker instead of credential-injector TS. Replaces TS credential injection per .agents/specs/arcrun/credential-primitives-wasm Phase 1. Co-Authored-By: Claude Opus 4.7 --- .../auth_static_key/package-lock.json | 1539 +++++++++++++++++ .../auth_static_key/package.json | 14 + .../auth_static_key/src/index.ts | 83 + .../auth_static_key/tsconfig.json | 11 + .../auth_static_key/wrangler.toml | 23 + .../src/actions/auth-dispatcher.ts | 94 + cypher-executor/src/lib/wasi-shim.ts | 189 +- .../auth_static_key/component.contract.yaml | 67 + registry/components/auth_static_key/go.mod | 3 + registry/components/auth_static_key/main.go | 276 +++ 10 files changed, 2290 insertions(+), 9 deletions(-) create mode 100644 .component-builds/auth_static_key/package-lock.json create mode 100644 .component-builds/auth_static_key/package.json create mode 100644 .component-builds/auth_static_key/src/index.ts create mode 100644 .component-builds/auth_static_key/tsconfig.json create mode 100644 .component-builds/auth_static_key/wrangler.toml create mode 100644 cypher-executor/src/actions/auth-dispatcher.ts create mode 100644 registry/components/auth_static_key/component.contract.yaml create mode 100644 registry/components/auth_static_key/go.mod create mode 100644 registry/components/auth_static_key/main.go diff --git a/.component-builds/auth_static_key/package-lock.json b/.component-builds/auth_static_key/package-lock.json new file mode 100644 index 0000000..ec0ff83 --- /dev/null +++ b/.component-builds/auth_static_key/package-lock.json @@ -0,0 +1,1539 @@ +{ + "name": "arcrun-auth-static-key", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "arcrun-auth-static-key", + "version": "1.0.0", + "dependencies": { + "hono": "^4.7.0" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250408.0", + "typescript": "^5.4.0", + "wrangler": "^4.0.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", + "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.0.tgz", + "integrity": "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260415.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260415.1.tgz", + "integrity": "sha512-dsxaKsQm3LnPGNPEdsRv09QN3Y4DqCw7kX5j6noKqbAtro2jTr95sVlYM1jUxZ5FkOl1f7SXgaKKB9t5H5Nkbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20260415.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260415.1.tgz", + "integrity": "sha512-+JgSgVA49KyKteHRA1SnonE4Zn5Ei5zdAp5FQMxFmXI8qulZw4Hl7safXxRyK4i9sTO8gl7TFOKO5Q64VPvSDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260415.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260415.1.tgz", + "integrity": "sha512-tU+9pwsqCy8afOVlGtiWrWQc/fedQK4SRm4KPIAt+zOiQWDxWASm6YGBUJis5c648WN80yz47qnmdDi8DQNOcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260415.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260415.1.tgz", + "integrity": "sha512-bR9uITnV19r5NQ14xnypi2xHXu2iQvfYV8cVgx0JouFUmWwTEEAwFVojDdssGq93VHX9hr/pi2IRUZeegbYBog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260415.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260415.1.tgz", + "integrity": "sha512-4NuMLlerI0Ijua3Ir8HXQ+qyNvCUDEG5gDco5Om+sAiK6rnWiz+aGoSlbB8W16yW9QAgzCstbmXLiVknUBflfQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20260420.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260420.1.tgz", + "integrity": "sha512-DHT9JnSn9cIiCSdL76OxW+Xvc1+ml1CWzWvgVwreoHQ+E604aeFxPPHp9X7nE+XRWm2NH4l0OgtxUI5T/nuI3g==", + "dev": true, + "license": "MIT OR Apache-2.0" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.15.tgz", + "integrity": "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hono": { + "version": "4.12.14", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz", + "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/miniflare": { + "version": "4.20260415.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260415.0.tgz", + "integrity": "sha512-JoExRWN4YBI2luA5BoSMFEgi8rQWXUGzo3mtE+58VXCLV3jj/Xnk5Yeqs/IXWz8Es5GJIaq6BtsixDvAxXSIng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "^0.34.5", + "undici": "7.24.8", + "workerd": "1.20260415.1", + "ws": "8.18.0", + "youch": "4.1.0-beta.10" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.8.tgz", + "integrity": "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/workerd": { + "version": "1.20260415.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260415.1.tgz", + "integrity": "sha512-phyPjRnx+mQDfkhN9ENPioL1L0SdhYs4S0YmJK/xF9Oga+ykNfdSy1MHnsOj8yqnOV96zcVQMx32dJ0r3pq0jQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20260415.1", + "@cloudflare/workerd-darwin-arm64": "1.20260415.1", + "@cloudflare/workerd-linux-64": "1.20260415.1", + "@cloudflare/workerd-linux-arm64": "1.20260415.1", + "@cloudflare/workerd-windows-64": "1.20260415.1" + } + }, + "node_modules/wrangler": { + "version": "4.83.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.83.0.tgz", + "integrity": "sha512-gw5g3LCiuAqVWxaoKY6+quE0HzAUEFb/FV3oAlNkE1ttd4XP3FiV91XDkkzUCcdqxS4WjhQvPhIDBNdhEi8P0A==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.2", + "@cloudflare/unenv-preset": "2.16.0", + "blake3-wasm": "2.1.5", + "esbuild": "0.27.3", + "miniflare": "4.20260415.0", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20260415.1" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=20.3.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20260415.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + } + } +} diff --git a/.component-builds/auth_static_key/package.json b/.component-builds/auth_static_key/package.json new file mode 100644 index 0000000..91c72bd --- /dev/null +++ b/.component-builds/auth_static_key/package.json @@ -0,0 +1,14 @@ +{ + "name": "arcrun-auth-static-key", + "version": "1.0.0", + "private": true, + "type": "module", + "dependencies": { + "hono": "^4.7.0" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250408.0", + "typescript": "^5.4.0", + "wrangler": "^4.0.0" + } +} diff --git a/.component-builds/auth_static_key/src/index.ts b/.component-builds/auth_static_key/src/index.ts new file mode 100644 index 0000000..2f761f2 --- /dev/null +++ b/.component-builds/auth_static_key/src/index.ts @@ -0,0 +1,83 @@ +/** + * arcrun auth_static_key Worker + * + * POST / → JSON input {action, api_key, service} → WASM (WASI preview1 stdin/stdout) → JSON output + * + * 方案 A:直接 import cypher-executor/src/lib/wasi-shim.ts 的 shim + host function factory, + * 確保 AES-GCM 解密邏輯只存在於一個檔案(rule 02 §2.2)。 + * + * 安全邊界: + * - api_key 經 stdin 傳進 WASM,同時綁到 host function 的 kv_get 做越權檢查 + * - ENCRYPTION_KEY 只存在於 host function 的 closure 中,不會進入 WASM 記憶體 + */ + +import componentWasm from '../component.wasm' assert { type: 'webassembly' }; +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { + createWasiShim, + createArcrunHostFunctions, + type ArcrunHostEnv, +} from '../../../cypher-executor/src/lib/wasi-shim'; + +type Env = ArcrunHostEnv; + +const app = new Hono<{ Bindings: Env }>(); +app.use('*', cors()); + +app.get('/', (c) => c.json({ ok: true, component: 'auth_static_key' })); + +app.post('/', async (c) => { + let input: Record; + try { + input = await c.req.json(); + } catch { + return c.json({ success: false, error: 'request body must be JSON' }, 400); + } + + const apiKey = typeof input.api_key === 'string' ? input.api_key : ''; + if (!apiKey) { + return c.json({ success: false, error: 'api_key 必填' }, 400); + } + + try { + const result = await runWasm(c.env, apiKey, input); + return c.json(result); + } catch (e) { + return c.json( + { success: false, error: e instanceof Error ? e.message : String(e) }, + 500, + ); + } +}); + +export default app; + +// ── WASM runner ────────────────────────────────────────────────────────────── + +async function runWasm(env: Env, apiKey: string, input: unknown): Promise { + const stdinData = JSON.stringify(input); + const hostFunctions = createArcrunHostFunctions(env, apiKey); + const shim = createWasiShim(stdinData, hostFunctions); + + const instance = await WebAssembly.instantiate( + componentWasm as WebAssembly.Module, + shim.imports, + ); + shim.setMemory(instance.exports.memory as WebAssembly.Memory); + + const start = (instance.exports._start ?? instance.exports.main) as () => void; + if (typeof start !== 'function') { + throw new Error('WASM missing _start or main export'); + } + + try { + start(); + } catch (e) { + if (!(e instanceof Error && e.message === 'wasm exit: 0')) throw e; + } + + const stdout = shim.getStdout().trim(); + if (!stdout) throw new Error('WASM component produced no output'); + return JSON.parse(stdout); +} diff --git a/.component-builds/auth_static_key/tsconfig.json b/.component-builds/auth_static_key/tsconfig.json new file mode 100644 index 0000000..b65fda7 --- /dev/null +++ b/.component-builds/auth_static_key/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "types": ["@cloudflare/workers-types"], + "strict": true, + "noEmit": true + } +} diff --git a/.component-builds/auth_static_key/wrangler.toml b/.component-builds/auth_static_key/wrangler.toml new file mode 100644 index 0000000..ca3993b --- /dev/null +++ b/.component-builds/auth_static_key/wrangler.toml @@ -0,0 +1,23 @@ +name = "arcrun-auth-static-key" +main = "src/index.ts" +compatibility_date = "2025-02-19" +compatibility_flags = ["nodejs_compat"] + +[vars] +COMPONENT_ID = "auth_static_key" + +[[routes]] +pattern = "auth-static-key.arcrun.dev/*" +zone_name = "arcrun.dev" + +# 與 cypher-executor/wrangler.toml 同一組 KV namespace +[[kv_namespaces]] +binding = "CREDENTIALS_KV" +id = "e7f4320f88d343f187e35e3543dd74c9" + +[[kv_namespaces]] +binding = "RECIPES" +id = "9cf9db905c6241f78503199e58b2ffe0" + +# ENCRYPTION_KEY 透過 wrangler secret set 設定 +# wrangler secret put ENCRYPTION_KEY diff --git a/cypher-executor/src/actions/auth-dispatcher.ts b/cypher-executor/src/actions/auth-dispatcher.ts new file mode 100644 index 0000000..bc72cec --- /dev/null +++ b/cypher-executor/src/actions/auth-dispatcher.ts @@ -0,0 +1,94 @@ +/** + * Auth Dispatcher + * + * 對需要認證的零件,在執行前 HTTP POST 到對應的 auth primitive Worker, + * 取回 auth_headers / auth_query / auth_body 合併進節點 context。 + * + * 嚴格邊界(rule 02 §2.2): + * - 本檔**不做**任何 credential 解密 / template 展開 / JWT 簽章 + * - 那些全部在 auth primitive WASM 零件內執行(透過 host function `crypto_decrypt` 等) + * - 本檔只做「查 recipe 決定走哪個 primitive Worker」+「HTTP fetch 取回注入結果」 + * + * 目前階段(Phase 2)接上 `auth_static_key` + `auth_service_account`, + * Phase 4(封測後)加 `auth_oauth2` / `auth_mtls`。 + * + * 執行時機:graph-executor 在節點 runner 執行前呼叫,取回的 ctx 會: + * 1. 先試本 dispatcher(命中才 return enriched ctx) + * 2. 沒命中 fallback 到 `injectCredentials`(Phase 1.9 才刪除) + */ + +import type { Bindings } from '../types'; +import { resolveAuthRecipe } from '../routes/recipes'; +import { wasmWorkerUrl } from '../lib/component-loader'; + +/** 對應 Phase 1-4 會部署的 auth primitive Worker */ +const SUPPORTED_PRIMITIVES = new Set(['static_key', 'service_account']); + +/** auth primitive 本身的 componentId(避免自引用) */ +const AUTH_PRIMITIVE_IDS = new Set([ + 'auth_static_key', + 'auth_service_account', + 'auth_oauth2', + 'auth_mtls', +]); + +/** + * 試著對零件做 auth 注入。 + * - 命中(有對應 auth recipe 且 primitive 已支援)→ 回傳注入後的 ctx + * - 未命中 → 回傳 null(呼叫端繼續跑舊路徑) + */ +export async function tryAuthDispatch( + componentId: string, + input: Record, + env: Bindings, + apiKey: string, +): Promise | null> { + if (AUTH_PRIMITIVE_IDS.has(componentId)) { + // auth primitive 本身不需要再做 auth + return null; + } + + const recipe = await resolveAuthRecipe(componentId, env.RECIPES); + if (!recipe) return null; + if (!SUPPORTED_PRIMITIVES.has(recipe.primitive)) return null; + + // 走新路徑:HTTP POST 到對應 auth primitive Worker + const primitiveUrl = wasmWorkerUrl(`auth_${recipe.primitive}`); + const res = await fetch(primitiveUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action: 'authenticate', + api_key: apiKey, + service: componentId, + }), + }); + + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error( + `auth primitive "${recipe.primitive}" 回傳 ${res.status}: ${text.slice(0, 200)}`, + ); + } + + const result = await res.json().catch(() => null) as { + success?: boolean; + error?: string; + auth_headers?: Record; + auth_query?: Record; + auth_body?: Record; + } | null; + + if (!result || result.success === false) { + throw new Error( + `auth primitive 失敗: ${result?.error ?? '未知錯誤'}`, + ); + } + + return { + ...input, + _auth_headers: result.auth_headers ?? {}, + _auth_query: result.auth_query ?? {}, + _auth_body: result.auth_body ?? {}, + }; +} diff --git a/cypher-executor/src/lib/wasi-shim.ts b/cypher-executor/src/lib/wasi-shim.ts index 4e5a09b..4e34f1d 100644 --- a/cypher-executor/src/lib/wasi-shim.ts +++ b/cypher-executor/src/lib/wasi-shim.ts @@ -7,6 +7,17 @@ * Requirements: 3.1, 3.3 */ +/** + * createArcrunHostFunctions 所需的最小 env 子集。 + * 不直接依賴 cypher-executor 的 Bindings,讓 auth primitive Worker 這類 + * 只綁 CREDENTIALS_KV / RECIPES / ENCRYPTION_KEY 的獨立 Worker 也能用。 + */ +export interface ArcrunHostEnv { + CREDENTIALS_KV: KVNamespace; + RECIPES: KVNamespace; + ENCRYPTION_KEY: string; +} + const WASI_ESUCCESS = 0; const WASI_ENOSYS = 76; @@ -29,10 +40,20 @@ export interface WasiShim { /** * Host function 注入介面 * 讓 .wasm 零件能透過 host function 呼叫外部服務,而不需要網路 syscall + * + * 嚴格邊界: + * - encryption key 只在 `crypto_decrypt` host function 內部使用,永遠不傳給 WASM + * - `kv_get` 必須在 Worker 側檢查 key 前綴以防越權(見 auth-dispatcher.ts) */ export interface WasiHostFunctions { /** HTTP 請求 host function:.wasm 呼叫此函數發出 HTTP 請求 */ http_request?: (url: string, method: string, headers: string, body: string) => Promise; + /** KV 讀取:key 前綴由 Worker 路由到對應 binding,並做越權檢查 */ + kv_get?: (key: string) => Promise; + /** AES-GCM 解密:encryption key 由 Worker 保管,不暴露給 WASM */ + crypto_decrypt?: (encryptedB64: string, ivB64: string) => Promise; + /** RS256 簽章:用 crypto.subtle 做 RSASSA-PKCS1-v1_5 + SHA-256 */ + crypto_sign_rs256?: (data: Uint8Array, pkcs8: Uint8Array) => Promise; } /** @@ -54,6 +75,18 @@ export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFuncti return new DataView(memory.buffer); } + // 寫入結果到 WASM 的 outPtr buffer(host function 共用) + // 回傳 0 = 成功,1 = memory 不可用 + function writeOut(buf: ArrayBuffer, outPtr: number, outLenPtr: number, data: Uint8Array): number { + try { + new Uint8Array(buf, outPtr, data.length).set(data); + new DataView(buf).setUint32(outLenPtr, data.length, true); + return 0; + } catch { + return 1; + } + } + /** * fd_write: 將 iovec 陣列的資料寫入 fd(stdout=1 或 stderr=2) * iovec 結構:{ buf: i32, buf_len: i32 }(各 4 bytes,little-endian) @@ -181,7 +214,7 @@ export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFuncti path_link: () => WASI_ENOSYS, }, // u6u host functions:讓 .wasm 零件透過 host function 呼叫外部服務 - // .wasm 零件用 //go:wasmimport u6u http_request 宣告 + // .wasm 零件用 //go:wasmimport u6u 宣告 u6u: { http_request: hostFunctions?.http_request ? async (urlPtr: number, urlLen: number, methodPtr: number, methodLen: number, @@ -196,17 +229,64 @@ export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFuncti const body = dec.decode(new Uint8Array(buf, bodyPtr, bodyLen)); try { const result = await hostFunctions!.http_request!(url, method, headers, body); - const encoded = new TextEncoder().encode(result); - // 寫入結果到 outPtr 指向的 buffer - const view = new DataView(buf); - new Uint8Array(buf, outPtr, encoded.length).set(encoded); - view.setUint32(outLenPtr, encoded.length, true); - return 0; // success + return writeOut(buf, outPtr, outLenPtr, new TextEncoder().encode(result)); } catch { - return 1; // error + return 1; } } - : () => 1, // host function 未注入時回傳錯誤 + : () => 1, + + // kv_get(keyPtr, keyLen, outPtr, outLenPtr) → 0 成功;1 錯誤;2 找不到 key + kv_get: hostFunctions?.kv_get + ? async (keyPtr: number, keyLen: number, outPtr: number, outLenPtr: number): Promise => { + if (!memory) return 1; + const buf = memory.buffer; + const key = new TextDecoder().decode(new Uint8Array(buf, keyPtr, keyLen)); + try { + const result = await hostFunctions!.kv_get!(key); + if (result === null) return 2; + return writeOut(buf, outPtr, outLenPtr, new TextEncoder().encode(result)); + } catch { + return 1; + } + } + : () => 1, + + // crypto_decrypt(encPtr, encLen, ivPtr, ivLen, outPtr, outLenPtr) → 0 成功 + // 輸入皆為 base64 字串(WASM 從 KV 讀到什麼就送什麼) + crypto_decrypt: hostFunctions?.crypto_decrypt + ? async (encPtr: number, encLen: number, ivPtr: number, ivLen: number, + outPtr: number, outLenPtr: number): Promise => { + if (!memory) return 1; + const buf = memory.buffer; + const dec = new TextDecoder(); + const encB64 = dec.decode(new Uint8Array(buf, encPtr, encLen)); + const ivB64 = dec.decode(new Uint8Array(buf, ivPtr, ivLen)); + try { + const plaintext = await hostFunctions!.crypto_decrypt!(encB64, ivB64); + return writeOut(buf, outPtr, outLenPtr, new TextEncoder().encode(plaintext)); + } catch { + return 1; + } + } + : () => 1, + + // crypto_sign_rs256(dataPtr, dataLen, pkcs8Ptr, pkcs8Len, outPtr, outLenPtr) → 0 成功 + crypto_sign_rs256: hostFunctions?.crypto_sign_rs256 + ? async (dataPtr: number, dataLen: number, pkcs8Ptr: number, pkcs8Len: number, + outPtr: number, outLenPtr: number): Promise => { + if (!memory) return 1; + const buf = memory.buffer; + const data = new Uint8Array(new Uint8Array(buf, dataPtr, dataLen)); + const pkcs8 = new Uint8Array(new Uint8Array(buf, pkcs8Ptr, pkcs8Len)); + try { + const sig = await hostFunctions!.crypto_sign_rs256!(data, pkcs8); + return writeOut(buf, outPtr, outLenPtr, sig); + } catch { + return 1; + } + } + : () => 1, }, }, @@ -241,3 +321,94 @@ export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFuncti return shim; } + +// ── Worker 端 host function 實作(Phase 0.6)────────────────────────────────── +// +// 唯一合法位置:AES-GCM 解密與 RS256 簽章只准出現在本檔(02-forbidden.md §2.2)。 +// 由 component-loader 的 WASM runner 路徑呼叫,注入進 createWasiShim。 +// +// 安全邊界: +// 1. `ENCRYPTION_KEY` 只在 `crypto_decrypt` 內部讀 env,絕不經 stdin/回傳值傳給 WASM +// 2. `kv_get` 依 key 前綴路由,且 `{api_key}:cred:*` 必須符合 stdin 傳入的 api_key(越權檢查) +// 3. 未知前綴回傳 null(WASM 收到 kv_get 回傳 2 = 找不到) + +function hexToUint8Array(hex: string): Uint8Array { + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16); + return bytes; +} + +function base64ToUint8Array(b64: string): Uint8Array { + const binary = atob(b64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + return bytes; +} + +/** + * 依 key 前綴路由到對應 KV binding,並做越權檢查。 + * - `auth_recipe:{service}` → env.RECIPES + * - `{apiKey}:cred:{name}` → env.CREDENTIALS_KV(前綴必須等於 caller 的 apiKey) + * - 其他前綴 → null(拒絕) + */ +async function routedKvGet(env: ArcrunHostEnv, apiKey: string, key: string): Promise { + if (key.startsWith('auth_recipe:')) { + return env.RECIPES.get(key); + } + const credMatch = key.match(/^([^:]+):cred:.+$/); + if (credMatch) { + if (credMatch[1] !== apiKey) { + // 越權:WASM 嘗試讀其他租戶的 credential + return null; + } + return env.CREDENTIALS_KV.get(key); + } + return null; +} + +/** + * AES-GCM 解密。encryption key 由 env.ENCRYPTION_KEY 在本 function 內讀取, + * 永不傳給 WASM。輸入為 base64 字串,輸出為 UTF-8 plaintext。 + */ +async function aesGcmDecrypt(env: ArcrunHostEnv, encryptedB64: string, ivB64: string): Promise { + const keyBytes = hexToUint8Array(env.ENCRYPTION_KEY); + const cryptoKey = await crypto.subtle.importKey( + 'raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt'], + ); + const plaintext = await crypto.subtle.decrypt( + { name: 'AES-GCM', iv: base64ToUint8Array(ivB64) }, + cryptoKey, + base64ToUint8Array(encryptedB64), + ); + return new TextDecoder().decode(plaintext); +} + +/** + * RSASSA-PKCS1-v1_5 + SHA-256 簽章。private key 以 PKCS8 bytes 傳入(由 WASM 零件解析 PEM 後送進來)。 + */ +async function rsaPkcs1Sha256Sign(data: Uint8Array, pkcs8: Uint8Array): Promise { + const cryptoKey = await crypto.subtle.importKey( + 'pkcs8', + pkcs8, + { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, + false, + ['sign'], + ); + const sig = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', cryptoKey, data); + return new Uint8Array(sig); +} + +/** + * 建立 arcrun host function 組合(kv_get / crypto_decrypt / crypto_sign_rs256)。 + * 由 WASM runner(component-loader 的 WASM 路徑)呼叫,與 api_key 綁定以做越權檢查。 + * + * http_request 不由本 factory 提供 — auth primitive WASM 與 API WASM 零件若需要 + * 發 HTTP,由呼叫者(component-loader)另外注入,以便個別限制可達主機。 + */ +export function createArcrunHostFunctions(env: ArcrunHostEnv, apiKey: string): WasiHostFunctions { + return { + kv_get: (key: string) => routedKvGet(env, apiKey, key), + crypto_decrypt: (encB64: string, ivB64: string) => aesGcmDecrypt(env, encB64, ivB64), + crypto_sign_rs256: (data: Uint8Array, pkcs8: Uint8Array) => rsaPkcs1Sha256Sign(data, pkcs8), + }; +} diff --git a/registry/components/auth_static_key/component.contract.yaml b/registry/components/auth_static_key/component.contract.yaml new file mode 100644 index 0000000..de2a028 --- /dev/null +++ b/registry/components/auth_static_key/component.contract.yaml @@ -0,0 +1,67 @@ +canonical_id: "auth_static_key" +display_name: "Auth Primitive — Static Key" +category: "auth" +version: "v1" +wasi_target: "preview1" +stability: "floating" +runtime_compat: + - "cf-workers" + - "workerd" + - "wazero" +constraints: + max_size_kb: 2048 + max_cold_start_ms: 50 + no_network_syscall: true + no_filesystem_syscall: true + io_model: "stdin_stdout_json" +input_schema: + type: object + required: [action, api_key, service] + properties: + action: + type: string + enum: [authenticate] + description: 目前僅支援 authenticate;static_key 無 refresh 概念 + api_key: + type: string + description: 租戶識別(ak_ 前綴),用來組 {api_key}:cred:{name} KV key + service: + type: string + description: auth recipe 名稱,對應 auth_recipe:{service} 的 KV 記錄 + request: + type: object + description: (保留)下游零件的 HTTP request 上下文;static_key 當前不使用 +output_schema: + type: object + properties: + success: + type: boolean + auth_headers: + type: object + additionalProperties: + type: string + auth_query: + type: object + additionalProperties: + type: string + auth_body: + type: object + additionalProperties: + type: string + runtime: + type: object + description: Static key 不使用;欄位保留以對齊其他 auth primitive +gherkin_tests: + - scenario: "缺少 api_key" + given: '{"action":"authenticate","service":"openai"}' + then_contains: '{"success":false' + - scenario: "找不到 auth recipe" + given: '{"action":"authenticate","api_key":"ak_nonexistent","service":"nonexistent"}' + then_contains: '{"success":false' +tags: [auth, credential, primitive, static_key] +description: "Static key auth primitive。讀取 auth_recipe + 解密 required_secrets + 展開 {{secret.X}} 模板,回傳 auth_headers / auth_query / auth_body。涵蓋 Bearer token / API key / Basic auth / 自訂 header 等 80% 服務。透過 host function kv_get + crypto_decrypt,plaintext 永不離開 WASM。" +config_example: | + auth_step: + component: "auth_static_key" + action: "authenticate" + service: "openai" # 對應 auth_recipe:openai 的 KV 記錄 diff --git a/registry/components/auth_static_key/go.mod b/registry/components/auth_static_key/go.mod new file mode 100644 index 0000000..185c5ee --- /dev/null +++ b/registry/components/auth_static_key/go.mod @@ -0,0 +1,3 @@ +module component + +go 1.21 diff --git a/registry/components/auth_static_key/main.go b/registry/components/auth_static_key/main.go new file mode 100644 index 0000000..29471a2 --- /dev/null +++ b/registry/components/auth_static_key/main.go @@ -0,0 +1,276 @@ +// auth_static_key — static key auth primitive +// +// 讀取 auth_recipe:{service} + 解密 required_secrets + 展開 {{secret.X}} 模板, +// 回傳 auth_headers / auth_query / auth_body。 +// +// 所有外部 I/O 都透過 host function: +// - u6u.kv_get — 依 key 前綴路由到 RECIPES / CREDENTIALS_KV (host 做越權檢查) +// - u6u.crypto_decrypt — AES-GCM 解密 (encryption key 由 host 持有,不暴露給 WASM) +// +//go:build tinygo + +package main + +import ( + "encoding/json" + "io" + "os" + "strings" + "unsafe" +) + +// ── host function 宣告 ─────────────────────────────────────────────────────── + +// kv_get(keyPtr, keyLen, outPtr, outLenPtr) → 0 成功 / 1 錯誤 / 2 找不到 +// +//go:wasmimport u6u kv_get +func hostKvGet( + keyPtr uintptr, keyLen uint32, + outPtr uintptr, outLenPtr uintptr, +) uint32 + +// crypto_decrypt(encPtr, encLen, ivPtr, ivLen, outPtr, outLenPtr) → 0 成功 +// enc/iv 為 base64 字串(即 KV 中儲存的格式) +// +//go:wasmimport u6u crypto_decrypt +func hostCryptoDecrypt( + encPtr uintptr, encLen uint32, + ivPtr uintptr, ivLen uint32, + outPtr uintptr, outLenPtr uintptr, +) uint32 + +// ── 型別 ───────────────────────────────────────────────────────────────────── + +type Input struct { + Action string `json:"action"` + APIKey string `json:"api_key"` + Service string `json:"service"` + Request json.RawMessage `json:"request,omitempty"` +} + +type SecretRequirement struct { + Key string `json:"key"` + Label string `json:"label"` + Optional bool `json:"optional,omitempty"` +} + +type AuthInjectSpec struct { + Header map[string]string `json:"header,omitempty"` + Query map[string]string `json:"query,omitempty"` + Body map[string]string `json:"body,omitempty"` +} + +type AuthRecipe struct { + Kind string `json:"kind"` + Service string `json:"service"` + Primitive string `json:"primitive"` + RequiredSecrets []SecretRequirement `json:"required_secrets"` + Inject AuthInjectSpec `json:"inject"` +} + +type EncryptedRecord struct { + Encrypted string `json:"encrypted"` + IV string `json:"iv"` +} + +// ── main ───────────────────────────────────────────────────────────────────── + +func main() { + raw, err := io.ReadAll(os.Stdin) + if err != nil { + writeError("failed to read stdin: " + err.Error()) + return + } + + var input Input + if err := json.Unmarshal(raw, &input); err != nil { + writeError("invalid input JSON: " + err.Error()) + return + } + + if input.APIKey == "" { + writeError("api_key 必填") + return + } + if input.Service == "" { + writeError("service 必填") + return + } + if input.Action != "" && input.Action != "authenticate" { + writeError("auth_static_key 僅支援 action=authenticate") + return + } + + // 1. 讀 auth recipe + recipeJSON, status := kvGet("auth_recipe:" + input.Service) + if status == 2 { + writeError("找不到 auth recipe: " + input.Service) + return + } + if status != 0 { + writeError("kv_get 失敗(auth_recipe)") + return + } + + var recipe AuthRecipe + if err := json.Unmarshal([]byte(recipeJSON), &recipe); err != nil { + writeError("auth recipe JSON 解析失敗: " + err.Error()) + return + } + if recipe.Primitive != "static_key" { + writeError("auth recipe " + input.Service + " 的 primitive 不是 static_key (是 " + recipe.Primitive + ")") + return + } + + // 2. 解密所有 non-optional required_secrets + secrets := make(map[string]string) + for _, req := range recipe.RequiredSecrets { + if req.Optional { + continue + } + kvKey := input.APIKey + ":cred:" + req.Key + encJSON, s := kvGet(kvKey) + if s == 2 { + writeError("缺少 credential: " + req.Key + " (" + req.Label + ")。修復: 編輯 credentials.yaml 後執行 acr creds push") + return + } + if s != 0 { + writeError("kv_get 失敗(credential " + req.Key + ")") + return + } + + var rec EncryptedRecord + if err := json.Unmarshal([]byte(encJSON), &rec); err != nil { + writeError("credential " + req.Key + " 格式錯誤: " + err.Error()) + return + } + + plaintext, ok := cryptoDecrypt(rec.Encrypted, rec.IV) + if !ok { + writeError("credential " + req.Key + " 解密失敗") + return + } + secrets[req.Key] = plaintext + } + + // 3. 展開模板 (static_key 沒有 runtime,傳空 map) + runtime := map[string]string{} + authHeaders := interpolateRecord(recipe.Inject.Header, secrets, runtime) + authQuery := interpolateRecord(recipe.Inject.Query, secrets, runtime) + authBody := interpolateRecord(recipe.Inject.Body, secrets, runtime) + + // 4. 輸出 + out, _ := json.Marshal(map[string]interface{}{ + "success": true, + "auth_headers": authHeaders, + "auth_query": authQuery, + "auth_body": authBody, + "runtime": runtime, + }) + os.Stdout.Write(out) +} + +// ── helpers ────────────────────────────────────────────────────────────────── + +func writeError(msg string) { + out, _ := json.Marshal(map[string]interface{}{ + "success": false, + "error": msg, + "auth_headers": map[string]string{}, + "auth_query": map[string]string{}, + "auth_body": map[string]string{}, + }) + os.Stdout.Write(out) +} + +// kvGet 呼叫 host function,回傳 (value, status)。status: 0=成功 1=錯誤 2=找不到 +func kvGet(key string) (string, uint32) { + keyBytes := []byte(key) + outBuf := make([]byte, 65536) + var outLen uint32 + + status := hostKvGet( + uintptr(unsafe.Pointer(&keyBytes[0])), uint32(len(keyBytes)), + uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)), + ) + if status != 0 { + return "", status + } + return string(outBuf[:outLen]), 0 +} + +// cryptoDecrypt 呼叫 host function 做 AES-GCM 解密 +// enc/iv 均為 base64 字串;回傳 UTF-8 plaintext +func cryptoDecrypt(encB64, ivB64 string) (string, bool) { + encBytes := []byte(encB64) + ivBytes := []byte(ivB64) + outBuf := make([]byte, 65536) + var outLen uint32 + + // 處理空字串的防呆(TinyGo 取 &[]byte{}[0] 會 panic) + if len(encBytes) == 0 || len(ivBytes) == 0 { + return "", false + } + + status := hostCryptoDecrypt( + uintptr(unsafe.Pointer(&encBytes[0])), uint32(len(encBytes)), + uintptr(unsafe.Pointer(&ivBytes[0])), uint32(len(ivBytes)), + uintptr(unsafe.Pointer(&outBuf[0])), uintptr(unsafe.Pointer(&outLen)), + ) + if status != 0 { + return "", false + } + return string(outBuf[:outLen]), true +} + +// interpolateTemplate 展開 {{secret.X}} 與 {{runtime.X}}。未知 key 展開為空字串(與 TS 版 parity)。 +// 其他 namespace 的 {{...}} 原樣保留(static_key 不解析)。 +func interpolateTemplate(template string, secrets, runtime map[string]string) string { + var b strings.Builder + b.Grow(len(template)) + i := 0 + for i < len(template) { + start := strings.Index(template[i:], "{{") + if start < 0 { + b.WriteString(template[i:]) + break + } + b.WriteString(template[i : i+start]) + openIdx := i + start + closeRel := strings.Index(template[openIdx+2:], "}}") + if closeRel < 0 { + b.WriteString(template[openIdx:]) + break + } + inner := template[openIdx+2 : openIdx+2+closeRel] + advance := openIdx + 2 + closeRel + 2 + + switch { + case strings.HasPrefix(inner, "secret."): + key := inner[len("secret."):] + b.WriteString(secrets[key]) + case strings.HasPrefix(inner, "runtime."): + key := inner[len("runtime."):] + b.WriteString(runtime[key]) + default: + // 非本 primitive 負責的 namespace,原樣寫回 + b.WriteString(template[openIdx:advance]) + } + i = advance + } + return b.String() +} + +func interpolateRecord( + record map[string]string, + secrets, runtime map[string]string, +) map[string]string { + if record == nil { + return map[string]string{} + } + result := make(map[string]string, len(record)) + for k, v := range record { + result[k] = interpolateTemplate(v, secrets, runtime) + } + return result +}