s3broker 0.6.2 → 0.6.3

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.
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=require(`./sigv4-CT4jkHNc.js`),l=s(require(`aws4fetch`)),u=s(require(`zod`));let d=function(e){return e[e.Forbidden=403]=`Forbidden`,e[e.UpstreamFailure=502]=`UpstreamFailure`,e}({});function f(e,t){return new Response(e,{status:t,headers:{"Content-Type":`text/plain`}})}function p(e){let t=null;return()=>(t||=e(),t)}const m=u.z.object({noDeleteBeforeSeconds:u.z.number().int()});var h=class{config;currentTimestampMs;constructor(e,t){this.config=e,this.currentTimestampMs=t}async evaluate(e,t){if(e.method!==`DELETE`)return null;try{let e=await t(),n=e.get(`Last-Modified`);if(!n)return null;let r=new Date(n).getTime(),i=this.currentTimestampMs-r,a=this.config.noDeleteBeforeSeconds*1e3;return i>a?{violation:`Cannot delete object: object is ${Math.floor(i/1e3)} seconds old, which exceeds the ${this.config.noDeleteBeforeSeconds} seconds threshold`}:null}catch(e){if(e instanceof x&&e.code===`404`)return null;throw e}}};const g=u.z.object({noReplaceBeforeSeconds:u.z.number().int()});var _=class{config;currentTimestampMs;constructor(e,t){this.config=e,this.currentTimestampMs=t}async evaluate(e,t){if(e.method!==`PUT`)return null;try{let e=await t(),n=e.get(`Last-Modified`);if(!n)return null;let r=new Date(n).getTime(),i=this.currentTimestampMs-r,a=this.config.noReplaceBeforeSeconds*1e3;return i>a?{violation:`Cannot replace object: object is ${Math.floor(i/1e3)} seconds old, which exceeds the ${this.config.noReplaceBeforeSeconds} seconds threshold`}:null}catch(e){if(e instanceof x&&e.code===`404`)return null;throw e}}};const v=u.z.object({key:u.z.string()}),y=e=>u.z.array(u.z.object({pattern:u.z.string(),config:e.nullable()})),b=u.z.object({noDeleteOld:y(m).optional(),noReplaceOld:y(g).optional(),managedSse:y(v).optional()});var x=class extends Error{code;constructor(e,t){super(t),this.code=e}};async function S(e,t,n){let r={};n&&(r[`x-amz-server-side-encryption-customer-algorithm`]=`AES256`,r[`x-amz-server-side-encryption-customer-key`]=n.key,r[`x-amz-server-side-encryption-customer-key-md5`]=n.keyMd5);let i=await e.fetch(t,{method:`HEAD`,headers:r});if(!i.ok)throw new x(i.status.toString(),i.statusText);return i.headers}const C=`x-amz-server-side-encryption-customer-algorithm`,w=`x-amz-server-side-encryption-customer-key`,T=`x-amz-server-side-encryption-customer-key-md5`;async function E(e){let t=Uint8Array.from(atob(e),e=>e.charCodeAt(0)),n=await crypto.subtle.digest(`MD5`,t),r=new Uint8Array(n);return btoa(String.fromCharCode(...r))}var D=class{base64Key;keyMd5Promise;constructor(e){this.base64Key=e.key,this.keyMd5Promise=E(e.key)}async modifyHeaders(e,t){if(t.has(C))return t;let n=e.method.toUpperCase();if(n!==`GET`&&n!==`HEAD`&&n!==`PUT`)return t;let r=new Headers(t);return r.set(C,`AES256`),r.set(w,this.base64Key),r.set(T,await this.keyMd5Promise),r}handleResponse(e,t){if(e.status===400){let e=new Headers(t);return e.delete(C),e.delete(w),e.delete(T),{retryRequest:e}}return{modifiedResponse:e}}};async function O(e,t,n,r,i){let a=new URL(e.url).pathname,o=A(i,a,r);if(o.length===0)return null;let s=p(async()=>{let e=M(i,a),r=e?{key:e.key,keyMd5:await E(e.key)}:void 0;return S(t,n+a,r)}),c=o.map(({name:t,policy:n})=>({policyName:t,promise:(async()=>{let r=await n.evaluate(e,s);return r?{...r,policy:t}:null})()}));return new Promise(e=>{let t=c.length;for(let{policyName:n,promise:r}of c)r.then(n=>{n===null?(t--,t===0&&e(null)):e(n)}).catch(t=>{let r=t instanceof Error?t.message:String(t);t instanceof x&&(r=`Error calling upstream when evaluating guardrail: ${r}`),e({violation:r,policy:n})})})}function k(e){if(e.startsWith(`^`))throw Error(`Invalid pattern "${e}": do not include ^ at the start, patterns are automatically anchored`);if(e.endsWith(`$`))throw Error(`Invalid pattern "${e}": do not include $ at the end, patterns are automatically anchored`);let t=e.startsWith(`/`)?e:`/`+e;return RegExp(`^${t}$`)}function A(e,t,n){let r=[];for(let i of e.noDeleteOld??[]){let e=k(i.pattern);if(e.test(t)){i.config!==null&&r.push({name:`noDeleteOld`,policy:new h(i.config,n)});break}}for(let i of e.noReplaceOld??[]){let e=k(i.pattern);if(e.test(t)){i.config!==null&&r.push({name:`noReplaceOld`,policy:new _(i.config,n)});break}}return r}function j(e,t){let n=[];for(let r of e.managedSse??[]){let e=k(r.pattern);if(e.test(t)){r.config!==null&&n.push(new D(r.config));break}}return n}function M(e,t){for(let n of e.managedSse??[]){let e=k(n.pattern);if(e.test(t))return n.config}return null}const N=new Set(`x-amz-date.x-amz-content-sha256.x-amz-security-token.x-amz-server-side-encryption.x-amz-server-side-encryption-aws-kms-key-id.x-amz-server-side-encryption-customer-algorithm.x-amz-server-side-encryption-customer-key.x-amz-server-side-encryption-customer-key-md5.x-amz-storage-class.x-amz-tagging.x-amz-website-redirect-location.x-amz-acl.x-amz-grant-read.x-amz-grant-write.x-amz-grant-read-acp.x-amz-grant-write-acp.x-amz-grant-full-control.x-amz-metadata-directive.x-amz-copy-source.x-amz-copy-source-if-match.x-amz-copy-source-if-none-match.x-amz-copy-source-if-unmodified-since.x-amz-copy-source-if-modified-since.x-amz-copy-source-range.content-type.content-length.content-md5.content-encoding.content-disposition.cache-control.expires.range.if-match.if-none-match.if-modified-since.if-unmodified-since.user-agent`.split(`.`)),P=new Set([`X-Amz-Algorithm`,`X-Amz-Credential`,`X-Amz-Date`,`X-Amz-Expires`,`X-Amz-SignedHeaders`,`X-Amz-Signature`,`X-Amz-Security-Token`]),F={noDeleteOld:[{pattern:`/.*`,config:{noDeleteBeforeSeconds:60}}],noReplaceOld:[{pattern:`/.*`,config:{noReplaceBeforeSeconds:60}}]};async function I(e,t){let n=Date.now(),r=await c.verifySignature(e,t.clientSecretAccessKey,t.clientAccessKeyId,n);if(!r.valid)return f(`Signature verification failed: ${r.error}`,d.Forbidden);let i=new URL(e.url),a=new l.AwsClient({accessKeyId:t.upstreamAccessKeyId,secretAccessKey:t.upstreamSecretAccessKey,retries:5}),o=await O(e,a,t.s3Endpoint,n,t.guardrailConfig||F);if(o)return console.log(`Guardrail violation`,{path:i.pathname,method:e.method,query:i.search,policy:o.policy,violation:o.violation}),f(`Request violating guardrail policy ${o.policy}: ${o.violation}`,d.Forbidden);let s=t.guardrailConfig||F,u=j(s,i.pathname),p=new URL(i.pathname,t.s3Endpoint);for(let[e,t]of i.searchParams.entries())P.has(e)||p.searchParams.set(e,t);let m=new Headers;for(let[t,n]of e.headers.entries())N.has(t.toLowerCase())&&m.set(t,n);m.set(`x-amz-content-sha256`,`UNSIGNED-PAYLOAD`);for(let t of u)m=await t.modifyHeaders(e,m);let h=new l.AwsClient({accessKeyId:t.upstreamAccessKeyId,secretAccessKey:t.upstreamSecretAccessKey,retries:0}),g=async(t,n)=>{let r=new Request(p.toString(),{method:e.method,headers:t,body:n,duplex:`half`});return h.fetch(r)};try{let t=await g(m,e.body);for(let e of u)if(e.handleResponse){let n=e.handleResponse(t,m);t=`retryRequest`in n?await g(n.retryRequest,null):n.modifiedResponse}return t}catch(e){return console.error(`Upstream request failed:`,e),f(`Upstream request failed: ${e instanceof Error?e.message:`Unknown error`}`,d.UpstreamFailure)}}exports.GuardrailConfigZod=b,exports.defaultGuardrailConfig=F,exports.handle=I;
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=require(`./sigv4-CT4jkHNc.js`),l=s(require(`aws4fetch`)),u=s(require(`zod`));let d=function(e){return e[e.Forbidden=403]=`Forbidden`,e[e.UpstreamFailure=502]=`UpstreamFailure`,e}({});function f(e,t){return new Response(e,{status:t,headers:{"Content-Type":`text/plain`}})}function p(e){let t=null;return()=>(t||=e(),t)}const m=u.z.object({noDeleteBeforeSeconds:u.z.number().int()});function h(e){if(e.method===`DELETE`)return`single`;if(e.method===`POST`){let t=new URL(e.url);if(t.searchParams.has(`delete`))return`bulk`}return!1}var g=class{config;currentTimestampMs;constructor(e,t){this.config=e,this.currentTimestampMs=t}async evaluate(e,t){let n=h(e);if(!n)return null;if(n===`bulk`)return{violation:`Bulk delete (DeleteObjects) is not allowed in protected paths. Use single object DELETE instead.`};try{let e=await t(),n=e.get(`Last-Modified`);if(!n)return null;let r=new Date(n).getTime(),i=this.currentTimestampMs-r,a=this.config.noDeleteBeforeSeconds*1e3;return i>a?{violation:`Cannot delete object: object is ${Math.floor(i/1e3)} seconds old, which exceeds the ${this.config.noDeleteBeforeSeconds} seconds threshold`}:null}catch(e){if(e instanceof C&&e.code===`404`)return null;throw e}}};const _=u.z.object({noReplaceBeforeSeconds:u.z.number().int()});function v(e){if(e.method===`PUT`)return!0;if(e.method===`POST`){let t=new URL(e.url);return!(t.searchParams.has(`delete`)||t.searchParams.has(`uploads`))}return!1}var y=class{config;currentTimestampMs;constructor(e,t){this.config=e,this.currentTimestampMs=t}async evaluate(e,t){if(!v(e))return null;try{let e=await t(),n=e.get(`Last-Modified`);if(!n)return null;let r=new Date(n).getTime(),i=this.currentTimestampMs-r,a=this.config.noReplaceBeforeSeconds*1e3;return i>a?{violation:`Cannot replace object: object is ${Math.floor(i/1e3)} seconds old, which exceeds the ${this.config.noReplaceBeforeSeconds} seconds threshold`}:null}catch(e){if(e instanceof C&&e.code===`404`)return null;throw e}}};const b=u.z.object({key:u.z.string()}),x=e=>u.z.array(u.z.object({pattern:u.z.string(),config:e.nullable()})),S=u.z.object({noDeleteOld:x(m).optional(),noReplaceOld:x(_).optional(),managedSse:x(b).optional()});var C=class extends Error{code;constructor(e,t){super(t),this.code=e}};async function w(e,t,n){let r={};n&&(r[`x-amz-server-side-encryption-customer-algorithm`]=`AES256`,r[`x-amz-server-side-encryption-customer-key`]=n.key,r[`x-amz-server-side-encryption-customer-key-md5`]=n.keyMd5);let i=await e.fetch(t,{method:`HEAD`,headers:r});if(!i.ok)throw new C(i.status.toString(),i.statusText);return i.headers}const T=`x-amz-server-side-encryption-customer-algorithm`,E=`x-amz-server-side-encryption-customer-key`,D=`x-amz-server-side-encryption-customer-key-md5`;async function O(e){let t=Uint8Array.from(atob(e),e=>e.charCodeAt(0)),n=await crypto.subtle.digest(`MD5`,t),r=new Uint8Array(n);return btoa(String.fromCharCode(...r))}var k=class{base64Key;keyMd5Promise;constructor(e){this.base64Key=e.key,this.keyMd5Promise=O(e.key)}async modifyHeaders(e,t){if(t.has(T))return t;let n=e.method.toUpperCase();if(n!==`GET`&&n!==`HEAD`&&n!==`PUT`&&n!==`POST`)return t;let r=new Headers(t);return r.set(T,`AES256`),r.set(E,this.base64Key),r.set(D,await this.keyMd5Promise),r}handleResponse(e,t){if(e.status===400){let e=new Headers(t);return e.delete(T),e.delete(E),e.delete(D),{retryRequest:e}}return{modifiedResponse:e}}};async function A(e,t,n,r,i){let a=new URL(e.url).pathname,o=M(i,a,r);if(o.length===0)return null;let s=p(async()=>{let e=P(i,a),r=e?{key:e.key,keyMd5:await O(e.key)}:void 0;return w(t,n+a,r)}),c=o.map(({name:t,policy:n})=>({policyName:t,promise:(async()=>{let r=await n.evaluate(e,s);return r?{...r,policy:t}:null})()}));return new Promise(e=>{let t=c.length;for(let{policyName:n,promise:r}of c)r.then(n=>{n===null?(t--,t===0&&e(null)):e(n)}).catch(t=>{let r=t instanceof Error?t.message:String(t);t instanceof C&&(r=`Error calling upstream when evaluating guardrail: ${r}`),e({violation:r,policy:n})})})}function j(e){if(e.startsWith(`^`))throw Error(`Invalid pattern "${e}": do not include ^ at the start, patterns are automatically anchored`);if(e.endsWith(`$`))throw Error(`Invalid pattern "${e}": do not include $ at the end, patterns are automatically anchored`);let t=e.startsWith(`/`)?e:`/`+e;return RegExp(`^${t}$`)}function M(e,t,n){let r=[];for(let i of e.noDeleteOld??[]){let e=j(i.pattern);if(e.test(t)){i.config!==null&&r.push({name:`noDeleteOld`,policy:new g(i.config,n)});break}}for(let i of e.noReplaceOld??[]){let e=j(i.pattern);if(e.test(t)){i.config!==null&&r.push({name:`noReplaceOld`,policy:new y(i.config,n)});break}}return r}function N(e,t){let n=[];for(let r of e.managedSse??[]){let e=j(r.pattern);if(e.test(t)){r.config!==null&&n.push(new k(r.config));break}}return n}function P(e,t){for(let n of e.managedSse??[]){let e=j(n.pattern);if(e.test(t))return n.config}return null}const F=new Set(`x-amz-date.x-amz-content-sha256.x-amz-security-token.x-amz-server-side-encryption.x-amz-server-side-encryption-aws-kms-key-id.x-amz-server-side-encryption-customer-algorithm.x-amz-server-side-encryption-customer-key.x-amz-server-side-encryption-customer-key-md5.x-amz-storage-class.x-amz-tagging.x-amz-website-redirect-location.x-amz-acl.x-amz-grant-read.x-amz-grant-write.x-amz-grant-read-acp.x-amz-grant-write-acp.x-amz-grant-full-control.x-amz-metadata-directive.x-amz-copy-source.x-amz-copy-source-if-match.x-amz-copy-source-if-none-match.x-amz-copy-source-if-unmodified-since.x-amz-copy-source-if-modified-since.x-amz-copy-source-range.content-type.content-length.content-md5.content-encoding.content-disposition.cache-control.expires.range.if-match.if-none-match.if-modified-since.if-unmodified-since.user-agent`.split(`.`)),I=new Set([`X-Amz-Algorithm`,`X-Amz-Credential`,`X-Amz-Date`,`X-Amz-Expires`,`X-Amz-SignedHeaders`,`X-Amz-Signature`,`X-Amz-Security-Token`]),L={noDeleteOld:[{pattern:`/.*`,config:{noDeleteBeforeSeconds:60}}],noReplaceOld:[{pattern:`/.*`,config:{noReplaceBeforeSeconds:60}}]};async function R(e,t){let n=Date.now(),r=await c.verifySignature(e,t.clientSecretAccessKey,t.clientAccessKeyId,n);if(!r.valid)return f(`Signature verification failed: ${r.error}`,d.Forbidden);let i=new URL(e.url),a=new l.AwsClient({accessKeyId:t.upstreamAccessKeyId,secretAccessKey:t.upstreamSecretAccessKey,retries:5}),o=await A(e,a,t.s3Endpoint,n,t.guardrailConfig||L);if(o)return console.log(`Guardrail violation`,{path:i.pathname,method:e.method,query:i.search,policy:o.policy,violation:o.violation}),f(`Request violating guardrail policy ${o.policy}: ${o.violation}`,d.Forbidden);let s=t.guardrailConfig||L,u=N(s,i.pathname),p=new URL(i.pathname,t.s3Endpoint);for(let[e,t]of i.searchParams.entries())I.has(e)||p.searchParams.set(e,t);let m=new Headers;for(let[t,n]of e.headers.entries())F.has(t.toLowerCase())&&m.set(t,n);m.set(`x-amz-content-sha256`,`UNSIGNED-PAYLOAD`);for(let t of u)m=await t.modifyHeaders(e,m);let h=new l.AwsClient({accessKeyId:t.upstreamAccessKeyId,secretAccessKey:t.upstreamSecretAccessKey,retries:0}),g=async(t,n)=>{let r=new Request(p.toString(),{method:e.method,headers:t,body:n,duplex:`half`});return h.fetch(r)};try{let t=await g(m,e.body);for(let e of u)if(e.handleResponse){let n=e.handleResponse(t,m);t=`retryRequest`in n?await g(n.retryRequest,null):n.modifiedResponse}return t}catch(e){return console.error(`Upstream request failed:`,e),f(`Upstream request failed: ${e instanceof Error?e.message:`Unknown error`}`,d.UpstreamFailure)}}exports.GuardrailConfigZod=S,exports.defaultGuardrailConfig=L,exports.handle=R;
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{verifySignature as e}from"./sigv4-Bh65MQC2.mjs";import{AwsClient as t}from"aws4fetch";import{z as n}from"zod";let r=function(e){return e[e.Forbidden=403]=`Forbidden`,e[e.UpstreamFailure=502]=`UpstreamFailure`,e}({});function i(e,t){return new Response(e,{status:t,headers:{"Content-Type":`text/plain`}})}function a(e){let t=null;return()=>(t||=e(),t)}const o=n.object({noDeleteBeforeSeconds:n.number().int()});var s=class{config;currentTimestampMs;constructor(e,t){this.config=e,this.currentTimestampMs=t}async evaluate(e,t){if(e.method!==`DELETE`)return null;try{let e=await t(),n=e.get(`Last-Modified`);if(!n)return null;let r=new Date(n).getTime(),i=this.currentTimestampMs-r,a=this.config.noDeleteBeforeSeconds*1e3;return i>a?{violation:`Cannot delete object: object is ${Math.floor(i/1e3)} seconds old, which exceeds the ${this.config.noDeleteBeforeSeconds} seconds threshold`}:null}catch(e){if(e instanceof p&&e.code===`404`)return null;throw e}}};const c=n.object({noReplaceBeforeSeconds:n.number().int()});var l=class{config;currentTimestampMs;constructor(e,t){this.config=e,this.currentTimestampMs=t}async evaluate(e,t){if(e.method!==`PUT`)return null;try{let e=await t(),n=e.get(`Last-Modified`);if(!n)return null;let r=new Date(n).getTime(),i=this.currentTimestampMs-r,a=this.config.noReplaceBeforeSeconds*1e3;return i>a?{violation:`Cannot replace object: object is ${Math.floor(i/1e3)} seconds old, which exceeds the ${this.config.noReplaceBeforeSeconds} seconds threshold`}:null}catch(e){if(e instanceof p&&e.code===`404`)return null;throw e}}};const u=n.object({key:n.string()}),d=e=>n.array(n.object({pattern:n.string(),config:e.nullable()})),f=n.object({noDeleteOld:d(o).optional(),noReplaceOld:d(c).optional(),managedSse:d(u).optional()});var p=class extends Error{code;constructor(e,t){super(t),this.code=e}};async function m(e,t,n){let r={};n&&(r[`x-amz-server-side-encryption-customer-algorithm`]=`AES256`,r[`x-amz-server-side-encryption-customer-key`]=n.key,r[`x-amz-server-side-encryption-customer-key-md5`]=n.keyMd5);let i=await e.fetch(t,{method:`HEAD`,headers:r});if(!i.ok)throw new p(i.status.toString(),i.statusText);return i.headers}const h=`x-amz-server-side-encryption-customer-algorithm`,g=`x-amz-server-side-encryption-customer-key`,_=`x-amz-server-side-encryption-customer-key-md5`;async function v(e){let t=Uint8Array.from(atob(e),e=>e.charCodeAt(0)),n=await crypto.subtle.digest(`MD5`,t),r=new Uint8Array(n);return btoa(String.fromCharCode(...r))}var y=class{base64Key;keyMd5Promise;constructor(e){this.base64Key=e.key,this.keyMd5Promise=v(e.key)}async modifyHeaders(e,t){if(t.has(h))return t;let n=e.method.toUpperCase();if(n!==`GET`&&n!==`HEAD`&&n!==`PUT`)return t;let r=new Headers(t);return r.set(h,`AES256`),r.set(g,this.base64Key),r.set(_,await this.keyMd5Promise),r}handleResponse(e,t){if(e.status===400){let e=new Headers(t);return e.delete(h),e.delete(g),e.delete(_),{retryRequest:e}}return{modifiedResponse:e}}};async function b(e,t,n,r,i){let o=new URL(e.url).pathname,s=S(i,o,r);if(s.length===0)return null;let c=a(async()=>{let e=w(i,o),r=e?{key:e.key,keyMd5:await v(e.key)}:void 0;return m(t,n+o,r)}),l=s.map(({name:t,policy:n})=>({policyName:t,promise:(async()=>{let r=await n.evaluate(e,c);return r?{...r,policy:t}:null})()}));return new Promise(e=>{let t=l.length;for(let{policyName:n,promise:r}of l)r.then(n=>{n===null?(t--,t===0&&e(null)):e(n)}).catch(t=>{let r=t instanceof Error?t.message:String(t);t instanceof p&&(r=`Error calling upstream when evaluating guardrail: ${r}`),e({violation:r,policy:n})})})}function x(e){if(e.startsWith(`^`))throw Error(`Invalid pattern "${e}": do not include ^ at the start, patterns are automatically anchored`);if(e.endsWith(`$`))throw Error(`Invalid pattern "${e}": do not include $ at the end, patterns are automatically anchored`);let t=e.startsWith(`/`)?e:`/`+e;return RegExp(`^${t}$`)}function S(e,t,n){let r=[];for(let i of e.noDeleteOld??[]){let e=x(i.pattern);if(e.test(t)){i.config!==null&&r.push({name:`noDeleteOld`,policy:new s(i.config,n)});break}}for(let i of e.noReplaceOld??[]){let e=x(i.pattern);if(e.test(t)){i.config!==null&&r.push({name:`noReplaceOld`,policy:new l(i.config,n)});break}}return r}function C(e,t){let n=[];for(let r of e.managedSse??[]){let e=x(r.pattern);if(e.test(t)){r.config!==null&&n.push(new y(r.config));break}}return n}function w(e,t){for(let n of e.managedSse??[]){let e=x(n.pattern);if(e.test(t))return n.config}return null}const T=new Set(`x-amz-date.x-amz-content-sha256.x-amz-security-token.x-amz-server-side-encryption.x-amz-server-side-encryption-aws-kms-key-id.x-amz-server-side-encryption-customer-algorithm.x-amz-server-side-encryption-customer-key.x-amz-server-side-encryption-customer-key-md5.x-amz-storage-class.x-amz-tagging.x-amz-website-redirect-location.x-amz-acl.x-amz-grant-read.x-amz-grant-write.x-amz-grant-read-acp.x-amz-grant-write-acp.x-amz-grant-full-control.x-amz-metadata-directive.x-amz-copy-source.x-amz-copy-source-if-match.x-amz-copy-source-if-none-match.x-amz-copy-source-if-unmodified-since.x-amz-copy-source-if-modified-since.x-amz-copy-source-range.content-type.content-length.content-md5.content-encoding.content-disposition.cache-control.expires.range.if-match.if-none-match.if-modified-since.if-unmodified-since.user-agent`.split(`.`)),E=new Set([`X-Amz-Algorithm`,`X-Amz-Credential`,`X-Amz-Date`,`X-Amz-Expires`,`X-Amz-SignedHeaders`,`X-Amz-Signature`,`X-Amz-Security-Token`]),D={noDeleteOld:[{pattern:`/.*`,config:{noDeleteBeforeSeconds:60}}],noReplaceOld:[{pattern:`/.*`,config:{noReplaceBeforeSeconds:60}}]};async function O(n,a){let o=Date.now(),s=await e(n,a.clientSecretAccessKey,a.clientAccessKeyId,o);if(!s.valid)return i(`Signature verification failed: ${s.error}`,r.Forbidden);let c=new URL(n.url),l=new t({accessKeyId:a.upstreamAccessKeyId,secretAccessKey:a.upstreamSecretAccessKey,retries:5}),u=await b(n,l,a.s3Endpoint,o,a.guardrailConfig||D);if(u)return console.log(`Guardrail violation`,{path:c.pathname,method:n.method,query:c.search,policy:u.policy,violation:u.violation}),i(`Request violating guardrail policy ${u.policy}: ${u.violation}`,r.Forbidden);let d=a.guardrailConfig||D,f=C(d,c.pathname),p=new URL(c.pathname,a.s3Endpoint);for(let[e,t]of c.searchParams.entries())E.has(e)||p.searchParams.set(e,t);let m=new Headers;for(let[e,t]of n.headers.entries())T.has(e.toLowerCase())&&m.set(e,t);m.set(`x-amz-content-sha256`,`UNSIGNED-PAYLOAD`);for(let e of f)m=await e.modifyHeaders(n,m);let h=new t({accessKeyId:a.upstreamAccessKeyId,secretAccessKey:a.upstreamSecretAccessKey,retries:0}),g=async(e,t)=>{let r=new Request(p.toString(),{method:n.method,headers:e,body:t,duplex:`half`});return h.fetch(r)};try{let e=await g(m,n.body);for(let t of f)if(t.handleResponse){let n=t.handleResponse(e,m);e=`retryRequest`in n?await g(n.retryRequest,null):n.modifiedResponse}return e}catch(e){return console.error(`Upstream request failed:`,e),i(`Upstream request failed: ${e instanceof Error?e.message:`Unknown error`}`,r.UpstreamFailure)}}export{f as GuardrailConfigZod,D as defaultGuardrailConfig,O as handle};
1
+ import{verifySignature as e}from"./sigv4-Bh65MQC2.mjs";import{AwsClient as t}from"aws4fetch";import{z as n}from"zod";let r=function(e){return e[e.Forbidden=403]=`Forbidden`,e[e.UpstreamFailure=502]=`UpstreamFailure`,e}({});function i(e,t){return new Response(e,{status:t,headers:{"Content-Type":`text/plain`}})}function a(e){let t=null;return()=>(t||=e(),t)}const o=n.object({noDeleteBeforeSeconds:n.number().int()});function s(e){if(e.method===`DELETE`)return`single`;if(e.method===`POST`){let t=new URL(e.url);if(t.searchParams.has(`delete`))return`bulk`}return!1}var c=class{config;currentTimestampMs;constructor(e,t){this.config=e,this.currentTimestampMs=t}async evaluate(e,t){let n=s(e);if(!n)return null;if(n===`bulk`)return{violation:`Bulk delete (DeleteObjects) is not allowed in protected paths. Use single object DELETE instead.`};try{let e=await t(),n=e.get(`Last-Modified`);if(!n)return null;let r=new Date(n).getTime(),i=this.currentTimestampMs-r,a=this.config.noDeleteBeforeSeconds*1e3;return i>a?{violation:`Cannot delete object: object is ${Math.floor(i/1e3)} seconds old, which exceeds the ${this.config.noDeleteBeforeSeconds} seconds threshold`}:null}catch(e){if(e instanceof h&&e.code===`404`)return null;throw e}}};const l=n.object({noReplaceBeforeSeconds:n.number().int()});function u(e){if(e.method===`PUT`)return!0;if(e.method===`POST`){let t=new URL(e.url);return!(t.searchParams.has(`delete`)||t.searchParams.has(`uploads`))}return!1}var d=class{config;currentTimestampMs;constructor(e,t){this.config=e,this.currentTimestampMs=t}async evaluate(e,t){if(!u(e))return null;try{let e=await t(),n=e.get(`Last-Modified`);if(!n)return null;let r=new Date(n).getTime(),i=this.currentTimestampMs-r,a=this.config.noReplaceBeforeSeconds*1e3;return i>a?{violation:`Cannot replace object: object is ${Math.floor(i/1e3)} seconds old, which exceeds the ${this.config.noReplaceBeforeSeconds} seconds threshold`}:null}catch(e){if(e instanceof h&&e.code===`404`)return null;throw e}}};const f=n.object({key:n.string()}),p=e=>n.array(n.object({pattern:n.string(),config:e.nullable()})),m=n.object({noDeleteOld:p(o).optional(),noReplaceOld:p(l).optional(),managedSse:p(f).optional()});var h=class extends Error{code;constructor(e,t){super(t),this.code=e}};async function g(e,t,n){let r={};n&&(r[`x-amz-server-side-encryption-customer-algorithm`]=`AES256`,r[`x-amz-server-side-encryption-customer-key`]=n.key,r[`x-amz-server-side-encryption-customer-key-md5`]=n.keyMd5);let i=await e.fetch(t,{method:`HEAD`,headers:r});if(!i.ok)throw new h(i.status.toString(),i.statusText);return i.headers}const _=`x-amz-server-side-encryption-customer-algorithm`,v=`x-amz-server-side-encryption-customer-key`,y=`x-amz-server-side-encryption-customer-key-md5`;async function b(e){let t=Uint8Array.from(atob(e),e=>e.charCodeAt(0)),n=await crypto.subtle.digest(`MD5`,t),r=new Uint8Array(n);return btoa(String.fromCharCode(...r))}var x=class{base64Key;keyMd5Promise;constructor(e){this.base64Key=e.key,this.keyMd5Promise=b(e.key)}async modifyHeaders(e,t){if(t.has(_))return t;let n=e.method.toUpperCase();if(n!==`GET`&&n!==`HEAD`&&n!==`PUT`&&n!==`POST`)return t;let r=new Headers(t);return r.set(_,`AES256`),r.set(v,this.base64Key),r.set(y,await this.keyMd5Promise),r}handleResponse(e,t){if(e.status===400){let e=new Headers(t);return e.delete(_),e.delete(v),e.delete(y),{retryRequest:e}}return{modifiedResponse:e}}};async function S(e,t,n,r,i){let o=new URL(e.url).pathname,s=w(i,o,r);if(s.length===0)return null;let c=a(async()=>{let e=E(i,o),r=e?{key:e.key,keyMd5:await b(e.key)}:void 0;return g(t,n+o,r)}),l=s.map(({name:t,policy:n})=>({policyName:t,promise:(async()=>{let r=await n.evaluate(e,c);return r?{...r,policy:t}:null})()}));return new Promise(e=>{let t=l.length;for(let{policyName:n,promise:r}of l)r.then(n=>{n===null?(t--,t===0&&e(null)):e(n)}).catch(t=>{let r=t instanceof Error?t.message:String(t);t instanceof h&&(r=`Error calling upstream when evaluating guardrail: ${r}`),e({violation:r,policy:n})})})}function C(e){if(e.startsWith(`^`))throw Error(`Invalid pattern "${e}": do not include ^ at the start, patterns are automatically anchored`);if(e.endsWith(`$`))throw Error(`Invalid pattern "${e}": do not include $ at the end, patterns are automatically anchored`);let t=e.startsWith(`/`)?e:`/`+e;return RegExp(`^${t}$`)}function w(e,t,n){let r=[];for(let i of e.noDeleteOld??[]){let e=C(i.pattern);if(e.test(t)){i.config!==null&&r.push({name:`noDeleteOld`,policy:new c(i.config,n)});break}}for(let i of e.noReplaceOld??[]){let e=C(i.pattern);if(e.test(t)){i.config!==null&&r.push({name:`noReplaceOld`,policy:new d(i.config,n)});break}}return r}function T(e,t){let n=[];for(let r of e.managedSse??[]){let e=C(r.pattern);if(e.test(t)){r.config!==null&&n.push(new x(r.config));break}}return n}function E(e,t){for(let n of e.managedSse??[]){let e=C(n.pattern);if(e.test(t))return n.config}return null}const D=new Set(`x-amz-date.x-amz-content-sha256.x-amz-security-token.x-amz-server-side-encryption.x-amz-server-side-encryption-aws-kms-key-id.x-amz-server-side-encryption-customer-algorithm.x-amz-server-side-encryption-customer-key.x-amz-server-side-encryption-customer-key-md5.x-amz-storage-class.x-amz-tagging.x-amz-website-redirect-location.x-amz-acl.x-amz-grant-read.x-amz-grant-write.x-amz-grant-read-acp.x-amz-grant-write-acp.x-amz-grant-full-control.x-amz-metadata-directive.x-amz-copy-source.x-amz-copy-source-if-match.x-amz-copy-source-if-none-match.x-amz-copy-source-if-unmodified-since.x-amz-copy-source-if-modified-since.x-amz-copy-source-range.content-type.content-length.content-md5.content-encoding.content-disposition.cache-control.expires.range.if-match.if-none-match.if-modified-since.if-unmodified-since.user-agent`.split(`.`)),O=new Set([`X-Amz-Algorithm`,`X-Amz-Credential`,`X-Amz-Date`,`X-Amz-Expires`,`X-Amz-SignedHeaders`,`X-Amz-Signature`,`X-Amz-Security-Token`]),k={noDeleteOld:[{pattern:`/.*`,config:{noDeleteBeforeSeconds:60}}],noReplaceOld:[{pattern:`/.*`,config:{noReplaceBeforeSeconds:60}}]};async function A(n,a){let o=Date.now(),s=await e(n,a.clientSecretAccessKey,a.clientAccessKeyId,o);if(!s.valid)return i(`Signature verification failed: ${s.error}`,r.Forbidden);let c=new URL(n.url),l=new t({accessKeyId:a.upstreamAccessKeyId,secretAccessKey:a.upstreamSecretAccessKey,retries:5}),u=await S(n,l,a.s3Endpoint,o,a.guardrailConfig||k);if(u)return console.log(`Guardrail violation`,{path:c.pathname,method:n.method,query:c.search,policy:u.policy,violation:u.violation}),i(`Request violating guardrail policy ${u.policy}: ${u.violation}`,r.Forbidden);let d=a.guardrailConfig||k,f=T(d,c.pathname),p=new URL(c.pathname,a.s3Endpoint);for(let[e,t]of c.searchParams.entries())O.has(e)||p.searchParams.set(e,t);let m=new Headers;for(let[e,t]of n.headers.entries())D.has(e.toLowerCase())&&m.set(e,t);m.set(`x-amz-content-sha256`,`UNSIGNED-PAYLOAD`);for(let e of f)m=await e.modifyHeaders(n,m);let h=new t({accessKeyId:a.upstreamAccessKeyId,secretAccessKey:a.upstreamSecretAccessKey,retries:0}),g=async(e,t)=>{let r=new Request(p.toString(),{method:n.method,headers:e,body:t,duplex:`half`});return h.fetch(r)};try{let e=await g(m,n.body);for(let t of f)if(t.handleResponse){let n=t.handleResponse(e,m);e=`retryRequest`in n?await g(n.retryRequest,null):n.modifiedResponse}return e}catch(e){return console.error(`Upstream request failed:`,e),i(`Upstream request failed: ${e instanceof Error?e.message:`Unknown error`}`,r.UpstreamFailure)}}export{m as GuardrailConfigZod,k as defaultGuardrailConfig,A as handle};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["text: string","errorCode: ErrorCode","fn: () => Promise<T>","maybeDonePromise: Promise<T> | null","config: NoDeleteOldPolicyConfig","currentTimestampMs: number","request: Request<unknown, IncomingRequestCfProperties>","metadata: () => Promise<Headers>","config: NoReplaceOldPolicyConfig","currentTimestampMs: number","request: Request<unknown, IncomingRequestCfProperties>","metadata: () => Promise<Headers>","policy: T","code: string","message: string","upstream: AwsClient","objectPath: string","sseHeaders?: { key: string; keyMd5: string }","headers: Record<string, string>","base64Key: string","config: ManagedSseConfig","request: Request<unknown, IncomingRequestCfProperties>","upstreamHeaders: Headers","upstreamResponse: Response","sentRequestHeaders: Headers","request: Request<unknown, IncomingRequestCfProperties>","upstreamFetcher: AwsClient","s3Endpoint: string","currentTimestampMs: number","config: GuardrailConfig","pattern: string","path: string","policies: { name: string; policy: GuardrailPolicy }[]","modifiers: HeaderModifier[]","defaultGuardrailConfig: GuardrailConfig","request: Request<unknown, IncomingRequestCfProperties>","options: S3BrokerOptions","headers: Headers","body: ReadableStream<Uint8Array> | null"],"sources":["../src/utils.ts","../src/guardrails/no-delete-old.ts","../src/guardrails/no-replace-old.ts","../src/guardrails/type.ts","../src/guardrails/s3_helper.ts","../src/sse/sse.ts","../src/guardrails/guardrails.ts","../src/index.ts"],"sourcesContent":["export enum ErrorCode {\n\tForbidden = 403,\n\tUpstreamFailure = 502,\n}\n\nexport function textErrorResponse(text: string, errorCode: ErrorCode): Response {\n\treturn new Response(text, { status: errorCode, headers: { 'Content-Type': 'text/plain' } });\n}\n\n/*\n * Given an async function, returns a function that will only call the provided function once, and return the same promise on subsequent calls.\n */\nexport function cached<T>(fn: () => Promise<T>): () => Promise<T> {\n\tlet maybeDonePromise: Promise<T> | null = null;\n\treturn () => {\n\t\tif (!maybeDonePromise) {\n\t\t\tmaybeDonePromise = fn();\n\t\t}\n\t\treturn maybeDonePromise;\n\t};\n}\n","import { GuardrailPolicy, GuardrailViolation, UpstreamError } from './type';\nimport { z } from 'zod';\n\nexport const NoDeleteOldPolicyConfig = z.object({\n\tnoDeleteBeforeSeconds: z.number().int(),\n});\n\nexport type NoDeleteOldPolicyConfig = z.infer<typeof NoDeleteOldPolicyConfig>;\n\nexport class NoDeleteOldPolicy implements GuardrailPolicy {\n\tprivate config: NoDeleteOldPolicyConfig;\n\tprivate currentTimestampMs: number;\n\n\tconstructor(config: NoDeleteOldPolicyConfig, currentTimestampMs: number) {\n\t\tthis.config = config;\n\t\tthis.currentTimestampMs = currentTimestampMs;\n\t}\n\n\tasync evaluate(\n\t\trequest: Request<unknown, IncomingRequestCfProperties>,\n\t\tmetadata: () => Promise<Headers>,\n\t): Promise<GuardrailViolation | null> {\n\t\t// Only applies to DELETE requests\n\t\tif (request.method !== 'DELETE') {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst headers = await metadata();\n\n\t\t\t// Get the Last-Modified header to determine object age\n\t\t\tconst lastModified = headers.get('Last-Modified');\n\t\t\tif (!lastModified) {\n\t\t\t\t// If no Last-Modified header, allow deletion (object might be newly created)\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst objectCreatedAtMs = new Date(lastModified).getTime();\n\t\t\tconst objectAgeMs = this.currentTimestampMs - objectCreatedAtMs;\n\t\t\tconst thresholdMs = this.config.noDeleteBeforeSeconds * 1000;\n\n\t\t\tif (objectAgeMs > thresholdMs) {\n\t\t\t\treturn {\n\t\t\t\t\tviolation: `Cannot delete object: object is ${Math.floor(objectAgeMs / 1000)} seconds old, which exceeds the ${this.config.noDeleteBeforeSeconds} seconds threshold`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t} catch (error) {\n\t\t\tif (error instanceof UpstreamError && error.code === '404') {\n\t\t\t\t// Object doesn't exist, allow deletion attempt (will fail at upstream)\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n","import { GuardrailPolicy, GuardrailViolation, UpstreamError } from './type';\nimport { z } from 'zod';\n\nexport const NoReplaceOldPolicyConfig = z.object({\n\tnoReplaceBeforeSeconds: z.number().int(),\n});\n\nexport type NoReplaceOldPolicyConfig = z.infer<typeof NoReplaceOldPolicyConfig>;\n\nexport class NoReplaceOldPolicy implements GuardrailPolicy {\n\tprivate config: NoReplaceOldPolicyConfig;\n\tprivate currentTimestampMs: number;\n\n\tconstructor(config: NoReplaceOldPolicyConfig, currentTimestampMs: number) {\n\t\tthis.config = config;\n\t\tthis.currentTimestampMs = currentTimestampMs;\n\t}\n\n\tasync evaluate(\n\t\trequest: Request<unknown, IncomingRequestCfProperties>,\n\t\tmetadata: () => Promise<Headers>,\n\t): Promise<GuardrailViolation | null> {\n\t\t// Only applies to PUT requests\n\t\tif (request.method !== 'PUT') {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\t// Check if object exists and get its metadata via HEAD request\n\t\t\t// This doesn't buffer the PUT body - just checks headers\n\t\t\tconst headers = await metadata();\n\n\t\t\t// Get the Last-Modified header to determine object age\n\t\t\tconst lastModified = headers.get('Last-Modified');\n\t\t\tif (!lastModified) {\n\t\t\t\t// If no Last-Modified header, allow replacement (object metadata issue)\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst objectCreatedAtMs = new Date(lastModified).getTime();\n\t\t\tconst objectAgeMs = this.currentTimestampMs - objectCreatedAtMs;\n\t\t\tconst thresholdMs = this.config.noReplaceBeforeSeconds * 1000;\n\n\t\t\tif (objectAgeMs > thresholdMs) {\n\t\t\t\treturn {\n\t\t\t\t\tviolation: `Cannot replace object: object is ${Math.floor(objectAgeMs / 1000)} seconds old, which exceeds the ${this.config.noReplaceBeforeSeconds} seconds threshold`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t} catch (error) {\n\t\t\tif (error instanceof UpstreamError && error.code === '404') {\n\t\t\t\t// Object doesn't exist, allow PUT (creating new object)\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n","import { z, ZodType } from 'zod';\nimport { NoDeleteOldPolicyConfig } from './no-delete-old';\nimport { NoReplaceOldPolicyConfig } from './no-replace-old';\n\n/**\n * Configuration for managed SSE-C (Server-Side Encryption with Customer-Provided Keys)\n */\nexport const ManagedSseConfig = z.object({\n\t// Base64-encoded 256-bit (32-byte) AES key\n\tkey: z.string(),\n});\nexport type ManagedSseConfig = z.infer<typeof ManagedSseConfig>;\n\n/**\n * Result of handling an upstream response.\n * Either retry with new headers, or return a (possibly modified) response.\n */\nexport type HandleResponseResult = { retryRequest: Headers } | { modifiedResponse: Response };\n\n/**\n * HeaderModifier modifies request headers before sending to upstream.\n * Used for SSE header injection and similar request transformations.\n */\nexport interface HeaderModifier {\n\t/**\n\t * Modify the upstream request headers.\n\t * Implementation can either mutate headers in place and return them,\n\t * or clone and return new headers.\n\t * @param request - The original incoming request\n\t * @param upstreamHeaders - The base headers built for upstream\n\t * @returns Modified headers to use for upstream request\n\t */\n\tmodifyHeaders(request: Request<unknown, IncomingRequestCfProperties>, upstreamHeaders: Headers): Promise<Headers>;\n\n\t/**\n\t * Handle response from upstream, optionally retrying with different headers.\n\t * @param upstreamResponse - Response from upstream\n\t * @param sentRequestHeaders - Headers that were sent in the request\n\t * @returns Either retry instructions or the final response\n\t */\n\thandleResponse?(upstreamResponse: Response, sentRequestHeaders: Headers): HandleResponseResult;\n}\n\nexport interface GuardrailPolicy {\n\tevaluate(request: Request<unknown, IncomingRequestCfProperties>, metadata: () => Promise<Headers>): Promise<GuardrailViolation | null>;\n}\n\nexport type GuardrailViolation = {\n\tviolation: string;\n};\n\n/*\n * Corresponding config for each object path pattern in regex. First match wins.\n * If config is null, the guardrail is disabled for that pattern.\n */\nexport const GuardrailPolicyConfigPerPattern = <T extends ZodType>(policy: T) =>\n\tz.array(\n\t\tz.object({\n\t\t\tpattern: z.string(),\n\t\t\tconfig: policy.nullable(),\n\t\t}),\n\t);\n/**\n * - `noDeleteOld`: Prevents deletion of old objects in protected paths\n * - `noReplaceOld`: Prevents replacement of old objects in protected paths\n * - `managedSse`: Automatically injects SSE-C headers for paths\n */\nexport const GuardrailConfig = z.object({\n\tnoDeleteOld: GuardrailPolicyConfigPerPattern(NoDeleteOldPolicyConfig).optional(),\n\tnoReplaceOld: GuardrailPolicyConfigPerPattern(NoReplaceOldPolicyConfig).optional(),\n\tmanagedSse: GuardrailPolicyConfigPerPattern(ManagedSseConfig).optional(),\n});\nexport type GuardrailConfig = z.infer<typeof GuardrailConfig>;\n\n/**\n * Error calling upstream when evaluating guardrails\n */\nexport class UpstreamError extends Error {\n\tcode: string;\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t}\n}\n","import { AwsClient } from 'aws4fetch';\nimport { UpstreamError } from './type';\n\n/**\n * Issue a `HEAD` request to retrieve object metadata\n * @param upstream - AWS client for signing requests\n * @param objectPath - Full path to the object (including endpoint)\n * @param sseHeaders - Optional SSE-C headers for encrypted objects\n */\nexport async function getObjectMetadata(\n\tupstream: AwsClient,\n\tobjectPath: string,\n\tsseHeaders?: { key: string; keyMd5: string },\n): Promise<Headers> {\n\tconst headers: Record<string, string> = {};\n\tif (sseHeaders) {\n\t\theaders['x-amz-server-side-encryption-customer-algorithm'] = 'AES256';\n\t\theaders['x-amz-server-side-encryption-customer-key'] = sseHeaders.key;\n\t\theaders['x-amz-server-side-encryption-customer-key-md5'] = sseHeaders.keyMd5;\n\t}\n\n\tconst response = await upstream.fetch(objectPath, {\n\t\tmethod: 'HEAD',\n\t\theaders,\n\t});\n\tif (!response.ok) {\n\t\tthrow new UpstreamError(response.status.toString(), response.statusText);\n\t}\n\treturn response.headers;\n}\n","/**\n * Managed SSE-C (Server-Side Encryption with Customer-Provided Keys) module.\n *\n * Automatically injects SSE-C headers for configured paths, making encryption\n * seamless to clients.\n */\n\nimport { HeaderModifier, HandleResponseResult, ManagedSseConfig } from '../guardrails/type';\n\n// SSE-C header names\nconst SSE_ALGORITHM_HEADER = 'x-amz-server-side-encryption-customer-algorithm';\nconst SSE_KEY_HEADER = 'x-amz-server-side-encryption-customer-key';\nconst SSE_KEY_MD5_HEADER = 'x-amz-server-side-encryption-customer-key-md5';\n\n/**\n * Compute MD5 hash of a base64-encoded key and return as base64.\n */\nexport async function computeKeyMd5(base64Key: string): Promise<string> {\n\t// Decode base64 key to bytes\n\tconst keyBytes = Uint8Array.from(atob(base64Key), (c) => c.charCodeAt(0));\n\n\t// Compute MD5 hash using Web Crypto API\n\tconst hashBuffer = await crypto.subtle.digest('MD5', keyBytes);\n\n\t// Encode hash as base64\n\tconst hashArray = new Uint8Array(hashBuffer);\n\treturn btoa(String.fromCharCode(...hashArray));\n}\n\n/**\n * ManagedSseModifier implements HeaderModifier to automatically inject\n * SSE-C headers for PUT/GET/HEAD requests when the client hasn't provided their own.\n */\nexport class ManagedSseModifier implements HeaderModifier {\n\tprivate base64Key: string;\n\tprivate keyMd5Promise: Promise<string>;\n\n\tconstructor(config: ManagedSseConfig) {\n\t\tthis.base64Key = config.key;\n\t\t// Compute MD5 eagerly but cache the promise for reuse\n\t\tthis.keyMd5Promise = computeKeyMd5(config.key);\n\t}\n\n\tasync modifyHeaders(request: Request<unknown, IncomingRequestCfProperties>, upstreamHeaders: Headers): Promise<Headers> {\n\t\t// Skip if client already provided SSE headers (passthrough)\n\t\tif (upstreamHeaders.has(SSE_ALGORITHM_HEADER)) {\n\t\t\treturn upstreamHeaders;\n\t\t}\n\n\t\t// For methods that don't use SSE-C (e.g., DELETE, LIST), skip\n\t\tconst method = request.method.toUpperCase();\n\t\tif (method !== 'GET' && method !== 'HEAD' && method !== 'PUT') {\n\t\t\treturn upstreamHeaders;\n\t\t}\n\n\t\t// Clone headers and add managed SSE headers\n\t\tconst headers = new Headers(upstreamHeaders);\n\t\theaders.set(SSE_ALGORITHM_HEADER, 'AES256');\n\t\theaders.set(SSE_KEY_HEADER, this.base64Key);\n\t\theaders.set(SSE_KEY_MD5_HEADER, await this.keyMd5Promise);\n\t\treturn headers;\n\t}\n\n\thandleResponse(upstreamResponse: Response, sentRequestHeaders: Headers): HandleResponseResult {\n\t\t// For GET/HEAD: If 400 error (wrong key), retry without SSE headers\n\t\t// This handles unencrypted legacy files in managed SSE paths\n\t\tif (upstreamResponse.status === 400) {\n\t\t\tconst retryHeaders = new Headers(sentRequestHeaders);\n\t\t\tretryHeaders.delete(SSE_ALGORITHM_HEADER);\n\t\t\tretryHeaders.delete(SSE_KEY_HEADER);\n\t\t\tretryHeaders.delete(SSE_KEY_MD5_HEADER);\n\t\t\treturn { retryRequest: retryHeaders };\n\t\t}\n\t\treturn { modifiedResponse: upstreamResponse };\n\t}\n}\n","import { AwsClient } from 'aws4fetch';\nimport { GuardrailConfig, GuardrailPolicy, GuardrailViolation, HeaderModifier, ManagedSseConfig, UpstreamError } from './type';\nimport { NoDeleteOldPolicy } from './no-delete-old';\nimport { NoReplaceOldPolicy } from './no-replace-old';\nimport { getObjectMetadata } from './s3_helper';\nimport { cached } from '../utils';\nimport { ManagedSseModifier, computeKeyMd5 } from '../sse/sse';\n\n/**\n * Evaluate guardrails for a given request\n * @param request The incoming request\n * @param config The guardrail configuration to use\n * @returns The first guardrail violation if the request violates a policy, null otherwise\n */\nexport async function evaluateGuardrails(\n\trequest: Request<unknown, IncomingRequestCfProperties>,\n\tupstreamFetcher: AwsClient,\n\ts3Endpoint: string,\n\tcurrentTimestampMs: number,\n\tconfig: GuardrailConfig,\n): Promise<(GuardrailViolation & { policy: string }) | null> {\n\tconst path = new URL(request.url).pathname;\n\tconst policies = getPolicies(config, path, currentTimestampMs);\n\n\tif (policies.length === 0) {\n\t\treturn null;\n\t}\n\n\t// Look up SSE config for this path (needed for HEAD request on encrypted objects)\n\tconst metadata = cached(async () => {\n\t\tconst sseConfig = getSseConfigForPath(config, path);\n\t\tconst sseHeaders = sseConfig ? { key: sseConfig.key, keyMd5: await computeKeyMd5(sseConfig.key) } : undefined;\n\t\treturn getObjectMetadata(upstreamFetcher, s3Endpoint + path, sseHeaders);\n\t});\n\n\t// Create evaluation promises that include policy name\n\tconst evalPromises = policies.map(({ name: policyName, policy }) => ({\n\t\tpolicyName,\n\t\tpromise: (async () => {\n\t\t\tconst violation = await policy.evaluate(request, metadata);\n\t\t\treturn violation ? { ...violation, policy: policyName } : null;\n\t\t})(),\n\t}));\n\n\t// Race until one returns a violation or all are done\n\t// Using Promise.race with a filter pattern to get first non-null result\n\treturn new Promise((resolve) => {\n\t\tlet pendingCount = evalPromises.length;\n\n\t\tfor (const { policyName, promise } of evalPromises) {\n\t\t\tpromise\n\t\t\t\t.then((result) => {\n\t\t\t\t\tif (result !== null) {\n\t\t\t\t\t\t// First violation wins\n\t\t\t\t\t\tresolve(result);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpendingCount--;\n\t\t\t\t\t\tif (pendingCount === 0) {\n\t\t\t\t\t\t\t// All policies passed\n\t\t\t\t\t\t\tresolve(null);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch((error) => {\n\t\t\t\t\t// If a policy throws, treat it as a violation\n\t\t\t\t\tlet message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tif (error instanceof UpstreamError) {\n\t\t\t\t\t\tmessage = `Error calling upstream when evaluating guardrail: ${message}`;\n\t\t\t\t\t}\n\t\t\t\t\tresolve({ violation: message, policy: policyName });\n\t\t\t\t});\n\t\t}\n\t});\n}\n\n/**\n * Create a regex that fully matches the path (not substring match).\n *\n * Pattern rules:\n * - Patterns are matched against the full path (anchored with ^ and $)\n * - If a pattern doesn't start with '/', it will be automatically prepended\n * - Do NOT include ^ at the start or $ at the end - they are added automatically\n *\n * Examples:\n * - '/bucket/tom/.*' matches '/bucket/tom/file.txt' but NOT '/alpha/bucket/tom/file.txt'\n * - 'bucket/.*' is equivalent to '/bucket/.*'\n *\n * @throws Error if pattern starts with ^ or ends with $\n */\nfunction createFullMatchRegex(pattern: string): RegExp {\n\t// Validate: user should not include anchors\n\tif (pattern.startsWith('^')) {\n\t\tthrow new Error(`Invalid pattern \"${pattern}\": do not include ^ at the start, patterns are automatically anchored`);\n\t}\n\tif (pattern.endsWith('$')) {\n\t\tthrow new Error(`Invalid pattern \"${pattern}\": do not include $ at the end, patterns are automatically anchored`);\n\t}\n\n\t// Normalize pattern: prepend '/' if missing\n\tconst normalizedPattern = pattern.startsWith('/') ? pattern : '/' + pattern;\n\t// Anchor the regex to match the entire path\n\treturn new RegExp(`^${normalizedPattern}$`);\n}\n\n/**\n * Get all applicable policies for a given path\n */\nexport function getPolicies(\n\tconfig: GuardrailConfig,\n\tpath: string,\n\tcurrentTimestampMs: number,\n): { name: string; policy: GuardrailPolicy }[] {\n\tconst policies: { name: string; policy: GuardrailPolicy }[] = [];\n\n\t// Check noDeleteOld policies - first matching pattern wins\n\t// If config is null, the guardrail is disabled for that pattern (exclude case)\n\tfor (const entry of config.noDeleteOld ?? []) {\n\t\tconst regex = createFullMatchRegex(entry.pattern);\n\t\tif (regex.test(path)) {\n\t\t\tif (entry.config !== null) {\n\t\t\t\tpolicies.push({\n\t\t\t\t\tname: 'noDeleteOld',\n\t\t\t\t\tpolicy: new NoDeleteOldPolicy(entry.config, currentTimestampMs),\n\t\t\t\t});\n\t\t\t}\n\t\t\tbreak; // First match wins - even if config is null, we don't check further patterns\n\t\t}\n\t}\n\n\t// Check noReplaceOld policies - first matching pattern wins\n\t// If config is null, the guardrail is disabled for that pattern (exclude case)\n\tfor (const entry of config.noReplaceOld ?? []) {\n\t\tconst regex = createFullMatchRegex(entry.pattern);\n\t\tif (regex.test(path)) {\n\t\t\tif (entry.config !== null) {\n\t\t\t\tpolicies.push({\n\t\t\t\t\tname: 'noReplaceOld',\n\t\t\t\t\tpolicy: new NoReplaceOldPolicy(entry.config, currentTimestampMs),\n\t\t\t\t});\n\t\t\t}\n\t\t\tbreak; // First match wins - even if config is null, we don't check further patterns\n\t\t}\n\t}\n\n\treturn policies;\n}\n\n/**\n * Get all applicable header modifiers for a given path\n */\nexport function getHeaderModifiers(config: GuardrailConfig, path: string): HeaderModifier[] {\n\tconst modifiers: HeaderModifier[] = [];\n\n\t// Check managedSse policies - first matching pattern wins\n\tfor (const entry of config.managedSse ?? []) {\n\t\tconst regex = createFullMatchRegex(entry.pattern);\n\t\tif (regex.test(path)) {\n\t\t\tif (entry.config !== null) {\n\t\t\t\tmodifiers.push(new ManagedSseModifier(entry.config));\n\t\t\t}\n\t\t\tbreak; // First match wins\n\t\t}\n\t}\n\n\treturn modifiers;\n}\n\n/**\n * Get the SSE config for a path, if any pattern matches\n */\nfunction getSseConfigForPath(config: GuardrailConfig, path: string): ManagedSseConfig | null {\n\tfor (const entry of config.managedSse ?? []) {\n\t\tconst regex = createFullMatchRegex(entry.pattern);\n\t\tif (regex.test(path)) {\n\t\t\treturn entry.config;\n\t\t}\n\t}\n\treturn null;\n}\n","/**\n * S3Broker - S3 Proxy Library for Cloudflare Workers\n *\n * ========== =========== ============\n * ||Client|| -- Key A --> ||S3Broker|| -- Key B --> ||Upstream||\n * ========== =========== ============\n *\n * S3Broker is a Cloudflare Workers library for building secure S3-compatible proxies.\n *\n * Features:\n * 1. Verifies incoming requests signed with Key A (client credentials)\n * 2. Enforces configurable guardrails policies (e.g., prevent deletion of recent objects)\n * 3. Re-signs requests with Key B (upstream credentials) for the upstream S3 service\n * 4. Proxies the request to any S3-compatible endpoint (AWS S3, Cloudflare R2, MinIO, etc.)\n */\n\nimport { AwsClient } from 'aws4fetch';\nimport { verifySignature } from './sigv4';\nimport { textErrorResponse, ErrorCode } from './utils';\nimport { evaluateGuardrails, getHeaderModifiers } from './guardrails/guardrails';\nimport type { S3BrokerOptions } from './types';\nimport { GuardrailConfig } from './guardrails/type';\n\n// Re-export types\nexport type { S3BrokerOptions } from './types';\nexport type { GuardrailConfig, GuardrailViolation } from './guardrails/type';\n// Re-export zod type\nexport { GuardrailConfig as GuardrailConfigZod } from './guardrails/type';\n\n// Headers that should be forwarded to upstream (allowlist approach)\nconst HEADERS_TO_INCLUDE = new Set([\n\t// S3-specific headers\n\t'x-amz-date',\n\t'x-amz-content-sha256',\n\t'x-amz-security-token',\n\t'x-amz-server-side-encryption',\n\t'x-amz-server-side-encryption-aws-kms-key-id',\n\t'x-amz-server-side-encryption-customer-algorithm',\n\t'x-amz-server-side-encryption-customer-key',\n\t'x-amz-server-side-encryption-customer-key-md5',\n\t'x-amz-storage-class',\n\t'x-amz-tagging',\n\t'x-amz-website-redirect-location',\n\t'x-amz-acl',\n\t'x-amz-grant-read',\n\t'x-amz-grant-write',\n\t'x-amz-grant-read-acp',\n\t'x-amz-grant-write-acp',\n\t'x-amz-grant-full-control',\n\t'x-amz-metadata-directive',\n\t'x-amz-copy-source',\n\t'x-amz-copy-source-if-match',\n\t'x-amz-copy-source-if-none-match',\n\t'x-amz-copy-source-if-unmodified-since',\n\t'x-amz-copy-source-if-modified-since',\n\t'x-amz-copy-source-range',\n\t// Standard HTTP headers that S3 uses\n\t'content-type',\n\t'content-length',\n\t'content-md5',\n\t'content-encoding',\n\t'content-disposition',\n\t'cache-control',\n\t'expires',\n\t'range',\n\t'if-match',\n\t'if-none-match',\n\t'if-modified-since',\n\t'if-unmodified-since',\n\t'user-agent',\n]);\n\n// Presigned URL parameters that should be stripped when re-signing\nconst PRESIGNED_PARAMS = new Set([\n\t'X-Amz-Algorithm',\n\t'X-Amz-Credential',\n\t'X-Amz-Date',\n\t'X-Amz-Expires',\n\t'X-Amz-SignedHeaders',\n\t'X-Amz-Signature',\n\t'X-Amz-Security-Token',\n]);\n\nexport const defaultGuardrailConfig: GuardrailConfig = {\n\tnoDeleteOld: [\n\t\t{\n\t\t\tpattern: '/.*',\n\t\t\tconfig: {\n\t\t\t\tnoDeleteBeforeSeconds: 60,\n\t\t\t},\n\t\t},\n\t],\n\tnoReplaceOld: [\n\t\t{\n\t\t\tpattern: '/.*',\n\t\t\tconfig: {\n\t\t\t\tnoReplaceBeforeSeconds: 60,\n\t\t\t},\n\t\t},\n\t],\n};\n\n/**\n * Handle an incoming S3 request with signature verification, guardrails, and proxying.\n *\n * @param request - The incoming HTTP request (must be a valid S3 API request)\n * @param options - S3Broker configuration options including credentials and guardrails\n * @returns Response from the upstream S3 service, or an error response if validation fails\n *\n * @example\n * ```typescript\n * import { handle } from 's3broker';\n *\n * const response = await handle(request, ctx, {\n * s3Endpoint: 'https://my-bucket.s3.amazonaws.com',\n * clientAccessKeyId: 'CLIENT_KEY',\n * clientSecretAccessKey: 'CLIENT_SECRET',\n * upstreamAccessKeyId: 'UPSTREAM_KEY',\n * upstreamSecretAccessKey: 'UPSTREAM_SECRET',\n * });\n * ```\n */\nexport async function handle(request: Request<unknown, IncomingRequestCfProperties>, options: S3BrokerOptions): Promise<Response> {\n\tconst currentTimestamp = Date.now();\n\n\t// Verify the incoming request signature (Client Key)\n\tconst verificationResult = await verifySignature(request, options.clientSecretAccessKey, options.clientAccessKeyId, currentTimestamp);\n\n\tif (!verificationResult.valid) {\n\t\treturn textErrorResponse(`Signature verification failed: ${verificationResult.error}`, ErrorCode.Forbidden);\n\t}\n\n\t// Parse the request URL\n\tconst url = new URL(request.url);\n\n\t// Evaluate guardrails\n\tconst guardrailUpstreamClient = new AwsClient({\n\t\taccessKeyId: options.upstreamAccessKeyId,\n\t\tsecretAccessKey: options.upstreamSecretAccessKey,\n\t\tretries: 5,\n\t});\n\n\tconst guardrailViolation = await evaluateGuardrails(\n\t\trequest,\n\t\tguardrailUpstreamClient,\n\t\toptions.s3Endpoint,\n\t\tcurrentTimestamp,\n\t\toptions.guardrailConfig || defaultGuardrailConfig,\n\t);\n\n\tif (guardrailViolation) {\n\t\tconsole.log(`Guardrail violation`, {\n\t\t\tpath: url.pathname,\n\t\t\tmethod: request.method,\n\t\t\tquery: url.search,\n\t\t\tpolicy: guardrailViolation.policy,\n\t\t\tviolation: guardrailViolation.violation,\n\t\t});\n\t\treturn textErrorResponse(\n\t\t\t`Request violating guardrail policy ${guardrailViolation.policy}: ${guardrailViolation.violation}`,\n\t\t\tErrorCode.Forbidden,\n\t\t);\n\t}\n\n\t// Get header modifiers for this path (e.g., managed SSE)\n\tconst guardrailConfig = options.guardrailConfig || defaultGuardrailConfig;\n\tconst headerModifiers = getHeaderModifiers(guardrailConfig, url.pathname);\n\n\t// Build upstream URL, stripping presigned parameters\n\tconst upstreamUrl = new URL(url.pathname, options.s3Endpoint);\n\tfor (const [key, value] of url.searchParams.entries()) {\n\t\tif (!PRESIGNED_PARAMS.has(key)) {\n\t\t\tupstreamUrl.searchParams.set(key, value);\n\t\t}\n\t}\n\n\t// Build base upstream headers (allowlist approach)\n\tlet upstreamHeaders = new Headers();\n\tfor (const [key, value] of request.headers.entries()) {\n\t\tif (HEADERS_TO_INCLUDE.has(key.toLowerCase())) {\n\t\t\tupstreamHeaders.set(key, value);\n\t\t}\n\t}\n\tupstreamHeaders.set('x-amz-content-sha256', 'UNSIGNED-PAYLOAD');\n\n\t// Apply header modifiers (e.g., inject managed SSE headers)\n\tfor (const modifier of headerModifiers) {\n\t\tupstreamHeaders = await modifier.modifyHeaders(request, upstreamHeaders);\n\t}\n\n\t// Sign and send to upstream\n\tconst proxyUpstreamAws = new AwsClient({\n\t\taccessKeyId: options.upstreamAccessKeyId,\n\t\tsecretAccessKey: options.upstreamSecretAccessKey,\n\t\tretries: 0,\n\t});\n\n\t// Helper to send request to upstream\n\tconst sendToUpstream = async (headers: Headers, body: ReadableStream<Uint8Array> | null): Promise<Response> => {\n\t\tconst upstreamRequest = new Request(upstreamUrl.toString(), {\n\t\t\tmethod: request.method,\n\t\t\theaders,\n\t\t\tbody,\n\t\t\t// @ts-ignore - duplex is needed for streaming bodies\n\t\t\tduplex: 'half',\n\t\t});\n\t\treturn proxyUpstreamAws.fetch(upstreamRequest);\n\t};\n\n\ttry {\n\t\t// Send initial request\n\t\tlet response = await sendToUpstream(upstreamHeaders, request.body);\n\n\t\t// Handle response with modifiers (for GET fallback on unencrypted files)\n\t\tfor (const modifier of headerModifiers) {\n\t\t\tif (modifier.handleResponse) {\n\t\t\t\tconst result = modifier.handleResponse(response, upstreamHeaders);\n\t\t\t\tif ('retryRequest' in result) {\n\t\t\t\t\t// Retry with modified headers (no body for GET/HEAD retry)\n\t\t\t\t\tresponse = await sendToUpstream(result.retryRequest, null);\n\t\t\t\t} else {\n\t\t\t\t\tresponse = result.modifiedResponse;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn response;\n\t} catch (error) {\n\t\tconsole.error('Upstream request failed:', error);\n\t\treturn textErrorResponse(\n\t\t\t`Upstream request failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t\tErrorCode.UpstreamFailure,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["text: string","errorCode: ErrorCode","fn: () => Promise<T>","maybeDonePromise: Promise<T> | null","request: Request<unknown, IncomingRequestCfProperties>","config: NoDeleteOldPolicyConfig","currentTimestampMs: number","metadata: () => Promise<Headers>","request: Request<unknown, IncomingRequestCfProperties>","config: NoReplaceOldPolicyConfig","currentTimestampMs: number","metadata: () => Promise<Headers>","policy: T","code: string","message: string","upstream: AwsClient","objectPath: string","sseHeaders?: { key: string; keyMd5: string }","headers: Record<string, string>","base64Key: string","config: ManagedSseConfig","request: Request<unknown, IncomingRequestCfProperties>","upstreamHeaders: Headers","upstreamResponse: Response","sentRequestHeaders: Headers","request: Request<unknown, IncomingRequestCfProperties>","upstreamFetcher: AwsClient","s3Endpoint: string","currentTimestampMs: number","config: GuardrailConfig","pattern: string","path: string","policies: { name: string; policy: GuardrailPolicy }[]","modifiers: HeaderModifier[]","defaultGuardrailConfig: GuardrailConfig","request: Request<unknown, IncomingRequestCfProperties>","options: S3BrokerOptions","headers: Headers","body: ReadableStream<Uint8Array> | null"],"sources":["../src/utils.ts","../src/guardrails/no-delete-old.ts","../src/guardrails/no-replace-old.ts","../src/guardrails/type.ts","../src/guardrails/s3_helper.ts","../src/sse/sse.ts","../src/guardrails/guardrails.ts","../src/index.ts"],"sourcesContent":["export enum ErrorCode {\n\tForbidden = 403,\n\tUpstreamFailure = 502,\n}\n\nexport function textErrorResponse(text: string, errorCode: ErrorCode): Response {\n\treturn new Response(text, { status: errorCode, headers: { 'Content-Type': 'text/plain' } });\n}\n\n/*\n * Given an async function, returns a function that will only call the provided function once, and return the same promise on subsequent calls.\n */\nexport function cached<T>(fn: () => Promise<T>): () => Promise<T> {\n\tlet maybeDonePromise: Promise<T> | null = null;\n\treturn () => {\n\t\tif (!maybeDonePromise) {\n\t\t\tmaybeDonePromise = fn();\n\t\t}\n\t\treturn maybeDonePromise;\n\t};\n}\n","import { GuardrailPolicy, GuardrailViolation, UpstreamError } from './type';\nimport { z } from 'zod';\n\nexport const NoDeleteOldPolicyConfig = z.object({\n\tnoDeleteBeforeSeconds: z.number().int(),\n});\n\nexport type NoDeleteOldPolicyConfig = z.infer<typeof NoDeleteOldPolicyConfig>;\n\n/**\n * Check if request is a deletion operation:\n * - DELETE method (single object deletion)\n * - POST with ?delete query param (bulk delete via DeleteObjects API)\n */\nfunction isDeletionRequest(request: Request<unknown, IncomingRequestCfProperties>): 'single' | 'bulk' | false {\n\tif (request.method === 'DELETE') {\n\t\treturn 'single';\n\t}\n\t// S3 DeleteObjects uses POST with ?delete query parameter\n\tif (request.method === 'POST') {\n\t\tconst url = new URL(request.url);\n\t\tif (url.searchParams.has('delete')) {\n\t\t\treturn 'bulk';\n\t\t}\n\t}\n\treturn false;\n}\n\nexport class NoDeleteOldPolicy implements GuardrailPolicy {\n\tprivate config: NoDeleteOldPolicyConfig;\n\tprivate currentTimestampMs: number;\n\n\tconstructor(config: NoDeleteOldPolicyConfig, currentTimestampMs: number) {\n\t\tthis.config = config;\n\t\tthis.currentTimestampMs = currentTimestampMs;\n\t}\n\n\tasync evaluate(\n\t\trequest: Request<unknown, IncomingRequestCfProperties>,\n\t\tmetadata: () => Promise<Headers>,\n\t): Promise<GuardrailViolation | null> {\n\t\tconst deleteType = isDeletionRequest(request);\n\n\t\t// Not a deletion request\n\t\tif (!deleteType) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Bulk delete (POST ?delete): Block entirely for protected paths\n\t\t// We cannot efficiently check the age of each object in the bulk request\n\t\t// without parsing the XML body, which would require buffering\n\t\tif (deleteType === 'bulk') {\n\t\t\treturn {\n\t\t\t\tviolation: `Bulk delete (DeleteObjects) is not allowed in protected paths. Use single object DELETE instead.`,\n\t\t\t};\n\t\t}\n\n\t\t// Single object DELETE: Check object age\n\t\ttry {\n\t\t\tconst headers = await metadata();\n\n\t\t\t// Get the Last-Modified header to determine object age\n\t\t\tconst lastModified = headers.get('Last-Modified');\n\t\t\tif (!lastModified) {\n\t\t\t\t// If no Last-Modified header, allow deletion (object might be newly created)\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst objectCreatedAtMs = new Date(lastModified).getTime();\n\t\t\tconst objectAgeMs = this.currentTimestampMs - objectCreatedAtMs;\n\t\t\tconst thresholdMs = this.config.noDeleteBeforeSeconds * 1000;\n\n\t\t\tif (objectAgeMs > thresholdMs) {\n\t\t\t\treturn {\n\t\t\t\t\tviolation: `Cannot delete object: object is ${Math.floor(objectAgeMs / 1000)} seconds old, which exceeds the ${this.config.noDeleteBeforeSeconds} seconds threshold`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t} catch (error) {\n\t\t\tif (error instanceof UpstreamError && error.code === '404') {\n\t\t\t\t// Object doesn't exist, allow deletion attempt (will fail at upstream)\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n","import { GuardrailPolicy, GuardrailViolation, UpstreamError } from './type';\nimport { z } from 'zod';\n\nexport const NoReplaceOldPolicyConfig = z.object({\n\tnoReplaceBeforeSeconds: z.number().int(),\n});\n\nexport type NoReplaceOldPolicyConfig = z.infer<typeof NoReplaceOldPolicyConfig>;\n\n/**\n * Check if request is a write/upload operation:\n * - PUT method (standard object upload)\n * - POST method without special query params (browser-based form upload)\n *\n * Note: POST with ?delete is handled by NoDeleteOldPolicy\n * Note: POST with ?uploads is multipart initiation (creates new, doesn't replace)\n */\nfunction isWriteRequest(request: Request<unknown, IncomingRequestCfProperties>): boolean {\n\tif (request.method === 'PUT') {\n\t\treturn true;\n\t}\n\t// POST without special query params is a form-based upload\n\tif (request.method === 'POST') {\n\t\tconst url = new URL(request.url);\n\t\t// Exclude bulk delete (handled by NoDeleteOldPolicy)\n\t\tif (url.searchParams.has('delete')) {\n\t\t\treturn false;\n\t\t}\n\t\t// Exclude multipart upload initiation (creates new object, doesn't replace)\n\t\tif (url.searchParams.has('uploads')) {\n\t\t\treturn false;\n\t\t}\n\t\t// Other POST requests (form uploads) can overwrite objects\n\t\treturn true;\n\t}\n\treturn false;\n}\n\nexport class NoReplaceOldPolicy implements GuardrailPolicy {\n\tprivate config: NoReplaceOldPolicyConfig;\n\tprivate currentTimestampMs: number;\n\n\tconstructor(config: NoReplaceOldPolicyConfig, currentTimestampMs: number) {\n\t\tthis.config = config;\n\t\tthis.currentTimestampMs = currentTimestampMs;\n\t}\n\n\tasync evaluate(\n\t\trequest: Request<unknown, IncomingRequestCfProperties>,\n\t\tmetadata: () => Promise<Headers>,\n\t): Promise<GuardrailViolation | null> {\n\t\t// Only applies to write/upload requests\n\t\tif (!isWriteRequest(request)) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\t// Check if object exists and get its metadata via HEAD request\n\t\t\t// This doesn't buffer the request body - just checks headers\n\t\t\tconst headers = await metadata();\n\n\t\t\t// Get the Last-Modified header to determine object age\n\t\t\tconst lastModified = headers.get('Last-Modified');\n\t\t\tif (!lastModified) {\n\t\t\t\t// If no Last-Modified header, allow replacement (object metadata issue)\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst objectCreatedAtMs = new Date(lastModified).getTime();\n\t\t\tconst objectAgeMs = this.currentTimestampMs - objectCreatedAtMs;\n\t\t\tconst thresholdMs = this.config.noReplaceBeforeSeconds * 1000;\n\n\t\t\tif (objectAgeMs > thresholdMs) {\n\t\t\t\treturn {\n\t\t\t\t\tviolation: `Cannot replace object: object is ${Math.floor(objectAgeMs / 1000)} seconds old, which exceeds the ${this.config.noReplaceBeforeSeconds} seconds threshold`,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn null;\n\t\t} catch (error) {\n\t\t\tif (error instanceof UpstreamError && error.code === '404') {\n\t\t\t\t// Object doesn't exist, allow upload (creating new object)\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n","import { z, ZodType } from 'zod';\nimport { NoDeleteOldPolicyConfig } from './no-delete-old';\nimport { NoReplaceOldPolicyConfig } from './no-replace-old';\n\n/**\n * Configuration for managed SSE-C (Server-Side Encryption with Customer-Provided Keys)\n */\nexport const ManagedSseConfig = z.object({\n\t// Base64-encoded 256-bit (32-byte) AES key\n\tkey: z.string(),\n});\nexport type ManagedSseConfig = z.infer<typeof ManagedSseConfig>;\n\n/**\n * Result of handling an upstream response.\n * Either retry with new headers, or return a (possibly modified) response.\n */\nexport type HandleResponseResult = { retryRequest: Headers } | { modifiedResponse: Response };\n\n/**\n * HeaderModifier modifies request headers before sending to upstream.\n * Used for SSE header injection and similar request transformations.\n */\nexport interface HeaderModifier {\n\t/**\n\t * Modify the upstream request headers.\n\t * Implementation can either mutate headers in place and return them,\n\t * or clone and return new headers.\n\t * @param request - The original incoming request\n\t * @param upstreamHeaders - The base headers built for upstream\n\t * @returns Modified headers to use for upstream request\n\t */\n\tmodifyHeaders(request: Request<unknown, IncomingRequestCfProperties>, upstreamHeaders: Headers): Promise<Headers>;\n\n\t/**\n\t * Handle response from upstream, optionally retrying with different headers.\n\t * @param upstreamResponse - Response from upstream\n\t * @param sentRequestHeaders - Headers that were sent in the request\n\t * @returns Either retry instructions or the final response\n\t */\n\thandleResponse?(upstreamResponse: Response, sentRequestHeaders: Headers): HandleResponseResult;\n}\n\nexport interface GuardrailPolicy {\n\tevaluate(request: Request<unknown, IncomingRequestCfProperties>, metadata: () => Promise<Headers>): Promise<GuardrailViolation | null>;\n}\n\nexport type GuardrailViolation = {\n\tviolation: string;\n};\n\n/*\n * Corresponding config for each object path pattern in regex. First match wins.\n * If config is null, the guardrail is disabled for that pattern.\n */\nexport const GuardrailPolicyConfigPerPattern = <T extends ZodType>(policy: T) =>\n\tz.array(\n\t\tz.object({\n\t\t\tpattern: z.string(),\n\t\t\tconfig: policy.nullable(),\n\t\t}),\n\t);\n/**\n * - `noDeleteOld`: Prevents deletion of old objects in protected paths\n * - `noReplaceOld`: Prevents replacement of old objects in protected paths\n * - `managedSse`: Automatically injects SSE-C headers for paths\n */\nexport const GuardrailConfig = z.object({\n\tnoDeleteOld: GuardrailPolicyConfigPerPattern(NoDeleteOldPolicyConfig).optional(),\n\tnoReplaceOld: GuardrailPolicyConfigPerPattern(NoReplaceOldPolicyConfig).optional(),\n\tmanagedSse: GuardrailPolicyConfigPerPattern(ManagedSseConfig).optional(),\n});\nexport type GuardrailConfig = z.infer<typeof GuardrailConfig>;\n\n/**\n * Error calling upstream when evaluating guardrails\n */\nexport class UpstreamError extends Error {\n\tcode: string;\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\t}\n}\n","import { AwsClient } from 'aws4fetch';\nimport { UpstreamError } from './type';\n\n/**\n * Issue a `HEAD` request to retrieve object metadata\n * @param upstream - AWS client for signing requests\n * @param objectPath - Full path to the object (including endpoint)\n * @param sseHeaders - Optional SSE-C headers for encrypted objects\n */\nexport async function getObjectMetadata(\n\tupstream: AwsClient,\n\tobjectPath: string,\n\tsseHeaders?: { key: string; keyMd5: string },\n): Promise<Headers> {\n\tconst headers: Record<string, string> = {};\n\tif (sseHeaders) {\n\t\theaders['x-amz-server-side-encryption-customer-algorithm'] = 'AES256';\n\t\theaders['x-amz-server-side-encryption-customer-key'] = sseHeaders.key;\n\t\theaders['x-amz-server-side-encryption-customer-key-md5'] = sseHeaders.keyMd5;\n\t}\n\n\tconst response = await upstream.fetch(objectPath, {\n\t\tmethod: 'HEAD',\n\t\theaders,\n\t});\n\tif (!response.ok) {\n\t\tthrow new UpstreamError(response.status.toString(), response.statusText);\n\t}\n\treturn response.headers;\n}\n","/**\n * Managed SSE-C (Server-Side Encryption with Customer-Provided Keys) module.\n *\n * Automatically injects SSE-C headers for configured paths, making encryption\n * seamless to clients.\n */\n\nimport { HeaderModifier, HandleResponseResult, ManagedSseConfig } from '../guardrails/type';\n\n// SSE-C header names\nconst SSE_ALGORITHM_HEADER = 'x-amz-server-side-encryption-customer-algorithm';\nconst SSE_KEY_HEADER = 'x-amz-server-side-encryption-customer-key';\nconst SSE_KEY_MD5_HEADER = 'x-amz-server-side-encryption-customer-key-md5';\n\n/**\n * Compute MD5 hash of a base64-encoded key and return as base64.\n */\nexport async function computeKeyMd5(base64Key: string): Promise<string> {\n\t// Decode base64 key to bytes\n\tconst keyBytes = Uint8Array.from(atob(base64Key), (c) => c.charCodeAt(0));\n\n\t// Compute MD5 hash using Web Crypto API\n\tconst hashBuffer = await crypto.subtle.digest('MD5', keyBytes);\n\n\t// Encode hash as base64\n\tconst hashArray = new Uint8Array(hashBuffer);\n\treturn btoa(String.fromCharCode(...hashArray));\n}\n\n/**\n * ManagedSseModifier implements HeaderModifier to automatically inject\n * SSE-C headers for PUT/GET/HEAD requests when the client hasn't provided their own.\n */\nexport class ManagedSseModifier implements HeaderModifier {\n\tprivate base64Key: string;\n\tprivate keyMd5Promise: Promise<string>;\n\n\tconstructor(config: ManagedSseConfig) {\n\t\tthis.base64Key = config.key;\n\t\t// Compute MD5 eagerly but cache the promise for reuse\n\t\tthis.keyMd5Promise = computeKeyMd5(config.key);\n\t}\n\n\tasync modifyHeaders(request: Request<unknown, IncomingRequestCfProperties>, upstreamHeaders: Headers): Promise<Headers> {\n\t\t// Skip if client already provided SSE headers (passthrough)\n\t\tif (upstreamHeaders.has(SSE_ALGORITHM_HEADER)) {\n\t\t\treturn upstreamHeaders;\n\t\t}\n\n\t\t// For methods that don't use SSE-C (e.g., DELETE, LIST), skip\n\t\t// Note: POST can be either form upload or other operations\n\t\tconst method = request.method.toUpperCase();\n\t\tif (method !== 'GET' && method !== 'HEAD' && method !== 'PUT' && method !== 'POST') {\n\t\t\treturn upstreamHeaders;\n\t\t}\n\n\t\t// Clone headers and add managed SSE headers\n\t\tconst headers = new Headers(upstreamHeaders);\n\t\theaders.set(SSE_ALGORITHM_HEADER, 'AES256');\n\t\theaders.set(SSE_KEY_HEADER, this.base64Key);\n\t\theaders.set(SSE_KEY_MD5_HEADER, await this.keyMd5Promise);\n\t\treturn headers;\n\t}\n\n\thandleResponse(upstreamResponse: Response, sentRequestHeaders: Headers): HandleResponseResult {\n\t\t// For GET/HEAD: If 400 error (wrong key), retry without SSE headers\n\t\t// This handles unencrypted legacy files in managed SSE paths\n\t\tif (upstreamResponse.status === 400) {\n\t\t\tconst retryHeaders = new Headers(sentRequestHeaders);\n\t\t\tretryHeaders.delete(SSE_ALGORITHM_HEADER);\n\t\t\tretryHeaders.delete(SSE_KEY_HEADER);\n\t\t\tretryHeaders.delete(SSE_KEY_MD5_HEADER);\n\t\t\treturn { retryRequest: retryHeaders };\n\t\t}\n\t\treturn { modifiedResponse: upstreamResponse };\n\t}\n}\n","import { AwsClient } from 'aws4fetch';\nimport { GuardrailConfig, GuardrailPolicy, GuardrailViolation, HeaderModifier, ManagedSseConfig, UpstreamError } from './type';\nimport { NoDeleteOldPolicy } from './no-delete-old';\nimport { NoReplaceOldPolicy } from './no-replace-old';\nimport { getObjectMetadata } from './s3_helper';\nimport { cached } from '../utils';\nimport { ManagedSseModifier, computeKeyMd5 } from '../sse/sse';\n\n/**\n * Evaluate guardrails for a given request\n * @param request The incoming request\n * @param config The guardrail configuration to use\n * @returns The first guardrail violation if the request violates a policy, null otherwise\n */\nexport async function evaluateGuardrails(\n\trequest: Request<unknown, IncomingRequestCfProperties>,\n\tupstreamFetcher: AwsClient,\n\ts3Endpoint: string,\n\tcurrentTimestampMs: number,\n\tconfig: GuardrailConfig,\n): Promise<(GuardrailViolation & { policy: string }) | null> {\n\tconst path = new URL(request.url).pathname;\n\tconst policies = getPolicies(config, path, currentTimestampMs);\n\n\tif (policies.length === 0) {\n\t\treturn null;\n\t}\n\n\t// Look up SSE config for this path (needed for HEAD request on encrypted objects)\n\tconst metadata = cached(async () => {\n\t\tconst sseConfig = getSseConfigForPath(config, path);\n\t\tconst sseHeaders = sseConfig ? { key: sseConfig.key, keyMd5: await computeKeyMd5(sseConfig.key) } : undefined;\n\t\treturn getObjectMetadata(upstreamFetcher, s3Endpoint + path, sseHeaders);\n\t});\n\n\t// Create evaluation promises that include policy name\n\tconst evalPromises = policies.map(({ name: policyName, policy }) => ({\n\t\tpolicyName,\n\t\tpromise: (async () => {\n\t\t\tconst violation = await policy.evaluate(request, metadata);\n\t\t\treturn violation ? { ...violation, policy: policyName } : null;\n\t\t})(),\n\t}));\n\n\t// Race until one returns a violation or all are done\n\t// Using Promise.race with a filter pattern to get first non-null result\n\treturn new Promise((resolve) => {\n\t\tlet pendingCount = evalPromises.length;\n\n\t\tfor (const { policyName, promise } of evalPromises) {\n\t\t\tpromise\n\t\t\t\t.then((result) => {\n\t\t\t\t\tif (result !== null) {\n\t\t\t\t\t\t// First violation wins\n\t\t\t\t\t\tresolve(result);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpendingCount--;\n\t\t\t\t\t\tif (pendingCount === 0) {\n\t\t\t\t\t\t\t// All policies passed\n\t\t\t\t\t\t\tresolve(null);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.catch((error) => {\n\t\t\t\t\t// If a policy throws, treat it as a violation\n\t\t\t\t\tlet message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tif (error instanceof UpstreamError) {\n\t\t\t\t\t\tmessage = `Error calling upstream when evaluating guardrail: ${message}`;\n\t\t\t\t\t}\n\t\t\t\t\tresolve({ violation: message, policy: policyName });\n\t\t\t\t});\n\t\t}\n\t});\n}\n\n/**\n * Create a regex that fully matches the path (not substring match).\n *\n * Pattern rules:\n * - Patterns are matched against the full path (anchored with ^ and $)\n * - If a pattern doesn't start with '/', it will be automatically prepended\n * - Do NOT include ^ at the start or $ at the end - they are added automatically\n *\n * Examples:\n * - '/bucket/tom/.*' matches '/bucket/tom/file.txt' but NOT '/alpha/bucket/tom/file.txt'\n * - 'bucket/.*' is equivalent to '/bucket/.*'\n *\n * @throws Error if pattern starts with ^ or ends with $\n */\nfunction createFullMatchRegex(pattern: string): RegExp {\n\t// Validate: user should not include anchors\n\tif (pattern.startsWith('^')) {\n\t\tthrow new Error(`Invalid pattern \"${pattern}\": do not include ^ at the start, patterns are automatically anchored`);\n\t}\n\tif (pattern.endsWith('$')) {\n\t\tthrow new Error(`Invalid pattern \"${pattern}\": do not include $ at the end, patterns are automatically anchored`);\n\t}\n\n\t// Normalize pattern: prepend '/' if missing\n\tconst normalizedPattern = pattern.startsWith('/') ? pattern : '/' + pattern;\n\t// Anchor the regex to match the entire path\n\treturn new RegExp(`^${normalizedPattern}$`);\n}\n\n/**\n * Get all applicable policies for a given path\n */\nexport function getPolicies(\n\tconfig: GuardrailConfig,\n\tpath: string,\n\tcurrentTimestampMs: number,\n): { name: string; policy: GuardrailPolicy }[] {\n\tconst policies: { name: string; policy: GuardrailPolicy }[] = [];\n\n\t// Check noDeleteOld policies - first matching pattern wins\n\t// If config is null, the guardrail is disabled for that pattern (exclude case)\n\tfor (const entry of config.noDeleteOld ?? []) {\n\t\tconst regex = createFullMatchRegex(entry.pattern);\n\t\tif (regex.test(path)) {\n\t\t\tif (entry.config !== null) {\n\t\t\t\tpolicies.push({\n\t\t\t\t\tname: 'noDeleteOld',\n\t\t\t\t\tpolicy: new NoDeleteOldPolicy(entry.config, currentTimestampMs),\n\t\t\t\t});\n\t\t\t}\n\t\t\tbreak; // First match wins - even if config is null, we don't check further patterns\n\t\t}\n\t}\n\n\t// Check noReplaceOld policies - first matching pattern wins\n\t// If config is null, the guardrail is disabled for that pattern (exclude case)\n\tfor (const entry of config.noReplaceOld ?? []) {\n\t\tconst regex = createFullMatchRegex(entry.pattern);\n\t\tif (regex.test(path)) {\n\t\t\tif (entry.config !== null) {\n\t\t\t\tpolicies.push({\n\t\t\t\t\tname: 'noReplaceOld',\n\t\t\t\t\tpolicy: new NoReplaceOldPolicy(entry.config, currentTimestampMs),\n\t\t\t\t});\n\t\t\t}\n\t\t\tbreak; // First match wins - even if config is null, we don't check further patterns\n\t\t}\n\t}\n\n\treturn policies;\n}\n\n/**\n * Get all applicable header modifiers for a given path\n */\nexport function getHeaderModifiers(config: GuardrailConfig, path: string): HeaderModifier[] {\n\tconst modifiers: HeaderModifier[] = [];\n\n\t// Check managedSse policies - first matching pattern wins\n\tfor (const entry of config.managedSse ?? []) {\n\t\tconst regex = createFullMatchRegex(entry.pattern);\n\t\tif (regex.test(path)) {\n\t\t\tif (entry.config !== null) {\n\t\t\t\tmodifiers.push(new ManagedSseModifier(entry.config));\n\t\t\t}\n\t\t\tbreak; // First match wins\n\t\t}\n\t}\n\n\treturn modifiers;\n}\n\n/**\n * Get the SSE config for a path, if any pattern matches\n */\nfunction getSseConfigForPath(config: GuardrailConfig, path: string): ManagedSseConfig | null {\n\tfor (const entry of config.managedSse ?? []) {\n\t\tconst regex = createFullMatchRegex(entry.pattern);\n\t\tif (regex.test(path)) {\n\t\t\treturn entry.config;\n\t\t}\n\t}\n\treturn null;\n}\n","/**\n * S3Broker - S3 Proxy Library for Cloudflare Workers\n *\n * ========== =========== ============\n * ||Client|| -- Key A --> ||S3Broker|| -- Key B --> ||Upstream||\n * ========== =========== ============\n *\n * S3Broker is a Cloudflare Workers library for building secure S3-compatible proxies.\n *\n * Features:\n * 1. Verifies incoming requests signed with Key A (client credentials)\n * 2. Enforces configurable guardrails policies (e.g., prevent deletion of recent objects)\n * 3. Re-signs requests with Key B (upstream credentials) for the upstream S3 service\n * 4. Proxies the request to any S3-compatible endpoint (AWS S3, Cloudflare R2, MinIO, etc.)\n */\n\nimport { AwsClient } from 'aws4fetch';\nimport { verifySignature } from './sigv4';\nimport { textErrorResponse, ErrorCode } from './utils';\nimport { evaluateGuardrails, getHeaderModifiers } from './guardrails/guardrails';\nimport type { S3BrokerOptions } from './types';\nimport { GuardrailConfig } from './guardrails/type';\n\n// Re-export types\nexport type { S3BrokerOptions } from './types';\nexport type { GuardrailConfig, GuardrailViolation } from './guardrails/type';\n// Re-export zod type\nexport { GuardrailConfig as GuardrailConfigZod } from './guardrails/type';\n\n// Headers that should be forwarded to upstream (allowlist approach)\nconst HEADERS_TO_INCLUDE = new Set([\n\t// S3-specific headers\n\t'x-amz-date',\n\t'x-amz-content-sha256',\n\t'x-amz-security-token',\n\t'x-amz-server-side-encryption',\n\t'x-amz-server-side-encryption-aws-kms-key-id',\n\t'x-amz-server-side-encryption-customer-algorithm',\n\t'x-amz-server-side-encryption-customer-key',\n\t'x-amz-server-side-encryption-customer-key-md5',\n\t'x-amz-storage-class',\n\t'x-amz-tagging',\n\t'x-amz-website-redirect-location',\n\t'x-amz-acl',\n\t'x-amz-grant-read',\n\t'x-amz-grant-write',\n\t'x-amz-grant-read-acp',\n\t'x-amz-grant-write-acp',\n\t'x-amz-grant-full-control',\n\t'x-amz-metadata-directive',\n\t'x-amz-copy-source',\n\t'x-amz-copy-source-if-match',\n\t'x-amz-copy-source-if-none-match',\n\t'x-amz-copy-source-if-unmodified-since',\n\t'x-amz-copy-source-if-modified-since',\n\t'x-amz-copy-source-range',\n\t// Standard HTTP headers that S3 uses\n\t'content-type',\n\t'content-length',\n\t'content-md5',\n\t'content-encoding',\n\t'content-disposition',\n\t'cache-control',\n\t'expires',\n\t'range',\n\t'if-match',\n\t'if-none-match',\n\t'if-modified-since',\n\t'if-unmodified-since',\n\t'user-agent',\n]);\n\n// Presigned URL parameters that should be stripped when re-signing\nconst PRESIGNED_PARAMS = new Set([\n\t'X-Amz-Algorithm',\n\t'X-Amz-Credential',\n\t'X-Amz-Date',\n\t'X-Amz-Expires',\n\t'X-Amz-SignedHeaders',\n\t'X-Amz-Signature',\n\t'X-Amz-Security-Token',\n]);\n\nexport const defaultGuardrailConfig: GuardrailConfig = {\n\tnoDeleteOld: [\n\t\t{\n\t\t\tpattern: '/.*',\n\t\t\tconfig: {\n\t\t\t\tnoDeleteBeforeSeconds: 60,\n\t\t\t},\n\t\t},\n\t],\n\tnoReplaceOld: [\n\t\t{\n\t\t\tpattern: '/.*',\n\t\t\tconfig: {\n\t\t\t\tnoReplaceBeforeSeconds: 60,\n\t\t\t},\n\t\t},\n\t],\n};\n\n/**\n * Handle an incoming S3 request with signature verification, guardrails, and proxying.\n *\n * @param request - The incoming HTTP request (must be a valid S3 API request)\n * @param options - S3Broker configuration options including credentials and guardrails\n * @returns Response from the upstream S3 service, or an error response if validation fails\n *\n * @example\n * ```typescript\n * import { handle } from 's3broker';\n *\n * const response = await handle(request, ctx, {\n * s3Endpoint: 'https://my-bucket.s3.amazonaws.com',\n * clientAccessKeyId: 'CLIENT_KEY',\n * clientSecretAccessKey: 'CLIENT_SECRET',\n * upstreamAccessKeyId: 'UPSTREAM_KEY',\n * upstreamSecretAccessKey: 'UPSTREAM_SECRET',\n * });\n * ```\n */\nexport async function handle(request: Request<unknown, IncomingRequestCfProperties>, options: S3BrokerOptions): Promise<Response> {\n\tconst currentTimestamp = Date.now();\n\n\t// Verify the incoming request signature (Client Key)\n\tconst verificationResult = await verifySignature(request, options.clientSecretAccessKey, options.clientAccessKeyId, currentTimestamp);\n\n\tif (!verificationResult.valid) {\n\t\treturn textErrorResponse(`Signature verification failed: ${verificationResult.error}`, ErrorCode.Forbidden);\n\t}\n\n\t// Parse the request URL\n\tconst url = new URL(request.url);\n\n\t// Evaluate guardrails\n\tconst guardrailUpstreamClient = new AwsClient({\n\t\taccessKeyId: options.upstreamAccessKeyId,\n\t\tsecretAccessKey: options.upstreamSecretAccessKey,\n\t\tretries: 5,\n\t});\n\n\tconst guardrailViolation = await evaluateGuardrails(\n\t\trequest,\n\t\tguardrailUpstreamClient,\n\t\toptions.s3Endpoint,\n\t\tcurrentTimestamp,\n\t\toptions.guardrailConfig || defaultGuardrailConfig,\n\t);\n\n\tif (guardrailViolation) {\n\t\tconsole.log(`Guardrail violation`, {\n\t\t\tpath: url.pathname,\n\t\t\tmethod: request.method,\n\t\t\tquery: url.search,\n\t\t\tpolicy: guardrailViolation.policy,\n\t\t\tviolation: guardrailViolation.violation,\n\t\t});\n\t\treturn textErrorResponse(\n\t\t\t`Request violating guardrail policy ${guardrailViolation.policy}: ${guardrailViolation.violation}`,\n\t\t\tErrorCode.Forbidden,\n\t\t);\n\t}\n\n\t// Get header modifiers for this path (e.g., managed SSE)\n\tconst guardrailConfig = options.guardrailConfig || defaultGuardrailConfig;\n\tconst headerModifiers = getHeaderModifiers(guardrailConfig, url.pathname);\n\n\t// Build upstream URL, stripping presigned parameters\n\tconst upstreamUrl = new URL(url.pathname, options.s3Endpoint);\n\tfor (const [key, value] of url.searchParams.entries()) {\n\t\tif (!PRESIGNED_PARAMS.has(key)) {\n\t\t\tupstreamUrl.searchParams.set(key, value);\n\t\t}\n\t}\n\n\t// Build base upstream headers (allowlist approach)\n\tlet upstreamHeaders = new Headers();\n\tfor (const [key, value] of request.headers.entries()) {\n\t\tif (HEADERS_TO_INCLUDE.has(key.toLowerCase())) {\n\t\t\tupstreamHeaders.set(key, value);\n\t\t}\n\t}\n\tupstreamHeaders.set('x-amz-content-sha256', 'UNSIGNED-PAYLOAD');\n\n\t// Apply header modifiers (e.g., inject managed SSE headers)\n\tfor (const modifier of headerModifiers) {\n\t\tupstreamHeaders = await modifier.modifyHeaders(request, upstreamHeaders);\n\t}\n\n\t// Sign and send to upstream\n\tconst proxyUpstreamAws = new AwsClient({\n\t\taccessKeyId: options.upstreamAccessKeyId,\n\t\tsecretAccessKey: options.upstreamSecretAccessKey,\n\t\tretries: 0,\n\t});\n\n\t// Helper to send request to upstream\n\tconst sendToUpstream = async (headers: Headers, body: ReadableStream<Uint8Array> | null): Promise<Response> => {\n\t\tconst upstreamRequest = new Request(upstreamUrl.toString(), {\n\t\t\tmethod: request.method,\n\t\t\theaders,\n\t\t\tbody,\n\t\t\t// @ts-ignore - duplex is needed for streaming bodies\n\t\t\tduplex: 'half',\n\t\t});\n\t\treturn proxyUpstreamAws.fetch(upstreamRequest);\n\t};\n\n\ttry {\n\t\t// Send initial request\n\t\tlet response = await sendToUpstream(upstreamHeaders, request.body);\n\n\t\t// Handle response with modifiers (for GET fallback on unencrypted files)\n\t\tfor (const modifier of headerModifiers) {\n\t\t\tif (modifier.handleResponse) {\n\t\t\t\tconst result = modifier.handleResponse(response, upstreamHeaders);\n\t\t\t\tif ('retryRequest' in result) {\n\t\t\t\t\t// Retry with modified headers (no body for GET/HEAD retry)\n\t\t\t\t\tresponse = await sendToUpstream(result.retryRequest, null);\n\t\t\t\t} else {\n\t\t\t\t\tresponse = result.modifiedResponse;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn response;\n\t} catch (error) {\n\t\tconsole.error('Upstream request failed:', error);\n\t\treturn textErrorResponse(\n\t\t\t`Upstream request failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t\tErrorCode.UpstreamFailure,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "s3broker",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "S3 proxy library with SigV4 verification and configurable guardrails policies",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",