capability-worker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import{base64UrlDecode as t,sha256Hex as e,utf8 as i,toArrayBuffer as n}from"../runtime/crypto/index.js";import{HttpError as a}from"../runtime/http/index.js";const s=new Map;class r{env;options;constructor(t,e={}){this.env=t,this.options=e}async getKey(t,e={}){const i=(await this.getKeys(e)).find(e=>e.kid===t);return i||e.forceRefresh?i:(await this.getKeys({forceRefresh:!0})).find(e=>e.kid===t)}async getKeys(t={}){const e=this.cacheKey(),i=Date.now(),n=this.options.cacheTtlMs??3e5,a=s.get(e);if(!t.forceRefresh&&a&&i-a.fetchedAt<n)return a.keys;const r=await this.fetchKeys();return s.set(e,{fetchedAt:i,keys:r}),r}clear(){s.delete(this.cacheKey())}cacheKey(){return this.env.CAPABILITY_PUBLIC_KEYS_JSON?`json:${this.env.CAPABILITY_PUBLIC_KEYS_JSON}`:this.env.CAPABILITY_PUBLIC_KEYS?`inline:${this.env.CAPABILITY_PUBLIC_KEYS}`:this.env.CAPABILITY_PUBLIC_KEYS_SERVICE?`service:${this.options.serviceBindingUrl??this.env.CAPABILITY_PUBLIC_KEYS_URL??""}`:`url:${this.env.CAPABILITY_PUBLIC_KEYS_URL??""}`}async fetchKeys(){if(!1!==this.options.allowInlineKeys){if(this.env.CAPABILITY_PUBLIC_KEYS_JSON?.trim())return o(this.env.CAPABILITY_PUBLIC_KEYS_JSON);if(this.env.CAPABILITY_PUBLIC_KEYS?.trim())return o(this.env.CAPABILITY_PUBLIC_KEYS)}const t=this.options.serviceBindingUrl??this.env.CAPABILITY_PUBLIC_KEYS_URL;if(this.env.CAPABILITY_PUBLIC_KEYS_SERVICE){if(!t)throw new a(500,"public_keys_unconfigured","Public keys URL is not configured.");const e=new Request(t,{headers:{accept:"application/json"}});return u(await this.env.CAPABILITY_PUBLIC_KEYS_SERVICE.fetch(e))}if(!t)throw new a(500,"public_keys_unconfigured","Public keys URL is not configured.");const e=this.options.fetcher??fetch;return u(await e(t,{headers:{accept:"application/json"}}))}}function c(){s.clear()}function o(t){const e="string"==typeof t?JSON.parse(t):t;return(Array.isArray(e)?e:e&&"object"==typeof e&&Array.isArray(e.keys)?e.keys:[]).filter(y)}async function u(t){if(!t.ok)throw new a(401,"key_fetch_failed","Could not fetch capability verification keys.");return o(await t.json())}function y(t){if(!t||"object"!=typeof t)return!1;const e=t;if("string"!=typeof e.kid||0===e.kid.trim().length)return!1;for(const t of["d","p","q","dp","dq","qi","oth","k"])if(t in e)return!1;return!e.use||"sig"===e.use}const h="CAPABILITY-SIGNATURE-V1";async function d(n,s,c={}){try{!function(t){const e=new URL(t.url);for(const t of e.searchParams.keys()){const e=t.toLowerCase();if("accountid"===e||"account_id"===e||"x-capability-account-id"===e||"capabilityaccountid"===e)throw new a(400,"invalid_request","Account identity must come from verified capability headers.")}}(n);const u=I(n,"x-capability-account-id"),y=I(n,"x-capability-timestamp"),h=I(n,"x-capability-key-id"),d=I(n,"x-capability-signature");if(!/^[a-zA-Z0-9._:-]{1,160}$/.test(u))return _(401,"invalid_account_id","Invalid capability account id.");const f=Number(y);if(!Number.isInteger(f))return _(401,"invalid_signature_timestamp","Invalid capability signature timestamp.");const m=(o=c.now??Date.now())instanceof Date?Math.floor(o.getTime()/1e3):Math.floor(o>1e12?o/1e3:o),A=c.maxSkewSeconds??300;if(Math.abs(m-f)>A)return _(401,"stale_signature","Capability signature timestamp is outside the allowed skew.");if(!d.startsWith("v1="))return _(401,"invalid_signature","Invalid capability signature header.");const w=t(d.slice(3)),g=new r(s),S=await g.getKey(h);if(!S)return _(401,"unknown_key","Unknown capability signature key.");const v=c.rawBody??await n.clone().arrayBuffer(),C=new URL(n.url),k=l({method:n.method,pathWithQuery:`${C.pathname}${C.search}`,timestamp:y,accountId:u,bodyHashHex:await e(v)});return await p(S,w,i(k),c.acceptedAlgorithms??["ES256","Ed25519","RS256"])?{ok:!0,accountId:u,keyId:h,source:"capability-signature"}:_(401,"invalid_signature","Invalid capability signature.")}catch(t){return t instanceof a?_(t.status,t.code,t.message):_(401,"invalid_signature","Invalid capability signature.")}var o}async function f(t,e,i={}){const n=await d(t,e,i);if(!n.ok)throw new a(n.status,n.code,n.message);return{accountId:n.accountId,keyId:n.keyId??""}}function l(t){return[h,t.method.toUpperCase(),t.pathWithQuery,t.timestamp,t.accountId,t.bodyHashHex].join("\n")}async function p(t,e,i,s=["ES256","Ed25519","RS256"]){const r=await async function(t,e){if(("ES256"===t.alg||"EC"===t.kty&&"P-256"===t.crv)&&e.includes("ES256"))return{key:await crypto.subtle.importKey("jwk",t,{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]),algorithm:{name:"ECDSA",hash:"SHA-256"}};if(("EdDSA"===t.alg||"OKP"===t.kty&&"Ed25519"===t.crv)&&e.includes("Ed25519")){const e={name:"Ed25519"};return{key:await crypto.subtle.importKey("jwk",t,e,!1,["verify"]),algorithm:e}}if(("RS256"===t.alg||"RSA"===t.kty)&&e.includes("RS256"))return{key:await crypto.subtle.importKey("jwk",t,{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["verify"]),algorithm:{name:"RSASSA-PKCS1-v1_5"}};throw new a(401,"unsupported_key","Capability verification key type is unsupported.")}(t,s);return crypto.subtle.verify(r.algorithm,r.key,n(e),n(i))}function I(t,e){const i=t.headers.get(e)?.trim();if(!i)throw new a(401,"missing_signature","Missing capability signature headers.");return i}function _(t,e,i){return{ok:!1,status:t,code:e,message:i}}export{r as P,p as a,l as b,c,o as p,f as r,d as v};
@@ -0,0 +1,2 @@
1
+
2
+ export { };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import s from"node:path";import{buildCapabilityAssets as e}from"./index.js";import"node:fs/promises";import"../runtime/validation/index.js";(async function(){const t=function(s){const e={};for(let t=0;t<s.length;t+=1){const o=s[t];if("--validate"!==o){if(o?.startsWith("--")){const i=o.slice(2),r=s[t+1];if(!r||r.startsWith("--"))throw new Error(`Missing value for ${o}.`);e[i]=r,t+=1}}else e.validate="true"}return e}(process.argv.slice(2)),o={workerRoot:s.resolve(t["worker-root"]??"worker"),repoRoot:s.resolve(t["repo-root"]??"."),capabilityBaseUrl:process.env.CAPABILITY_BASE_URL??t["capability-base-url"]??"https://example.com/api",validate:"true"===t.validate},i=t["public-root"]?s.resolve(t["public-root"]):void 0;i&&Object.assign(o,{publicRoot:i});const r=process.env.APP_ASSET_VERSION??t["asset-version"];r&&Object.assign(o,{assetVersion:r});const n=process.env.CAPABILITY_INFO_TEMPLATE_PATH??t["info-template"];n&&Object.assign(o,{infoTemplatePath:n});const a=process.env.CAPABILITY_DEFINITION_PATH??t.definition;a&&Object.assign(o,{definitionPath:a});const c=process.env.CAPABILITY_MAPPING_PATH??t.mapping;c&&Object.assign(o,{mappingPath:c});const p=process.env.CAPABILITY_SKILL_ENTRYPOINT??t.skill;p&&Object.assign(o,{skillEntrypointPath:p});const l=await e(o);process.stdout.write(`Wrote capability assets to ${l.assetDir}\n`);const v=l.issues.filter(s=>"warning"===s.severity);for(const s of v)process.stderr.write(`warning ${s.path}: ${s.message}\n`)})().catch(s=>{const e=s instanceof Error?s.message:String(s);process.stderr.write(`${e}\n`),process.exitCode=1});
@@ -0,0 +1,44 @@
1
+ import { ValidationIssue } from '../runtime/validation/index.js';
2
+
3
+ type BuildCapabilityAssetsInput = {
4
+ workerRoot: string;
5
+ repoRoot: string;
6
+ publicRoot?: string;
7
+ capabilityBaseUrl: string;
8
+ assetVersion?: string;
9
+ infoTemplatePath?: string;
10
+ definitionPath?: string;
11
+ mappingPath?: string;
12
+ skillEntrypointPath?: string;
13
+ validate?: boolean;
14
+ };
15
+ type BuildCapabilityAssetsResult = {
16
+ assetDir: string;
17
+ basePath: string;
18
+ info: unknown;
19
+ definition: unknown;
20
+ mapping: unknown;
21
+ issues: ValidationIssue[];
22
+ };
23
+ declare function buildCapabilityAssets(input: BuildCapabilityAssetsInput): Promise<BuildCapabilityAssetsResult>;
24
+ declare function loadCapabilityAssetSet(input: {
25
+ workerRoot: string;
26
+ repoRoot: string;
27
+ publicRoot?: string;
28
+ capabilityBaseUrl: string;
29
+ }): Promise<{
30
+ info: unknown;
31
+ definition: unknown;
32
+ mapping: unknown;
33
+ skillFiles: Array<{
34
+ path: string;
35
+ content: string;
36
+ }>;
37
+ }>;
38
+
39
+ declare function safeRelativePath(value: string): string;
40
+ declare function trimLeadingSlash(value: string): string;
41
+ declare function normalizeBaseUrl(value: string): string;
42
+
43
+ export { buildCapabilityAssets, loadCapabilityAssetSet, normalizeBaseUrl, safeRelativePath, trimLeadingSlash };
44
+ export type { BuildCapabilityAssetsInput, BuildCapabilityAssetsResult };
@@ -0,0 +1 @@
1
+ import{rm as i,mkdir as t,writeFile as n,readFile as e,copyFile as a}from"node:fs/promises";import o from"node:path";import{validateCapabilityAssets as r}from"../runtime/validation/index.js";function s(i){const t=o.posix.normalize(String(i).replace(/\\/g,"/"));if(!t||"."===t||".."===t||t.startsWith("../")||o.posix.isAbsolute(t))throw new Error(`Invalid relative path: ${i}`);return t}function p(i){return i.replace(/^\/+/,"")}function l(i){const t=i.trim().replace(/\/+$/,"");if(!t)throw new Error("CAPABILITY_BASE_URL is required.");return t}async function c(e){const a=o.resolve(e.workerRoot),c=o.resolve(e.repoRoot),f=o.resolve(e.publicRoot??o.join(a,"public")),g=l(e.capabilityBaseUrl),j=e.assetVersion??new Date(0).toISOString(),d=new URL(g).pathname.replace(/\/+$/,""),b=o.join(f,p(d)),k=e.infoTemplatePath?o.resolve(e.infoTemplatePath):o.join(a,"capability","info.template.json"),P=e.definitionPath?o.resolve(e.definitionPath):o.join(c,"cli","definition.json"),v=e.mappingPath?o.resolve(e.mappingPath):o.join(c,"cli","mapping.json"),A=y(await u(k,"capability info template"),{APP_ASSET_VERSION:j,CAPABILITY_BASE_URL:g}),C=await u(P,"CLI definition"),E=await u(v,"HTTP mapping"),L=w(A)&&"string"==typeof A.name?A.name:"capability",S=e.skillEntrypointPath?o.resolve(e.skillEntrypointPath):o.join(c,"skills",L,"SKILL.md");await i(f,{recursive:!0,force:!0}),await t(b,{recursive:!0}),await m(o.join(b,"info"),A),await m(o.join(b,"definition"),C),await m(o.join(b,"mapping"),E);const $=await async function(i){const t=w(i.info)?i.info:{},n=w(t.skill)?t.skill:{},e=w(n.entrypoint)?n.entrypoint:{},a=s("string"==typeof e.path?e.path:"SKILL.md"),r=o.dirname(i.skillEntrypointPath),p=[];p.push(await h(i.skillEntrypointPath,o.join(i.assetDir,"skill",a),a));const l=Array.isArray(n.resources)?n.resources:[];for(const t of l){if(!w(t)||"string"!=typeof t.path)continue;const n=s(t.path);p.push(await h(o.join(r,n),o.join(i.assetDir,"skill",n),n))}return p}({info:A,skillEntrypointPath:S,assetDir:b});await n(o.join(f,"_headers"),function(i){const t=i||"";return[`${t}/info`," Content-Type: application/json; charset=utf-8"," Cache-Control: public, max-age=86400","",`${t}/definition`," Content-Type: application/json; charset=utf-8"," Cache-Control: public, max-age=86400","",`${t}/mapping`," Content-Type: application/json; charset=utf-8"," Cache-Control: public, max-age=86400","",`${t}/skill/*`," Content-Type: text/markdown; charset=utf-8"," Cache-Control: public, max-age=86400",""].join("\n")}(d));const I=r({info:A,definition:C,mapping:E,skillFiles:$});if(e.validate){const i=I.filter(i=>"error"===i.severity);if(i.length>0)throw new Error(i.map(i=>`${i.path}: ${i.message}`).join("\n"))}return{assetDir:b,basePath:d,info:A,definition:C,mapping:E,issues:I}}async function f(i){const t=o.resolve(i.publicRoot??o.join(i.workerRoot,"public")),n=new URL(l(i.capabilityBaseUrl)).pathname.replace(/\/+$/,""),a=o.join(t,p(n)),r=o.join(a,"skill","SKILL.md");return{info:await u(o.join(a,"info"),"generated info asset"),definition:await u(o.join(a,"definition"),"generated definition asset"),mapping:await u(o.join(a,"mapping"),"generated mapping asset"),skillFiles:[{path:"SKILL.md",content:await e(r,"utf8")}]}}async function u(i,t){try{return JSON.parse(await e(i,"utf8"))}catch(n){const e=n instanceof Error?n.message:String(n);throw new Error(`Unable to read ${t} at ${i}: ${e}`)}}async function m(i,t){await n(i,`${JSON.stringify(t,null,2)}\n`)}async function h(i,n,r){return await t(o.dirname(n),{recursive:!0}),await a(i,n),{path:r,content:await e(i,"utf8")}}function y(i,t){return"string"==typeof i?i.replace(/\$\{([A-Z0-9_]+)\}/g,(i,n)=>t[n]??""):Array.isArray(i)?i.map(i=>y(i,t)):w(i)?Object.fromEntries(Object.entries(i).map(([i,n])=>[i,y(n,t)])):i}function w(i){return Boolean(i&&"object"==typeof i&&!Array.isArray(i))}export{c as buildCapabilityAssets,f as loadCapabilityAssetSet,l as normalizeBaseUrl,s as safeRelativePath,p as trimLeadingSlash};
@@ -0,0 +1,2 @@
1
+
2
+ export { };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import e from"node:path";import{validateCapabilityAssets as o}from"../runtime/validation/index.js";import{loadCapabilityAssetSet as r,buildCapabilityAssets as t}from"./index.js";import"node:fs/promises";(async function(){const s=function(e){const o={};for(let r=0;r<e.length;r+=1){const t=e[r];if("--generated"!==t){if(t?.startsWith("--")){const s=t.slice(2),i=e[r+1];if(!i||i.startsWith("--"))throw new Error(`Missing value for ${t}.`);o[s]=i,r+=1}}else o.generated="true"}return o}(process.argv.slice(2)),i=e.resolve(s["worker-root"]??"worker"),n=e.resolve(s["repo-root"]??"."),a=process.env.CAPABILITY_BASE_URL??s["capability-base-url"]??"https://example.com/api",c=s["public-root"]?e.resolve(s["public-root"]):void 0,p="true"===s.generated?await r(function(e,o){return o?{...e,publicRoot:o}:e}({workerRoot:i,repoRoot:n,capabilityBaseUrl:a},c)):await t(function(e,o){const r={...e};for(const[e,t]of Object.entries(o))void 0!==t&&Object.assign(r,{[e]:t});return r}({workerRoot:i,repoRoot:n,capabilityBaseUrl:a,validate:!1},{publicRoot:c,assetVersion:process.env.APP_ASSET_VERSION??s["asset-version"],infoTemplatePath:process.env.CAPABILITY_INFO_TEMPLATE_PATH??s["info-template"],definitionPath:process.env.CAPABILITY_DEFINITION_PATH??s.definition,mappingPath:process.env.CAPABILITY_MAPPING_PATH??s.mapping,skillEntrypointPath:process.env.CAPABILITY_SKILL_ENTRYPOINT??s.skill})),l=o(p);for(const e of l){const o=`${e.severity} ${e.path}: ${e.message}\n`;"error"===e.severity?process.stderr.write(o):process.stdout.write(o)}l.some(e=>"error"===e.severity)&&(process.exitCode=1)})().catch(e=>{const o=e instanceof Error?e.message:String(e);process.stderr.write(`${o}\n`),process.exitCode=1});
@@ -0,0 +1,55 @@
1
+ import { a as CapabilityPublicKeysEnv, V as VerifyCapabilitySignatureOptions, A as AccountAuthResult, P as PublicJwk, b as PublicKeyStoreOptions } from '../../types-BCUAqVuU.js';
2
+ export { C as CapabilityBaseEnv } from '../../types-BCUAqVuU.js';
3
+
4
+ declare function verifyCapabilitySignature(request: Request, env: CapabilityPublicKeysEnv, options?: VerifyCapabilitySignatureOptions): Promise<AccountAuthResult>;
5
+ declare function requireCapabilitySignature(request: Request, env: CapabilityPublicKeysEnv, options?: VerifyCapabilitySignatureOptions): Promise<{
6
+ accountId: string;
7
+ keyId: string;
8
+ }>;
9
+ declare function buildCapabilityCanonicalString(input: {
10
+ method: string;
11
+ pathWithQuery: string;
12
+ timestamp: string;
13
+ accountId: string;
14
+ bodyHashHex: string;
15
+ }): string;
16
+ declare function verifyPublicKeySignature(jwk: PublicJwk, signature: Uint8Array, data: Uint8Array, acceptedAlgorithms?: Array<"ES256" | "Ed25519" | "RS256">): Promise<boolean>;
17
+
18
+ declare class PublicKeyStore {
19
+ readonly env: CapabilityPublicKeysEnv;
20
+ readonly options: PublicKeyStoreOptions;
21
+ constructor(env: CapabilityPublicKeysEnv, options?: PublicKeyStoreOptions);
22
+ getKey(keyId: string, options?: {
23
+ forceRefresh?: boolean;
24
+ }): Promise<PublicJwk | undefined>;
25
+ getKeys(options?: {
26
+ forceRefresh?: boolean;
27
+ }): Promise<PublicJwk[]>;
28
+ clear(): void;
29
+ private cacheKey;
30
+ private fetchKeys;
31
+ }
32
+ declare function clearGlobalPublicKeyCache(): void;
33
+ declare function parsePublicKeys(value: string | unknown): PublicJwk[];
34
+
35
+ type VerifyAuthCookieOptions = {
36
+ cookieName?: string;
37
+ now?: number | Date;
38
+ maxClockSkewSeconds?: number;
39
+ allowedAlgorithms?: Array<"ES256">;
40
+ };
41
+ declare function verifyAuthCookie(request: Request, env: CapabilityPublicKeysEnv, options?: VerifyAuthCookieOptions): Promise<AccountAuthResult>;
42
+ declare function accountAuthFromRequest(request: Request, env: CapabilityPublicKeysEnv, options?: {
43
+ cookieName?: string;
44
+ requireMatchingCookieAndSignature?: boolean;
45
+ }): Promise<AccountAuthResult>;
46
+ declare function browserAuthOriginError(request: Request, auth: AccountAuthResult, options: {
47
+ allowedOrigins: string[];
48
+ }): {
49
+ status: number;
50
+ code: string;
51
+ message: string;
52
+ } | null;
53
+
54
+ export { AccountAuthResult, CapabilityPublicKeysEnv, PublicJwk, PublicKeyStore, PublicKeyStoreOptions, VerifyCapabilitySignatureOptions, accountAuthFromRequest, browserAuthOriginError, buildCapabilityCanonicalString, clearGlobalPublicKeyCache, parsePublicKeys, requireCapabilitySignature, verifyAuthCookie, verifyCapabilitySignature, verifyPublicKeySignature };
55
+ export type { VerifyAuthCookieOptions };
@@ -0,0 +1 @@
1
+ import{v as e,P as i}from"../../chunks/yaIxL224.js";export{b as buildCapabilityCanonicalString,c as clearGlobalPublicKeyCache,p as parsePublicKeys,r as requireCapabilitySignature,a as verifyPublicKeySignature}from"../../chunks/yaIxL224.js";import{decodeUtf8 as t,base64UrlDecode as o,toArrayBuffer as n,utf8 as u}from"../crypto/index.js";import"../http/index.js";async function s(e,a,r={}){const c=r.cookieName??"auth",s=function(e){const i={};for(const t of e.split(";")){const[e,...o]=t.trim().split("=");e&&(i[e]=decodeURIComponent(o.join("=")))}return i}(e.headers.get("cookie")??"")[c];if(!s)return k(401,"missing_auth_cookie","Missing auth cookie.");const l=s.split(".");if(3!==l.length||!l[0]||!l[1]||!l[2])return k(401,"invalid_auth_cookie","Invalid auth cookie.");let d,h;try{d=JSON.parse(t(o(l[0]))),h=JSON.parse(t(o(l[1])))}catch{return k(401,"invalid_auth_cookie","Invalid auth cookie.")}const m="string"==typeof d.alg?d.alg:"",f="string"==typeof d.kid?d.kid:"";if(!f||"ES256"!==m||!(r.allowedAlgorithms??["ES256"]).includes("ES256"))return k(401,"unsupported_auth_cookie","Unsupported auth cookie signature.");const p=await new i(a).getKey(f);if(!p)return k(401,"unknown_key","Unknown auth cookie key.");let g=!1;try{const e=await crypto.subtle.importKey("jwk",p,{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);g=await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},e,n(o(l[2])),n(u(`${l[0]}.${l[1]}`)))}catch{g=!1}if(!g)return k(401,"invalid_auth_cookie","Invalid auth cookie signature.");const y="string"==typeof h.sub?h.sub.trim():"";if(!y)return k(401,"invalid_auth_cookie","Auth cookie subject is missing.");if("string"==typeof h.account_id&&h.account_id!==y)return k(401,"invalid_auth_cookie","Auth cookie account id does not match subject.");const _=function(e){return e instanceof Date?Math.floor(e.getTime()/1e3):Math.floor(e>1e12?e/1e3:e)}(r.now??Date.now()),b=r.maxClockSkewSeconds??60;if("number"==typeof h.exp&&_-b>h.exp)return k(401,"expired_auth_cookie","Auth cookie has expired.");if("number"==typeof h.nbf&&_+b<h.nbf)return k(401,"invalid_auth_cookie","Auth cookie is not valid yet.");if("number"==typeof h.iat&&_+b<h.iat)return k(401,"invalid_auth_cookie","Auth cookie was issued in the future.");const v="string"==typeof h.email?h.email:void 0,w=!0===h.email_verified,S="string"==typeof h.email_domain?h.email_domain.toLowerCase():void 0;return v&&S&&v.split("@").at(-1)?.toLowerCase()!==S?k(401,"invalid_auth_cookie","Auth cookie email domain does not match email."):{ok:!0,accountId:y,keyId:f,source:"auth-cookie",...v&&w?{email:v}:{},emailVerified:w}}async function l(i,t,o={}){const a=i.headers.has("x-capability-signature");if(o.requireMatchingCookieAndSignature){const a=await e(i,t);if(!a.ok)return a;const n=await s(i,t,o.cookieName?{cookieName:o.cookieName}:{});return n.ok?n.accountId!==a.accountId?k(403,"account_mismatch","Auth cookie and capability signature account ids do not match."):a:n}return a?e(i,t):s(i,t,o.cookieName?{cookieName:o.cookieName}:{})}function d(e,i,t){if(!i.ok)return null;if(!["POST","PUT","PATCH","DELETE"].includes(e.method.toUpperCase()))return null;const o=e.headers.get("origin");return o&&t.allowedOrigins.includes(o)?null:{status:403,code:"invalid_origin",message:"Request origin is not allowed."}}function k(e,i,t){return{ok:!1,status:e,code:i,message:t}}export{i as PublicKeyStore,l as accountAuthFromRequest,d as browserAuthOriginError,s as verifyAuthCookie,e as verifyCapabilitySignature};
@@ -0,0 +1,33 @@
1
+ type CredentialKeyringEnv = {
2
+ CREDENTIAL_ENCRYPTION_KEYS?: string;
3
+ CREDENTIAL_ACTIVE_KEY_ID?: string;
4
+ ENVIRONMENT?: string;
5
+ };
6
+ type CredentialKeyring = Record<string, string>;
7
+ type EncryptedField = {
8
+ keyId: string;
9
+ nonce: string;
10
+ ciphertext: string;
11
+ };
12
+ type EncryptFieldInput = {
13
+ env: CredentialKeyringEnv;
14
+ accountId: string;
15
+ rowId: string;
16
+ fieldName: string;
17
+ plaintext: string;
18
+ keyId?: string;
19
+ };
20
+ declare function encryptCredentialField(input: EncryptFieldInput): Promise<EncryptedField>;
21
+ declare function decryptCredentialField(input: {
22
+ env: CredentialKeyringEnv;
23
+ accountId: string;
24
+ rowId: string;
25
+ fieldName: string;
26
+ encrypted: EncryptedField;
27
+ }): Promise<string>;
28
+ declare function parseCredentialKeyring(value: string): CredentialKeyring;
29
+ declare function activeCredentialKeyId(env: CredentialKeyringEnv): string;
30
+ declare function developmentCredentialKey(seed: string): Promise<string>;
31
+
32
+ export { activeCredentialKeyId, decryptCredentialField, developmentCredentialKey, encryptCredentialField, parseCredentialKeyring };
33
+ export type { CredentialKeyring, CredentialKeyringEnv, EncryptFieldInput, EncryptedField };
@@ -0,0 +1 @@
1
+ import{toArrayBuffer as e,base64UrlDecode as n,sha256Hex as t,base64UrlEncode as r,utf8 as i}from"../crypto/index.js";import{HttpError as c}from"../http/index.js";async function o(n){const t=n.keyId??d(n.env),c=new Uint8Array(12);crypto.getRandomValues(c);const o=await l(n.env,t),a=await crypto.subtle.encrypt({name:"AES-GCM",iv:c,additionalData:e(u(n.accountId,n.rowId,n.fieldName,t))},o,e(i(n.plaintext)));return{keyId:t,nonce:r(c),ciphertext:r(a)}}async function a(t){const r=await l(t.env,t.encrypted.keyId),i=await crypto.subtle.decrypt({name:"AES-GCM",iv:e(n(t.encrypted.nonce)),additionalData:e(u(t.accountId,t.rowId,t.fieldName,t.encrypted.keyId))},r,e(n(t.encrypted.ciphertext)));return(new TextDecoder).decode(i)}function y(e){let t;try{t=JSON.parse(e)}catch{throw new c(500,"credential_keys_invalid","Credential encryption keys are invalid JSON.")}if(!t||"object"!=typeof t||Array.isArray(t))throw new c(500,"credential_keys_invalid","Credential encryption keys must be an object.");const r={};for(const[e,i]of Object.entries(t)){if("string"!=typeof i||!i.trim())throw new c(500,"credential_key_invalid","Credential encryption key values must be strings.");if(32!==n(i).byteLength)throw new c(500,"credential_key_invalid","Credential encryption keys must be 32 bytes.");r[e]=i}if(0===Object.keys(r).length)throw new c(500,"credential_keys_invalid","Credential encryption keyring is empty.");return r}function d(e){const n=e.CREDENTIAL_ACTIVE_KEY_ID?.trim();if(n)return n;const t=p(e),r=Object.keys(t)[0];if(!r)throw new c(500,"credential_keys_unconfigured","Credential encryption keys are not configured.");return r}async function s(e){const n=await t(e),i=new Uint8Array(32);for(let e=0;e<i.length;e+=1)i[e]=Number.parseInt(n.slice(2*e,2*e+2),16);return r(i)}async function l(t,r){const i=p(t)[r];if(!i)throw new c(500,"credential_key_missing","Credential encryption key is missing.");const o=n(i);if(32!==o.byteLength)throw new c(500,"credential_key_invalid","Credential encryption key must be 32 bytes.");return crypto.subtle.importKey("raw",e(o),"AES-GCM",!1,["encrypt","decrypt"])}function p(e){if(!e.CREDENTIAL_ENCRYPTION_KEYS?.trim())throw new c(500,"credential_keys_unconfigured","Credential encryption keys are not configured.");return y(e.CREDENTIAL_ENCRYPTION_KEYS)}function u(e,n,t,r){return i(`${e}\n${n}\n${t}\n${r}`)}export{d as activeCredentialKeyId,a as decryptCredentialField,s as developmentCredentialKey,o as encryptCredentialField,y as parseCredentialKeyring};
@@ -0,0 +1,16 @@
1
+ declare function utf8(value: string): Uint8Array;
2
+ declare function decodeUtf8(value: ArrayBuffer | Uint8Array): string;
3
+ declare function toArrayBuffer(bytes: Uint8Array): ArrayBuffer;
4
+ declare function base64UrlEncode(value: ArrayBuffer | Uint8Array): string;
5
+ declare function base64UrlDecode(value: string): Uint8Array;
6
+ declare function base64Encode(value: ArrayBuffer | Uint8Array): string;
7
+ declare function base64Decode(value: string): Uint8Array;
8
+ declare function sha256Hex(value: ArrayBuffer | Uint8Array | string): Promise<string>;
9
+ declare function hmacSha256Base64Url(secret: string | Uint8Array, payload: string | Uint8Array): Promise<string>;
10
+ declare function hmacSha256Bytes(secret: string | Uint8Array, payload: string | Uint8Array): Promise<Uint8Array>;
11
+ declare function randomBytes(byteLength: number): Uint8Array;
12
+ declare function randomBase64Url(byteLength?: number): string;
13
+ declare function timingSafeEqual(a: string | Uint8Array, b: string | Uint8Array): boolean;
14
+ declare function bytesToHex(bytes: Uint8Array): string;
15
+
16
+ export { base64Decode, base64Encode, base64UrlDecode, base64UrlEncode, bytesToHex, decodeUtf8, hmacSha256Base64Url, hmacSha256Bytes, randomBase64Url, randomBytes, sha256Hex, timingSafeEqual, toArrayBuffer, utf8 };
@@ -0,0 +1 @@
1
+ const t=new TextEncoder,n=new TextDecoder;function e(n){return t.encode(n)}function r(t){return n.decode(t)}function o(t){return t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength)}function a(t){return c(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function i(t){if(!/^[A-Za-z0-9_-]*={0,2}$/.test(t)||t.length%4==1)throw new TypeError("Invalid base64url value.");const n=t.replace(/=+$/g,"");return u(n.replace(/-/g,"+").replace(/_/g,"/").padEnd(4*Math.ceil(n.length/4),"="))}function c(t){const n=t instanceof Uint8Array?t:new Uint8Array(t);let e="";for(const t of n)e+=String.fromCharCode(t);return btoa(e)}function u(t){if(!/^[A-Za-z0-9+/]*={0,2}$/.test(t)||t.length%4==1)throw new TypeError("Invalid base64 value.");const n=atob(t),e=new Uint8Array(n.length);for(let t=0;t<n.length;t+=1)e[t]=n.charCodeAt(t);return e}async function s(t){const n="string"==typeof t?e(t):t,r=n instanceof Uint8Array?o(n):n,a=await crypto.subtle.digest("SHA-256",r);return h(new Uint8Array(a))}async function f(t,n){return a(await g(t,n))}async function g(t,n){const r="string"==typeof t?e(t):t,a="string"==typeof n?e(n):n,i=await crypto.subtle.importKey("raw",o(r),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),c=await crypto.subtle.sign("HMAC",i,o(a));return new Uint8Array(c)}function l(t){if(!Number.isInteger(t)||t<0)throw new RangeError("byteLength must be a non-negative integer.");const n=new Uint8Array(t);return crypto.getRandomValues(n),n}function y(t=32){return a(l(t))}function p(t,n){const r="string"==typeof t?e(t):t,o="string"==typeof n?e(n):n,a=Math.max(r.length,o.length);let i=r.length===o.length?0:1;for(let t=0;t<a;t+=1)i|=(r[t]??0)^(o[t]??0);return 0===i}function h(t){return[...t].map(t=>t.toString(16).padStart(2,"0")).join("")}export{u as base64Decode,c as base64Encode,i as base64UrlDecode,a as base64UrlEncode,h as bytesToHex,r as decodeUtf8,f as hmacSha256Base64Url,g as hmacSha256Bytes,y as randomBase64Url,l as randomBytes,s as sha256Hex,p as timingSafeEqual,o as toArrayBuffer,e as utf8};
@@ -0,0 +1,39 @@
1
+ type CapabilityFileConstraints = {
2
+ allowedExtensions?: string[];
3
+ allowedMimeTypes?: string[];
4
+ maxBytes?: number;
5
+ maxCount?: number;
6
+ maxTotalBytes?: number;
7
+ };
8
+ type CapabilityFile = {
9
+ fieldName: string;
10
+ filename: string;
11
+ contentType: string;
12
+ bytes: Uint8Array;
13
+ };
14
+ type ParsedMultipart = {
15
+ payload: Record<string, unknown>;
16
+ files: CapabilityFile[];
17
+ };
18
+ declare function validateCapabilityFiles(files: CapabilityFile[], constraints: CapabilityFileConstraints): {
19
+ ok: true;
20
+ } | {
21
+ ok: false;
22
+ status: number;
23
+ code: string;
24
+ message: string;
25
+ };
26
+ declare function parseMultipartRequest(request: Request, options?: {
27
+ payloadFieldName?: string;
28
+ fileFieldNames?: string[];
29
+ maxTotalBytes?: number;
30
+ }): Promise<ParsedMultipart>;
31
+ declare function parseMultipartBody(contentType: string, body: Uint8Array, options?: {
32
+ payloadFieldName?: string;
33
+ fileFieldNames?: string[];
34
+ }): ParsedMultipart;
35
+ declare function safeFilename(value: string, fallback?: string): string;
36
+ declare function contentDispositionAttachment(filename: string): string;
37
+
38
+ export { contentDispositionAttachment, parseMultipartBody, parseMultipartRequest, safeFilename, validateCapabilityFiles };
39
+ export type { CapabilityFile, CapabilityFileConstraints, ParsedMultipart };
@@ -0,0 +1 @@
1
+ import{decodeUtf8 as e}from"../crypto/index.js";import{HttpError as t}from"../http/index.js";function n(e,t){if(void 0!==t.maxCount&&e.length>t.maxCount)return{ok:!1,status:400,code:"too_many_files",message:`Expected at most ${t.maxCount} files.`};const n=e.reduce((e,t)=>e+t.bytes.byteLength,0);if(void 0!==t.maxTotalBytes&&n>t.maxTotalBytes)return{ok:!1,status:400,code:"files_too_large",message:"Uploaded files exceed the total size limit."};const a=t.allowedExtensions?.map(c),o=t.allowedMimeTypes?.map(e=>e.toLowerCase());for(const n of e){if(i(n.filename)!==n.filename)return{ok:!1,status:400,code:"unsafe_filename",message:"Uploaded file has an unsafe filename."};if(void 0!==t.maxBytes&&n.bytes.byteLength>t.maxBytes)return{ok:!1,status:400,code:"file_too_large",message:`Uploaded file ${n.filename} exceeds the per-file size limit.`};if(a&&!a.includes(f(n.filename)))return{ok:!1,status:400,code:"file_extension_not_allowed",message:`Uploaded file ${n.filename} has an unsupported extension.`};if(o&&!o.includes(n.contentType.toLowerCase()))return{ok:!1,status:400,code:"file_type_not_allowed",message:`Uploaded file ${n.filename} has an unsupported content type.`}}return{ok:!0}}async function a(e,n={}){const a=new Uint8Array(await e.arrayBuffer());if(void 0!==n.maxTotalBytes&&a.byteLength>n.maxTotalBytes)throw new t(400,"multipart_too_large","Multipart request exceeds the size limit.");return o(e.headers.get("content-type")??"",a,n)}function o(n,a,o={}){const s=n.match(/boundary=([^;]+)/i)?.[1]?.replace(/^"|"$/g,"");if(!s)throw new t(400,"invalid_multipart","Missing multipart boundary.");const c=o.payloadFieldName??"payload",f=new Set(o.fileFieldNames??["attachments","files","file"]),u=function(e,t){const n=[];let a=r(e,t);for(;a>=0&&(a+=t.byteLength,45!==e[a]||45!==e[a+1]);){13===e[a]&&10===e[a+1]&&(a+=2);const o=r(e,t,a);if(o<0)break;n.push(e.slice(a,o)),a=o}return n}(a,(new TextEncoder).encode(`--${s}`));let d;const m=[];for(const n of u){const a=r(n,(new TextEncoder).encode("\r\n\r\n"));if(a<0)continue;const o=e(n.slice(0,a)),s=l(n.slice(a+4)),u=o.match(/^content-disposition:\s*([^\r\n]+)/im)?.[1]??"",p=u.match(/(?:^|;)\s*name="([^"]+)"/)?.[1],y=u.match(/(?:^|;)\s*filename="([^"]*)"/)?.[1],h=o.match(/^content-type:\s*([^\r\n]+)/im)?.[1]?.trim()||"application/octet-stream";if(p===c){let n;try{n=JSON.parse(e(s))}catch{throw new t(400,"invalid_payload","Multipart payload must be valid JSON.")}if(!n||"object"!=typeof n||Array.isArray(n))throw new t(400,"invalid_payload","Multipart payload must be a JSON object.");d=n;continue}p&&f.has(p)&&m.push({fieldName:p,filename:i(y||"attachment"),contentType:h,bytes:s})}if(!d)throw new t(400,"invalid_payload","Multipart payload part is required.");return{payload:d,files:m}}function i(e,t="attachment"){const n=e.normalize("NFKC").replace(/[/\\\0\r\n]/g,"_").trim().replace(/[\u0000-\u001f\u007f]/g,"_");return n&&"."!==n&&".."!==n?n:t}function s(e){const t=i(e);return`attachment; filename="${t.replace(/["\\]/g,"_")}"; filename*=UTF-8''${n=t,encodeURIComponent(n).replace(/['()*]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`)}`;var n}function r(e,t,n=0){e:for(let a=n;a<=e.byteLength-t.byteLength;a+=1){for(let n=0;n<t.byteLength;n+=1)if(e[a+n]!==t[n])continue e;return a}return-1}function l(e){return e.byteLength>=2&&13===e[e.byteLength-2]&&10===e[e.byteLength-1]?e.slice(0,-2):e}function c(e){return e.startsWith(".")?e.toLowerCase():`.${e.toLowerCase()}`}function f(e){const t=e.lastIndexOf(".");return t>=0?e.slice(t).toLowerCase():""}export{s as contentDispositionAttachment,o as parseMultipartBody,a as parseMultipartRequest,i as safeFilename,n as validateCapabilityFiles};
@@ -0,0 +1,24 @@
1
+ declare class HttpError extends Error {
2
+ readonly status: number;
3
+ readonly code: string;
4
+ readonly expose: boolean;
5
+ constructor(status: number, code: string, message: string, options?: {
6
+ expose?: boolean;
7
+ });
8
+ }
9
+ declare function jsonResponse(value: unknown, init?: ResponseInit | number): Response;
10
+ declare function textResponse(value: string, init?: ResponseInit | number): Response;
11
+ declare function htmlResponse(value: string, init?: ResponseInit | number): Response;
12
+ declare function redirectResponse(location: string, status?: number): Response;
13
+ declare function errorJson(code: string, message: string, status?: number, extra?: Record<string, unknown>): Response;
14
+ declare function errorResponse(error: unknown): Response;
15
+ declare function parseJsonBody(request: Request): Promise<Record<string, unknown>>;
16
+ declare function readJsonObject(request: Request): Promise<Record<string, unknown>>;
17
+ declare function isJsonRequested(url: URL, body?: Record<string, unknown>): boolean;
18
+ declare function stringParam(value: unknown): string | undefined;
19
+ declare function stringArrayParam(value: unknown): string[];
20
+ declare function booleanParam(value: unknown): boolean;
21
+ declare function limitParam(value: unknown, defaultValue?: number, max?: number): number;
22
+ declare function getRepeated(url: URL, key: string): string[];
23
+
24
+ export { HttpError, booleanParam, errorJson, errorResponse, getRepeated, htmlResponse, isJsonRequested, jsonResponse, limitParam, parseJsonBody, readJsonObject, redirectResponse, stringArrayParam, stringParam, textResponse };
@@ -0,0 +1 @@
1
+ class t extends Error{status;code;expose;constructor(t,e,n,r){super(n),this.name="HttpError",this.status=t,this.code=e,this.expose=r?.expose??t<500}}function e(t,e=200){const n=d(e);return new Response(JSON.stringify(t,null,2),{...n,headers:m(n.headers,"content-type","application/json; charset=utf-8")})}function n(t,e=200){return new Response(t.endsWith("\n")?t:`${t}\n`,{...d(e),headers:m(d(e).headers,"content-type","text/plain; charset=utf-8")})}function r(t,e=200){const n=d(e);return new Response(t,{...n,headers:m(n.headers,"content-type","text/html; charset=utf-8")})}function o(t,e=302){return new Response(null,{status:e,headers:{location:t,"cache-control":"no-store"}})}function s(t,n,r=400,o={}){return e({ok:!1,error:{code:t,message:n},...o},r)}function a(e){return e instanceof t?s(e.code,e.expose?e.message:"Internal error.",e.status):s("internal_error","Internal error.",500)}async function c(t){if(!t.body)return{};try{const e=await t.json();return!e||"object"!=typeof e||Array.isArray(e)?{}:e}catch{return{}}}async function u(e){const n=await e.json().catch(()=>null);if(!n||"object"!=typeof n||Array.isArray(n))throw new t(400,"invalid_json","Expected a JSON object.");return n}function i(t,e){const n=t.searchParams.get("json");return""===n||"1"===n||"true"===n||!0===e?.json}function f(t){if("string"!=typeof t)return;const e=t.trim();return e.length>0?e:void 0}function h(t){if(Array.isArray(t))return t.filter(t=>"string"==typeof t).map(t=>t.trim()).filter(Boolean);const e=f(t);return e?[e]:[]}function p(t){return!0===t||"true"===t||"1"===t||""===t}function l(t,e=20,n=100){const r="number"==typeof t?t:Number(t);return Number.isFinite(r)?Math.max(1,Math.min(n,Math.floor(r))):e}function y(t,e){return t.searchParams.getAll(e).map(t=>t.trim()).filter(Boolean)}function d(t){return"number"==typeof t?{status:t}:t}function m(t,e,n){const r=new Headers(t);return r.has(e)||r.set(e,n),r}export{t as HttpError,p as booleanParam,s as errorJson,a as errorResponse,y as getRepeated,r as htmlResponse,i as isJsonRequested,e as jsonResponse,l as limitParam,c as parseJsonBody,u as readJsonObject,o as redirectResponse,h as stringArrayParam,f as stringParam,n as textResponse};
@@ -0,0 +1,10 @@
1
+ export { A as AccountAuthResult, C as CapabilityBaseEnv, a as CapabilityPublicKeysEnv, P as PublicJwk } from '../types-BCUAqVuU.js';
2
+ export { HttpError } from './http/index.js';
3
+
4
+ declare function normalizePathname(pathname: string): string;
5
+ declare function isCapabilityAssetPath(pathname: string, options?: {
6
+ basePath?: string;
7
+ includeSkillResources?: boolean;
8
+ }): boolean;
9
+
10
+ export { isCapabilityAssetPath, normalizePathname };
@@ -0,0 +1 @@
1
+ export{HttpError}from"./http/index.js";function t(t){return(t.startsWith("/")?t:`/${t}`).replace(/\/{2,}/g,"/").replace(/\/+$/g,"")||"/"}function e(e,i={}){const r=t(e),n=t(i.basePath??""),s=new Set;for(const t of["","/"===n?"":n])s.add(`${t}/info`),s.add(`${t}/definition`),s.add(`${t}/mapping`),s.add(`${t}/skill/SKILL.md`),i.includeSkillResources&&s.add(`${t}/skill`);return!!s.has(r)||!!i.includeSkillResources&&r.startsWith(`${"/"===n?"":n}/skill/`)}export{e as isCapabilityAssetPath,t as normalizePathname};
@@ -0,0 +1,86 @@
1
+ import { C as CapabilityBaseEnv } from '../../types-BCUAqVuU.js';
2
+
3
+ type PresignObjectMethod = "GET" | "PUT" | "HEAD";
4
+ type S3PresignCredentials = {
5
+ accessKeyId: string;
6
+ secretAccessKey: string;
7
+ };
8
+ type S3PresignConfig = {
9
+ baseUrl: string;
10
+ region?: string;
11
+ service?: "s3";
12
+ credentials: S3PresignCredentials;
13
+ };
14
+ type PresignS3ObjectUrlInput = {
15
+ method?: PresignObjectMethod;
16
+ key: string;
17
+ expiresInSeconds: number;
18
+ now?: Date;
19
+ config: S3PresignConfig;
20
+ signedHeaders?: Record<string, string>;
21
+ query?: Record<string, string>;
22
+ payloadHash?: "UNSIGNED-PAYLOAD" | string;
23
+ };
24
+ type R2PresignEnv = {
25
+ R2_PUBLIC_BASE_URL?: string;
26
+ R2_ACCOUNT_ID?: string;
27
+ R2_BUCKET_NAME?: string;
28
+ R2_ACCESS_KEY_ID?: string;
29
+ R2_SECRET_ACCESS_KEY?: string;
30
+ };
31
+ type S3PresignEnv = {
32
+ S3_PUBLIC_BASE_URL?: string;
33
+ S3_ENDPOINT?: string;
34
+ S3_REGION?: string;
35
+ S3_BUCKET_NAME?: string;
36
+ S3_ACCESS_KEY_ID?: string;
37
+ S3_SECRET_ACCESS_KEY?: string;
38
+ };
39
+ type SignedDownloadEnv = CapabilityBaseEnv & {
40
+ DEV_DOWNLOAD_SECRET?: string;
41
+ };
42
+ type SignedDownloadPayload = {
43
+ accountId: string;
44
+ storageKey: string;
45
+ filename: string;
46
+ contentType: string;
47
+ expiresAt: string;
48
+ disposition?: "attachment" | "inline";
49
+ };
50
+ type R2ObjectBodyLike = {
51
+ arrayBuffer(): Promise<ArrayBuffer>;
52
+ httpMetadata?: {
53
+ contentType?: string;
54
+ contentDisposition?: string;
55
+ };
56
+ };
57
+ type R2BucketLike = {
58
+ get(key: string): Promise<R2ObjectBodyLike | null>;
59
+ };
60
+ declare function presignS3ObjectUrl(input: PresignS3ObjectUrlInput): Promise<string>;
61
+ declare function s3PresignConfigFromEnv(env: S3PresignEnv): S3PresignConfig;
62
+ declare function r2PresignConfigFromEnv(env: R2PresignEnv): S3PresignConfig;
63
+ declare function presignR2ObjectUrl(env: R2PresignEnv, input: {
64
+ method?: PresignObjectMethod;
65
+ key: string;
66
+ expiresInSeconds: number;
67
+ now?: Date;
68
+ query?: Record<string, string>;
69
+ signedHeaders?: Record<string, string>;
70
+ }): Promise<string>;
71
+ declare function createSignedDownloadUrl(input: {
72
+ env: SignedDownloadEnv;
73
+ payload: SignedDownloadPayload;
74
+ routePrefix?: string;
75
+ secret?: string;
76
+ }): Promise<string>;
77
+ declare function verifySignedDownloadToken(env: SignedDownloadEnv, token: string, options?: {
78
+ secret?: string;
79
+ }): Promise<SignedDownloadPayload>;
80
+ declare function r2DownloadResponse(input: {
81
+ bucket: R2BucketLike;
82
+ payload: SignedDownloadPayload;
83
+ }): Promise<Response>;
84
+
85
+ export { createSignedDownloadUrl, presignR2ObjectUrl, presignS3ObjectUrl, r2DownloadResponse, r2PresignConfigFromEnv, s3PresignConfigFromEnv, verifySignedDownloadToken };
86
+ export type { PresignObjectMethod, PresignS3ObjectUrlInput, R2BucketLike, R2ObjectBodyLike, R2PresignEnv, S3PresignConfig, S3PresignCredentials, S3PresignEnv, SignedDownloadEnv, SignedDownloadPayload };
@@ -0,0 +1 @@
1
+ import{sha256Hex as e,bytesToHex as n,hmacSha256Bytes as t,timingSafeEqual as o,base64UrlDecode as i,base64UrlEncode as r,utf8 as a}from"../crypto/index.js";import{contentDispositionAttachment as s}from"../files/index.js";import{HttpError as c}from"../http/index.js";async function d(o){const i=o.method??"GET";if(!Number.isInteger(o.expiresInSeconds)||o.expiresInSeconds<=0)throw new c(500,"invalid_presign_expiry","Presigned URL expiry must be a positive integer.");const r=o.config.service??"s3",s=o.config.region??"auto",d=(o.now??new Date).toISOString().replace(/[:-]|\.\d{3}/g,""),_=d.slice(0,8),l=`${_}/${s}/${r}/aws4_request`,p=o.config.baseUrl.replace(/\/+$/,""),w=new URL(`${p}/${f=o.key,f.split("/").map(A).join("/")}`);var f;const u=function(e){return Object.fromEntries(Object.entries(e).map(([e,n])=>[e.toLowerCase(),n.trim().replace(/\s+/g," ")]))}({host:w.host,...o.signedHeaders??{}}),S=Object.keys(u).sort(),g=new Map(Object.entries(o.query??{}));g.set("X-Amz-Algorithm","AWS4-HMAC-SHA256"),g.set("X-Amz-Credential",`${o.config.credentials.accessKeyId}/${l}`),g.set("X-Amz-Date",d),g.set("X-Amz-Expires",String(o.expiresInSeconds)),g.set("X-Amz-SignedHeaders",S.join(";"));const C=o.payloadHash??"UNSIGNED-PAYLOAD",y=[i,w.pathname,E(g),S.map(e=>`${e}:${u[e]}`).join("\n")+"\n",S.join(";"),C].join("\n"),m=["AWS4-HMAC-SHA256",d,l,await e(y)].join("\n"),I=await async function(e,n,o,i){const r=await t(a(`AWS4${e}`),n),s=await t(r,o),c=await t(s,i);return t(c,"aws4_request")}(o.config.credentials.secretAccessKey,_,s,r);return g.set("X-Amz-Signature",n(await t(I,m))),w.search=E(g),w.toString()}function _(e){const n=C(e.S3_ACCESS_KEY_ID,"S3_ACCESS_KEY_ID"),t=C(e.S3_SECRET_ACCESS_KEY,"S3_SECRET_ACCESS_KEY");let o=e.S3_PUBLIC_BASE_URL?.trim();return o||(o=`${C(e.S3_ENDPOINT,"S3_ENDPOINT").replace(/\/+$/,"")}/${C(e.S3_BUCKET_NAME,"S3_BUCKET_NAME")}`),{baseUrl:o,region:e.S3_REGION||"auto",service:"s3",credentials:{accessKeyId:n,secretAccessKey:t}}}function l(e){const n=C(e.R2_ACCESS_KEY_ID,"R2_ACCESS_KEY_ID"),t=C(e.R2_SECRET_ACCESS_KEY,"R2_SECRET_ACCESS_KEY");return{baseUrl:e.R2_PUBLIC_BASE_URL?.trim()||`https://${C(e.R2_ACCOUNT_ID,"R2_ACCOUNT_ID")}.r2.cloudflarestorage.com/${C(e.R2_BUCKET_NAME,"R2_BUCKET_NAME")}`,region:"auto",service:"s3",credentials:{accessKeyId:n,secretAccessKey:t}}}async function p(e,n){return d({...n,config:l(e)})}async function w(e){const n=await async function(e,n,t){g(n);const o=r(a(JSON.stringify(n)));return`${o}.${await S(e,o,t)}`}(e.env,e.payload,e.secret),t=e.env.CAPABILITY_BASE_URL??e.env.PUBLIC_BASE_URL;if(!t)throw new c(500,"download_base_url_unconfigured","Download base URL is not configured.");const o=new URL(e.routePrefix??"/__dev/r2-download",t.endsWith("/")?t:`${t}/`);return o.searchParams.set("token",n),o.toString()}async function f(e,n,t={}){const[r,a,...s]=n.split(".");if(!r||!a||s.length>0)throw new c(403,"invalid_download_token","Invalid download token.");const d=await S(e,r,t.secret);if(!o(d,a))throw new c(403,"invalid_download_token","Invalid download token.");let _;try{_=JSON.parse((new TextDecoder).decode(i(r)))}catch{throw new c(403,"invalid_download_token","Invalid download token.")}if(g(_),Date.now()>Date.parse(_.expiresAt))throw new c(403,"download_expired","Download URL has expired.");return _}async function u(e){const n=await e.bucket.get(e.payload.storageKey);if(!n)throw new c(404,"download_not_found","Download object was not found.");const t=new Headers({"content-type":e.payload.contentType,"cache-control":"no-store","content-disposition":"inline"===e.payload.disposition?`inline; filename="${e.payload.filename.replace(/["\\]/g,"_")}"`:s(e.payload.filename)});return new Response(await n.arrayBuffer(),{headers:t})}async function S(e,n,o){const i=o??e.DEV_DOWNLOAD_SECRET;if(!i)throw new c(500,"download_secret_unconfigured","Download signing secret is not configured.");return r(await t(i,n))}function g(e){if(!e||"string"!=typeof e.accountId||"string"!=typeof e.storageKey||"string"!=typeof e.filename||"string"!=typeof e.contentType||"string"!=typeof e.expiresAt||Number.isNaN(Date.parse(e.expiresAt)))throw new c(403,"invalid_download_token","Download token payload is invalid.")}function E(e){return[...e.entries()].flatMap(([e,n])=>[[A(e),A(n)]]).sort(([e,n],[t,o])=>e===t?n.localeCompare(o):e.localeCompare(t)).map(([e,n])=>`${e}=${n}`).join("&")}function A(e){return encodeURIComponent(e).replace(/[!'()*]/g,e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`)}function C(e,n){if(!e)throw new c(500,"missing_storage_configuration",`${n} is not configured.`);return e}export{w as createSignedDownloadUrl,p as presignR2ObjectUrl,d as presignS3ObjectUrl,u as r2DownloadResponse,l as r2PresignConfigFromEnv,_ as s3PresignConfigFromEnv,f as verifySignedDownloadToken};
@@ -0,0 +1,22 @@
1
+ type CapabilityAssetSet = {
2
+ info: unknown;
3
+ definition: unknown;
4
+ mapping: unknown;
5
+ skillFiles?: Array<{
6
+ path: string;
7
+ content: string;
8
+ }>;
9
+ };
10
+ type ValidationIssue = {
11
+ path: string;
12
+ code: string;
13
+ message: string;
14
+ severity: "error" | "warning";
15
+ };
16
+ declare function validateCapabilityInfo(info: unknown): ValidationIssue[];
17
+ declare function validateCliDefinition(definition: unknown): ValidationIssue[];
18
+ declare function validateHttpMapping(mapping: unknown, definition: unknown): ValidationIssue[];
19
+ declare function validateCapabilityAssets(assets: CapabilityAssetSet): ValidationIssue[];
20
+
21
+ export { validateCapabilityAssets, validateCapabilityInfo, validateCliDefinition, validateHttpMapping };
22
+ export type { CapabilityAssetSet, ValidationIssue };
@@ -0,0 +1 @@
1
+ const i=/^[a-z][a-z0-9_-]{1,31}$/,t=new Set(["bash","sh","cd","ls","cat","rm","mv","cp","curl","env","export","unset","alias","unalias","help","which"]);function n(i){const t=[];if(!$(i))return[y("$","invalid_info","Capability info must be an object.")];m(t,i,"schemaVersion","capability.info.v1");for(const n of["id","name","displayName","summary","description","apiBaseUrl"])m(t,i,n);var n;if("string"==typeof i.name&&l(t,"$.name",i.name),"string"!=typeof i.apiBaseUrl||(n=i.apiBaseUrl).startsWith("http://localhost")||n.startsWith("http://127.0.0.1")||i.apiBaseUrl.startsWith("https://")||t.push(y("$.apiBaseUrl","api_base_url_not_https","Production apiBaseUrl must use HTTPS.")),$(i.endpoints)){m(t,i.endpoints,"definition"),m(t,i.endpoints,"mapping");for(const n of["definition","mapping"]){const e=i.endpoints[n];"string"==typeof e&&f(e)&&t.push(y(`$.endpoints.${n}`,"endpoint_not_relative","Endpoint paths should be path-relative."))}}else t.push(y("$.endpoints","missing_endpoints","Capability info endpoints are required."));if($(i.skill)){const n=i.skill.entrypoint;$(n)&&(c(t,"$.skill.entrypoint.path",n.path),c(t,"$.skill.entrypoint.url",n.url)),Array.isArray(i.skill.resources)&&i.skill.resources.forEach((i,n)=>{$(i)&&(c(t,`$.skill.resources[${n}].path`,i.path),c(t,`$.skill.resources[${n}].url`,i.url))})}return t}function e(i){const t=[];if(!$(i))return[y("$","invalid_definition","CLI definition must be an object.")];m(t,i,"schemaVersion","cli.definition.v1"),$(i.cli)?(m(t,i.cli,"name"),m(t,i.cli,"summary"),m(t,i.cli,"description"),"string"==typeof i.cli.name&&l(t,"$.cli.name",i.cli.name)):t.push(y("$.cli","missing_cli","CLI metadata is required."));const n=new Set;return Array.isArray(i.commands)?i.commands.forEach((i,e)=>{$(i)?(m(t,i,"id"),"string"==typeof i.id&&(n.has(i.id)&&t.push(y(`$.commands[${e}].id`,"duplicate_command_id",`Duplicate command id: ${i.id}.`)),n.add(i.id)),m(t,i,"command"),m(t,i,"summary"),m(t,i,"description")):t.push(y(`$.commands[${e}]`,"invalid_command","Command must be an object."))}):t.push(y("$.commands","missing_commands","Definition commands must be an array.")),t}function s(i,t){const n=[];if(!$(i))return[y("$","invalid_mapping","HTTP mapping must be an object.")];const e=function(i){return $(i)&&Array.isArray(i.commands)?new Set(i.commands.filter($).map(i=>i.id).filter(i=>"string"==typeof i)):new Set}(t),s=function(i){const t=new Map;if(!$(i)||!Array.isArray(i.commands))return t;for(const n of i.commands){if(!$(n)||"string"!=typeof n.id)continue;const i=new Map;for(const t of[...p(n.arguments),...p(n.options)])"string"==typeof t.key&&i.set(t.key,t),"string"==typeof t.name&&"boolean"===t.type&&i.set(t.name.replace(/^--/,""),t);t.set(n.id,i)}return t}(t);for(const[t,a]of Object.entries(i)){e.has(t)||n.push(y(`$.${t}`,"unknown_command",`Mapping key does not exist in definition: ${t}.`));const i=$(a)&&Array.isArray(a.routes)?a.routes:[a];i.forEach((e,a)=>{const r=i.length>1?`$.${t}.routes[${a}]`:`$.${t}`;o(n,r,e,s.get(t)??new Map)})}return n}function a(i){const t=[...n(i.info),...e(i.definition),...s(i.mapping,i.definition)];$(i.info)&&$(i.definition)&&$(i.definition.cli)&&i.info.name!==i.definition.cli.name&&t.push(y("$.definition.cli.name","name_mismatch","definition.cli.name must match info.name."));for(const n of i.skillFiles??[])c(t,`skillFiles.${n.path}`,n.path),u(n.content)&&t.push(y(`skillFiles.${n.path}`,"possible_secret","Skill file appears to contain secret material."));for(const[n,e]of[["info",i.info],["definition",i.definition],["mapping",i.mapping]])u(JSON.stringify(e))&&t.push(y(n,"possible_secret","Public asset appears to contain secret material."));return t}function o(i,t,n,e){if($(n)){if(m(i,n,"method"),m(i,n,"path"),"string"==typeof n.path){f(n.path)&&i.push(y(`${t}.path`,"mapping_path_not_relative","Mapping paths must be relative."));for(const e of r(n.params))n.path.includes(`:${e}`)||i.push(y(`${t}.params`,"missing_path_param",`Path parameter ${e} does not appear in route path.`))}for(const[s,a]of[["params",r(n.params)],["query",r(n.query)],["body",r(n.body)],["files",r(n.files)]])for(const n of a){const a=e.get(n);a||i.push(y(`${t}.${s}`,"unknown_field",`Mapped field does not exist in definition: ${n}.`)),"files"!==s&&d(n,a)&&i.push(y(`${t}.${s}`,"file_field_not_in_files",`File field ${n} must be mapped through files.`)),"files"===s&&a&&!h(a)&&i.push(y(`${t}.files`,"missing_file_constraints",`File field ${n} must define file constraints.`))}}else i.push(y(t,"invalid_route","Mapping route must be an object."))}function r(i){return Array.isArray(i)?i.filter(i=>"string"==typeof i):$(i)?Object.values(i).filter(i=>"string"==typeof i):[]}function p(i){return Array.isArray(i)?i.filter($):[]}function m(i,t,n,e){const s=t[n];"string"==typeof s&&0!==s.trim().length?void 0!==e&&s!==e&&i.push(y(`$.${n}`,"invalid_value",`${n} must be ${e}.`)):i.push(y(`$.${n}`,"missing_string",`${n} must be a non-empty string.`))}function l(n,e,s){i.test(s)||n.push(y(e,"invalid_command_name","Command name does not match the safe command name pattern.")),t.has(s)&&n.push(y(e,"reserved_command_name","Command name is reserved."))}function c(i,t,n){void 0!==n&&("string"!=typeof n||!n||n.startsWith("/")||n.includes("\\")||n.split("/").includes(".."))&&i.push(y(t,"unsafe_path","Path must be a safe relative path."))}function f(i){return i.startsWith("http://")||i.startsWith("https://")||i.startsWith("/")}function u(i){return!!i&&/(BEGIN [A-Z ]*PRIVATE KEY|SECRET_ACCESS_KEY|ACCESS_TOKEN|REFRESH_TOKEN|PRIVATE_KEY|api[_-]?key)/i.test(i)}function d(i,t){return"file"===t?.type||"files"===t?.type||/file|attachment|upload/i.test(i)}function h(i){return!(!$(i.constraints)&&!$(i.fileConstraints))||["allowedExtensions","allowedMimeTypes","maxBytes","maxCount","maxTotalBytes"].some(t=>void 0!==i[t])}function y(i,t,n,e="error"){return{path:i,code:t,message:n,severity:e}}function $(i){return Boolean(i&&"object"==typeof i&&!Array.isArray(i))}export{a as validateCapabilityAssets,n as validateCapabilityInfo,e as validateCliDefinition,s as validateHttpMapping};
@@ -0,0 +1,28 @@
1
+ declare function createCapabilityTestKeyPair(options?: {
2
+ alg?: "ES256" | "Ed25519";
3
+ }): Promise<{
4
+ keyId: string;
5
+ publicJwk: JsonWebKey & {
6
+ kid: string;
7
+ };
8
+ privateJwk: JsonWebKey;
9
+ }>;
10
+ declare function signCapabilityRequest(input: {
11
+ request: Request;
12
+ accountId: string;
13
+ keyId: string;
14
+ privateJwk: JsonWebKey;
15
+ timestamp?: number;
16
+ }): Promise<Headers>;
17
+ declare function withCapabilitySignature(input: {
18
+ request: Request;
19
+ accountId: string;
20
+ keyId: string;
21
+ privateJwk: JsonWebKey;
22
+ timestamp?: number;
23
+ }): Promise<Request>;
24
+ declare function publicKeysResponse(keys: Array<JsonWebKey & {
25
+ kid: string;
26
+ }>): Response;
27
+
28
+ export { createCapabilityTestKeyPair, publicKeysResponse, signCapabilityRequest, withCapabilitySignature };
@@ -0,0 +1 @@
1
+ import{sha256Hex as e,toArrayBuffer as t,utf8 as a,base64UrlEncode as r}from"../runtime/crypto/index.js";import{b as n}from"../chunks/yaIxL224.js";import"../runtime/http/index.js";async function i(e={}){const t=e.alg??"ES256",a="Ed25519"===t?await crypto.subtle.generateKey({name:"Ed25519"},!0,["sign","verify"]):await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!0,["sign","verify"]),r=`test-${t.toLowerCase()}-${crypto.randomUUID()}`,n=await crypto.subtle.exportKey("jwk",a.publicKey),i=await crypto.subtle.exportKey("jwk",a.privateKey);return n.kid=r,n.use="sig",n.alg="Ed25519"===t?"EdDSA":"ES256",i.kid=r,i.alg=n.alg,{keyId:r,publicJwk:n,privateJwk:i}}async function s(i){const s=i.timestamp??Math.floor(Date.now()/1e3),o=await i.request.clone().arrayBuffer(),c=new URL(i.request.url),u=n({method:i.request.method,pathWithQuery:`${c.pathname}${c.search}`,timestamp:String(s),accountId:i.accountId,bodyHashHex:await e(o)}),y=await async function(e){return"OKP"===e.kty&&"Ed25519"===e.crv?crypto.subtle.importKey("jwk",e,{name:"Ed25519"},!1,["sign"]):crypto.subtle.importKey("jwk",e,{name:"ECDSA",namedCurve:"P-256"},!1,["sign"])}(i.privateJwk),p=await crypto.subtle.sign((d=i.privateJwk,"OKP"===d.kty&&"Ed25519"===d.crv?{name:"Ed25519"}:{name:"ECDSA",hash:"SHA-256"}),y,t(a(u)));var d;const m=new Headers(i.request.headers);return m.set("x-capability-account-id",i.accountId),m.set("x-capability-timestamp",String(s)),m.set("x-capability-key-id",i.keyId),m.set("x-capability-signature",`v1=${r(p)}`),m}async function o(e){const t=await s(e);return new Request(e.request,{headers:t})}function c(e){return new Response(JSON.stringify({keys:e},null,2),{headers:{"content-type":"application/json; charset=utf-8"}})}export{i as createCapabilityTestKeyPair,c as publicKeysResponse,s as signCapabilityRequest,o as withCapabilitySignature};
@@ -0,0 +1,44 @@
1
+ type CapabilityPublicKeysEnv = {
2
+ CAPABILITY_PUBLIC_KEYS_URL?: string;
3
+ CAPABILITY_PUBLIC_KEYS_JSON?: string;
4
+ CAPABILITY_PUBLIC_KEYS?: string;
5
+ CAPABILITY_PUBLIC_KEYS_SERVICE?: {
6
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
7
+ };
8
+ };
9
+ type CapabilityBaseEnv = CapabilityPublicKeysEnv & {
10
+ CAPABILITY_BASE_URL?: string;
11
+ PUBLIC_BASE_URL?: string;
12
+ ENVIRONMENT?: string;
13
+ };
14
+ type AccountAuthResult = {
15
+ ok: true;
16
+ accountId: string;
17
+ keyId?: string;
18
+ source: "capability-signature" | "auth-cookie";
19
+ email?: string;
20
+ emailVerified?: boolean;
21
+ } | {
22
+ ok: false;
23
+ status: number;
24
+ code: string;
25
+ message: string;
26
+ };
27
+ type PublicJwk = JsonWebKey & {
28
+ kid: string;
29
+ alg?: string;
30
+ };
31
+ type PublicKeyStoreOptions = {
32
+ cacheTtlMs?: number;
33
+ fetcher?: typeof fetch;
34
+ allowInlineKeys?: boolean;
35
+ serviceBindingUrl?: string;
36
+ };
37
+ type VerifyCapabilitySignatureOptions = {
38
+ rawBody?: ArrayBuffer | Uint8Array;
39
+ now?: number | Date;
40
+ maxSkewSeconds?: number;
41
+ acceptedAlgorithms?: Array<"ES256" | "Ed25519" | "RS256">;
42
+ };
43
+
44
+ export type { AccountAuthResult as A, CapabilityBaseEnv as C, PublicJwk as P, VerifyCapabilitySignatureOptions as V, CapabilityPublicKeysEnv as a, PublicKeyStoreOptions as b };
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "capability-worker",
3
+ "version": "0.1.0",
4
+ "description": "Shared toolkit for Cloudflare Workers that expose CLI-like capabilities over HTTP.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "bin": {
13
+ "capability-build-assets": "dist/node/build-capability-assets-cli.js",
14
+ "capability-validate": "dist/node/validate-cli.js"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/runtime/index.d.ts",
19
+ "import": "./dist/runtime/index.js"
20
+ },
21
+ "./auth": {
22
+ "types": "./dist/runtime/auth/index.d.ts",
23
+ "import": "./dist/runtime/auth/index.js"
24
+ },
25
+ "./crypto": {
26
+ "types": "./dist/runtime/crypto/index.d.ts",
27
+ "import": "./dist/runtime/crypto/index.js"
28
+ },
29
+ "./credentials": {
30
+ "types": "./dist/runtime/credentials/index.d.ts",
31
+ "import": "./dist/runtime/credentials/index.js"
32
+ },
33
+ "./files": {
34
+ "types": "./dist/runtime/files/index.d.ts",
35
+ "import": "./dist/runtime/files/index.js"
36
+ },
37
+ "./storage": {
38
+ "types": "./dist/runtime/storage/index.d.ts",
39
+ "import": "./dist/runtime/storage/index.js"
40
+ },
41
+ "./http": {
42
+ "types": "./dist/runtime/http/index.d.ts",
43
+ "import": "./dist/runtime/http/index.js"
44
+ },
45
+ "./validation": {
46
+ "types": "./dist/runtime/validation/index.d.ts",
47
+ "import": "./dist/runtime/validation/index.js"
48
+ },
49
+ "./testing": {
50
+ "types": "./dist/testing/index.d.ts",
51
+ "import": "./dist/testing/index.js"
52
+ },
53
+ "./node": {
54
+ "types": "./dist/node/index.d.ts",
55
+ "import": "./dist/node/index.js"
56
+ },
57
+ "./package.json": "./package.json"
58
+ },
59
+ "devDependencies": {
60
+ "@rollup/plugin-node-resolve": "^16.0.0",
61
+ "@rollup/plugin-terser": "^0.4.0",
62
+ "@types/node": "^24.0.0",
63
+ "rollup": "^4.0.0",
64
+ "rollup-plugin-dts": "^6.0.0",
65
+ "rollup-plugin-esbuild": "^6.0.0",
66
+ "typescript": "^5.9.0",
67
+ "vitest": "^4.1.0"
68
+ },
69
+ "scripts": {
70
+ "clean": "rm -rf dist",
71
+ "build": "pnpm clean && rollup -c",
72
+ "typecheck": "tsc --noEmit",
73
+ "test": "vitest run",
74
+ "pack:check": "pnpm build && pnpm pack --dry-run",
75
+ "publish:npm": "pnpm typecheck && pnpm test && pnpm pack:check && pnpm publish --access public"
76
+ }
77
+ }