diff --git a/src/observability/tracing/service-tracer.test.ts b/src/observability/tracing/service-tracer.test.ts index 8b0d01f318..f3e05b7339 100644 --- a/src/observability/tracing/service-tracer.test.ts +++ b/src/observability/tracing/service-tracer.test.ts @@ -607,6 +607,92 @@ describe("observability/tracing/service-tracer", () => { }); }); + it("serializes object attributes without inherited toJSON hooks", () => { + const originalObjectToJSON = Object.getOwnPropertyDescriptor(Object.prototype, "toJSON"); + const originalArrayToJSON = Object.getOwnPropertyDescriptor(Array.prototype, "toJSON"); + Object.defineProperty(Object.prototype, "toJSON", { + configurable: true, + value() { + throw new Error("polluted object serializer"); + }, + }); + Object.defineProperty(Array.prototype, "toJSON", { + configurable: true, + value() { + throw new Error("polluted array serializer"); + }, + }); + + try { + const harness = createHarness(); + const serviceTracer = createOpenTelemetryServiceTracer({ + serviceName: "test-service", + context: harness.contextApi, + trace: harness.traceApi, + errorStatusCode: 2, + }); + const span = serviceTracer.tracer.startSpan("manual-operation"); + + span.setTag("metadata", { + apiKey: "secret", + nested: [{ ok: true }], + }); + + assertEquals( + harness.startedSpans[0]?.attributes.metadata, + '{"apiKey":"[REDACTED]","nested":[{"ok":true}]}', + ); + } finally { + if (originalObjectToJSON) { + Object.defineProperty(Object.prototype, "toJSON", originalObjectToJSON); + } else { + delete (Object.prototype as { toJSON?: unknown }).toJSON; + } + if (originalArrayToJSON) { + Object.defineProperty(Array.prototype, "toJSON", originalArrayToJSON); + } else { + delete (Array.prototype as { toJSON?: unknown }).toJSON; + } + } + }); + + it("ignores hooks added through the intrinsic array prototype chain", () => { + const originalArrayPrototypeParent = Object.getPrototypeOf(Array.prototype); + let hookCalls = 0; + const hostileParent = Object.create(originalArrayPrototypeParent) as { + toJSON?: () => unknown; + }; + hostileParent.toJSON = () => { + hookCalls += 1; + return "polluted-array"; + }; + + Object.setPrototypeOf(Array.prototype, hostileParent); + try { + const harness = createHarness(); + const serviceTracer = createOpenTelemetryServiceTracer({ + serviceName: "test-service", + context: harness.contextApi, + trace: harness.traceApi, + errorStatusCode: 2, + }); + const span = serviceTracer.tracer.startSpan("manual-operation"); + + span.setTag("metadata", { + apiKey: "secret", + nested: [{ ok: true }], + }); + + assertEquals(hookCalls, 0); + assertEquals( + harness.startedSpans[0]?.attributes.metadata, + '{"apiKey":"[REDACTED]","nested":[{"ok":true}]}', + ); + } finally { + Object.setPrototypeOf(Array.prototype, originalArrayPrototypeParent); + } + }); + it("isolates manual span attribute and finish failures", () => { const harness = createHarness(); const serviceTracer = createOpenTelemetryServiceTracer({ diff --git a/src/observability/tracing/service-tracer.ts b/src/observability/tracing/service-tracer.ts index 6acd233a54..3df3e6ca88 100644 --- a/src/observability/tracing/service-tracer.ts +++ b/src/observability/tracing/service-tracer.ts @@ -1,4 +1,4 @@ -import { REDACTED, redactForSerialization } from "#veryfront/utils/logger/redact.ts"; +import { stringifyRedactedAttributeValue } from "#veryfront/utils/logger/serialization.ts"; import { MAX_SPAN_NAME_LENGTH } from "#veryfront/utils/constants/index.ts"; import { MAX_OBSERVABILITY_NAME_LENGTH, @@ -158,13 +158,7 @@ function toAttributeValue( } if (typeof value === "object") { - try { - const redacted = redactForSerialization(value); - if (typeof redacted === "string") return redacted; - return JSON.stringify(redacted) ?? REDACTED; - } catch (_) { - return REDACTED; - } + return stringifyRedactedAttributeValue(value); } return value; diff --git a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts index 65bb090cbb..7dd4d9ca3b 100644 --- a/src/server/services/rsc/endpoints/rsc-bundles.generated.ts +++ b/src/server/services/rsc/endpoints/rsc-bundles.generated.ts @@ -7,7 +7,7 @@ */ export const CLIENT_BOOT_BUNDLE: string = - 'var lt=Object.defineProperty;var dt=(e,t,r)=>t in e?lt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var m=(e,t,r)=>dt(e,typeof t!="symbol"?t+"":t,r);var Jo=Array.prototype.at,Zo=Array.prototype.filter,gt=Array.prototype.join,Qo=Array.prototype.map,ei=Array.prototype.pop,ft=Array.prototype.push,ti=Array.prototype.sort,he=Reflect.apply;function k(e,t){return he(gt,e,[t])}function O(e,t){he(ft,e,[t])}var pt="3.2.3",yt=Object.entries;function mt(e){let t=[];if(e?.external?.length&&O(t,`external=${k(e.external,",")}`),O(t,`target=${e?.target??"es2022"}`),e?.deps){let r=[],n=yt(e.deps);for(let i=0;it||r?.(n,...i)}debug(t,...r){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...r)}info(t,...r){this.log(1,console.log,`[${this.prefix}] ${t}`,...r)}warn(t,...r){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...r)}error(t,...r){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...r)}};function Dt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var F=Dt(),l=new T("RSC",F),Ii=new T("PREFETCH",F),Ni=new T("HYDRATE",F),Di=new T("VERYFRONT",F);var wt="veryfront-hydration-data";function se(e){try{let t=[...e.querySelectorAll(`[id="${wt}"]`)];if(t.length!==1)return null;let r=e.body;if(!r)return null;let n=t[0];return r.firstElementChild!==n&&n.parentElement!==r||n.tagName?.toLowerCase()!=="script"||n.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:n}catch{return null}}function S(e=document){try{let t=se(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return l.debug("hydration data parse failed",t),null}}function B(e,t){if(!t?.startsWith("on:"))return!1;try{let r=se(e);if(!r)return!1;let n=JSON.parse(r.textContent||"{}");return n.dependencyPinningCacheKey=t,r.textContent=JSON.stringify(n),!0}catch(r){return l.debug("hydration dependency snapshot seed failed",r),!1}}function G(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Mt(e,t){if(!t)return e;let r=e.includes("?")?"&":"?";return`${e}${r}v=${encodeURIComponent(t)}`}function j(e,t){if(!t?.startsWith("on:"))return e;let r=e.indexOf("#"),n=r===-1?"":e.slice(r),i=r===-1?e:e.slice(0,r),s=i.indexOf("?"),a=s===-1?i:i.slice(0,s),u=new URLSearchParams(s===-1?"":i.slice(s+1));u.set("pins",t);let d=u.toString();return`${a}${d?`?${d}`:""}${n}`}function Lt(e,t){return Mt(`${Ce}${ne(e)}.js`,t)}function Pt(e,t,r){let n=t?`&v=${encodeURIComponent(t)}`:"";return j(`${N}module?rel=${encodeURIComponent(e)}${n}`,r)}function w(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[V]:t}:{}}function Ht(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var Ut=/\\.(tsx|ts|jsx|mdx|js)$/;function vt(e){let t=Ht(e),r=[e,t];return Ut.test(t)||r.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(r))}function kt(e,t){if(!e)return null;for(let r of vt(t)){let n=e[r];if(n)return n}return null}function z(e){if(e.strategy==="fs"){let r=e.absPath??e.rel;return r?j(Lt(r,e.version),e.dependencyPinningCacheKey):null}let t=kt(e.releaseAssetModules,e.rel);return t||Pt(e.rel,e.version,e.dependencyPinningCacheKey)}function Y(e=document,t=I){let r=oe(e);return{react:$("react",r)?"react":xe(t),reactDomClient:$("react-dom/client",r)?"react-dom/client":Te(t)}}function be(e=document){let t=oe(e);return $("veryfront/router",t)?"veryfront/router":null}var $t=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Vt(e,...t){let r=Object.create(null),n=e.charAt(0).toUpperCase()+e.slice(1);for(let i of t)for(let[s,a]of Object.entries(i)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${n} entry "${s}" must define a slug`);let u=a.slug;if(typeof u!="string")throw new Error(`${n} entry "${s}" must define a string slug`);if(u!==s)throw new Error(`${n} key "${s}" does not match entry slug "${u}"`);if(Object.hasOwn(r,s))throw new Error(`Duplicate ${e} slug "${s}"`);r[s]=a}return Object.freeze(r)}function Oe(...e){for(let t of e)for(let r of Object.values(t)){if(typeof r.slug!="string"||r.slug.length<3||r.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(r.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${r.slug}"`);if(typeof r.category!="string"||!$t.has(r.category))throw new TypeError(`Registered error has unknown category "${r.category}"`);if(!Number.isInteger(r.status)||r.status<400||r.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${r.status}`);if(typeof r.title!="string"||r.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(r.suggestion!==void 0&&(typeof r.suggestion!="string"||r.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Vt("error registry",...e)}var K={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Bi={debug:K.gray,info:K.green,warn:K.yellow,error:K.red};var y="[REDACTED]",E=Reflect.apply;var Ie=RegExp.prototype.exec,x=RegExp.prototype[Symbol.replace],ji=String.prototype.charCodeAt,Ne=String.prototype.slice,Ft=String.prototype.toLowerCase,Bt=/[^a-z0-9]/g;function ae(e){let t=E(Ft,e,[]);return E(x,Bt,[t,""])}function W(e,t,r){return r===void 0?E(Ne,e,[t]):E(Ne,e,[t,r])}var Gt=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],jt=512,zt=128,M=new Map;function Me(e){let t=e.length<=zt;if(t){let i=M.get(e);if(i!==void 0)return i}let r=ae(e),n=Gt.some(i=>r.includes(i));if(t){if(M.size>=jt){let i=M.keys().next().value;i!==void 0&&M.delete(i)}M.set(e,n)}return n}var Yt=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Kt=new Set(Yt.map(ae)),Wt=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Xt=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,qt=3;function Jt(e){return e===" "||e==="\t"||e===","||e===";"||e==="&"||e==="?"||e==="#"}function Zt(e){if(!e)return!1;let t=e.charCodeAt(0);return t>=65&&t<=90||t>=97&&t<=122}function Le(e){return Zt(e)||e==="_"||e==="$"}function Qt(e){if(!e)return!1;let t=e.charCodeAt(0);return Le(e)||t>=48&&t<=57||e==="."||e==="-"}function Pe(e,t){let r=t,n=e[r]===\'"\'||e[r]==="\'"?e[r++]:"";if(!Le(e[r]))return!1;for(r++;Qt(e[r]);)r++;if(n){if(e[r]!==n)return!1;r++}for(;e[r]===" "||e[r]==="\t";)r++;return e[r]===":"||e[r]==="="}function He(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||Jt(e)}function Ue(e,t){let r=t;for(;r=e.length||Pe(e,r)}function er(e,t){let r=t,n=!0;if(e.startsWith(y,t)){let g=t+y.length;if(De(e,g))return{end:g,replacement:y};r=g,n=!1}let i=n&&(e[r]===\'"\'||e[r]==="\'"||e[r]==="`")?e[r]:"",s=!1,a=()=>i?`${i}${y}${s?i:""}`:y,u=[],d="",c=-1;for(let g=r;g0&&(f==="}"||f==="]")){if(u.at(-1)!==f)return{end:e.length,replacement:a()};if(u.pop(),g++,u.length===0&&De(e,g))return{end:g,replacement:a()};continue}if(u.length>0||!He(f)){g++;continue}let R=g;if(g=Ue(e,g),g>=e.length||Pe(e,g))return{end:R,replacement:a()}}return{end:e.length,replacement:a()}}function we(e,t,r,n){let i=0,s="";for(let a=E(Ie,t,[e]);a;a=E(Ie,t,[e])){let u=a[r];if(!Me(u))continue;let d=t.lastIndex,c=n===void 0?void 0:a[n],g=d+y.length;if((c==="?"||c==="&"||c===";")&&e.startsWith(y,d)&&e[g]==="#")continue;let f=er(e,d);s+=W(e,i,a.index),s+=a[0],s+=f.replacement,i=f.end,t.lastIndex=f.end}return i===0?e:s+W(e,i)}function tr(e,t,r){let n=r.search(/[ \\t]/);if(n<0)return!1;let i=`${t}:${W(r,0,n)}`,s=e==="//"?`https://${i}`:`${e}${i}`;try{let a=new URL(s);return a.username.length===0&&a.password.length===0}catch{return!1}}function rr(e){let t=e;for(let r=0;r{let s=i.indexOf(":");if(s===-1)return`${n}${y}@`;let a=W(i,0,s);return`${n}${a}:${y}@`}]);return t=E(x,Xt,[t,(r,n,i,s)=>tr(n,i,s)?r:`${n}${i}:${y}@`]),t=E(x,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[t,(r,n,i,s)=>{let a=rr(i);return Kt.has(ae(a))||Me(a)?`${n}${i}=${y}`:r}]),t=E(x,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[t,(r,n,i)=>`${n}${i}${y}`]),t=E(x,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[t,(r,n)=>`${n}${y}`]),t=E(x,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[t,(r,n,i)=>`${n}${i}${y}`]),t=we(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=we(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var nr=2048;var Xi=64*1024,or=256,ir="https://veryfront.com/docs/errors/",ve="...[truncated]",ue="unknown-error";function ke(e,t){if(e.length<=t)return e;let r=Math.max(0,t-ve.length);return`${sr(e,r)}${ve}`}function sr(e,t){let r=e.slice(0,t),n=r.charCodeAt(r.length-1);return n>=55296&&n<=56319&&(r=r.slice(0,-1)),r}function ar(e){let t="";for(let r=0;r=55296&&n<=56319){let i=e.charCodeAt(r+1);i>=56320&&i<=57343?(t+=e.slice(r,r+2),r++):t+="\\uFFFD";continue}t+=n>=56320&&n<=57343?"\\uFFFD":e.charAt(r)}return t}function A(e){return typeof e!="string"?y:ke(ce(e),nr)}function cr(e){let t=typeof e=="string"?ce(e):ue,r=ke(t||ue,or),n=ar(r);return n==="."||n===".."?ue:n}function X(e){let t=encodeURIComponent(cr(e));return`${ir}${t}`}var ur=Object.freeze,lr=Object.getOwnPropertyDescriptors,$e=Number.isFinite,Fe=new WeakSet,dr=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function o(e){let t={...e},r={...t,create(n){let i=n?.message,s=n?.detail,a=n?.cause,u=n?.instance,d=n?.context,c=n?.status??t.status;return new le(i||s||t.title,{slug:t.slug,category:t.category,status:c,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:s,cause:a,instance:u,context:d})}};return ur(r)}var le=class extends Error{constructor(r,n){super(r);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");Fe.add(this),this.name="VeryfrontError",this.slug=n.slug,this.category=n.category,this.status=n.status,this.title=n.title,this.suggestion=n.suggestion,this.exitCode=n.exitCode,this.detail=n.detail,this.cause=n.cause,this.instance=n.instance,this.context=n.context}toRFC9457(){let r=Ve(this);return r?{type:X(r.slug),title:A(r.title),status:r.status,detail:r.detail===void 0?void 0:A(r.detail),instance:r.instance===void 0?void 0:A(r.instance),category:r.category,suggestion:r.suggestion===void 0?void 0:A(r.suggestion),cause:typeof r.cause=="string"?A(r.cause):void 0}:{type:X("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let r=Ve(this);return X(r?.slug??"unknown-error")}};function Be(e){return typeof e=="object"&&e!==null&&Fe.has(e)}function Ve(e){return Be(e)?gr(e):null}function gr(e){try{if(!Be(e))return null;let t=lr(e),r=Q=>{let b=t[Q];return b&&"value"in b?b.value:void 0},n=r("slug"),i=r("category"),s=r("status"),a=r("title"),u=r("message"),d=r("suggestion"),c=r("exitCode"),g=r("detail"),f=r("cause"),R=r("instance"),H=r("context"),h=r("stack");return typeof n!="string"||!dr.has(i)||typeof s!="number"||!$e(s)||typeof a!="string"||typeof u!="string"||d!==void 0&&typeof d!="string"||c!==void 0&&(typeof c!="number"||!$e(c))||g!==void 0&&typeof g!="string"||R!==void 0&&typeof R!="string"||h!==void 0&&typeof h!="string"?null:{slug:n,category:i,status:s,title:a,message:u,suggestion:d,exitCode:c,detail:g,cause:f,instance:R,context:H,stack:h}}catch{return null}}var fr=o({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),pr=o({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),yr=o({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),mr=o({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),Er=o({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),Rr=o({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),hr=o({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),_r=o({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),xr=o({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),Tr=o({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),Sr=o({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Ge={"config-not-found":fr,"config-invalid":pr,"config-parse-error":yr,"config-validation-error":mr,"config-type-error":Er,"import-map-invalid":Rr,"cors-config-invalid":hr,"config-validation-failed":_r,"webhook-config-invalid":xr,"schedule-config-invalid":Tr,"trigger-config-invalid":Sr};var Ar=o({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),Cr=o({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),br=o({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),Or=o({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),Ir=o({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),Nr=o({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),Dr=o({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),wr=o({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),je={"build-failed":Ar,"bundle-error":Cr,"typescript-error":br,"mdx-compile-error":Or,"asset-optimization-error":Ir,"ssg-generation-error":Nr,"sourcemap-error":Dr,"compilation-error":wr};var Mr=o({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),Lr=o({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),Pr=o({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Hr=o({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),Ur=o({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),vr=o({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),kr=o({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),$r=o({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),Vr=o({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),Fr=o({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ze={"hydration-mismatch":Mr,"render-error":Lr,"component-error":Pr,"layout-not-found":Hr,"page-not-found":Ur,"api-error":vr,"middleware-error":kr,"trigger-target-not-found":$r,"trigger-execution-failed":Vr,"trigger-not-supported":Fr};var Br=o({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Gr=o({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),jr=o({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),zr=o({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),Yr=o({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),Kr=o({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),Ye={"route-conflict":Br,"invalid-route-file":Gr,"route-handler-invalid":jr,"dynamic-route-error":zr,"route-params-error":Yr,"api-route-error":Kr};var Wr=o({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Xr=o({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),qr=o({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),Jr=o({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),Zr=o({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Qr=o({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),Ke={"module-not-found":Wr,"import-resolution-error":Xr,"circular-dependency":qr,"invalid-import":Jr,"dependency-missing":Zr,"version-mismatch":Qr};var en=o({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),tn=o({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),rn=o({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),nn=o({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),on=o({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),sn=o({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),an=o({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),cn=o({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),un=o({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),ln=o({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),dn=o({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),gn=o({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),fn=o({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),pn=o({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),yn=o({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),mn=o({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),En=o({slug:"rag-store-corrupt",category:"SERVER",status:500,title:"RAG store file is corrupt",suggestion:"Repair or move the store file aside, then retry; it was not overwritten"}),Rn=o({slug:"rag-store-unavailable",category:"SERVER",status:500,title:"RAG store file is unavailable",suggestion:"Check storage availability, permissions, and concurrent operations, then retry"}),We={"port-in-use":en,"server-start-error":tn,"cache-error":rn,"file-watch-error":nn,"request-error":on,"service-overloaded":sn,"project-execution-unavailable":an,"semaphore-timeout":cn,"circuit-breaker-open":un,"cache-path-mismatch":ln,"network-error":dn,"api-client-error":gn,"token-storage-error":fn,"cache-invariant-violation":pn,"release-not-found":yn,"fallback-exhausted":mn,"rag-store-corrupt":En,"rag-store-unavailable":Rn};var hn=o({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),_n=o({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),xn=o({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),Tn=o({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),Sn=o({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),An=o({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),Cn=o({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),Xe={"client-boundary-violation":hn,"server-only-in-client":_n,"client-only-in-server":xn,"invalid-use-client":Tn,"invalid-use-server":Sn,"rsc-payload-error":An,"ssr-output-limit-exceeded":Cn};var bn=o({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),On=o({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),In=o({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),Nn=o({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),Dn=o({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),qe={"hmr-error":bn,"dev-server-error":On,"fast-refresh-error":In,"error-overlay-error":Nn,"source-map-error":Dn};var wn=o({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),Mn=o({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),Ln=o({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),Pn=o({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),Hn=o({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),Un=o({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),vn=o({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),kn=o({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),$n=o({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),Vn=o({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),Fn=o({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),Bn=o({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),Je={"deployment-error":wn,"platform-error":Mn,"env-var-missing":Ln,"production-build-required":Pn,"environment-not-found":Hn,"release-missing-version":Un,"release-build-timeout":vn,"deployment-verification-timeout":kn,"push-receipt-missing":$n,"source-digest-mismatch":Vn,"preview-hostname-too-long":Fn,"branch-not-found":Bn};var Gn=o({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),jn=o({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),zn=o({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),Yn=o({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),Kn=o({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),Wn=o({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Xn=o({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),qn=o({slug:"durable-run-event-persistence-failed",category:"AGENT",status:500,title:"Durable run event persistence failed",suggestion:"Correct invalid or oversized event data, or retry after durable event storage recovers"}),Ze={"agent-error":Gn,"agent-not-found":jn,"agent-timeout":zn,"agent-intent-error":Yn,"orchestration-error":Kn,"cost-limit-exceeded":Wn,"tool-id-conflict":Xn,"durable-run-event-persistence-failed":qn};var Jn=o({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),Zn=o({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Qn=o({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),eo=o({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),to=o({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),ro=o({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),no=o({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),oo=o({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),io=o({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),de=o({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),so=o({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),ao=o({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),Qe={"unknown-error":Jn,"authentication-required":Zn,"permission-denied":Qn,"file-not-found":eo,"resource-not-found":to,"invalid-argument":ro,"timeout-error":no,"initialization-error":oo,"not-supported":io,"security-violation":de,"input-validation-failed":so,"project-source-empty":ao};var vs=Oe(Ge,je,ze,Ye,Ke,We,Xe,qe,Je,Ze,Qe);var co=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function uo(){return co.map(({source:e,flags:t,name:r})=>({pattern:new RegExp(e,t),name:r}))}function lo(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function L(e,t={}){let{allowInlineScripts:r=!1,strict:n=!1,warn:i=!0}=t;for(let{pattern:s,name:a}of uo())if(!(r&&a==="inline script")&&(s.lastIndex=0,!!s.test(e)&&(i&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),n||!lo())))throw de.create({detail:`Potentially unsafe HTML: ${a} detected`});return e}function P(e,t){let r=t==="root"?D:`rsc-slot-${t}`,n=e.getElementById(r);if(n)return n;let i=e.createElement("div");return i.id=r,e.body.appendChild(i),i}function go(e,t){if(t.type!=="slot")return;let r=P(e,t.id);r.innerHTML=L(String(t.html??""))}function et(e,t){let r=t.split(`\n`),n=r.pop()??"";for(let i of r){let s=i.trim();if(!s)continue;let a;try{a=JSON.parse(s)}catch(d){l.debug("[client-dom] malformed NDJSON line",{line:s,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let u=a;if(u.type==="slot"){go(e,u);try{yo(e,u.id||"root")}catch(d){l.debug("[client-dom] hydration optional failed",d)}}}return n}function fo(e){return new Promise((t,r)=>{let n=()=>r(new DOMException("aborted","AbortError"));if(e.aborted){n();return}e.addEventListener("abort",n,{once:!0})})}async function tt(e,t=document,r){let n="body"in e?e:null,i=n?.body??e;if(!i)return;n&&B(t,n.headers.get(V));let s=i.getReader(),a=new TextDecoder,u="",d=!1;try{for(;;){if(r?.aborted)throw new DOMException("aborted","AbortError");let c=s.read(),{done:g,value:f}=r?await Promise.race([c,fo(r)]):await c;if(g){d=!0;break}u+=a.decode(f,{stream:!0}),u=et(t,u)}u&&et(t,`${u}\n`)}catch(c){throw c instanceof Error&&c.name==="AbortError"||l.debug("[client-dom] consumeNdjsonStream error",c),c}finally{try{await s.cancel()}catch(c){d||l.debug("[client-dom] reader.cancel failed",c)}try{s.releaseLock()}catch(c){l.debug("[client-dom] reader.releaseLock failed",c)}if(typeof i.cancel=="function")try{await i.cancel()}catch(c){l.debug("[client-dom] stream.cancel failed",c)}if(typeof n?.body?.cancel=="function")try{await n.body.cancel()}catch(c){l.debug("[client-dom] response.body.cancel failed",c)}}}function po(e,t){let r=P(e,t),n=[],i=s=>{let a=s;a.dataset?.clientRef&&n.push(a);for(let u of s.children)i(u)};return i(r),n}function yo(e,t){let r=po(e,t);for(let n of r){let i=n.dataset?.clientRef;i&&(n.dataset.hydrated="true",l.debug("[client-dom] marked for hydration",i))}}var mo=new Set(["server","client","html","fragment"]);function rt(e){if(!e)return[];try{let t=JSON.parse(e);return Ro(t)?t.nodes:[]}catch{return[]}}async function fe(e,t,r){return await Promise.all(e.map(n=>Eo(n,t,r)))}async function Eo(e,t,r){if(e.type==="html")return e.text??e.html??"";let n=await fe(e.children??[],t,r);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...n);if(e.type==="server")return t.createElement(e.component,e.props??{},...n);let i=await r(e.component);return i?t.createElement(i,e.props??{},...n):null}function Ro(e){return!ge(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>nt(t,0))}function nt(e,t){return t>100||!ge(e)||!mo.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!ge(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(r=>nt(r,t+1))}function ge(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function ho(e){if(!e)return{};let t={};for(let[r,n]of Object.entries(e))t[r]=Array.isArray(n)?n.join("/"):n;return t}async function q(e,t,r=document){try{let n=be(r);if(!n)return e;let s=(await import(n)).wrapForHydration;return typeof s!="function"?e:s(e,{params:ho(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(n){return l.debug("router provider wrap failed",n),e}}var _o="Unknown dependency snapshot",xo="export default null; // Unknown dependency snapshot",pe="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function To(){return globalThis}async function So(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===_o||t===xo}catch{return!1}}async function C(e,t=()=>globalThis.location.reload()){if(!await So(e))return!1;let r=To();if(r[pe])return!0;r[pe]=!0;try{t()}catch{return delete r[pe],!1}return!0}async function J(e,t=globalThis.fetch,r=()=>globalThis.location.reload()){try{let n=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(n.length!==1||!n[0]?.startsWith("on:"))return!1;let i=await t(e,{cache:"no-store"});return await C(i,r)}catch{return!1}}var Ao=100;function Co(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=Ao){let r=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;r&&globalThis.__VF_CLIENT_MOD_CACHE.delete(r)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function ot(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let r=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return r?{moduleUrl:r[1],exportName:r[2]||"default"}:(l.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function bo(e){let t=e.dataset?.rscProps;if(!t)return{};try{let r=JSON.parse(t);return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}catch(r){return l.debug("hydrate: invalid client boundary props, using empty props",r),{}}}function Oo(e){return rt(e.dataset?.rscChildren)}function Io(e){return"/_veryfront/rsc/manifest"}function No(e){return w(e)}async function Do(e=document){try{let t=S(e),r=await fetch(Io(t),{headers:No(t)});return r.ok?await r.json():(await C(r),null)}catch{return null}}async function it(e,t,r,n={}){let i=wo(e,t,r,n.releaseAssetModules),s=t.moduleUrl??t.rel;if(!s)return null;let a=`${s}#${e.hash??""}`;try{let u=globalThis.__VF_CLIENT_MOD_CACHE?.get(a);if(u)return u}catch(u){l.debug("hydrate: cache get failed",u)}if(!i)return null;try{let u=await(n.importModule??(d=>import(d)))(i);try{Co(a,u)}catch(d){l.debug("hydrate: cache set failed",d)}return u}catch(u){return l.debug("hydrate: failed to import module",{moduleUrl:i,error:u}),await(n.recoverSnapshotFailure??J)(i),null}}function wo(e,t,r,n){if(t.moduleUrl)return j(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let i=e.graphIds?.client.find(s=>s.rel===t.rel)?.path;return z({strategy:r,rel:t.rel,absPath:i,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:n})}function Mo(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),r=new Set(t);return t.filter(n=>{let i=n.parentElement;for(;i;){if(r.has(i))return!1;i=i.parentElement}return!0})}async function st(e=document){let t=null;try{t=await Do(e)}catch(c){l.debug("hydrate: fetch manifest failed",c)}if(!t){l.debug("hydrate: no manifest");return}let r=Mo(e);try{let c=globalThis.__VF_MANIFEST_HASH;if(!r.some(f=>f.dataset?.hydrated!=="true")&&c&&t.hash&&c===t.hash)return}catch(c){l.debug("hydrate: hmr hash read failed",c)}if(r.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){l.debug("hydrate: set hash failed",c)}return}let n=S(e),i=G(n),s=n?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(c){l.debug("hydrate: test mode flags failed",c)}let a=Y(e,n?.reactVersion),[{default:u},{createRoot:d}]=await Promise.all([import(a.react),import(a.reactDomClient)]);for(let c of r){let g=c.dataset?.clientRef??"";if(!g||c.dataset?.hydrated==="true")continue;let f=ot(g);if(!f)continue;let R=await it(t,f,i,{releaseAssetModules:s});if(!R)continue;let H=R[f.exportName]??R.default;if(typeof H=="function")try{let h=d(c),Q=bo(c),b=Oo(c),at=await fe(b,{Fragment:u.Fragment,createElement(U,ee,...v){return u.createElement(U,ee,...v)}},async U=>{let ee=t.modules.find(ut=>ut.id===U),v=t.components?.[U],Ee=ee?.clientRef??(v?`${v}#default`:void 0);if(!Ee)return null;let te=ot(Ee);if(!te)return null;let re=await it(t,te,i,{releaseAssetModules:s});if(!re)return null;let Re=re[te.exportName]??re.default;return typeof Re=="function"?Re:null}),ct=await q(u.createElement(H,Q,...at),n,e);h.render(ct),c.dataset.hydrated="true"}catch(h){l.warn("hydrate: render failed",h)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(c){l.debug("hydrate: set hash failed (post)",c)}}var ye="data-vf-react-head-owner";var Lo=2*1024*1024,ya=Lo*2;var ma=64*1024,Ea=1024*1024,Ra=1024*1024;var ha=new TextEncoder;async function Po(){let e=S(document),t=Y(document,e?.reactVersion),[r,n]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:r,ReactDOM:n}}var Ho=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function me(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||Ho.has(e.tagName.toUpperCase())}function Uo(e,t){return e.find(r=>r.tagName.toUpperCase()==="DIV"&&!!r.getAttribute("class")?.trim()&&!me(r))??t}function vo(e,t){return e===t}function ko(e,t){let r=document.createElement("div");r.setAttribute("data-veryfront-hydration-root","page");let n=e.find(i=>!me(i));n?.parentNode===t?t.insertBefore(r,n):t.appendChild(r);for(let i of e)!me(i)&&i.parentNode===t&&r.appendChild(i);return r}function $o(e,t){for(let r of e){let n=[...r.hasAttribute(ye)?[r]:[],...r.querySelectorAll(`[${ye}]`)];for(let i of n)t.contains(i)||i.remove()}}function Vo(e,t,r=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!r.getElementById("root")}function Fo(e,t){return t?.pagePath?!1:!!e.getElementById(D)}function Bo(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function Go(e){return e==="rsc-module"}function jo(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function zo(e,t,r){return z({strategy:t,rel:e,releaseAssetModules:r?.releaseAssetModules,dependencyPinningCacheKey:r?.dependencyPinningCacheKey})}async function Yo(e,t){try{let r=await fetch(N+"stream"+e,{headers:w(t)});if(!r.ok)return await C(r)?"snapshot-conflict":"failure";if(!r.body)return"failure";let n=new AbortController;return addEventListener("pagehide",()=>n.abort(),{once:!0}),await tt(r,document,n.signal),"success"}catch(r){return l.debug("tryStream failed",r),"failure"}}async function Z(){try{await st(document)}catch(e){l.debug("hydration failed",e)}}async function Ko(e,t,r){try{let{React:n,ReactDOM:i}=await Po(),s=zo(e,t,r);if(!s)return!1;l.debug("Loading component from:",s);let a;try{a=await import(s)}catch(R){throw await J(s),R}let u=a.default;if(typeof u!="function")return l.debug("Page component is not a function"),!1;let d=Array.from(document.body.children),c=Uo(d,document.body),g=vo(c,document.body)?ko(d,document.body):c;$o(d,g);let f=await q(n.createElement(u,{}),r);return Go(t)?i.createRoot(g).render(f):i.hydrateRoot(g,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),l.debug("Page component hydrated successfully"),!0}catch(n){return l.error("Page hydration failed",n),!1}}async function Wo(e,t){try{let r=await fetch(N+"payload"+e,{headers:w(t)});if(!r.ok)return await C(r)?"snapshot-conflict":"failure";let n=await r.json();if(B(document,n?.dependencyPinningCacheKey),n?.slots){for(let[i,s]of Object.entries(n.slots))P(document,i).innerHTML=L(String(s||""));return"success"}return P(document,D).innerHTML=L(String(n?.html||"")),"success"}catch(r){return l.debug("payload fetch failed",r),"failure"}}async function Xo(){try{let e=S(document),t=jo(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(Bo()){await Z();return}let r=e?.pagePath,n=G(e);if(r){if(Vo(globalThis.window,e,document)){l.debug("Page renderer owns hydration");return}l.debug("Found page component in hydration data:",r),await Ko(r,n,e)&&l.debug("Client component hydrated successfully");return}if(!Fo(document,e))return;let i=await Yo(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await Z();return}let s=await Wo(t,e);if(s==="snapshot-conflict")return;if(s==="success"){await Z();return}await Z()}catch(e){l.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{Xo()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{Xo as boot,zo as buildPageHydrationModuleUrl,jo as buildRSCTransportQuery,$o as retireAbandonedHeadOwnerMarkers,Uo as selectHydrationRoot,Fo as shouldAttemptRSCTransport,Bo as shouldHydrateOnly,Go as shouldRenderPageComponent,Vo as shouldUsePageRendererHydration,vo as shouldWrapPageHydrationRoot};\n'; + 'var Rt=Object.defineProperty;var ht=(e,t,r)=>t in e?Rt(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var E=(e,t,r)=>ht(e,typeof t!="symbol"?t+"":t,r);var Ns=Array.prototype.at,Ds=Array.prototype.filter,_t=Array.prototype.join,ws=Array.prototype.map,Ms=Array.prototype.pop,xt=Array.prototype.push,Ps=Array.prototype.sort,Ae=Reflect.apply;function G(e,t){return Ae(_t,e,[t])}function w(e,t){Ae(xt,e,[t])}var Tt="3.2.3",St=Object.entries;function At(e){let t=[];if(e?.external?.length&&w(t,`external=${G(e.external,",")}`),w(t,`target=${e?.target??"es2022"}`),e?.deps){let r=[],n=St(e.deps);for(let o=0;ot||r?.(n,...o)}debug(t,...r){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${t}`,...r)}info(t,...r){this.log(1,console.log,`[${this.prefix}] ${t}`,...r)}warn(t,...r){this.log(2,console.warn,`[${this.prefix}] WARN: ${t}`,...r)}error(t,...r){this.log(3,console.error,`[${this.prefix}] ERROR: ${t}`,...r)}};function kt(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var z=kt(),g=new C("RSC",z),ii=new C("PREFETCH",z),ai=new C("HYDRATE",z),ci=new C("VERYFRONT",z);var $t="veryfront-hydration-data";function le(e){try{let t=[...e.querySelectorAll(`[id="${$t}"]`)];if(t.length!==1)return null;let r=e.body;if(!r)return null;let n=t[0];return r.firstElementChild!==n&&n.parentElement!==r||n.tagName?.toLowerCase()!=="script"||n.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:n}catch{return null}}function b(e=document){try{let t=le(e);return t?JSON.parse(t.textContent||"{}"):null}catch(t){return g.debug("hydration data parse failed",t),null}}function Y(e,t){if(!t?.startsWith("on:"))return!1;try{let r=le(e);if(!r)return!1;let n=JSON.parse(r.textContent||"{}");return n.dependencyPinningCacheKey=t,r.textContent=JSON.stringify(n),!0}catch(r){return g.debug("hydration dependency snapshot seed failed",r),!1}}function K(e){return e?.clientModuleStrategy?e.clientModuleStrategy:e?.dev?"fs":"rsc-module"}function Vt(e,t){if(!t)return e;let r=e.includes("?")?"&":"?";return`${e}${r}v=${encodeURIComponent(t)}`}function W(e,t){if(!t?.startsWith("on:"))return e;let r=e.indexOf("#"),n=r===-1?"":e.slice(r),o=r===-1?e:e.slice(0,r),i=o.indexOf("?"),a=i===-1?o:o.slice(0,i),c=new URLSearchParams(i===-1?"":o.slice(i+1));c.set("pins",t);let l=c.toString();return`${a}${l?`?${l}`:""}${n}`}function Ft(e,t){return Vt(`${De}${ae(e)}.js`,t)}function Gt(e,t,r){let n=t?`&v=${encodeURIComponent(t)}`:"";return W(`${P}module?rel=${encodeURIComponent(e)}${n}`,r)}function H(e){let t=e?.dependencyPinningCacheKey;return t?.startsWith("on:")?{[j]:t}:{}}function Bt(e){return e.replace(/^\\/+_vf_modules\\//,"").replace(/^\\/+/,"").replace(/\\.js$/,"")}var jt=/\\.(tsx|ts|jsx|mdx|js)$/;function zt(e){let t=Bt(e),r=[e,t];return jt.test(t)||r.push(`${t}.tsx`,`${t}.ts`,`${t}.jsx`,`${t}.mdx`,`${t}.js`),Array.from(new Set(r))}function Yt(e,t){if(!e)return null;for(let r of zt(t)){let n=e[r];if(n)return n}return null}function X(e){if(e.strategy==="fs"){let r=e.absPath??e.rel;return r?W(Ft(r,e.version),e.dependencyPinningCacheKey):null}let t=Yt(e.releaseAssetModules,e.rel);return t||Gt(e.rel,e.version,e.dependencyPinningCacheKey)}function q(e=document,t=M){let r=ce(e);return{react:B("react",r)?"react":be(t),reactDomClient:B("react-dom/client",r)?"react-dom/client":Oe(t)}}function we(e=document){let t=ce(e);return B("veryfront/router",t)?"veryfront/router":null}var Kt=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Wt(e,...t){let r=Object.create(null),n=e.charAt(0).toUpperCase()+e.slice(1);for(let o of t)for(let[i,a]of Object.entries(o)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${n} entry "${i}" must define a slug`);let c=a.slug;if(typeof c!="string")throw new Error(`${n} entry "${i}" must define a string slug`);if(c!==i)throw new Error(`${n} key "${i}" does not match entry slug "${c}"`);if(Object.hasOwn(r,i))throw new Error(`Duplicate ${e} slug "${i}"`);r[i]=a}return Object.freeze(r)}function Me(...e){for(let t of e)for(let r of Object.values(t)){if(typeof r.slug!="string"||r.slug.length<3||r.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(r.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${r.slug}"`);if(typeof r.category!="string"||!Kt.has(r.category))throw new TypeError(`Registered error has unknown category "${r.category}"`);if(!Number.isInteger(r.status)||r.status<400||r.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${r.status}`);if(typeof r.title!="string"||r.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(r.suggestion!==void 0&&(typeof r.suggestion!="string"||r.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Wt("error registry",...e)}var J={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},_i={debug:J.gray,info:J.green,warn:J.yellow,error:J.red};var m="[REDACTED]",y=Reflect.apply,Xt=Array.prototype.pop,qt=Array.prototype.push;var Ti=Array.prototype,Si=BigInt.prototype.toString,ve=Map,Jt=Map.prototype.delete,Zt=Map.prototype.get,Qt=Map.prototype.keys,er=Map.prototype.set;var _=Object.getOwnPropertyDescriptor,tr=Object.getPrototypeOf,Ai=Object.hasOwn,Ci=Object.prototype,rr=Set,nr=decodeURIComponent,A=URL,bi=Number.isFinite,Oi=Number.isInteger,de=RegExp.prototype.exec,or=_(RegExp.prototype,"global").get,sr=_(RegExp.prototype,"unicode").get,ir=String.prototype.charCodeAt,ar=String.prototype.includes,cr=String.prototype.indexOf,Pe=String.prototype.slice,ke=String.prototype.startsWith,$e=String.prototype.toLowerCase,ur=Set.prototype.add,Ii=Set.prototype.delete,lr=Set.prototype.has,dr=tr(new ve().keys()).next,gr=_(Map.prototype,"size").get,Ni=_(A.prototype,"host").get,Di=_(A.prototype,"origin").get,fr=_(A.prototype,"password").get,wi=_(A.prototype,"pathname").get,Mi=_(A.prototype,"protocol").get,pr=_(A.prototype,"username").get,yr=/[^a-z0-9]/g,mr=/([a-z0-9])([A-Z])/g,Er=/([A-Z])([A-Z][a-z])/g,Rr=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function h(e,t,r){let n=y(or,t,[]),o=y(sr,t,[]),i=0,a=!1,c="";t.lastIndex=0;try{for(;;){let l=y(de,t,[e]);if(l===null)break;let u=l[0],d=l.index;if(c+=S(e,i,d),c+=typeof r=="string"?r:r(l),i=d+u.length,a=!0,!n)break;u.length===0&&(t.lastIndex=hr(e,d,o))}}finally{t.lastIndex=0}return a?c+S(e,i):e}function ge(e){let t=y($e,e,[]);return h(t,yr,"")}function O(e,t){return y(ir,e,[t])}function hr(e,t,r){let n=t+1;if(!r||n>=e.length)return n;let o=O(e,t);if(o<55296||o>56319)return n;let i=O(e,n);return i>=56320&&i<=57343?t+2:n}function S(e,t,r){return r===void 0?y(Pe,e,[t]):y(Pe,e,[t,r])}function _r(e){let t=[],r=0;for(let n=0;n<=e.length;n++){let o=n===e.length?-1:O(e,n);o>=97&&o<=122||o>=48&&o<=57||(n>r&&(t[t.length]=S(e,r,n)),r=n+1)}return t}var Z=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],xr=512,Tr=128,U=new ve;var Sr=256;function Ar(e){let t=e.length<=Tr;if(t){let o=y(Zt,U,[e]);if(o!==void 0)return o}let r=ge(e),n=r==="auth";for(let o=0;!n&&o=xr){let i=y(Qt,U,[]),a=y(dr,i,[]).value;a!==void 0&&y(Jt,U,[a])}y(er,U,[e,n])}return n}var Le=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],Ve=new rr;for(let e=0;e=65&&t<=90||t>=97&&t<=122}function Fe(e){return Nr(e)||e==="_"||e==="$"}function Dr(e){if(!e)return!1;let t=O(e,0);return Fe(e)||t>=48&&t<=57||e==="."||e==="-"}function Ge(e,t){let r=t,n=e[r]===\'"\'||e[r]==="\'"?e[r++]:"";if(!Fe(e[r]))return!1;for(r++;Dr(e[r]);)r++;if(n){if(e[r]!==n)return!1;r++}for(;e[r]===" "||e[r]==="\t";)r++;return e[r]===":"||e[r]==="="}function Be(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||Ir(e)}function je(e,t){let r=t;for(;r=e.length||Ge(e,r)}function wr(e,t){let r=t,n=!0;if(y(ke,e,[m,t])){let d=t+m.length;if(He(e,d))return{end:d,replacement:m};r=d,n=!1}let o=n&&(e[r]===\'"\'||e[r]==="\'"||e[r]==="`")?e[r]:"",i=!1,a=()=>o?`${o}${m}${i?o:""}`:m,c=[],l="",u=-1;for(let d=r;d0&&(f==="}"||f==="]")){if(c[c.length-1]!==f)return{end:e.length,replacement:a()};if(y(Xt,c,[]),d++,c.length===0&&He(e,d))return{end:d,replacement:a()};continue}if(c.length>0||!Be(f)){d++;continue}let R=d;if(d=je(e,d),d>=e.length||Ge(e,d))return{end:R,replacement:a()}}return{end:e.length,replacement:a()}}function Ue(e,t,r,n){let o=0,i="";for(let a=y(de,t,[e]);a;a=y(de,t,[e])){let c=a[r];if(!Mr(c))continue;let l=t.lastIndex,u=n===void 0?void 0:a[n],d=l+m.length;if((u==="?"||u==="&"||u===";")&&y(ke,e,[m,l])&&e[d]==="#")continue;let f=wr(e,l);i+=S(e,o,a.index),i+=a[0],i+=f.replacement,o=f.end,t.lastIndex=f.end}return o===0?e:i+S(e,o)}function Mr(e){if(e.length>Sr)return!0;let t=h(e,Er,i=>`${i[1]} ${i[2]}`),r=h(t,mr,i=>`${i[1]} ${i[2]}`),n=y($e,r,[]),o=_r(n);for(let i=0;i{let n=r[1],o=r[2],i=y(cr,o,[":"]);if(i===-1)return`${n}${m}@`;let a=S(o,0,i);return`${n}${a}:${m}@`});return t=h(t,br,r=>{let n=r[1],o=r[2],i=r[3];return Pr(n,o,i)?r[0]:`${n}${o}:${m}@`}),t=h(t,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,r=>{let n=r[1],o=r[2],i=Lr(o);return y(lr,Ve,[ge(i)])||Ar(i)?`${n}${o}=${m}`:r[0]}),t=h(t,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,r=>`${r[1]}${r[2]}${m}`),t=h(t,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,r=>`${r[1]}${m}`),t=h(t,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,r=>`${r[1]}${r[2]}${m}`),t=h(t,Rr,m),t=Ue(t,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),t=Ue(t,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),t}var Hr=2048;var vi=64*1024,Ur=256,vr="https://veryfront.com/docs/errors/",ze="...[truncated]",pe="unknown-error";function Ye(e,t){if(e.length<=t)return e;let r=Math.max(0,t-ze.length);return`${kr(e,r)}${ze}`}function kr(e,t){let r=e.slice(0,t),n=r.charCodeAt(r.length-1);return n>=55296&&n<=56319&&(r=r.slice(0,-1)),r}function $r(e){let t="";for(let r=0;r=55296&&n<=56319){let o=e.charCodeAt(r+1);o>=56320&&o<=57343?(t+=e.slice(r,r+2),r++):t+="\\uFFFD";continue}t+=n>=56320&&n<=57343?"\\uFFFD":e.charAt(r)}return t}function I(e){return typeof e!="string"?m:Ye(fe(e),Hr)}function Vr(e){let t=typeof e=="string"?fe(e):pe,r=Ye(t||pe,Ur),n=$r(r);return n==="."||n===".."?pe:n}function Q(e){let t=encodeURIComponent(Vr(e));return`${vr}${t}`}var Fr=Object.freeze,Gr=Object.getOwnPropertyDescriptors,Ke=Number.isFinite,Xe=new WeakSet,Br=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function s(e){let t={...e},r={...t,create(n){let o=n?.message,i=n?.detail,a=n?.cause,c=n?.instance,l=n?.context,u=n?.status??t.status;return new ye(o||i||t.title,{slug:t.slug,category:t.category,status:u,title:t.title,suggestion:t.suggestion,exitCode:t.exitCode,detail:i,cause:a,instance:c,context:l})}};return Fr(r)}var ye=class extends Error{constructor(r,n){super(r);E(this,"slug");E(this,"category");E(this,"status");E(this,"title");E(this,"suggestion");E(this,"exitCode");E(this,"detail");E(this,"cause");E(this,"instance");E(this,"context");Xe.add(this),this.name="VeryfrontError",this.slug=n.slug,this.category=n.category,this.status=n.status,this.title=n.title,this.suggestion=n.suggestion,this.exitCode=n.exitCode,this.detail=n.detail,this.cause=n.cause,this.instance=n.instance,this.context=n.context}toRFC9457(){let r=We(this);return r?{type:Q(r.slug),title:I(r.title),status:r.status,detail:r.detail===void 0?void 0:I(r.detail),instance:r.instance===void 0?void 0:I(r.instance),category:r.category,suggestion:r.suggestion===void 0?void 0:I(r.suggestion),cause:typeof r.cause=="string"?I(r.cause):void 0}:{type:Q("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let r=We(this);return Q(r?.slug??"unknown-error")}};function qe(e){return typeof e=="object"&&e!==null&&Xe.has(e)}function We(e){return qe(e)?jr(e):null}function jr(e){try{if(!qe(e))return null;let t=Gr(e),r=ne=>{let D=t[ne];return D&&"value"in D?D.value:void 0},n=r("slug"),o=r("category"),i=r("status"),a=r("title"),c=r("message"),l=r("suggestion"),u=r("exitCode"),d=r("detail"),f=r("cause"),R=r("instance"),$=r("context"),x=r("stack");return typeof n!="string"||!Br.has(o)||typeof i!="number"||!Ke(i)||typeof a!="string"||typeof c!="string"||l!==void 0&&typeof l!="string"||u!==void 0&&(typeof u!="number"||!Ke(u))||d!==void 0&&typeof d!="string"||R!==void 0&&typeof R!="string"||x!==void 0&&typeof x!="string"?null:{slug:n,category:o,status:i,title:a,message:c,suggestion:l,exitCode:u,detail:d,cause:f,instance:R,context:$,stack:x}}catch{return null}}var zr=s({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),Yr=s({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),Kr=s({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),Wr=s({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),Xr=s({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),qr=s({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),Jr=s({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),Zr=s({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),Qr=s({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),en=s({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),tn=s({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Je={"config-not-found":zr,"config-invalid":Yr,"config-parse-error":Kr,"config-validation-error":Wr,"config-type-error":Xr,"import-map-invalid":qr,"cors-config-invalid":Jr,"config-validation-failed":Zr,"webhook-config-invalid":Qr,"schedule-config-invalid":en,"trigger-config-invalid":tn};var rn=s({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),nn=s({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),on=s({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),sn=s({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),an=s({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),cn=s({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),un=s({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),ln=s({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),Ze={"build-failed":rn,"bundle-error":nn,"typescript-error":on,"mdx-compile-error":sn,"asset-optimization-error":an,"ssg-generation-error":cn,"sourcemap-error":un,"compilation-error":ln};var dn=s({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),gn=s({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),fn=s({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),pn=s({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),yn=s({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),mn=s({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),En=s({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),Rn=s({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),hn=s({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),_n=s({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),Qe={"hydration-mismatch":dn,"render-error":gn,"component-error":fn,"layout-not-found":pn,"page-not-found":yn,"api-error":mn,"middleware-error":En,"trigger-target-not-found":Rn,"trigger-execution-failed":hn,"trigger-not-supported":_n};var xn=s({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Tn=s({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),Sn=s({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),An=s({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),Cn=s({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),bn=s({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),et={"route-conflict":xn,"invalid-route-file":Tn,"route-handler-invalid":Sn,"dynamic-route-error":An,"route-params-error":Cn,"api-route-error":bn};var On=s({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),In=s({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),Nn=s({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),Dn=s({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),wn=s({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Mn=s({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),tt={"module-not-found":On,"import-resolution-error":In,"circular-dependency":Nn,"invalid-import":Dn,"dependency-missing":wn,"version-mismatch":Mn};var Pn=s({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),Ln=s({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),Hn=s({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),Un=s({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),vn=s({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),kn=s({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),$n=s({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),Vn=s({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),Fn=s({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),Gn=s({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),Bn=s({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),jn=s({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),zn=s({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),Yn=s({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),Kn=s({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),Wn=s({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),Xn=s({slug:"rag-store-corrupt",category:"SERVER",status:500,title:"RAG store file is corrupt",suggestion:"Repair or move the store file aside, then retry; it was not overwritten"}),qn=s({slug:"rag-store-unavailable",category:"SERVER",status:500,title:"RAG store file is unavailable",suggestion:"Check storage availability, permissions, and concurrent operations, then retry"}),rt={"port-in-use":Pn,"server-start-error":Ln,"cache-error":Hn,"file-watch-error":Un,"request-error":vn,"service-overloaded":kn,"project-execution-unavailable":$n,"semaphore-timeout":Vn,"circuit-breaker-open":Fn,"cache-path-mismatch":Gn,"network-error":Bn,"api-client-error":jn,"token-storage-error":zn,"cache-invariant-violation":Yn,"release-not-found":Kn,"fallback-exhausted":Wn,"rag-store-corrupt":Xn,"rag-store-unavailable":qn};var Jn=s({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),Zn=s({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),Qn=s({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),eo=s({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),to=s({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),ro=s({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),no=s({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),nt={"client-boundary-violation":Jn,"server-only-in-client":Zn,"client-only-in-server":Qn,"invalid-use-client":eo,"invalid-use-server":to,"rsc-payload-error":ro,"ssr-output-limit-exceeded":no};var oo=s({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),so=s({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),io=s({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),ao=s({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),co=s({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),ot={"hmr-error":oo,"dev-server-error":so,"fast-refresh-error":io,"error-overlay-error":ao,"source-map-error":co};var uo=s({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),lo=s({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),go=s({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),fo=s({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),po=s({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),yo=s({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),mo=s({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),Eo=s({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),Ro=s({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),ho=s({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),_o=s({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),xo=s({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),st={"deployment-error":uo,"platform-error":lo,"env-var-missing":go,"production-build-required":fo,"environment-not-found":po,"release-missing-version":yo,"release-build-timeout":mo,"deployment-verification-timeout":Eo,"push-receipt-missing":Ro,"source-digest-mismatch":ho,"preview-hostname-too-long":_o,"branch-not-found":xo};var To=s({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),So=s({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),Ao=s({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),Co=s({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),bo=s({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),Oo=s({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Io=s({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),No=s({slug:"durable-run-event-persistence-failed",category:"AGENT",status:500,title:"Durable run event persistence failed",suggestion:"Correct invalid or oversized event data, or retry after durable event storage recovers"}),it={"agent-error":To,"agent-not-found":So,"agent-timeout":Ao,"agent-intent-error":Co,"orchestration-error":bo,"cost-limit-exceeded":Oo,"tool-id-conflict":Io,"durable-run-event-persistence-failed":No};var Do=s({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),wo=s({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),Mo=s({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Po=s({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),Lo=s({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),Ho=s({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Uo=s({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),vo=s({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),ko=s({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),me=s({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),$o=s({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Vo=s({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),at={"unknown-error":Do,"authentication-required":wo,"permission-denied":Mo,"file-not-found":Po,"resource-not-found":Lo,"invalid-argument":Ho,"timeout-error":Uo,"initialization-error":vo,"not-supported":ko,"security-violation":me,"input-validation-failed":$o,"project-source-empty":Vo};var Ca=Me(Je,Ze,Qe,et,tt,rt,nt,ot,st,it,at);var Fo=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function Go(){return Fo.map(({source:e,flags:t,name:r})=>({pattern:new RegExp(e,t),name:r}))}function Bo(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function v(e,t={}){let{allowInlineScripts:r=!1,strict:n=!1,warn:o=!0}=t;for(let{pattern:i,name:a}of Go())if(!(r&&a==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(o&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),n||!Bo())))throw me.create({detail:`Potentially unsafe HTML: ${a} detected`});return e}function k(e,t){let r=t==="root"?L:`rsc-slot-${t}`,n=e.getElementById(r);if(n)return n;let o=e.createElement("div");return o.id=r,e.body.appendChild(o),o}function jo(e,t){if(t.type!=="slot")return;let r=k(e,t.id);r.innerHTML=v(String(t.html??""))}function ct(e,t){let r=t.split(`\n`),n=r.pop()??"";for(let o of r){let i=o.trim();if(!i)continue;let a;try{a=JSON.parse(i)}catch(l){g.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!a||typeof a!="object")continue;let c=a;if(c.type==="slot"){jo(e,c);try{Ko(e,c.id||"root")}catch(l){g.debug("[client-dom] hydration optional failed",l)}}}return n}function zo(e){return new Promise((t,r)=>{let n=()=>r(new DOMException("aborted","AbortError"));if(e.aborted){n();return}e.addEventListener("abort",n,{once:!0})})}async function ut(e,t=document,r){let n="body"in e?e:null,o=n?.body??e;if(!o)return;n&&Y(t,n.headers.get(j));let i=o.getReader(),a=new TextDecoder,c="",l=!1;try{for(;;){if(r?.aborted)throw new DOMException("aborted","AbortError");let u=i.read(),{done:d,value:f}=r?await Promise.race([u,zo(r)]):await u;if(d){l=!0;break}c+=a.decode(f,{stream:!0}),c=ct(t,c)}c&&ct(t,`${c}\n`)}catch(u){throw u instanceof Error&&u.name==="AbortError"||g.debug("[client-dom] consumeNdjsonStream error",u),u}finally{try{await i.cancel()}catch(u){l||g.debug("[client-dom] reader.cancel failed",u)}try{i.releaseLock()}catch(u){g.debug("[client-dom] reader.releaseLock failed",u)}if(typeof o.cancel=="function")try{await o.cancel()}catch(u){g.debug("[client-dom] stream.cancel failed",u)}if(typeof n?.body?.cancel=="function")try{await n.body.cancel()}catch(u){g.debug("[client-dom] response.body.cancel failed",u)}}}function Yo(e,t){let r=k(e,t),n=[],o=i=>{let a=i;a.dataset?.clientRef&&n.push(a);for(let c of i.children)o(c)};return o(r),n}function Ko(e,t){let r=Yo(e,t);for(let n of r){let o=n.dataset?.clientRef;o&&(n.dataset.hydrated="true",g.debug("[client-dom] marked for hydration",o))}}var Wo=new Set(["server","client","html","fragment"]);function lt(e){if(!e)return[];try{let t=JSON.parse(e);return qo(t)?t.nodes:[]}catch{return[]}}async function Re(e,t,r){return await Promise.all(e.map(n=>Xo(n,t,r)))}async function Xo(e,t,r){if(e.type==="html")return e.text??e.html??"";let n=await Re(e.children??[],t,r);if(e.type==="fragment"||e.type==="server"&&!e.component)return t.createElement(t.Fragment,{},...n);if(e.type==="server")return t.createElement(e.component,e.props??{},...n);let o=await r(e.component);return o?t.createElement(o,e.props??{},...n):null}function qo(e){return!Ee(e)||e.version!==1||!Array.isArray(e.nodes)?!1:e.nodes.every(t=>dt(t,0))}function dt(e,t){return t>100||!Ee(e)||!Wo.has(e.type)||e.type==="html"&&typeof e.html!="string"&&typeof e.text!="string"||e.type==="client"&&typeof e.component!="string"||e.type==="server"&&e.component!==void 0&&typeof e.component!="string"||e.props!==void 0&&!Ee(e.props)?!1:e.children===void 0?!0:Array.isArray(e.children)&&e.children.every(r=>dt(r,t+1))}function Ee(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Jo(e){if(!e)return{};let t={};for(let[r,n]of Object.entries(e))t[r]=Array.isArray(n)?n.join("/"):n;return t}async function ee(e,t,r=document){try{let n=we(r);if(!n)return e;let i=(await import(n)).wrapForHydration;return typeof i!="function"?e:i(e,{params:Jo(t?.params),frontmatter:t?.frontmatter??{},data:t?.props??{}})}catch(n){return g.debug("router provider wrap failed",n),e}}var Zo="Unknown dependency snapshot",Qo="export default null; // Unknown dependency snapshot",he="__VF_DEPENDENCY_SNAPSHOT_RECOVERY_STARTED__";function es(){return globalThis}async function ts(e){if(e.status!==409)return!1;try{let t=(await e.clone().text()).trim();return t===Zo||t===Qo}catch{return!1}}async function N(e,t=()=>globalThis.location.reload()){if(!await ts(e))return!1;let r=es();if(r[he])return!0;r[he]=!0;try{t()}catch{return delete r[he],!1}return!0}async function te(e,t=globalThis.fetch,r=()=>globalThis.location.reload()){try{let n=new URL(e,"http://veryfront.local").searchParams.getAll("pins");if(n.length!==1||!n[0]?.startsWith("on:"))return!1;let o=await t(e,{cache:"no-store"});return await N(o,r)}catch{return!1}}var rs=100;function ns(e,t){if(globalThis.__VF_CLIENT_MOD_CACHE??(globalThis.__VF_CLIENT_MOD_CACHE=new Map),globalThis.__VF_CLIENT_MOD_CACHE.size>=rs){let r=globalThis.__VF_CLIENT_MOD_CACHE.keys().next().value;r&&globalThis.__VF_CLIENT_MOD_CACHE.delete(r)}globalThis.__VF_CLIENT_MOD_CACHE.set(e,t)}function gt(e){let t=e.match(/^\\/app\\/(.+)#([\\w$.-]+)$/);if(t)return{rel:`/${t[1]||""}`,exportName:t[2]||"default"};let r=e.match(/^(\\/_veryfront\\/[^#]+)#([\\w$.-]+)$/);return r?{moduleUrl:r[1],exportName:r[2]||"default"}:(g.debug("hydrate: unrecognised client ref format, skipping",{ref:e}),null)}function os(e){let t=e.dataset?.rscProps;if(!t)return{};try{let r=JSON.parse(t);return r&&typeof r=="object"&&!Array.isArray(r)?r:{}}catch(r){return g.debug("hydrate: invalid client boundary props, using empty props",r),{}}}function ss(e){return lt(e.dataset?.rscChildren)}function is(e){return"/_veryfront/rsc/manifest"}function as(e){return H(e)}async function cs(e=document){try{let t=b(e),r=await fetch(is(t),{headers:as(t)});return r.ok?await r.json():(await N(r),null)}catch{return null}}async function ft(e,t,r,n={}){let o=us(e,t,r,n.releaseAssetModules),i=t.moduleUrl??t.rel;if(!i)return null;let a=`${i}#${e.hash??""}`;try{let c=globalThis.__VF_CLIENT_MOD_CACHE?.get(a);if(c)return c}catch(c){g.debug("hydrate: cache get failed",c)}if(!o)return null;try{let c=await(n.importModule??(l=>import(l)))(o);try{ns(a,c)}catch(l){g.debug("hydrate: cache set failed",l)}return c}catch(c){return g.debug("hydrate: failed to import module",{moduleUrl:o,error:c}),await(n.recoverSnapshotFailure??te)(o),null}}function us(e,t,r,n){if(t.moduleUrl)return W(t.moduleUrl,e.dependencyPinningCacheKey);if(!t.rel)return null;let o=e.graphIds?.client.find(i=>i.rel===t.rel)?.path;return X({strategy:r,rel:t.rel,absPath:o,version:e.hash,dependencyPinningCacheKey:e.dependencyPinningCacheKey,releaseAssetModules:n})}function ls(e){let t=Array.from(e.querySelectorAll("[data-client-ref]")),r=new Set(t);return t.filter(n=>{let o=n.parentElement;for(;o;){if(r.has(o))return!1;o=o.parentElement}return!0})}async function pt(e=document){let t=null;try{t=await cs(e)}catch(u){g.debug("hydrate: fetch manifest failed",u)}if(!t){g.debug("hydrate: no manifest");return}let r=ls(e);try{let u=globalThis.__VF_MANIFEST_HASH;if(!r.some(f=>f.dataset?.hydrated!=="true")&&u&&t.hash&&u===t.hash)return}catch(u){g.debug("hydrate: hmr hash read failed",u)}if(r.length===0){try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(u){g.debug("hydrate: set hash failed",u)}return}let n=b(e),o=K(n),i=n?.releaseAssetModules;try{if(globalThis.__VF_TEST_MODE__){globalThis.__VF_HYDRATE_CALLED=!0,globalThis.__VF_MANIFEST_HASH=t.hash??"";return}}catch(u){g.debug("hydrate: test mode flags failed",u)}let a=q(e,n?.reactVersion),[{default:c},{createRoot:l}]=await Promise.all([import(a.react),import(a.reactDomClient)]);for(let u of r){let d=u.dataset?.clientRef??"";if(!d||u.dataset?.hydrated==="true")continue;let f=gt(d);if(!f)continue;let R=await ft(t,f,o,{releaseAssetModules:i});if(!R)continue;let $=R[f.exportName]??R.default;if(typeof $=="function")try{let x=l(u),ne=os(u),D=ss(u),yt=await Re(D,{Fragment:c.Fragment,createElement(V,oe,...F){return c.createElement(V,oe,...F)}},async V=>{let oe=t.modules.find(Et=>Et.id===V),F=t.components?.[V],Te=oe?.clientRef??(F?`${F}#default`:void 0);if(!Te)return null;let se=gt(Te);if(!se)return null;let ie=await ft(t,se,o,{releaseAssetModules:i});if(!ie)return null;let Se=ie[se.exportName]??ie.default;return typeof Se=="function"?Se:null}),mt=await ee(c.createElement($,ne,...yt),n,e);x.render(mt),u.dataset.hydrated="true"}catch(x){g.warn("hydrate: render failed",x)}}try{globalThis.__VF_MANIFEST_HASH=t.hash??""}catch(u){g.debug("hydrate: set hash failed (post)",u)}}var _e="data-vf-react-head-owner";var ds=2*1024*1024,nc=ds*2;var oc=64*1024,sc=1024*1024,ic=1024*1024;var ac=new TextEncoder;async function gs(){let e=b(document),t=q(document,e?.reactVersion),[r,n]=await Promise.all([import(t.react),import(t.reactDomClient)]);return{React:r,ReactDOM:n}}var fs=new Set(["SCRIPT","STYLE","NOSCRIPT","TEMPLATE"]);function xe(e){let t=e.getAttribute("style")??"";return e.hasAttribute("data-veryfront-head")||e.hasAttribute("hidden")||/(?:^|;)\\s*display\\s*:\\s*none(?:\\s*;|$)/i.test(t)||fs.has(e.tagName.toUpperCase())}function ps(e,t){return e.find(r=>r.tagName.toUpperCase()==="DIV"&&!!r.getAttribute("class")?.trim()&&!xe(r))??t}function ys(e,t){return e===t}function ms(e,t){let r=document.createElement("div");r.setAttribute("data-veryfront-hydration-root","page");let n=e.find(o=>!xe(o));n?.parentNode===t?t.insertBefore(r,n):t.appendChild(r);for(let o of e)!xe(o)&&o.parentNode===t&&r.appendChild(o);return r}function Es(e,t){for(let r of e){let n=[...r.hasAttribute(_e)?[r]:[],...r.querySelectorAll(`[${_e}]`)];for(let o of n)t.contains(o)||o.remove()}}function Rs(e,t,r=document){return!!t?.pagePath&&typeof e?.__veryfrontRenderPage=="function"&&!!r.getElementById("root")}function hs(e,t){return t?.pagePath?!1:!!e.getElementById(L)}function _s(e=import.meta.url){try{return new URL(e,"http://veryfront.local").searchParams.get("hydrate")==="1"}catch{return!1}}function xs(e){return e==="rsc-module"}function Ts(e,t){return e?e.startsWith("?")?e:`?${e}`:""}function Ss(e,t,r){return X({strategy:t,rel:e,releaseAssetModules:r?.releaseAssetModules,dependencyPinningCacheKey:r?.dependencyPinningCacheKey})}async function As(e,t){try{let r=await fetch(P+"stream"+e,{headers:H(t)});if(!r.ok)return await N(r)?"snapshot-conflict":"failure";if(!r.body)return"failure";let n=new AbortController;return addEventListener("pagehide",()=>n.abort(),{once:!0}),await ut(r,document,n.signal),"success"}catch(r){return g.debug("tryStream failed",r),"failure"}}async function re(){try{await pt(document)}catch(e){g.debug("hydration failed",e)}}async function Cs(e,t,r){try{let{React:n,ReactDOM:o}=await gs(),i=Ss(e,t,r);if(!i)return!1;g.debug("Loading component from:",i);let a;try{a=await import(i)}catch(R){throw await te(i),R}let c=a.default;if(typeof c!="function")return g.debug("Page component is not a function"),!1;let l=Array.from(document.body.children),u=ps(l,document.body),d=ys(u,document.body)?ms(l,document.body):u;Es(l,d);let f=await ee(n.createElement(c,{}),r);return xs(t)?o.createRoot(d).render(f):o.hydrateRoot(d,f,{identifierPrefix:"vf",onRecoverableError:()=>{}}),g.debug("Page component hydrated successfully"),!0}catch(n){return g.error("Page hydration failed",n),!1}}async function bs(e,t){try{let r=await fetch(P+"payload"+e,{headers:H(t)});if(!r.ok)return await N(r)?"snapshot-conflict":"failure";let n=await r.json();if(Y(document,n?.dependencyPinningCacheKey),n?.slots){for(let[o,i]of Object.entries(n.slots))k(document,o).innerHTML=v(String(i||""));return"success"}return k(document,L).innerHTML=v(String(n?.html||"")),"success"}catch(r){return g.debug("payload fetch failed",r),"failure"}}async function Os(){try{let e=b(document),t=Ts(globalThis.window?.location.search??"",e?.dependencyPinningCacheKey);if(_s()){await re();return}let r=e?.pagePath,n=K(e);if(r){if(Rs(globalThis.window,e,document)){g.debug("Page renderer owns hydration");return}g.debug("Found page component in hydration data:",r),await Cs(r,n,e)&&g.debug("Client component hydrated successfully");return}if(!hs(document,e))return;let o=await As(t,e);if(o==="snapshot-conflict")return;if(o==="success"){await re();return}let i=await bs(t,e);if(i==="snapshot-conflict")return;if(i==="success"){await re();return}await re()}catch(e){g.error("boot failed",e)}}if(typeof document<"u"){let e=()=>{Os()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e()}export{Os as boot,Ss as buildPageHydrationModuleUrl,Ts as buildRSCTransportQuery,Es as retireAbandonedHeadOwnerMarkers,ps as selectHydrationRoot,hs as shouldAttemptRSCTransport,_s as shouldHydrateOnly,xs as shouldRenderPageComponent,Rs as shouldUsePageRendererHydration,ys as shouldWrapPageHydrationRoot};\n'; export const CLIENT_DOM_BUNDLE: string = - 'var xe=Object.defineProperty;var he=(t,r,e)=>r in t?xe(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e;var E=(t,r,e)=>he(t,typeof r!="symbol"?r+"":r,e);var _e=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Se(t,...r){let e=Object.create(null),o=t.charAt(0).toUpperCase()+t.slice(1);for(let s of r)for(let[i,a]of Object.entries(s)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${o} entry "${i}" must define a slug`);let c=a.slug;if(typeof c!="string")throw new Error(`${o} entry "${i}" must define a string slug`);if(c!==i)throw new Error(`${o} key "${i}" does not match entry slug "${c}"`);if(Object.hasOwn(e,i))throw new Error(`Duplicate ${t} slug "${i}"`);e[i]=a}return Object.freeze(e)}function M(...t){for(let r of t)for(let e of Object.values(r)){if(typeof e.slug!="string"||e.slug.length<3||e.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(e.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${e.slug}"`);if(typeof e.category!="string"||!_e.has(e.category))throw new TypeError(`Registered error has unknown category "${e.category}"`);if(!Number.isInteger(e.status)||e.status<400||e.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${e.status}`);if(typeof e.title!="string"||e.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(e.suggestion!==void 0&&(typeof e.suggestion!="string"||e.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Se("error registry",...t)}var T={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},ln={debug:T.gray,info:T.green,warn:T.yellow,error:T.red};var p="[REDACTED]",m=Reflect.apply;var $=RegExp.prototype.exec,y=RegExp.prototype[Symbol.replace],dn=String.prototype.charCodeAt,k=String.prototype.slice,Te=String.prototype.toLowerCase,Ie=/[^a-z0-9]/g;function b(t){let r=m(Te,t,[]);return m(y,Ie,[r,""])}function I(t,r,e){return e===void 0?m(k,t,[r]):m(k,t,[r,e])}var Oe=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],Ae=512,Ce=128,S=new Map;function F(t){let r=t.length<=Ce;if(r){let s=S.get(t);if(s!==void 0)return s}let e=b(t),o=Oe.some(s=>e.includes(s));if(r){if(S.size>=Ae){let s=S.keys().next().value;s!==void 0&&S.delete(s)}S.set(t,o)}return o}var Ne=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],be=new Set(Ne.map(b)),De=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([^/?#\\s]+)@/gi,Le=/(\\b[a-z][a-z0-9+.-]*:\\/\\/|\\/\\/)([a-z0-9._~!$&\'()*+,;=%-]+):([^/?#@\\r\\n \\t]+[ \\t][^/?#@\\r\\n]*)@/gi,Ue=3;function ve(t){return t===" "||t==="\t"||t===","||t===";"||t==="&"||t==="?"||t==="#"}function we(t){if(!t)return!1;let r=t.charCodeAt(0);return r>=65&&r<=90||r>=97&&r<=122}function H(t){return we(t)||t==="_"||t==="$"}function Pe(t){if(!t)return!1;let r=t.charCodeAt(0);return H(t)||r>=48&&r<=57||t==="."||t==="-"}function j(t,r){let e=r,o=t[e]===\'"\'||t[e]==="\'"?t[e++]:"";if(!H(t[e]))return!1;for(e++;Pe(t[e]);)e++;if(o){if(t[e]!==o)return!1;e++}for(;t[e]===" "||t[e]==="\t";)e++;return t[e]===":"||t[e]==="="}function z(t){return t==="\\r"||t===`\n`||t==="}"||t==="]"||ve(t)}function Y(t,r){let e=r;for(;e=t.length||j(t,e)}function Me(t,r){let e=r,o=!0;if(t.startsWith(p,r)){let g=r+p.length;if(V(t,g))return{end:g,replacement:p};e=g,o=!1}let s=o&&(t[e]===\'"\'||t[e]==="\'"||t[e]==="`")?t[e]:"",i=!1,a=()=>s?`${s}${p}${i?s:""}`:p,c=[],d="",u=-1;for(let g=e;g0&&(f==="}"||f==="]")){if(c.at(-1)!==f)return{end:t.length,replacement:a()};if(c.pop(),g++,c.length===0&&V(t,g))return{end:g,replacement:a()};continue}if(c.length>0||!z(f)){g++;continue}let _=g;if(g=Y(t,g),g>=t.length||j(t,g))return{end:_,replacement:a()}}return{end:t.length,replacement:a()}}function G(t,r,e,o){let s=0,i="";for(let a=m($,r,[t]);a;a=m($,r,[t])){let c=a[e];if(!F(c))continue;let d=r.lastIndex,u=o===void 0?void 0:a[o],g=d+p.length;if((u==="?"||u==="&"||u===";")&&t.startsWith(p,d)&&t[g]==="#")continue;let f=Me(t,d);i+=I(t,s,a.index),i+=a[0],i+=f.replacement,s=f.end,r.lastIndex=f.end}return s===0?t:i+I(t,s)}function $e(t,r,e){let o=e.search(/[ \\t]/);if(o<0)return!1;let s=`${r}:${I(e,0,o)}`,i=t==="//"?`https://${s}`:`${t}${s}`;try{let a=new URL(i);return a.username.length===0&&a.password.length===0}catch{return!1}}function ke(t){let r=t;for(let e=0;e{let i=s.indexOf(":");if(i===-1)return`${o}${p}@`;let a=I(s,0,i);return`${o}${a}:${p}@`}]);return r=m(y,Le,[r,(e,o,s,i)=>$e(o,s,i)?e:`${o}${s}:${p}@`]),r=m(y,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,[r,(e,o,s,i)=>{let a=ke(s);return be.has(b(a))||F(a)?`${o}${s}=${p}`:e}]),r=m(y,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=m(y,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,[r,(e,o)=>`${o}${p}`]),r=m(y,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,[r,(e,o,s)=>`${o}${s}${p}`]),r=G(r,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),r=G(r,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),r}var Ve=2048;var Rn=64*1024,Ge=256,Fe="https://veryfront.com/docs/errors/",B="...[truncated]",L="unknown-error";function W(t,r){if(t.length<=r)return t;let e=Math.max(0,r-B.length);return`${He(t,e)}${B}`}function He(t,r){let e=t.slice(0,r),o=e.charCodeAt(e.length-1);return o>=55296&&o<=56319&&(e=e.slice(0,-1)),e}function je(t){let r="";for(let e=0;e=55296&&o<=56319){let s=t.charCodeAt(e+1);s>=56320&&s<=57343?(r+=t.slice(e,e+2),e++):r+="\\uFFFD";continue}r+=o>=56320&&o<=57343?"\\uFFFD":t.charAt(e)}return r}function x(t){return typeof t!="string"?p:W(D(t),Ve)}function ze(t){let r=typeof t=="string"?D(t):L,e=W(r||L,Ge),o=je(e);return o==="."||o===".."?L:o}function O(t){let r=encodeURIComponent(ze(t));return`${Fe}${r}`}var Ye=Object.freeze,Be=Object.getOwnPropertyDescriptors,K=Number.isFinite,X=new WeakSet,We=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function n(t){let r={...t},e={...r,create(o){let s=o?.message,i=o?.detail,a=o?.cause,c=o?.instance,d=o?.context,u=o?.status??r.status;return new U(s||i||r.title,{slug:r.slug,category:r.category,status:u,title:r.title,suggestion:r.suggestion,exitCode:r.exitCode,detail:i,cause:a,instance:c,context:d})}};return Ye(e)}var U=class extends Error{constructor(e,o){super(e);E(this,"slug");E(this,"category");E(this,"status");E(this,"title");E(this,"suggestion");E(this,"exitCode");E(this,"detail");E(this,"cause");E(this,"instance");E(this,"context");X.add(this),this.name="VeryfrontError",this.slug=o.slug,this.category=o.category,this.status=o.status,this.title=o.title,this.suggestion=o.suggestion,this.exitCode=o.exitCode,this.detail=o.detail,this.cause=o.cause,this.instance=o.instance,this.context=o.context}toRFC9457(){let e=q(this);return e?{type:O(e.slug),title:x(e.title),status:e.status,detail:e.detail===void 0?void 0:x(e.detail),instance:e.instance===void 0?void 0:x(e.instance),category:e.category,suggestion:e.suggestion===void 0?void 0:x(e.suggestion),cause:typeof e.cause=="string"?x(e.cause):void 0}:{type:O("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let e=q(this);return O(e?.slug??"unknown-error")}};function J(t){return typeof t=="object"&&t!==null&&X.has(t)}function q(t){return J(t)?Ke(t):null}function Ke(t){try{if(!J(t))return null;let r=Be(t),e=ye=>{let N=r[ye];return N&&"value"in N?N.value:void 0},o=e("slug"),s=e("category"),i=e("status"),a=e("title"),c=e("message"),d=e("suggestion"),u=e("exitCode"),g=e("detail"),f=e("cause"),_=e("instance"),Re=e("context"),C=e("stack");return typeof o!="string"||!We.has(s)||typeof i!="number"||!K(i)||typeof a!="string"||typeof c!="string"||d!==void 0&&typeof d!="string"||u!==void 0&&(typeof u!="number"||!K(u))||g!==void 0&&typeof g!="string"||_!==void 0&&typeof _!="string"||C!==void 0&&typeof C!="string"?null:{slug:o,category:s,status:i,title:a,message:c,suggestion:d,exitCode:u,detail:g,cause:f,instance:_,context:Re,stack:C}}catch{return null}}var qe=n({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),Xe=n({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),Je=n({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),Ze=n({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),Qe=n({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),et=n({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),tt=n({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),rt=n({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),nt=n({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),ot=n({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),st=n({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),Z={"config-not-found":qe,"config-invalid":Xe,"config-parse-error":Je,"config-validation-error":Ze,"config-type-error":Qe,"import-map-invalid":et,"cors-config-invalid":tt,"config-validation-failed":rt,"webhook-config-invalid":nt,"schedule-config-invalid":ot,"trigger-config-invalid":st};var it=n({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),at=n({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),ct=n({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),ut=n({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),lt=n({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),gt=n({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),dt=n({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),ft=n({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),Q={"build-failed":it,"bundle-error":at,"typescript-error":ct,"mdx-compile-error":ut,"asset-optimization-error":lt,"ssg-generation-error":gt,"sourcemap-error":dt,"compilation-error":ft};var pt=n({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),Et=n({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),mt=n({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),Rt=n({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),yt=n({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),xt=n({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),ht=n({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),_t=n({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),St=n({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),Tt=n({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ee={"hydration-mismatch":pt,"render-error":Et,"component-error":mt,"layout-not-found":Rt,"page-not-found":yt,"api-error":xt,"middleware-error":ht,"trigger-target-not-found":_t,"trigger-execution-failed":St,"trigger-not-supported":Tt};var It=n({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),Ot=n({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),At=n({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),Ct=n({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),Nt=n({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),bt=n({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),te={"route-conflict":It,"invalid-route-file":Ot,"route-handler-invalid":At,"dynamic-route-error":Ct,"route-params-error":Nt,"api-route-error":bt};var Dt=n({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),Lt=n({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),Ut=n({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),vt=n({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),wt=n({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),Pt=n({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),re={"module-not-found":Dt,"import-resolution-error":Lt,"circular-dependency":Ut,"invalid-import":vt,"dependency-missing":wt,"version-mismatch":Pt};var Mt=n({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),$t=n({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),kt=n({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),Vt=n({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),Gt=n({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),Ft=n({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),Ht=n({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),jt=n({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),zt=n({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),Yt=n({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),Bt=n({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),Wt=n({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),Kt=n({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),qt=n({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),Xt=n({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),Jt=n({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),Zt=n({slug:"rag-store-corrupt",category:"SERVER",status:500,title:"RAG store file is corrupt",suggestion:"Repair or move the store file aside, then retry; it was not overwritten"}),Qt=n({slug:"rag-store-unavailable",category:"SERVER",status:500,title:"RAG store file is unavailable",suggestion:"Check storage availability, permissions, and concurrent operations, then retry"}),ne={"port-in-use":Mt,"server-start-error":$t,"cache-error":kt,"file-watch-error":Vt,"request-error":Gt,"service-overloaded":Ft,"project-execution-unavailable":Ht,"semaphore-timeout":jt,"circuit-breaker-open":zt,"cache-path-mismatch":Yt,"network-error":Bt,"api-client-error":Wt,"token-storage-error":Kt,"cache-invariant-violation":qt,"release-not-found":Xt,"fallback-exhausted":Jt,"rag-store-corrupt":Zt,"rag-store-unavailable":Qt};var er=n({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),tr=n({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),rr=n({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),nr=n({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),or=n({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),sr=n({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),ir=n({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),oe={"client-boundary-violation":er,"server-only-in-client":tr,"client-only-in-server":rr,"invalid-use-client":nr,"invalid-use-server":or,"rsc-payload-error":sr,"ssr-output-limit-exceeded":ir};var ar=n({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),cr=n({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),ur=n({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),lr=n({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),gr=n({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),se={"hmr-error":ar,"dev-server-error":cr,"fast-refresh-error":ur,"error-overlay-error":lr,"source-map-error":gr};var dr=n({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),fr=n({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),pr=n({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),Er=n({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),mr=n({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),Rr=n({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),yr=n({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),xr=n({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),hr=n({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),_r=n({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),Sr=n({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),Tr=n({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),ie={"deployment-error":dr,"platform-error":fr,"env-var-missing":pr,"production-build-required":Er,"environment-not-found":mr,"release-missing-version":Rr,"release-build-timeout":yr,"deployment-verification-timeout":xr,"push-receipt-missing":hr,"source-digest-mismatch":_r,"preview-hostname-too-long":Sr,"branch-not-found":Tr};var Ir=n({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),Or=n({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),Ar=n({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),Cr=n({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),Nr=n({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),br=n({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),Dr=n({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),Lr=n({slug:"durable-run-event-persistence-failed",category:"AGENT",status:500,title:"Durable run event persistence failed",suggestion:"Correct invalid or oversized event data, or retry after durable event storage recovers"}),ae={"agent-error":Ir,"agent-not-found":Or,"agent-timeout":Ar,"agent-intent-error":Cr,"orchestration-error":Nr,"cost-limit-exceeded":br,"tool-id-conflict":Dr,"durable-run-event-persistence-failed":Lr};var Ur=n({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),vr=n({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),wr=n({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),Pr=n({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),Mr=n({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),$r=n({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),kr=n({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),Vr=n({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),Gr=n({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),v=n({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),Fr=n({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),Hr=n({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),ce={"unknown-error":Ur,"authentication-required":vr,"permission-denied":wr,"file-not-found":Pr,"resource-not-found":Mr,"invalid-argument":$r,"timeout-error":kr,"initialization-error":Vr,"not-supported":Gr,"security-violation":v,"input-validation-failed":Fr,"project-source-empty":Hr};var so=M(Z,Q,ee,te,re,ne,oe,se,ie,ae,ce);var jr=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function zr(){return jr.map(({source:t,flags:r,name:e})=>({pattern:new RegExp(t,r),name:e}))}function Yr(){let t=globalThis;return t.__VERYFRONT_DEV__===!0||t.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function ue(t,r={}){let{allowInlineScripts:e=!1,strict:o=!1,warn:s=!0}=r;for(let{pattern:i,name:a}of zr())if(!(e&&a==="inline script")&&(i.lastIndex=0,!!i.test(t)&&(s&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),o||!Yr())))throw v.create({detail:`Potentially unsafe HTML: ${a} detected`});return t}var h=class{constructor(r,e){E(this,"prefix",r);E(this,"level",e)}log(r,e,o,...s){this.level>r||e?.(o,...s)}debug(r,...e){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${r}`,...e)}info(r,...e){this.log(1,console.log,`[${this.prefix}] ${r}`,...e)}warn(r,...e){this.log(2,console.warn,`[${this.prefix}] WARN: ${r}`,...e)}error(r,...e){this.log(3,console.error,`[${this.prefix}] ERROR: ${r}`,...e)}};function Br(){if(typeof window>"u")return 2;let t=globalThis;return t.__VERYFRONT_DEV__||t.__RSC_DEV__?t.__VERYFRONT_DEBUG__||t.__RSC_DEBUG__?0:1:2}var A=Br(),R=new h("RSC",A),_o=new h("PREFETCH",A),So=new h("HYDRATE",A),To=new h("VERYFRONT",A);var Ao=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var Wr=5e3,Kr=1e4,bo=16*1024*1024,qr=5e3;var Xr=100;var Jr=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),Do=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),Lo=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:Wr,api:3e4,ssr:Kr,hmr:3e4,sandbox:qr}),cache:Object.freeze({jit:Object.freeze({maxSize:Xr,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:Jr})});var l="/_veryfront",w={RSC:`${l}/rsc/`,FS:`${l}/fs/`,MODULES:`${l}/modules/`,PAGES:`${l}/pages/`,DATA:`${l}/data/`,LIB:`${l}/lib/`,CHUNKS:`${l}/chunks/`,CLIENT:`${l}/client/`},ge={HMR_RUNTIME:`${l}/hmr-runtime.js`,HMR:`${l}/hmr.js`,ERROR_OVERLAY:`${l}/error-overlay.js`,DEV_LOADER:`${l}/dev-loader.js`,CLIENT_LOG:`${l}/log`,CLIENT_JS:`${l}/client.js`,ROUTER_JS:`${l}/router.js`,PREFETCH_JS:`${l}/prefetch.js`,MANIFEST_JSON:`${l}/manifest.json`,APP_JS:`${l}/app.js`,RSC_CLIENT:`${l}/rsc/client.js`,RSC_MANIFEST:`${l}/rsc/manifest`,RSC_STREAM:`${l}/rsc/stream`,RSC_PAYLOAD:`${l}/rsc/payload`,RSC_RENDER:`${l}/rsc/render`,RSC_PAGE:`${l}/rsc/page`,RSC_MODULE:`${l}/rsc/module`,RSC_DOM:`${l}/rsc/dom.js`,LIB_CHAT_REACT:`${l}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${l}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${l}/lib/chat/primitives.js`};var Zr={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},vo=Zr.CACHE;var wo={HMR_RUNTIME:ge.HMR_RUNTIME,ERROR_OVERLAY:ge.ERROR_OVERLAY};var Qr=w.RSC,en=w.FS;var de="rsc-root",P="x-veryfront-dependency-pins";var Vo=Array.prototype.at,Go=Array.prototype.filter,Fo=Array.prototype.join,Ho=Array.prototype.map,jo=Array.prototype.pop,zo=Array.prototype.push,Yo=Array.prototype.sort;var is=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var rn="veryfront-hydration-data";function fe(t){try{let r=[...t.querySelectorAll(`[id="${rn}"]`)];if(r.length!==1)return null;let e=t.body;if(!e)return null;let o=r[0];return e.firstElementChild!==o&&o.parentElement!==e||o.tagName?.toLowerCase()!=="script"||o.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:o}catch{return null}}function pe(t,r){if(!r?.startsWith("on:"))return!1;try{let e=fe(t);if(!e)return!1;let o=JSON.parse(e.textContent||"{}");return o.dependencyPinningCacheKey=r,e.textContent=JSON.stringify(o),!0}catch(e){return R.debug("hydration dependency snapshot seed failed",e),!1}}function me(t,r){let e=r==="root"?de:`rsc-slot-${r}`,o=t.getElementById(e);if(o)return o;let s=t.createElement("div");return s.id=e,t.body.appendChild(s),s}function nn(t,r){if(r.type!=="slot")return;let e=me(t,r.id);e.innerHTML=ue(String(r.html??""))}function Ee(t,r){let e=r.split(`\n`),o=e.pop()??"";for(let s of e){let i=s.trim();if(!i)continue;let a;try{a=JSON.parse(i)}catch(d){R.debug("[client-dom] malformed NDJSON line",{line:i,error:d instanceof Error?d.message:String(d)});continue}if(!a||typeof a!="object")continue;let c=a;if(c.type==="slot"){nn(t,c);try{an(t,c.id||"root")}catch(d){R.debug("[client-dom] hydration optional failed",d)}}}return o}function on(t){return new Promise((r,e)=>{let o=()=>e(new DOMException("aborted","AbortError"));if(t.aborted){o();return}t.addEventListener("abort",o,{once:!0})})}async function As(t,r=document,e){let o="body"in t?t:null,s=o?.body??t;if(!s)return;o&&pe(r,o.headers.get(P));let i=s.getReader(),a=new TextDecoder,c="",d=!1;try{for(;;){if(e?.aborted)throw new DOMException("aborted","AbortError");let u=i.read(),{done:g,value:f}=e?await Promise.race([u,on(e)]):await u;if(g){d=!0;break}c+=a.decode(f,{stream:!0}),c=Ee(r,c)}c&&Ee(r,`${c}\n`)}catch(u){throw u instanceof Error&&u.name==="AbortError"||R.debug("[client-dom] consumeNdjsonStream error",u),u}finally{try{await i.cancel()}catch(u){d||R.debug("[client-dom] reader.cancel failed",u)}try{i.releaseLock()}catch(u){R.debug("[client-dom] reader.releaseLock failed",u)}if(typeof s.cancel=="function")try{await s.cancel()}catch(u){R.debug("[client-dom] stream.cancel failed",u)}if(typeof o?.body?.cancel=="function")try{await o.body.cancel()}catch(u){R.debug("[client-dom] response.body.cancel failed",u)}}}function sn(t,r){let e=me(t,r),o=[],s=i=>{let a=i;a.dataset?.clientRef&&o.push(a);for(let c of i.children)s(c)};return s(e),o}function an(t,r){let e=sn(t,r);for(let o of e){let s=o.dataset?.clientRef;s&&(o.dataset.hydrated="true",R.debug("[client-dom] marked for hydration",s))}}export{As as consumeNdjsonStream,me as getContainer};\n'; + 'var Ct=Object.defineProperty;var Nt=(e,r,t)=>r in e?Ct(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t;var m=(e,r,t)=>Nt(e,typeof r!="symbol"?r+"":r,t);var bt=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function Dt(e,...r){let t=Object.create(null),n=e.charAt(0).toUpperCase()+e.slice(1);for(let s of r)for(let[i,a]of Object.entries(s)){if(typeof a!="object"||a===null||!Object.hasOwn(a,"slug"))throw new Error(`${n} entry "${i}" must define a slug`);let c=a.slug;if(typeof c!="string")throw new Error(`${n} entry "${i}" must define a string slug`);if(c!==i)throw new Error(`${n} key "${i}" does not match entry slug "${c}"`);if(Object.hasOwn(t,i))throw new Error(`Duplicate ${e} slug "${i}"`);t[i]=a}return Object.freeze(t)}function F(...e){for(let r of e)for(let t of Object.values(r)){if(typeof t.slug!="string"||t.slug.length<3||t.slug.length>40||!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(t.slug))throw new TypeError(`Registered error slug must be 3-40 characters of lowercase kebab-case, got "${t.slug}"`);if(typeof t.category!="string"||!bt.has(t.category))throw new TypeError(`Registered error has unknown category "${t.category}"`);if(!Number.isInteger(t.status)||t.status<400||t.status>=600)throw new RangeError(`Registered error status must be an integer from 400 through 599, got ${t.status}`);if(typeof t.title!="string"||t.title.trim().length===0)throw new TypeError("Registered error title must be a non-empty string");if(t.suggestion!==void 0&&(typeof t.suggestion!="string"||t.suggestion.trim().length===0))throw new TypeError("Registered error suggestion must be non-empty when provided")}return Dt("error registry",...e)}var C={reset:"\\x1B[0m",dim:"\\x1B[2m",gray:"\\x1B[90m",red:"\\x1B[31m",green:"\\x1B[32m",yellow:"\\x1B[33m",blue:"\\x1B[34m",magenta:"\\x1B[35m",cyan:"\\x1B[36m"},Hn={debug:C.gray,info:C.green,warn:C.yellow,error:C.red};var E="[REDACTED]",f=Reflect.apply,Lt=Array.prototype.pop,Ut=Array.prototype.push;var zn=Array.prototype,Yn=BigInt.prototype.toString,B=Map,wt=Map.prototype.delete,vt=Map.prototype.get,Pt=Map.prototype.keys,Mt=Map.prototype.set;var x=Object.getOwnPropertyDescriptor,kt=Object.getPrototypeOf,Bn=Object.hasOwn,Wn=Object.prototype,$t=Set,Vt=decodeURIComponent,_=URL,Kn=Number.isFinite,qn=Number.isInteger,w=RegExp.prototype.exec,Gt=x(RegExp.prototype,"global").get,Ft=x(RegExp.prototype,"unicode").get,Ht=String.prototype.charCodeAt,jt=String.prototype.includes,zt=String.prototype.indexOf,H=String.prototype.slice,W=String.prototype.startsWith,K=String.prototype.toLowerCase,Yt=Set.prototype.add,Xn=Set.prototype.delete,Bt=Set.prototype.has,Wt=kt(new B().keys()).next,Kt=x(Map.prototype,"size").get,Jn=x(_.prototype,"host").get,Zn=x(_.prototype,"origin").get,qt=x(_.prototype,"password").get,Qn=x(_.prototype,"pathname").get,to=x(_.prototype,"protocol").get,Xt=x(_.prototype,"username").get,Jt=/[^a-z0-9]/g,Zt=/([a-z0-9])([A-Z])/g,Qt=/([A-Z])([A-Z][a-z])/g,te=/\\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\\b/g;function y(e,r,t){let n=f(Gt,r,[]),s=f(Ft,r,[]),i=0,a=!1,c="";r.lastIndex=0;try{for(;;){let l=f(w,r,[e]);if(l===null)break;let u=l[0],g=l.index;if(c+=h(e,i,g),c+=typeof t=="string"?t:t(l),i=g+u.length,a=!0,!n)break;u.length===0&&(r.lastIndex=ee(e,g,s))}}finally{r.lastIndex=0}return a?c+h(e,i):e}function v(e){let r=f(K,e,[]);return y(r,Jt,"")}function S(e,r){return f(Ht,e,[r])}function ee(e,r,t){let n=r+1;if(!t||n>=e.length)return n;let s=S(e,r);if(s<55296||s>56319)return n;let i=S(e,n);return i>=56320&&i<=57343?r+2:n}function h(e,r,t){return t===void 0?f(H,e,[r]):f(H,e,[r,t])}function re(e){let r=[],t=0;for(let n=0;n<=e.length;n++){let s=n===e.length?-1:S(e,n);s>=97&&s<=122||s>=48&&s<=57||(n>t&&(r[r.length]=h(e,t,n)),t=n+1)}return r}var N=["password","passwd","pwd","passphrase","secret","clientsecret","token","apikey","accesskey","privatekey","credential","authheader","authorization","cookie","bearer","jwt","connectionstring","signature","sessionid","sid","otp","mfa","pin","salt","xsrf","csrf"],ne=512,oe=128,A=new B;var se=256;function ie(e){let r=e.length<=oe;if(r){let s=f(vt,A,[e]);if(s!==void 0)return s}let t=v(e),n=t==="auth";for(let s=0;!n&&s=ne){let i=f(Pt,A,[]),a=f(Wt,i,[]).value;a!==void 0&&f(wt,A,[a])}f(Mt,A,[e,n])}return n}var j=["access_token","accesstoken","refresh_token","api_key","apikey","code","token","secret","client_secret","password","passwd","pwd","state","sig","signature","auth","x-amz-credential","x-amz-signature","x-amz-security-token","x-goog-credential","x-goog-signature"],q=new $t;for(let e=0;e=65&&r<=90||r>=97&&r<=122}function X(e){return ge(e)||e==="_"||e==="$"}function de(e){if(!e)return!1;let r=S(e,0);return X(e)||r>=48&&r<=57||e==="."||e==="-"}function J(e,r){let t=r,n=e[t]===\'"\'||e[t]==="\'"?e[t++]:"";if(!X(e[t]))return!1;for(t++;de(e[t]);)t++;if(n){if(e[t]!==n)return!1;t++}for(;e[t]===" "||e[t]==="\t";)t++;return e[t]===":"||e[t]==="="}function Z(e){return e==="\\r"||e===`\n`||e==="}"||e==="]"||le(e)}function Q(e,r){let t=r;for(;t=e.length||J(e,t)}function fe(e,r){let t=r,n=!0;if(f(W,e,[E,r])){let g=r+E.length;if(z(e,g))return{end:g,replacement:E};t=g,n=!1}let s=n&&(e[t]===\'"\'||e[t]==="\'"||e[t]==="`")?e[t]:"",i=!1,a=()=>s?`${s}${E}${i?s:""}`:E,c=[],l="",u=-1;for(let g=t;g0&&(p==="}"||p==="]")){if(c[c.length-1]!==p)return{end:e.length,replacement:a()};if(f(Lt,c,[]),g++,c.length===0&&z(e,g))return{end:g,replacement:a()};continue}if(c.length>0||!Z(p)){g++;continue}let O=g;if(g=Q(e,g),g>=e.length||J(e,g))return{end:O,replacement:a()}}return{end:e.length,replacement:a()}}function Y(e,r,t,n){let s=0,i="";for(let a=f(w,r,[e]);a;a=f(w,r,[e])){let c=a[t];if(!pe(c))continue;let l=r.lastIndex,u=n===void 0?void 0:a[n],g=l+E.length;if((u==="?"||u==="&"||u===";")&&f(W,e,[E,l])&&e[g]==="#")continue;let p=fe(e,l);i+=h(e,s,a.index),i+=a[0],i+=p.replacement,s=p.end,r.lastIndex=p.end}return s===0?e:i+h(e,s)}function pe(e){if(e.length>se)return!0;let r=y(e,Qt,i=>`${i[1]} ${i[2]}`),t=y(r,Zt,i=>`${i[1]} ${i[2]}`),n=f(K,t,[]),s=re(n);for(let i=0;i{let n=t[1],s=t[2],i=f(zt,s,[":"]);if(i===-1)return`${n}${E}@`;let a=h(s,0,i);return`${n}${a}:${E}@`});return r=y(r,ce,t=>{let n=t[1],s=t[2],i=t[3];return Ee(n,s,i)?t[0]:`${n}${s}:${E}@`}),r=y(r,/([?#&;])([-a-z0-9_.%\\[\\]]+)=([^&#;\\s]*)/gi,t=>{let n=t[1],s=t[2],i=me(s);return f(Bt,q,[v(i)])||ie(i)?`${n}${s}=${E}`:t[0]}),r=y(r,/(^|[^a-z0-9_-])((?:set-cookie|cookie)\\s*:\\s*)[^\\r\\n]*/gi,t=>`${t[1]}${t[2]}${E}`),r=y(r,/\\b(authorization\\s*[:=]\\s*)[^\\r\\n]*/gi,t=>`${t[1]}${E}`),r=y(r,/\\b(bearer|basic)(\\s+)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[a-z0-9._~+/=-]+)/gi,t=>`${t[1]}${t[2]}${E}`),r=y(r,te,E),r=Y(r,/(["\'])([_$a-z][a-z0-9_.$-]*)\\1(\\s*[:=]\\s*)/gi,2),r=Y(r,/(^|[^a-z0-9_.$-])([_$a-z][a-z0-9_.$-]*)(\\s*[:=]\\s*)/gi,2,1),r}var Re=2048;var so=64*1024,ye=256,xe="https://veryfront.com/docs/errors/",tt="...[truncated]",M="unknown-error";function et(e,r){if(e.length<=r)return e;let t=Math.max(0,r-tt.length);return`${he(e,t)}${tt}`}function he(e,r){let t=e.slice(0,r),n=t.charCodeAt(t.length-1);return n>=55296&&n<=56319&&(t=t.slice(0,-1)),t}function _e(e){let r="";for(let t=0;t=55296&&n<=56319){let s=e.charCodeAt(t+1);s>=56320&&s<=57343?(r+=e.slice(t,t+2),t++):r+="\\uFFFD";continue}r+=n>=56320&&n<=57343?"\\uFFFD":e.charAt(t)}return r}function I(e){return typeof e!="string"?E:et(P(e),Re)}function Se(e){let r=typeof e=="string"?P(e):M,t=et(r||M,ye),n=_e(t);return n==="."||n===".."?M:n}function b(e){let r=encodeURIComponent(Se(e));return`${xe}${r}`}var Ie=Object.freeze,Te=Object.getOwnPropertyDescriptors,rt=Number.isFinite,ot=new WeakSet,Oe=new Set(["CONFIG","BUILD","RUNTIME","ROUTE","MODULE","SERVER","BOUNDARY","DEV","DEPLOY","AGENT","GENERAL"]);function o(e){let r={...e},t={...r,create(n){let s=n?.message,i=n?.detail,a=n?.cause,c=n?.instance,l=n?.context,u=n?.status??r.status;return new k(s||i||r.title,{slug:r.slug,category:r.category,status:u,title:r.title,suggestion:r.suggestion,exitCode:r.exitCode,detail:i,cause:a,instance:c,context:l})}};return Ie(t)}var k=class extends Error{constructor(t,n){super(t);m(this,"slug");m(this,"category");m(this,"status");m(this,"title");m(this,"suggestion");m(this,"exitCode");m(this,"detail");m(this,"cause");m(this,"instance");m(this,"context");ot.add(this),this.name="VeryfrontError",this.slug=n.slug,this.category=n.category,this.status=n.status,this.title=n.title,this.suggestion=n.suggestion,this.exitCode=n.exitCode,this.detail=n.detail,this.cause=n.cause,this.instance=n.instance,this.context=n.context}toRFC9457(){let t=nt(this);return t?{type:b(t.slug),title:I(t.title),status:t.status,detail:t.detail===void 0?void 0:I(t.detail),instance:t.instance===void 0?void 0:I(t.instance),category:t.category,suggestion:t.suggestion===void 0?void 0:I(t.suggestion),cause:typeof t.cause=="string"?I(t.cause):void 0}:{type:b("unknown-error"),title:"Unknown/unclassified error",status:500,category:"GENERAL"}}getDocsUrl(){let t=nt(this);return b(t?.slug??"unknown-error")}};function st(e){return typeof e=="object"&&e!==null&&ot.has(e)}function nt(e){return st(e)?Ae(e):null}function Ae(e){try{if(!st(e))return null;let r=Te(e),t=At=>{let U=r[At];return U&&"value"in U?U.value:void 0},n=t("slug"),s=t("category"),i=t("status"),a=t("title"),c=t("message"),l=t("suggestion"),u=t("exitCode"),g=t("detail"),p=t("cause"),O=t("instance"),Ot=t("context"),L=t("stack");return typeof n!="string"||!Oe.has(s)||typeof i!="number"||!rt(i)||typeof a!="string"||typeof c!="string"||l!==void 0&&typeof l!="string"||u!==void 0&&(typeof u!="number"||!rt(u))||g!==void 0&&typeof g!="string"||O!==void 0&&typeof O!="string"||L!==void 0&&typeof L!="string"?null:{slug:n,category:s,status:i,title:a,message:c,suggestion:l,exitCode:u,detail:g,cause:p,instance:O,context:Ot,stack:L}}catch{return null}}var Ce=o({slug:"config-not-found",category:"CONFIG",status:404,title:"Configuration file not found",suggestion:"Create veryfront.config.js, veryfront.config.ts, or veryfront.config.mjs in the project root"}),Ne=o({slug:"config-invalid",category:"CONFIG",status:400,title:"Invalid configuration format",suggestion:"Check the reported configuration path and validation details"}),be=o({slug:"config-parse-error",category:"CONFIG",status:400,title:"Failed to parse configuration",suggestion:"Ensure your configuration file contains valid JavaScript or TypeScript"}),De=o({slug:"config-validation-error",category:"CONFIG",status:422,title:"Configuration validation failed",suggestion:"Check the configuration against the schema requirements"}),Le=o({slug:"config-type-error",category:"CONFIG",status:400,title:"Configuration type mismatch",suggestion:"Ensure configuration values match expected types"}),Ue=o({slug:"import-map-invalid",category:"CONFIG",status:400,title:"Invalid import map configuration",suggestion:"Check your import map syntax and paths"}),we=o({slug:"cors-config-invalid",category:"CONFIG",status:400,title:"Invalid CORS configuration",suggestion:"Review CORS settings in your configuration"}),ve=o({slug:"config-validation-failed",category:"CONFIG",status:400,title:"Configuration validation failed",suggestion:"Check configuration values against requirements"}),Pe=o({slug:"webhook-config-invalid",category:"CONFIG",status:400,title:"Invalid webhook configuration",suggestion:"Check webhook definition fields, target settings, and eventFilter conditions"}),Me=o({slug:"schedule-config-invalid",category:"CONFIG",status:400,title:"Invalid schedule configuration",suggestion:"Check schedule definition fields, cron expression, target settings, and positive-integer limits"}),ke=o({slug:"trigger-config-invalid",category:"CONFIG",status:400,title:"Invalid trigger configuration",suggestion:"Check trigger ID format (lowercase, alphanumeric, dots/slashes/hyphens) and ensure all input values are JSON-serializable"}),it={"config-not-found":Ce,"config-invalid":Ne,"config-parse-error":be,"config-validation-error":De,"config-type-error":Le,"import-map-invalid":Ue,"cors-config-invalid":we,"config-validation-failed":ve,"webhook-config-invalid":Pe,"schedule-config-invalid":Me,"trigger-config-invalid":ke};var $e=o({slug:"build-failed",category:"BUILD",status:500,title:"Build process failed",suggestion:"Check the build output for specific errors"}),Ve=o({slug:"bundle-error",category:"BUILD",status:500,title:"Bundle generation failed",suggestion:"Review bundler output for details"}),Ge=o({slug:"typescript-error",category:"BUILD",status:500,title:"TypeScript compilation error",suggestion:"Fix TypeScript errors shown in the output"}),Fe=o({slug:"mdx-compile-error",category:"BUILD",status:500,title:"MDX compilation failed",suggestion:"Check your MDX file syntax"}),He=o({slug:"asset-optimization-error",category:"BUILD",status:500,title:"Asset optimization failed",suggestion:"Check asset file formats and paths"}),je=o({slug:"ssg-generation-error",category:"BUILD",status:500,title:"Static site generation failed",suggestion:"Review SSG configuration and data fetching"}),ze=o({slug:"sourcemap-error",category:"BUILD",status:500,title:"Source map generation failed",suggestion:"Check source map configuration"}),Ye=o({slug:"compilation-error",category:"BUILD",status:500,title:"Compilation failed",suggestion:"Review compiler output for specific errors"}),at={"build-failed":$e,"bundle-error":Ve,"typescript-error":Ge,"mdx-compile-error":Fe,"asset-optimization-error":He,"ssg-generation-error":je,"sourcemap-error":ze,"compilation-error":Ye};var Be=o({slug:"hydration-mismatch",category:"RUNTIME",status:500,title:"Client/server hydration mismatch",suggestion:"Ensure server and client render the same content"}),We=o({slug:"render-error",category:"RUNTIME",status:500,title:"Component render failed",suggestion:"Check component for runtime errors"}),Ke=o({slug:"component-error",category:"RUNTIME",status:500,title:"Component execution error",suggestion:"Review component logic and props"}),qe=o({slug:"layout-not-found",category:"RUNTIME",status:404,title:"Layout component not found",suggestion:"Ensure layout file exists at the expected path"}),Xe=o({slug:"page-not-found",category:"RUNTIME",status:404,title:"Page component not found",suggestion:"Check that the page file exists in the routes directory"}),Je=o({slug:"api-error",category:"RUNTIME",status:500,title:"API route handler error",suggestion:"Review API route handler for errors"}),Ze=o({slug:"middleware-error",category:"RUNTIME",status:500,title:"Middleware execution error",suggestion:"Check middleware function for errors"}),Qe=o({slug:"trigger-target-not-found",category:"RUNTIME",status:404,title:"Trigger target not found",suggestion:"Ensure the referenced task or workflow ID is registered in the project"}),tr=o({slug:"trigger-execution-failed",category:"RUNTIME",status:500,title:"Trigger target execution failed",suggestion:"Check the task or workflow for errors and review the trigger input"}),er=o({slug:"trigger-not-supported",category:"RUNTIME",status:501,title:"Trigger target type not supported in local runtime",suggestion:"Use a workflow or task target for local trigger runs; agent targets require the Cloud runtime"}),ct={"hydration-mismatch":Be,"render-error":We,"component-error":Ke,"layout-not-found":qe,"page-not-found":Xe,"api-error":Je,"middleware-error":Ze,"trigger-target-not-found":Qe,"trigger-execution-failed":tr,"trigger-not-supported":er};var rr=o({slug:"route-conflict",category:"ROUTE",status:409,title:"Conflicting route definitions",suggestion:"Rename or reorganize conflicting route files"}),nr=o({slug:"invalid-route-file",category:"ROUTE",status:400,title:"Invalid route file structure",suggestion:"Ensure route file exports required functions"}),or=o({slug:"route-handler-invalid",category:"ROUTE",status:400,title:"Invalid route handler export",suggestion:"Export a valid handler function from the route file"}),sr=o({slug:"dynamic-route-error",category:"ROUTE",status:500,title:"Dynamic route parsing failed",suggestion:"Check dynamic route segment syntax"}),ir=o({slug:"route-params-error",category:"ROUTE",status:400,title:"Route parameters invalid",suggestion:"Validate route parameter values"}),ar=o({slug:"api-route-error",category:"ROUTE",status:500,title:"API route definition error",suggestion:"Review API route configuration"}),ut={"route-conflict":rr,"invalid-route-file":nr,"route-handler-invalid":or,"dynamic-route-error":sr,"route-params-error":ir,"api-route-error":ar};var cr=o({slug:"module-not-found",category:"MODULE",status:404,title:"Module could not be resolved",suggestion:"Check the import path and ensure the module is installed"}),ur=o({slug:"import-resolution-error",category:"MODULE",status:500,title:"Import path resolution failed",suggestion:"Verify import paths and module configuration"}),lr=o({slug:"circular-dependency",category:"MODULE",status:500,title:"Circular dependency detected",suggestion:"Refactor imports to break the circular dependency"}),gr=o({slug:"invalid-import",category:"MODULE",status:400,title:"Invalid import statement",suggestion:"Fix import syntax or path"}),dr=o({slug:"dependency-missing",category:"MODULE",status:404,title:"Required dependency not installed",suggestion:"Install the missing dependency with your package manager"}),fr=o({slug:"version-mismatch",category:"MODULE",status:409,title:"Dependency version mismatch",suggestion:"Update dependencies to compatible versions"}),lt={"module-not-found":cr,"import-resolution-error":ur,"circular-dependency":lr,"invalid-import":gr,"dependency-missing":dr,"version-mismatch":fr};var pr=o({slug:"port-in-use",category:"SERVER",status:409,title:"Server port already in use",suggestion:"Use a different port or stop the process using this port"}),Er=o({slug:"server-start-error",category:"SERVER",status:500,title:"Server failed to start",suggestion:"Check server configuration and port availability"}),mr=o({slug:"cache-error",category:"SERVER",status:500,title:"Cache operation failed",suggestion:"Clear the cache and try again"}),Rr=o({slug:"file-watch-error",category:"SERVER",status:500,title:"File watcher error",suggestion:"Restart the development server"}),yr=o({slug:"request-error",category:"SERVER",status:500,title:"HTTP request handling error",suggestion:"Check request handler and middleware"}),xr=o({slug:"service-overloaded",category:"SERVER",status:503,title:"Service overloaded",suggestion:"Reduce load or scale up resources"}),hr=o({slug:"project-execution-unavailable",category:"SERVER",status:503,title:"Project execution unavailable",suggestion:"Route the project to a dedicated isolated runtime"}),_r=o({slug:"semaphore-timeout",category:"SERVER",status:503,title:"Semaphore acquire timeout",suggestion:"Reduce concurrency or increase the semaphore acquire timeout"}),Sr=o({slug:"circuit-breaker-open",category:"SERVER",status:503,title:"Circuit breaker is open",suggestion:"Wait for the breaker reset timeout before retrying"}),Ir=o({slug:"cache-path-mismatch",category:"SERVER",status:500,title:"Cache path mismatch",suggestion:"Clear the cache directory and rebuild"}),Tr=o({slug:"network-error",category:"SERVER",status:502,title:"Network operation failed",suggestion:"Check network connectivity and retry"}),Or=o({slug:"api-client-error",category:"SERVER",status:500,title:"API client request failed",suggestion:"Check API connectivity and authentication"}),Ar=o({slug:"token-storage-error",category:"SERVER",status:500,title:"Token storage operation failed",suggestion:"Check token storage backend and credentials"}),Cr=o({slug:"cache-invariant-violation",category:"SERVER",status:500,title:"Cache path invariant violated",suggestion:"Clear the cache and rebuild"}),Nr=o({slug:"release-not-found",category:"SERVER",status:404,title:"No active release found",suggestion:"Deploy the project to create a release for this environment"}),br=o({slug:"fallback-exhausted",category:"SERVER",status:500,title:"Primary and fallback operations both failed",suggestion:"Check service availability and connectivity"}),Dr=o({slug:"rag-store-corrupt",category:"SERVER",status:500,title:"RAG store file is corrupt",suggestion:"Repair or move the store file aside, then retry; it was not overwritten"}),Lr=o({slug:"rag-store-unavailable",category:"SERVER",status:500,title:"RAG store file is unavailable",suggestion:"Check storage availability, permissions, and concurrent operations, then retry"}),gt={"port-in-use":pr,"server-start-error":Er,"cache-error":mr,"file-watch-error":Rr,"request-error":yr,"service-overloaded":xr,"project-execution-unavailable":hr,"semaphore-timeout":_r,"circuit-breaker-open":Sr,"cache-path-mismatch":Ir,"network-error":Tr,"api-client-error":Or,"token-storage-error":Ar,"cache-invariant-violation":Cr,"release-not-found":Nr,"fallback-exhausted":br,"rag-store-corrupt":Dr,"rag-store-unavailable":Lr};var Ur=o({slug:"client-boundary-violation",category:"BOUNDARY",status:400,title:"Client boundary rule violation",suggestion:"Add \'use client\' directive or move code to a client component"}),wr=o({slug:"server-only-in-client",category:"BOUNDARY",status:400,title:"Server-only code in client component",suggestion:"Move server-only code to a server component"}),vr=o({slug:"client-only-in-server",category:"BOUNDARY",status:400,title:"Client-only code in server component",suggestion:"Move client-only code to a client component"}),Pr=o({slug:"invalid-use-client",category:"BOUNDARY",status:400,title:"Invalid \'use client\' directive",suggestion:"Place \'use client\' at the top of the file"}),Mr=o({slug:"invalid-use-server",category:"BOUNDARY",status:400,title:"Invalid \'use server\' directive",suggestion:"Place \'use server\' at the top of the file or function"}),kr=o({slug:"rsc-payload-error",category:"BOUNDARY",status:500,title:"RSC payload serialization error",suggestion:"Ensure props are serializable (no functions, symbols, etc.)"}),$r=o({slug:"ssr-output-limit-exceeded",category:"BOUNDARY",status:500,title:"SSR output limit exceeded",suggestion:"Reduce the rendered HTML size or split the response into smaller pages"}),dt={"client-boundary-violation":Ur,"server-only-in-client":wr,"client-only-in-server":vr,"invalid-use-client":Pr,"invalid-use-server":Mr,"rsc-payload-error":kr,"ssr-output-limit-exceeded":$r};var Vr=o({slug:"hmr-error",category:"DEV",status:500,title:"Hot module replacement error",suggestion:"Restart the development server"}),Gr=o({slug:"dev-server-error",category:"DEV",status:500,title:"Development server error",suggestion:"Check the dev server logs and restart"}),Fr=o({slug:"fast-refresh-error",category:"DEV",status:500,title:"Fast refresh failed",suggestion:"Save the file again or restart the dev server"}),Hr=o({slug:"error-overlay-error",category:"DEV",status:500,title:"Error overlay failed",suggestion:"Check browser console for details"}),jr=o({slug:"source-map-error",category:"DEV",status:500,title:"Source map loading error",suggestion:"Rebuild or clear cache"}),ft={"hmr-error":Vr,"dev-server-error":Gr,"fast-refresh-error":Fr,"error-overlay-error":Hr,"source-map-error":jr};var zr=o({slug:"deployment-error",category:"DEPLOY",status:500,title:"Deployment process failed",suggestion:"Check deployment logs for details"}),Yr=o({slug:"platform-error",category:"DEPLOY",status:500,title:"Platform-specific error",suggestion:"Check platform documentation and requirements"}),Br=o({slug:"env-var-missing",category:"DEPLOY",status:500,title:"Required environment variable missing",suggestion:"Set the required environment variable"}),Wr=o({slug:"production-build-required",category:"DEPLOY",status:400,title:"Production build required",suggestion:"Run \'veryfront build\' before deploying"}),Kr=o({slug:"environment-not-found",category:"DEPLOY",status:404,title:"Deployment environment not found",suggestion:"Check environment names with: veryfront config"}),qr=o({slug:"release-missing-version",category:"DEPLOY",status:500,title:"Release has no version",suggestion:"Try again or check the build logs in Studio"}),Xr=o({slug:"release-build-timeout",category:"DEPLOY",status:408,title:"Release build timed out",suggestion:"Try again or check the build logs in Studio"}),Jr=o({slug:"deployment-verification-timeout",category:"DEPLOY",status:408,title:"Deployment verification timed out",suggestion:"Try again or check the deployment status in Studio"}),Zr=o({slug:"push-receipt-missing",category:"DEPLOY",status:400,title:"Push receipt not found",suggestion:"Run: veryfront push --branch main first"}),Qr=o({slug:"source-digest-mismatch",category:"DEPLOY",status:409,title:"Release source digest mismatch",suggestion:"Run veryfront push again to re-upload source files"}),tn=o({slug:"preview-hostname-too-long",category:"DEPLOY",status:400,title:"Preview hostname too long",suggestion:"Use a shorter project slug or branch name"}),en=o({slug:"branch-not-found",category:"DEPLOY",status:404,title:"Branch not found",suggestion:"List branches in Studio or push a new one with: veryfront push --branch "}),pt={"deployment-error":zr,"platform-error":Yr,"env-var-missing":Br,"production-build-required":Wr,"environment-not-found":Kr,"release-missing-version":qr,"release-build-timeout":Xr,"deployment-verification-timeout":Jr,"push-receipt-missing":Zr,"source-digest-mismatch":Qr,"preview-hostname-too-long":tn,"branch-not-found":en};var rn=o({slug:"agent-error",category:"AGENT",status:500,title:"Agent operation error",suggestion:"Check agent configuration and logs"}),nn=o({slug:"agent-not-found",category:"AGENT",status:404,title:"Agent not found",suggestion:"Verify the agent ID exists"}),on=o({slug:"agent-timeout",category:"AGENT",status:408,title:"Agent operation timed out",suggestion:"Increase timeout or simplify the request"}),sn=o({slug:"agent-intent-error",category:"AGENT",status:400,title:"Agent intent parsing error",suggestion:"Rephrase the request more clearly"}),an=o({slug:"orchestration-error",category:"AGENT",status:500,title:"Multi-agent orchestration error",suggestion:"Check agent coordination logic"}),cn=o({slug:"cost-limit-exceeded",category:"AGENT",status:429,title:"Cost limit exceeded",suggestion:"Wait for the budget period to reset or increase the limit"}),un=o({slug:"tool-id-conflict",category:"AGENT",status:409,title:"Tool ID conflict",suggestion:"Use a unique tool ID or rename one of the conflicting tools"}),ln=o({slug:"durable-run-event-persistence-failed",category:"AGENT",status:500,title:"Durable run event persistence failed",suggestion:"Correct invalid or oversized event data, or retry after durable event storage recovers"}),Et={"agent-error":rn,"agent-not-found":nn,"agent-timeout":on,"agent-intent-error":sn,"orchestration-error":an,"cost-limit-exceeded":cn,"tool-id-conflict":un,"durable-run-event-persistence-failed":ln};var gn=o({slug:"unknown-error",category:"GENERAL",status:500,title:"Unknown/unclassified error",suggestion:"Check logs for more details"}),dn=o({slug:"authentication-required",category:"GENERAL",status:401,title:"Authentication required",suggestion:"Set VERYFRONT_API_TOKEN or run \'veryfront login\'"}),fn=o({slug:"permission-denied",category:"GENERAL",status:403,title:"File/resource permission denied",suggestion:"Check file permissions and access rights"}),pn=o({slug:"file-not-found",category:"GENERAL",status:404,title:"File not found",suggestion:"Verify the file path exists"}),En=o({slug:"resource-not-found",category:"GENERAL",status:404,title:"Requested resource not found",suggestion:"Verify the referenced resource ID or name exists"}),mn=o({slug:"invalid-argument",category:"GENERAL",status:400,title:"Invalid function argument",suggestion:"Check argument types and values",exitCode:2}),Rn=o({slug:"timeout-error",category:"GENERAL",status:408,title:"Operation timed out",suggestion:"Increase timeout or optimize the operation"}),yn=o({slug:"initialization-error",category:"GENERAL",status:500,title:"Initialization failed",suggestion:"Check initialization requirements and dependencies"}),xn=o({slug:"not-supported",category:"GENERAL",status:501,title:"Feature not supported",suggestion:"Check documentation for supported features"}),$=o({slug:"security-violation",category:"GENERAL",status:403,title:"Security violation detected",suggestion:"Check for path traversal or unauthorized access attempts"}),hn=o({slug:"input-validation-failed",category:"GENERAL",status:400,title:"Input validation failed",suggestion:"Check request input against validation rules"}),_n=o({slug:"project-source-empty",category:"GENERAL",status:400,title:"Project source is empty",suggestion:"Add project files or run \'veryfront init\'"}),mt={"unknown-error":gn,"authentication-required":dn,"permission-denied":fn,"file-not-found":pn,"resource-not-found":En,"invalid-argument":mn,"timeout-error":Rn,"initialization-error":yn,"not-supported":xn,"security-violation":$,"input-validation-failed":hn,"project-source-empty":_n};var Ko=F(it,at,ct,ut,lt,gt,dt,ft,pt,Et,mt);var Sn=[{source:String.raw`]*>[\\s\\S]*?<\\/script>`,flags:"gi",name:"inline script"},{source:String.raw`javascript:`,flags:"gi",name:"javascript: URL"},{source:String.raw`\\bon\\w+\\s*=`,flags:"gi",name:"event handler attribute"},{source:String.raw`data:\\s*text\\/html`,flags:"gi",name:"data: HTML URL"}];function In(){return Sn.map(({source:e,flags:r,name:t})=>({pattern:new RegExp(e,r),name:t}))}function Tn(){let e=globalThis;return e.__VERYFRONT_DEV__===!0||e.Deno?.env?.get?.("VERYFRONT_ENV")==="development"}function Rt(e,r={}){let{allowInlineScripts:t=!1,strict:n=!1,warn:s=!0}=r;for(let{pattern:i,name:a}of In())if(!(t&&a==="inline script")&&(i.lastIndex=0,!!i.test(e)&&(s&&console.warn(`[Security] Suspicious ${a} detected in server HTML`),n||!Tn())))throw $.create({detail:`Potentially unsafe HTML: ${a} detected`});return e}var T=class{constructor(r,t){m(this,"prefix",r);m(this,"level",t)}log(r,t,n,...s){this.level>r||t?.(n,...s)}debug(r,...t){this.log(0,console.debug,`[${this.prefix}] DEBUG: ${r}`,...t)}info(r,...t){this.log(1,console.log,`[${this.prefix}] ${r}`,...t)}warn(r,...t){this.log(2,console.warn,`[${this.prefix}] WARN: ${r}`,...t)}error(r,...t){this.log(3,console.error,`[${this.prefix}] ERROR: ${r}`,...t)}};function On(){if(typeof window>"u")return 2;let e=globalThis;return e.__VERYFRONT_DEV__||e.__RSC_DEV__?e.__VERYFRONT_DEBUG__||e.__RSC_DEBUG__?0:1:2}var D=On(),R=new T("RSC",D),us=new T("PREFETCH",D),ls=new T("HYDRATE",D),gs=new T("VERYFRONT",D);var ps=Object.freeze({IPV4:"127.0.0.1",IPV6:"::1",HOSTNAME:"localhost"});var An=5e3,Cn=1e4,Rs=16*1024*1024,Nn=5e3;var bn=100;var Dn=Object.freeze([5,10,25,50,75,100,250,500,750,1e3,2500,5e3,7500,1e4]),ys=Object.freeze([1,5,10,25,50,100,250,500,1e3,2500,5e3,1e4]),xs=Object.freeze({server:Object.freeze({port:3e3,hostname:"0.0.0.0"}),timeouts:Object.freeze({default:An,api:3e4,ssr:Cn,hmr:3e4,sandbox:Nn}),cache:Object.freeze({jit:Object.freeze({maxSize:bn,tempDirPrefix:"vf-bundle-"})}),metrics:Object.freeze({ssrBoundaries:Dn})});var d="/_veryfront",V={RSC:`${d}/rsc/`,FS:`${d}/fs/`,MODULES:`${d}/modules/`,PAGES:`${d}/pages/`,DATA:`${d}/data/`,LIB:`${d}/lib/`,CHUNKS:`${d}/chunks/`,CLIENT:`${d}/client/`},xt={HMR_RUNTIME:`${d}/hmr-runtime.js`,HMR:`${d}/hmr.js`,ERROR_OVERLAY:`${d}/error-overlay.js`,DEV_LOADER:`${d}/dev-loader.js`,CLIENT_LOG:`${d}/log`,CLIENT_JS:`${d}/client.js`,ROUTER_JS:`${d}/router.js`,PREFETCH_JS:`${d}/prefetch.js`,MANIFEST_JSON:`${d}/manifest.json`,APP_JS:`${d}/app.js`,RSC_CLIENT:`${d}/rsc/client.js`,RSC_MANIFEST:`${d}/rsc/manifest`,RSC_STREAM:`${d}/rsc/stream`,RSC_PAYLOAD:`${d}/rsc/payload`,RSC_RENDER:`${d}/rsc/render`,RSC_PAGE:`${d}/rsc/page`,RSC_MODULE:`${d}/rsc/module`,RSC_DOM:`${d}/rsc/dom.js`,LIB_CHAT_REACT:`${d}/lib/chat/react.js`,LIB_CHAT_COMPONENTS:`${d}/lib/chat/components.js`,LIB_CHAT_PRIMITIVES:`${d}/lib/chat/primitives.js`};var Ln={ROOT:".veryfront",CACHE:".veryfront/cache",KV:".veryfront/kv",LOGS:".veryfront/logs",TMP:".veryfront/tmp"},_s=Ln.CACHE;var Ss={HMR_RUNTIME:xt.HMR_RUNTIME,ERROR_OVERLAY:xt.ERROR_OVERLAY};var Un=V.RSC,wn=V.FS;var ht="rsc-root",G="x-veryfront-dependency-pins";var Cs=Array.prototype.at,Ns=Array.prototype.filter,bs=Array.prototype.join,Ds=Array.prototype.map,Ls=Array.prototype.pop,Us=Array.prototype.push,ws=Array.prototype.sort;var Ks=Object.freeze({react:"","react-dom":"","react-dom/client":"","react-dom/server":"","react/jsx-runtime":"","react/jsx-dev-runtime":""});var Pn="veryfront-hydration-data";function _t(e){try{let r=[...e.querySelectorAll(`[id="${Pn}"]`)];if(r.length!==1)return null;let t=e.body;if(!t)return null;let n=r[0];return t.firstElementChild!==n&&n.parentElement!==t||n.tagName?.toLowerCase()!=="script"||n.getAttribute("type")?.trim().toLowerCase()!=="application/json"?null:n}catch{return null}}function St(e,r){if(!r?.startsWith("on:"))return!1;try{let t=_t(e);if(!t)return!1;let n=JSON.parse(t.textContent||"{}");return n.dependencyPinningCacheKey=r,t.textContent=JSON.stringify(n),!0}catch(t){return R.debug("hydration dependency snapshot seed failed",t),!1}}function Tt(e,r){let t=r==="root"?ht:`rsc-slot-${r}`,n=e.getElementById(t);if(n)return n;let s=e.createElement("div");return s.id=t,e.body.appendChild(s),s}function Mn(e,r){if(r.type!=="slot")return;let t=Tt(e,r.id);t.innerHTML=Rt(String(r.html??""))}function It(e,r){let t=r.split(`\n`),n=t.pop()??"";for(let s of t){let i=s.trim();if(!i)continue;let a;try{a=JSON.parse(i)}catch(l){R.debug("[client-dom] malformed NDJSON line",{line:i,error:l instanceof Error?l.message:String(l)});continue}if(!a||typeof a!="object")continue;let c=a;if(c.type==="slot"){Mn(e,c);try{Vn(e,c.id||"root")}catch(l){R.debug("[client-dom] hydration optional failed",l)}}}return n}function kn(e){return new Promise((r,t)=>{let n=()=>t(new DOMException("aborted","AbortError"));if(e.aborted){n();return}e.addEventListener("abort",n,{once:!0})})}async function pi(e,r=document,t){let n="body"in e?e:null,s=n?.body??e;if(!s)return;n&&St(r,n.headers.get(G));let i=s.getReader(),a=new TextDecoder,c="",l=!1;try{for(;;){if(t?.aborted)throw new DOMException("aborted","AbortError");let u=i.read(),{done:g,value:p}=t?await Promise.race([u,kn(t)]):await u;if(g){l=!0;break}c+=a.decode(p,{stream:!0}),c=It(r,c)}c&&It(r,`${c}\n`)}catch(u){throw u instanceof Error&&u.name==="AbortError"||R.debug("[client-dom] consumeNdjsonStream error",u),u}finally{try{await i.cancel()}catch(u){l||R.debug("[client-dom] reader.cancel failed",u)}try{i.releaseLock()}catch(u){R.debug("[client-dom] reader.releaseLock failed",u)}if(typeof s.cancel=="function")try{await s.cancel()}catch(u){R.debug("[client-dom] stream.cancel failed",u)}if(typeof n?.body?.cancel=="function")try{await n.body.cancel()}catch(u){R.debug("[client-dom] response.body.cancel failed",u)}}}function $n(e,r){let t=Tt(e,r),n=[],s=i=>{let a=i;a.dataset?.clientRef&&n.push(a);for(let c of i.children)s(c)};return s(t),n}function Vn(e,r){let t=$n(e,r);for(let n of t){let s=n.dataset?.clientRef;s&&(n.dataset.hydrated="true",R.debug("[client-dom] marked for hydration",s))}}export{pi as consumeNdjsonStream,Tt as getContainer};\n'; diff --git a/src/utils/env-loader.test.ts b/src/utils/env-loader.test.ts index ec5fd36064..7c9577f4d7 100644 --- a/src/utils/env-loader.test.ts +++ b/src/utils/env-loader.test.ts @@ -252,5 +252,67 @@ describe("env-loader", () => { __resetLoggerConfigForTests(); } }); + + it("should not print environment values in debug logs", async () => { + const key = createKey("SECRET_LOG"); + const secret = "highly-sensitive-value"; + const previousLogLevel = getEnv("LOG_LEVEL"); + const previousLogFormat = getEnv("LOG_FORMAT"); + const originalDebug = console.debug; + const output: string[] = []; + + try { + setEnv("LOG_LEVEL", "DEBUG"); + setEnv("LOG_FORMAT", "json"); + __resetLoggerConfigForTests(); + console.debug = (message: string) => output.push(message); + await writeEnvFile(".env", `${key}=${secret}`); + + await loadEnv({ cwd: tempDir, override: true, debug: true }); + + assertEquals(output.join("\n").includes("highly-sensitive"), false); + assertEquals(output.join("\n").includes(key), true); + } finally { + console.debug = originalDebug; + cleanupKeys(key); + if (previousLogLevel === undefined) deleteEnv("LOG_LEVEL"); + else setEnv("LOG_LEVEL", previousLogLevel); + if (previousLogFormat === undefined) deleteEnv("LOG_FORMAT"); + else setEnv("LOG_FORMAT", previousLogFormat); + __resetLoggerConfigForTests(); + } + }); + + it("should strip credentials from the logged VERYFRONT_API_BASE_URL", async () => { + const previousValue = getEnv("VERYFRONT_API_BASE_URL"); + const previousLogFormat = getEnv("LOG_FORMAT"); + const { getOutput, restore } = captureConsoleLog(); + + try { + setEnv("LOG_FORMAT", "json"); + __resetLoggerConfigForTests(); + await writeEnvFile( + ".env", + "VERYFRONT_API_BASE_URL=https://user:hybrid-basic-secret@api.example.com/api", + ); + + await loadEnv({ cwd: tempDir, override: true }); + + const output = getOutput(); + const entry = JSON.parse(output) as LogEntry; + assertEquals( + entry.message, + "VERYFRONT_API_BASE_URL loaded: https://user:[REDACTED]@api.example.com/api", + ); + assertEquals(output.includes("hybrid-basic-secret"), false); + } finally { + restore(); + if (previousValue === undefined) deleteEnv("VERYFRONT_API_BASE_URL"); + else setEnv("VERYFRONT_API_BASE_URL", previousValue); + if (previousLogFormat === undefined) deleteEnv("LOG_FORMAT"); + else setEnv("LOG_FORMAT", previousLogFormat); + __resetLoggerConfigForTests(); + } + }); }); }); diff --git a/src/utils/env-loader.ts b/src/utils/env-loader.ts index 757f28e764..07d04b2d2a 100644 --- a/src/utils/env-loader.ts +++ b/src/utils/env-loader.ts @@ -1,4 +1,5 @@ import { refreshLoggerConfig, serverLogger } from "./logger/index.ts"; +import { sanitizeUrlCredentials } from "./logger/redact.ts"; import { cwd as getCwd, getEnv, setEnv } from "#veryfront/platform/compat/process.ts"; import { isNotFoundError, readTextFile } from "#veryfront/platform/compat/fs.ts"; @@ -37,13 +38,15 @@ export async function loadEnv( envSources.set(key, file); totalVars++; + // Log only the key name and value length — never any part of the value. + // Env files routinely carry credentials (VERYFRONT_API_TOKEN, DSNs), and + // a 20-char prefix is enough to leak most of a token. if (debug) { - logger.debug( - `[env] ${key}=${value.substring(0, 20)}${value.length > 20 ? "..." : ""}`, - ); + logger.debug(`[env] ${key} (${value.length} chars)`); } if (key === "VERYFRONT_API_BASE_URL") { - logger.info(`VERYFRONT_API_BASE_URL loaded: ${value}`); + // Hybrid setups can embed userinfo credentials in the URL; strip them. + logger.info(`VERYFRONT_API_BASE_URL loaded: ${sanitizeUrlCredentials(value)}`); } } diff --git a/src/utils/logger/logger-hostile-fallback.fixture.ts b/src/utils/logger/logger-hostile-fallback.fixture.ts new file mode 100644 index 0000000000..b8a21d14fa --- /dev/null +++ b/src/utils/logger/logger-hostile-fallback.fixture.ts @@ -0,0 +1,45 @@ +const originalObjectValues = Object.values; +const originalObjectToJSON = Object.getOwnPropertyDescriptor(Object.prototype, "toJSON"); +let output = ""; + +try { + Object.values = () => { + throw new Error("polluted Object.values"); + }; + Object.defineProperty(Object.prototype, "toJSON", { + configurable: true, + value() { + throw new Error("inherited serializer must not run"); + }, + }); + + // Initialize the serializer while the intrinsic is hostile so the logger's + // degraded fallback path is exercised after the global is restored. + await import("./serialization.ts"); + Object.values = originalObjectValues; + + Deno.env.set("LOG_FORMAT", "json"); + const { __resetLoggerConfigForTests, getBaseLogger } = await import("./logger.ts"); + __resetLoggerConfigForTests(); + + const originalConsoleLog = console.log; + try { + console.log = (value: unknown) => { + output = String(value); + }; + getBaseLogger("SERVER") + .component("token=synthetic-component-secret") + .info("Fallback probe", { ok: true }); + } finally { + console.log = originalConsoleLog; + } +} finally { + Object.values = originalObjectValues; + if (originalObjectToJSON !== undefined) { + Object.defineProperty(Object.prototype, "toJSON", originalObjectToJSON); + } else { + delete (Object.prototype as { toJSON?: unknown }).toJSON; + } +} + +console.log(output); diff --git a/src/utils/logger/logger.test.ts b/src/utils/logger/logger.test.ts index 16ba62d38f..59a7b8c174 100644 --- a/src/utils/logger/logger.test.ts +++ b/src/utils/logger/logger.test.ts @@ -3,6 +3,7 @@ import { assertEquals } from "#veryfront/testing/assert.ts"; import { describe, it } from "#veryfront/testing/bdd.ts"; import { __registerLogRecordEmitter, + __registerRequestContextGetter, __registerTraceContextGetter, __resetLoggerConfigForTests, __resetLogRecordEmitterForTests, @@ -12,11 +13,17 @@ import { getBaseLogger, getDefaultLevel, type LogEntry, + type Logger, LogLevel, refreshLoggerConfig, serverLogger, } from "./logger.ts"; -import { type RequestContext, runWithRequestContextAsync } from "./request-context.ts"; +import { + getRequestContext, + type RequestContext, + requestContextStore, + runWithRequestContextAsync, +} from "./request-context.ts"; import { runWithProjectEnv } from "../../server/project-env/storage.ts"; import { VERSION } from "../version.ts"; @@ -105,6 +112,152 @@ describe("logger", () => { } }); + it("contains throwing console access inside the logging boundary", () => { + const originalLog = Object.getOwnPropertyDescriptor(console, "log")!; + let threw = false; + + Object.defineProperty(console, "log", { + configurable: true, + get() { + throw new Error("project code replaced the console sink"); + }, + }); + try { + try { + getBaseLogger("SERVER").info("contained console getter"); + } catch { + threw = true; + } + } finally { + Object.defineProperty(console, "log", originalLog); + } + + assertEquals(threw, false); + }); + + it("uses captured subscriber iteration after the Set iterator changes", () => { + const originalIterator = Object.getOwnPropertyDescriptor(Set.prototype, Symbol.iterator)!; + const messages: string[] = []; + const unsubscribe = __subscribeLogRecordEmitter((entry) => messages.push(entry.message)); + let threw = false; + + Object.defineProperty(Set.prototype, Symbol.iterator, { + configurable: true, + value() { + throw new Error("project code replaced Set iteration"); + }, + }); + try { + try { + getBaseLogger("SERVER").info("captured subscriber iteration"); + } catch { + threw = true; + } + } finally { + Object.defineProperty(Set.prototype, Symbol.iterator, originalIterator); + unsubscribe(); + } + + assertEquals(threw, false); + assertEquals(messages, ["captured subscriber iteration"]); + }); + + it("delivers one record once when a subscriber reinserts itself", () => { + const originalLog = console.log; + let calls = 0; + let unsubscribe = () => {}; + const subscriber = () => { + calls++; + unsubscribe(); + unsubscribe = __subscribeLogRecordEmitter(subscriber); + }; + + console.log = () => {}; + unsubscribe = __subscribeLogRecordEmitter(subscriber); + try { + getBaseLogger("SERVER").info("single delivery"); + } finally { + unsubscribe(); + console.log = originalLog; + } + + assertEquals(calls, 1); + }); + + it("keeps emitting after the Date constructor and prototype method change", () => { + const { getOutput, restore } = captureConsoleLog(); + const OriginalDate = globalThis.Date; + const originalToISOString = OriginalDate.prototype.toISOString; + + try { + withJsonLogFormat(() => { + globalThis.Date = function ReplacementDate() { + throw new Error("project code replaced Date"); + } as unknown as DateConstructor; + OriginalDate.prototype.toISOString = () => { + throw new Error("project code replaced Date serialization"); + }; + getBaseLogger("SERVER").info("captured timestamp intrinsics"); + }); + } finally { + globalThis.Date = OriginalDate; + OriginalDate.prototype.toISOString = originalToISOString; + restore(); + } + + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.message, "captured timestamp intrinsics"); + }); + + it("uses captured case conversion in normal and emergency logging", () => { + const { getOutput, restore } = captureConsoleLog(); + const originalToLowerCase = String.prototype.toLowerCase; + let threw = false; + + try { + withJsonLogFormat(() => { + String.prototype.toLowerCase = () => { + throw new Error("project code replaced lowercase conversion"); + }; + try { + getBaseLogger("SERVER").info("captured lowercase conversion"); + } catch { + threw = true; + } finally { + String.prototype.toLowerCase = originalToLowerCase; + } + }); + } finally { + String.prototype.toLowerCase = originalToLowerCase; + restore(); + } + + assertEquals(threw, false); + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.message, "captured lowercase conversion"); + }); + + it("omits empty component names in emergency JSON entries", () => { + const { getOutput, restore } = captureConsoleLog(); + const originalKeys = Object.keys; + + try { + withJsonLogFormat(() => { + Object.keys = () => { + throw new Error("project code replaced Object.keys"); + }; + getBaseLogger("SERVER").component("").info("emergency component"); + }); + } finally { + Object.keys = originalKeys; + restore(); + } + + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.message, "[REDACTED]"); + assertEquals(entry.component, undefined); + }); + describe("getDefaultLevel", () => { // Note: Pass explicit values to avoid reading process env in parallel tests. @@ -306,6 +459,205 @@ describe("logger", () => { restore(); } }); + + it("falls back when the request-context provider or logger accessor throws", () => { + const { getOutput, restore } = captureConsoleLog(); + const hostileContext = Object.create(null) as { logger: Logger }; + Object.defineProperty(hostileContext, "logger", { + get() { + throw new Error("unreadable request logger"); + }, + }); + + try { + withJsonLogFormat(() => { + __registerRequestContextGetter(() => { + throw new Error("request context unavailable"); + }); + serverLogger.info("provider fallback"); + assertEquals((JSON.parse(getOutput()) as LogEntry).message, "provider fallback"); + + __registerRequestContextGetter(() => hostileContext); + serverLogger.info("accessor fallback"); + assertEquals((JSON.parse(getOutput()) as LogEntry).message, "accessor fallback"); + }); + } finally { + __registerRequestContextGetter(getRequestContext); + restore(); + } + }); + + it("falls back when request logger dispatch or AsyncLocalStorage lookup throws", () => { + const { getOutput, restore } = captureConsoleLog(); + const hostileLogger = new Proxy({} as Logger, { + get(_target, property) { + if (property === "info") throw new Error("unreadable logger method"); + return undefined; + }, + }); + const storagePrototype = Object.getPrototypeOf(requestContextStore); + const originalGetStore = Object.getOwnPropertyDescriptor(storagePrototype, "getStore")!; + + try { + withJsonLogFormat(() => { + __registerRequestContextGetter(() => ({ logger: hostileLogger })); + serverLogger.info("dispatch fallback"); + assertEquals((JSON.parse(getOutput()) as LogEntry).message, "dispatch fallback"); + + __registerRequestContextGetter(getRequestContext); + Object.defineProperty(storagePrototype, "getStore", { + configurable: true, + value() { + throw new Error("project code replaced AsyncLocalStorage.getStore"); + }, + }); + try { + serverLogger.info("storage fallback"); + } finally { + Object.defineProperty(storagePrototype, "getStore", originalGetStore); + } + assertEquals((JSON.parse(getOutput()) as LogEntry).message, "storage fallback"); + }); + } finally { + Object.defineProperty(storagePrototype, "getStore", originalGetStore); + __registerRequestContextGetter(getRequestContext); + restore(); + } + }); + + it("contains hostile request logger timing and child composition", async () => { + const { getOutput, restore } = captureConsoleLog(); + const originalError = console.error; + const hostileLogger = new Proxy({} as Logger, { + get() { + throw new Error("unreadable logger operation"); + }, + }); + let executions = 0; + + Deno.env.set("LOG_FORMAT", "json"); + console.error = () => {}; + __resetLoggerConfigForTests(); + __registerRequestContextGetter(() => ({ logger: hostileLogger })); + try { + const directResult = await serverLogger.time("direct timer", () => { + executions++; + return Promise.resolve("direct result"); + }); + assertEquals(directResult, "direct result"); + assertEquals(executions, 1); + + serverLogger.child({ scope: "direct" }).info("direct child fallback"); + assertEquals((JSON.parse(getOutput()) as LogEntry).message, "direct child fallback"); + + const componentLogger = serverLogger.component("request"); + const componentResult = await componentLogger.time("component timer", () => { + executions++; + return Promise.resolve("component result"); + }); + assertEquals(componentResult, "component result"); + assertEquals(executions, 2); + + componentLogger.child({ scope: "component" }).info("component child fallback"); + const componentEntry = JSON.parse(getOutput()) as LogEntry; + assertEquals(componentEntry.message, "component child fallback"); + assertEquals(componentEntry.component, "request"); + + const applicationError = new Error("application failure"); + let caught: unknown; + try { + await serverLogger.time("rejected timer", () => { + executions++; + return Promise.reject(applicationError); + }); + } catch (error) { + caught = error; + } + assertEquals(caught, applicationError); + assertEquals(executions, 3); + + const returnedHostileLogger = new Proxy({} as Logger, { + get() { + throw new Error("unreadable returned child logger"); + }, + }); + const hostileChildFactory = { + child() { + return returnedHostileLogger; + }, + component() { + return hostileChildFactory; + }, + } as unknown as Logger; + __registerRequestContextGetter(() => ({ logger: hostileChildFactory })); + + const guardedChild = serverLogger.child({ scope: "returned" }); + guardedChild.info("returned child fallback"); + assertEquals((JSON.parse(getOutput()) as LogEntry).message, "returned child fallback"); + assertEquals( + await guardedChild.time("returned child timer", () => Promise.resolve("timed")), + "timed", + ); + guardedChild.child({ nested: true }).info("nested child fallback"); + assertEquals((JSON.parse(getOutput()) as LogEntry).message, "nested child fallback"); + guardedChild.component("nested").info("nested component fallback"); + assertEquals((JSON.parse(getOutput()) as LogEntry).message, "nested component fallback"); + + serverLogger.component("request").child({ scope: "component" }).info( + "returned component child fallback", + ); + const returnedComponentEntry = JSON.parse(getOutput()) as LogEntry; + assertEquals(returnedComponentEntry.message, "returned component child fallback"); + assertEquals(returnedComponentEntry.component, "request"); + } finally { + __registerRequestContextGetter(getRequestContext); + Deno.env.delete("LOG_FORMAT"); + __resetLoggerConfigForTests(); + console.error = originalError; + restore(); + } + }); + + it("preserves timer outcomes when label coercion throws", async () => { + const originalError = console.error; + const hostileLabel = { + [Symbol.toPrimitive]() { + throw new Error("unreadable timer label"); + }, + } as unknown as string; + const loggers: Logger[] = [ + getBaseLogger("timer"), + serverLogger, + serverLogger.component("timer"), + ]; + let executions = 0; + + console.error = () => {}; + try { + for (const timerLogger of loggers) { + const result = await timerLogger.time(hostileLabel, () => { + executions++; + return Promise.resolve("application result"); + }); + assertEquals(result, "application result"); + + const applicationError = new Error("application rejection"); + let caught: unknown; + try { + await timerLogger.time(hostileLabel, () => { + executions++; + return Promise.reject(applicationError); + }); + } catch (error) { + caught = error; + } + assertEquals(caught, applicationError); + } + assertEquals(executions, 6); + } finally { + console.error = originalError; + } + }); }); describe("JSON output format", () => { @@ -494,6 +846,204 @@ describe("logger", () => { restore(); } }); + + it("scrubs credentials embedded in the log message", () => { + const { getOutput, restore } = captureConsoleLog(); + + try { + withJsonLogFormat(() => { + serverLogger.info( + "Fetching https://user:password@example.com/cb?access_token=secret", + ); + + const line = getOutput(); + const entry = JSON.parse(line) as LogEntry; + assertEquals(line.includes("password"), false); + assertEquals(line.includes("secret"), false); + assertEquals(entry.message.includes("[REDACTED]"), true); + }); + } finally { + restore(); + } + }); + + it("keeps benign assignment-shaped log messages intact", () => { + const { getOutput, restore } = captureConsoleLog(); + + try { + withJsonLogFormat(() => { + serverLogger.info("mapping: 4 routes resolved"); + }); + + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.message, "mapping: 4 routes resolved"); + } finally { + restore(); + } + }); + + it("serializes BigInt and hostile toJSON getters without throwing", () => { + const { getOutput, restore } = captureConsoleLog(); + const hostile: Record = {}; + Object.defineProperty(hostile, "toJSON", { + get() { + throw new Error("hostile serializer getter"); + }, + }); + + try { + withJsonLogFormat(() => { + serverLogger.info("Unusual values", { count: 42n, hostile }); + }); + + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.context?.count, "42"); + assertEquals(entry.context?.hostile, "[REDACTED]"); + } finally { + restore(); + } + }); + + it("contains hostile child context, component, and message values", () => { + const { getOutput, restore } = captureConsoleLog(); + const hostileValue = new Proxy({}, { + get() { + throw new Error("hostile value read"); + }, + ownKeys() { + throw new Error("hostile keys read"); + }, + }); + + try { + withJsonLogFormat(() => { + const hostileContext = hostileValue as Record; + const hostileString = hostileValue as unknown as string; + getBaseLogger("SERVER") + .child(hostileContext) + .component(hostileString) + .info(hostileString, hostileContext); + }); + + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.message, "[REDACTED]"); + assertEquals(entry.component, "[REDACTED]"); + } finally { + restore(); + } + }); + + it("ignores inherited serialization hooks and preserves component fields", () => { + const { getOutput, restore } = captureConsoleLog(); + const objectToJson = Object.getOwnPropertyDescriptor(Object.prototype, "toJSON"); + const arrayToJson = Object.getOwnPropertyDescriptor(Array.prototype, "toJSON"); + let hookCalls = 0; + + Object.defineProperty(Object.prototype, "toJSON", { + configurable: true, + value() { + hookCalls += 1; + throw new Error("inherited object serializer must not run"); + }, + }); + Object.defineProperty(Array.prototype, "toJSON", { + configurable: true, + value() { + hookCalls += 1; + throw new Error("inherited array serializer must not run"); + }, + }); + + try { + withJsonLogFormat(() => { + getBaseLogger("SERVER").component("routing").info("Routes", { + values: ["one", "two"], + }); + }); + } finally { + if (objectToJson) { + Object.defineProperty(Object.prototype, "toJSON", objectToJson); + } else { + delete (Object.prototype as { toJSON?: unknown }).toJSON; + } + if (arrayToJson) { + Object.defineProperty(Array.prototype, "toJSON", arrayToJson); + } else { + delete (Array.prototype as { toJSON?: unknown }).toJSON; + } + restore(); + } + + assertEquals(hookCalls, 0); + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.component, "routing"); + assertEquals(entry.context?.values, ["one", "two"]); + }); + + it("ignores hooks added through the intrinsic array prototype chain", () => { + const { getOutput, restore } = captureConsoleLog(); + const originalArrayPrototypeParent = Object.getPrototypeOf(Array.prototype); + let hookCalls = 0; + const hostileParent = Object.create(originalArrayPrototypeParent) as { + toJSON?: () => unknown; + }; + hostileParent.toJSON = () => { + hookCalls += 1; + return "polluted-array"; + }; + + Object.setPrototypeOf(Array.prototype, hostileParent); + try { + withJsonLogFormat(() => { + serverLogger.info("Routes", { + values: [{ apiKey: "secret", ok: true }], + }); + }); + } finally { + Object.setPrototypeOf(Array.prototype, originalArrayPrototypeParent); + restore(); + } + + assertEquals(hookCalls, 0); + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.context?.values, [{ apiKey: "[REDACTED]", ok: true }]); + }); + + it("preserves non-callable own toJSON fields", () => { + const { getOutput, restore } = captureConsoleLog(); + + try { + withJsonLogFormat(() => { + serverLogger.info("Metadata", { toJSON: "plain metadata" }); + }); + + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.context?.toJSON, "plain metadata"); + } finally { + restore(); + } + }); + + it("uses the captured JSON serializer when the global is replaced", () => { + const { getOutput, restore } = captureConsoleLog(); + const originalStringify = JSON.stringify; + + try { + withJsonLogFormat(() => { + JSON.stringify = () => { + throw new Error("project code replaced JSON.stringify"); + }; + serverLogger.info("Protected serializer", { apiKey: "sk-project-secret" }); + }); + } finally { + JSON.stringify = originalStringify; + restore(); + } + + const entry = JSON.parse(getOutput()) as LogEntry; + assertEquals(entry.message, "Protected serializer"); + assertEquals(entry.context?.apiKey, "[REDACTED]"); + }); }); describe("text output format", () => { @@ -546,6 +1096,26 @@ describe("logger", () => { } }); + it("scrubs credentials embedded in the rendered message", () => { + Deno.env.set("LOG_FORMAT", "text"); + Deno.env.set("NO_COLOR", "1"); + __resetLoggerConfigForTests(); + const { getOutput, restore } = captureConsoleLog(); + + try { + serverLogger.info("Fetching https://user:password@example.com?token=secret"); + const output = getOutput(); + assertEquals(output.includes("password"), false); + assertEquals(output.includes("secret"), false); + assertEquals(output.includes("[REDACTED]"), true); + } finally { + restore(); + Deno.env.delete("LOG_FORMAT"); + Deno.env.delete("NO_COLOR"); + __resetLoggerConfigForTests(); + } + }); + it("scrubs credential-shaped text from rendered context values (#341)", () => { Deno.env.set("LOG_FORMAT", "text"); Deno.env.set("NO_COLOR", "1"); diff --git a/src/utils/logger/logger.ts b/src/utils/logger/logger.ts index 14d3955394..324367ebe0 100644 --- a/src/utils/logger/logger.ts +++ b/src/utils/logger/logger.ts @@ -14,7 +14,42 @@ import { type SerializedError, serializeError, } from "./core.ts"; -import { redactSensitive, sanitizeSerializedError, sanitizeUrlCredentials } from "./redact.ts"; +import { + REDACTED, + redactForSerialization, + redactSensitive, + sanitizeSerializedError, + sanitizeUrlCredentials, +} from "./redact.ts"; +import { stringifyRedactedJson } from "./serialization.ts"; + +const apply = Reflect.apply; +const arrayPush = Array.prototype.push; +const arrayIsArray = Array.isArray; +const NativeConsole = console; +const NativeDate = Date; +const dateToISOString = Date.prototype.toISOString; +const NativePerformance = performance; +const performanceNow = Performance.prototype.now; +const numberRound = Math.round; +const objectCreate = Object.create; +const objectGetPrototypeOf = Object.getPrototypeOf; +const NativeSet = Set; +const setAdd = Set.prototype.add; +const setClear = Set.prototype.clear; +const setDelete = Set.prototype.delete; +const setValues = Set.prototype.values; +const setIteratorNext = objectGetPrototypeOf(new NativeSet().values()).next; +const stringToLowerCase = String.prototype.toLowerCase; +const stringToUpperCase = String.prototype.toUpperCase; + +function readPerformanceNow(): number { + try { + return apply(performanceNow, NativePerformance, []) as number; + } catch { + return 0; + } +} export enum LogLevel { DEBUG = 0, @@ -132,7 +167,7 @@ const LOG_LEVEL_MAP: Readonly> = { function parseLogLevel(levelString: string | undefined): LogLevel | undefined { if (!levelString) return undefined; - return LOG_LEVEL_MAP[levelString.toUpperCase()]; + return LOG_LEVEL_MAP[apply(stringToUpperCase, levelString, []) as string]; } /** @@ -174,7 +209,7 @@ function getDefaultFormat( let loggerConfig: LoggerConfig | null = null; let legacyLogRecordEmitter: LogRecordEmitter | null = null; -const logRecordSubscribers = new Set(); +const logRecordSubscribers = new NativeSet(); /** * Re-read logger configuration from environment variables. @@ -220,16 +255,16 @@ export function __registerLogRecordEmitter(emitter: LogRecordEmitter | null): vo /** Subscribe to process-level structured log records. Returns an unregister function. */ export function __subscribeLogRecordEmitter(emitter: LogRecordEmitter): () => void { - logRecordSubscribers.add(emitter); + apply(setAdd, logRecordSubscribers, [emitter]); return () => { - logRecordSubscribers.delete(emitter); + apply(setDelete, logRecordSubscribers, [emitter]); }; } /** Reset the process-level structured log emitter. Only intended for tests. */ export function __resetLogRecordEmitterForTests(): void { legacyLogRecordEmitter = null; - logRecordSubscribers.clear(); + apply(setClear, logRecordSubscribers, []); } function resolveLoggerConfig(): LoggerConfig { @@ -243,6 +278,29 @@ function resolveLoggerConfig(): LoggerConfig { return loggerConfig; } +function sanitizeLogString(value: unknown, fallback: string): string { + try { + return sanitizeUrlCredentials(typeof value === "string" ? value : String(value)); + } catch { + return fallback; + } +} + +function currentIsoTimestamp(): string { + return apply(dateToISOString, new NativeDate(), []) as string; +} + +function snapshotLogContext(context: unknown): Record { + try { + const snapshot = redactForSerialization(context); + return typeof snapshot === "object" && snapshot !== null && !arrayIsArray(snapshot) + ? snapshot as Record + : {}; + } catch { + return {}; + } +} + /** * Extract context from variadic args. * First object argument becomes context, errors are handled specially. @@ -254,21 +312,26 @@ function extractContext( let error: LogEntry["error"] | undefined; for (const arg of args) { - if (arg instanceof Error) { - error = serializeError(arg); - continue; - } - if (typeof arg === "object" && arg !== null && !Array.isArray(arg)) { - const contextArg = arg as Record; - if (contextArg.error instanceof Error) { - const { error: contextError, ...rest } = contextArg; - error = serializeError(contextError); - if (Object.keys(rest).length > 0) { - context = { ...context, ...rest }; - } + try { + if (arg instanceof Error) { + error = serializeError(arg); continue; } - context = { ...context, ...contextArg }; + if (typeof arg === "object" && arg !== null && !arrayIsArray(arg)) { + const contextArg = arg as Record; + if (contextArg.error instanceof Error) { + const { error: contextError, ...rest } = contextArg; + error = serializeError(contextError); + if (Object.keys(rest).length > 0) { + context = { ...context, ...rest }; + } + continue; + } + context = { ...context, ...contextArg }; + } + } catch { + // Logging accepts application-owned objects. Ignore an unreadable value + // rather than allowing proxy traps or getters to break the caller. } } @@ -342,6 +405,30 @@ function sanitizeStringFieldValue(value: unknown): string { return sanitizeUrlCredentials(String(value)); } +function createFallbackLogEntry(entry: LogEntry): Record { + const fallback = objectCreate(null) as Record; + fallback.timestamp = entry.timestamp; + fallback.level = entry.level; + fallback.service = entry.service; + fallback.veryfrontVersion = entry.veryfrontVersion; + fallback.message = entry.message; + if (entry.component !== undefined) fallback.component = entry.component; + const context = objectCreate(null) as Record; + context.unserializable_context = REDACTED; + fallback.context = context; + return fallback; +} + +/** + * Serialize a log entry without letting caller-controlled values or inherited + * `toJSON` hooks escape the logging boundary. The redacted snapshot normalizes + * BigInt and deliberate serializers first, then shadows inherited hooks on + * every owned object and array before native JSON serialization. + */ +function stringifyLogEntry(entry: LogEntry): string { + return stringifyRedactedJson(entry, createFallbackLogEntry(entry)); +} + class ConsoleLogger implements Logger { private boundContext: Record; private componentName?: string; @@ -352,21 +439,46 @@ class ConsoleLogger implements Logger { componentName?: string, private readonly options: ConsoleLoggerOptions = {}, ) { - this.boundContext = boundContext ?? {}; - this.componentName = componentName; + this.boundContext = snapshotLogContext(boundContext ?? {}); + this.componentName = componentName === undefined + ? undefined + : sanitizeLogString(componentName, REDACTED); } child(context: Record): Logger { + const childContext = snapshotLogContext(context); return new ConsoleLogger( this.prefix, - { ...this.boundContext, ...context }, + { ...this.boundContext, ...childContext }, this.componentName, this.options, ); } component(name: string): Logger { - return new ConsoleLogger(this.prefix, { ...this.boundContext }, name, this.options); + return new ConsoleLogger( + this.prefix, + { ...this.boundContext }, + name, + this.options, + ); + } + + private createEmergencyEntry(level: LogEntry["level"]): LogEntry { + const entry: LogEntry = { + timestamp: currentIsoTimestamp(), + level, + service: apply( + stringToLowerCase, + sanitizeLogString(this.prefix, "veryfront"), + [], + ) as string, + veryfrontVersion: RUNTIME_VERSION, + message: REDACTED, + context: { unserializable_context: REDACTED }, + }; + if (this.componentName) entry.component = this.componentName; + return entry; } private createEntry(level: LogEntry["level"], message: string, args: unknown[]): LogEntry { @@ -374,11 +486,14 @@ class ConsoleLogger implements Logger { const mergedContext: Record = { ...this.boundContext, ...context }; const entry: LogEntry = { - timestamp: new Date().toISOString(), + timestamp: currentIsoTimestamp(), level, - service: this.prefix.toLowerCase(), + service: apply(stringToLowerCase, this.prefix, []) as string, veryfrontVersion: RUNTIME_VERSION, - message, + // The message string bypasses the key-based context redactor, so scrub + // credential-shaped text (URL userinfo, ?access_token=, header dumps) + // embedded directly in the message before emission (#1989). + message: sanitizeLogString(message, REDACTED), }; if (this.componentName) entry.component = this.componentName; @@ -495,13 +610,16 @@ class ConsoleLogger implements Logger { private formatJson(level: LogEntry["level"], message: string, args: unknown[]): string { const entry = this.createEntry(level, message, args); - return JSON.stringify(entry); + return stringifyLogEntry(entry); } private formatTextLine(level: LogEntry["level"], message: string, args: unknown[]): string { const { context, error } = extractContext(args); const mergedContext = { ...this.boundContext, ...context }; const enableColor = shouldUseColor(); + // Mirror the JSON path: the message string bypasses the key-based context + // redactor, so scrub credential-shaped text before rendering (#1989). + const safeMessage = sanitizeLogString(message, REDACTED); const contextText = formatContextText( redactSensitive(mergedContext), @@ -513,7 +631,7 @@ class ConsoleLogger implements Logger { if (preset === "cli") { // CLI preset: no timestamp or tag — 2-space indent + glyph only. const glyph = colorize(CLI_LEVEL_GLYPHS[level], LEVEL_COLORS[level], enableColor); - return ` ${glyph} ${message}${contextText}`; + return ` ${glyph} ${safeMessage}${contextText}`; } const timestamp = colorize(formatTimestamp(), ANSI.dim, enableColor); @@ -522,73 +640,109 @@ class ConsoleLogger implements Logger { const componentTag = this.componentName ? ` ${colorize(`[${this.componentName}]`, ANSI.dim, enableColor)}` : ""; - return `${timestamp} ${tag} ${glyph}${componentTag} ${message}${contextText}`; + return `${timestamp} ${tag} ${glyph}${componentTag} ${safeMessage}${contextText}`; } private log( level: LogEntry["level"], logLevel: LogLevel, - consoleFn: (...args: unknown[]) => void, + consoleMethod: "debug" | "log" | "warn" | "error", message: string, args: unknown[], ): void { - const { level: resolvedLevel, format: resolvedFormat } = resolveLoggerConfig(); - if (resolvedLevel > logLevel) return; - - let entry: LogEntry | undefined; - const line = resolvedFormat === "json" - ? (() => { - entry = this.createEntry(level, message, args); - return JSON.stringify(entry); - })() - : this.formatTextLine(level, message, args); - - const emittedEntry = entry ?? this.createEntry(level, message, args); - if (legacyLogRecordEmitter) { + try { + const { level: resolvedLevel, format: resolvedFormat } = resolveLoggerConfig(); + if (resolvedLevel > logLevel) return; + + let emittedEntry: LogEntry; + let line: string; try { - legacyLogRecordEmitter(emittedEntry); - } catch (_) { - /* do not let telemetry export failures affect application logging */ + if (resolvedFormat === "json") { + emittedEntry = this.createEntry(level, message, args); + line = stringifyLogEntry(emittedEntry); + } else { + line = this.formatTextLine(level, message, args); + emittedEntry = this.createEntry(level, message, args); + } + } catch { + try { + emittedEntry = this.createEmergencyEntry(level); + line = resolvedFormat === "json" + ? stringifyLogEntry(emittedEntry) + : `${apply(stringToUpperCase, level, [])}: ${REDACTED}`; + } catch { + return; + } } - } - for (const subscriber of logRecordSubscribers) { - if (subscriber === legacyLogRecordEmitter) continue; - try { - subscriber(emittedEntry); - } catch (_) { - /* do not let telemetry export failures affect application logging */ + + if (legacyLogRecordEmitter) { + try { + legacyLogRecordEmitter(emittedEntry); + } catch (_) { + /* do not let telemetry export failures affect application logging */ + } } - } - consoleFn(line); + // Snapshot before invoking callbacks. Native Set iterators observe values + // deleted and reinserted during iteration, which can otherwise invoke one + // subscriber repeatedly (or keep a single log call alive indefinitely). + const subscribers: LogRecordEmitter[] = []; + const iterator = apply(setValues, logRecordSubscribers, []) as SetIterator; + while (true) { + const next = apply(setIteratorNext, iterator, []) as IteratorResult; + if (next.done) break; + apply(arrayPush, subscribers, [next.value]); + } + for (let index = 0; index < subscribers.length; index++) { + const subscriber = subscribers[index]!; + if (subscriber === legacyLogRecordEmitter) continue; + try { + subscriber(emittedEntry); + } catch (_) { + /* do not let telemetry export failures affect application logging */ + } + } + + const consoleFn = NativeConsole[consoleMethod]; + if (typeof consoleFn === "function") { + try { + apply(consoleFn, NativeConsole, [line]); + } catch (_) { + /* logging sink failures must not affect application control flow */ + } + } + } catch (_) { + /* every logging concern is contained by this final nonthrowing boundary */ + } } debug(message: string, ...args: unknown[]): void { - this.log("debug", LogLevel.DEBUG, console.debug, message, args); + this.log("debug", LogLevel.DEBUG, "debug", message, args); } info(message: string, ...args: unknown[]): void { - this.log("info", LogLevel.INFO, console.log, message, args); + this.log("info", LogLevel.INFO, "log", message, args); } warn(message: string, ...args: unknown[]): void { - this.log("warn", LogLevel.WARN, console.warn, message, args); + this.log("warn", LogLevel.WARN, "warn", message, args); } error(message: string, ...args: unknown[]): void { - this.log("error", LogLevel.ERROR, console.error, message, args); + this.log("error", LogLevel.ERROR, "error", message, args); } async time(label: string, fn: () => Promise): Promise { - const start = performance.now(); + const safeLabel = sanitizeLogString(label, REDACTED); + const start = readPerformanceNow(); try { const result = await fn(); - const durationMs = performance.now() - start; - this.debug(`${label} completed`, { durationMs: Math.round(durationMs) }); + const durationMs = readPerformanceNow() - start; + this.debug(`${safeLabel} completed`, { durationMs: numberRound(durationMs) }); return result; } catch (error) { - const durationMs = performance.now() - start; - this.error(`${label} failed`, { durationMs: Math.round(durationMs) }, error); + const durationMs = readPerformanceNow() - start; + this.error(`${safeLabel} failed`, { durationMs: numberRound(durationMs) }, error); throw error; } } @@ -662,8 +816,193 @@ export function __resetTraceContextGetterForTests(): void { } function withRequestLogger(base: Logger): Logger { - const ctx = requestContextGetter?.(); - return ctx?.logger ?? base; + try { + const ctx = requestContextGetter?.(); + return ctx?.logger ?? base; + } catch { + return base; + } +} + +type ContextAwareLogMethod = "debug" | "info" | "warn" | "error"; + +type LoggerSelection = { + selected: Logger; + fallback: Logger; +}; + +function selectContextLogger(base: Logger): LoggerSelection { + return { selected: withRequestLogger(base), fallback: base }; +} + +function selectComponentLoggers(base: Logger, componentName: string): LoggerSelection { + let fallback: Logger = base; + try { + fallback = base.component(componentName); + } catch { + // The base logger itself is still a safe final fallback. + } + + const requestLogger = withRequestLogger(base); + if (requestLogger === base) return { selected: fallback, fallback }; + + try { + return { selected: requestLogger.component(componentName), fallback }; + } catch { + return { selected: fallback, fallback }; + } +} + +function invokeLoggerMethod( + logger: Logger, + method: ContextAwareLogMethod, + message: string, + args: unknown[], +): boolean { + try { + const callback = logger[method]; + if (typeof callback !== "function") return false; + const callArgs: unknown[] = [message]; + for (let index = 0; index < args.length; index++) callArgs[index + 1] = args[index]; + apply(callback, logger, callArgs); + return true; + } catch { + return false; + } +} + +function invokeContextAwareLog( + base: Logger, + method: ContextAwareLogMethod, + message: string, + args: unknown[], +): void { + invokeSelectedLoggerMethod(selectContextLogger(base), method, message, args); +} + +function invokeSelectedLoggerMethod( + selection: LoggerSelection, + method: ContextAwareLogMethod, + message: string, + args: unknown[], +): void { + if (invokeLoggerMethod(selection.selected, method, message, args)) return; + if (selection.selected !== selection.fallback) { + invokeLoggerMethod(selection.fallback, method, message, args); + } +} + +function invokeContextAwareComponentLog( + base: Logger, + componentName: string, + method: ContextAwareLogMethod, + message: string, + args: unknown[], +): void { + invokeSelectedLoggerMethod( + selectComponentLoggers(base, componentName), + method, + message, + args, + ); +} + +function invokeLoggerChild( + logger: Logger, + context: Record, +): Logger | undefined { + try { + const callback = logger.child; + if (typeof callback !== "function") return undefined; + const child = apply(callback, logger, [context]) as unknown; + if ((typeof child === "object" && child !== null) || typeof child === "function") { + return child as Logger; + } + } catch { + // Request-scoped logger composition must not escape the logging boundary. + } + return undefined; +} + +function invokeSelectedLoggerChild( + selection: LoggerSelection, + context: Record, +): Logger { + const fallback = invokeLoggerChild(selection.fallback, context) ?? selection.fallback; + const selected = selection.selected === selection.fallback + ? fallback + : invokeLoggerChild(selection.selected, context) ?? fallback; + return createGuardedLogger({ selected, fallback }); +} + +function invokeLoggerComponent(logger: Logger, name: string): Logger | undefined { + try { + const callback = logger.component; + if (typeof callback !== "function") return undefined; + const component = apply(callback, logger, [name]) as unknown; + if ( + (typeof component === "object" && component !== null) || + typeof component === "function" + ) { + return component as Logger; + } + } catch { + // Component composition must remain inside the guarded facade. + } + return undefined; +} + +function selectLoggerComponents(selection: LoggerSelection, name: string): LoggerSelection { + const fallback = invokeLoggerComponent(selection.fallback, name) ?? selection.fallback; + const selected = selection.selected === selection.fallback + ? fallback + : invokeLoggerComponent(selection.selected, name) ?? fallback; + return { selected, fallback }; +} + +async function invokeSelectedLoggerTime( + selection: LoggerSelection, + label: string, + fn: () => Promise, +): Promise { + const safeLabel = sanitizeLogString(label, REDACTED); + const start = readPerformanceNow(); + try { + const result = await fn(); + const durationMs = numberRound(readPerformanceNow() - start); + invokeSelectedLoggerMethod(selection, "debug", `${safeLabel} completed`, [{ durationMs }]); + return result; + } catch (error) { + const durationMs = numberRound(readPerformanceNow() - start); + invokeSelectedLoggerMethod(selection, "error", `${safeLabel} failed`, [{ durationMs }, error]); + throw error; + } +} + +function createGuardedLogger(selection: LoggerSelection): Logger { + return { + debug(message: string, ...args: unknown[]): void { + invokeSelectedLoggerMethod(selection, "debug", message, args); + }, + info(message: string, ...args: unknown[]): void { + invokeSelectedLoggerMethod(selection, "info", message, args); + }, + warn(message: string, ...args: unknown[]): void { + invokeSelectedLoggerMethod(selection, "warn", message, args); + }, + error(message: string, ...args: unknown[]): void { + invokeSelectedLoggerMethod(selection, "error", message, args); + }, + time(label: string, fn: () => Promise): Promise { + return invokeSelectedLoggerTime(selection, label, fn); + }, + child(context: Record): Logger { + return invokeSelectedLoggerChild(selection, context); + }, + component(name: string): Logger { + return createGuardedLogger(selectLoggerComponents(selection, name)); + }, + }; } /** @@ -673,22 +1012,22 @@ function withRequestLogger(base: Logger): Logger { function createContextAwareLogger(base: ConsoleLogger): Logger { return { debug(message: string, ...args: unknown[]): void { - withRequestLogger(base).debug(message, ...args); + invokeContextAwareLog(base, "debug", message, args); }, info(message: string, ...args: unknown[]): void { - withRequestLogger(base).info(message, ...args); + invokeContextAwareLog(base, "info", message, args); }, warn(message: string, ...args: unknown[]): void { - withRequestLogger(base).warn(message, ...args); + invokeContextAwareLog(base, "warn", message, args); }, error(message: string, ...args: unknown[]): void { - withRequestLogger(base).error(message, ...args); + invokeContextAwareLog(base, "error", message, args); }, time(label: string, fn: () => Promise): Promise { - return withRequestLogger(base).time(label, fn); + return invokeSelectedLoggerTime(selectContextLogger(base), label, fn); }, child(context: Record): Logger { - return withRequestLogger(base).child(context); + return invokeSelectedLoggerChild(selectContextLogger(base), context); }, component(name: string): Logger { return createComponentAwareLogger(base, name); @@ -705,22 +1044,25 @@ function createContextAwareLogger(base: ConsoleLogger): Logger { function createComponentAwareLogger(base: ConsoleLogger, componentName: string): Logger { return { debug(message: string, ...args: unknown[]): void { - withRequestLogger(base).component(componentName).debug(message, ...args); + invokeContextAwareComponentLog(base, componentName, "debug", message, args); }, info(message: string, ...args: unknown[]): void { - withRequestLogger(base).component(componentName).info(message, ...args); + invokeContextAwareComponentLog(base, componentName, "info", message, args); }, warn(message: string, ...args: unknown[]): void { - withRequestLogger(base).component(componentName).warn(message, ...args); + invokeContextAwareComponentLog(base, componentName, "warn", message, args); }, error(message: string, ...args: unknown[]): void { - withRequestLogger(base).component(componentName).error(message, ...args); + invokeContextAwareComponentLog(base, componentName, "error", message, args); }, time(label: string, fn: () => Promise): Promise { - return withRequestLogger(base).component(componentName).time(label, fn); + return invokeSelectedLoggerTime(selectComponentLoggers(base, componentName), label, fn); }, child(context: Record): Logger { - return withRequestLogger(base).component(componentName).child(context); + return invokeSelectedLoggerChild( + selectComponentLoggers(base, componentName), + context, + ); }, component(name: string): Logger { return createComponentAwareLogger(base, name); @@ -750,7 +1092,7 @@ export function getBaseLogger( prefix: string, options?: ConsoleLoggerOptions, ): ConsoleLogger { - const resolvedPrefix = prefix.toUpperCase(); + const resolvedPrefix = apply(stringToUpperCase, prefix, []) as string; const validPrefix = resolvedPrefix in BASE_LOGGER_MAP ? resolvedPrefix : "VERYFRONT"; if (options?.injectTraceContext === false) { diff --git a/src/utils/logger/redact.test.ts b/src/utils/logger/redact.test.ts index 9e4dddab23..ef7840536e 100644 --- a/src/utils/logger/redact.test.ts +++ b/src/utils/logger/redact.test.ts @@ -4,6 +4,7 @@ import { describe, it } from "#veryfront/testing/bdd.ts"; import { isSensitiveKey, REDACTED, + redactForSerialization, redactSensitive, sanitizeSerializedError, sanitizeUrlCredentials, @@ -32,6 +33,9 @@ describe("logger/redact", () => { "accessKey", "privateKey", "credential", + "auth", + "authHeader", + "auth.header", "authorization", "Authorization", "Cookie", @@ -53,9 +57,9 @@ describe("logger/redact", () => { }); it("does not flag benign keys that merely look similar", () => { - // `author` must NOT match (the deny-list deliberately omits bare `auth`), - // and short tokens like `dsn`/`sas` are omitted to avoid masking e.g. - // `feedsNamespace`. + // Exact `auth` is sensitive, but it must not turn ordinary words such as + // `author` into sensitive keys. Short tokens like `dsn`/`sas` remain + // omitted to avoid masking e.g. `feedsNamespace`. for ( const key of ["author", "count", "userId", "requestId", "url", "domain", "feedsNamespace"] ) { @@ -194,6 +198,37 @@ describe("logger/redact", () => { }); }); + it("fails closed on a cyclic serializer prototype chain", () => { + const cyclicPrototype: object = new Proxy({}, { + getPrototypeOf: () => cyclicPrototype, + }); + + assertEquals(redactSensitive({ wrap: cyclicPrototype }) as Record, { + wrap: REDACTED, + }); + }); + + it("keeps serializer hook cycle detection stable after the global Set changes", () => { + const originalSetConstructor = globalThis.Set; + let redacted: unknown; + + try { + globalThis.Set = function ReplacementSet() { + throw new Error("project code replaced Set"); + } as unknown as SetConstructor; + + redacted = redactForSerialization({ + wrap: { + toJSON: () => ({ apiKey: "synthetic-opaque-credential" }), + }, + }); + } finally { + globalThis.Set = originalSetConstructor; + } + + assertEquals(redacted, { wrap: { apiKey: REDACTED } }); + }); + it("fails closed past the max traversal depth", () => { // Build a structure deeper than MAX_DEPTH (16) with a secret at the bottom. let node: Record = { token: "deep-secret" }; @@ -219,6 +254,52 @@ describe("logger/redact", () => { assertEquals(serialized.includes("t-1"), false); assertEquals(serialized.includes("2"), true); }); + + it("ignores intrinsic serialization hooks after global constructors are replaced", () => { + const originalObjectConstructor = globalThis.Object; + const originalArrayConstructor = globalThis.Array; + const originalObjectToJSON = Object.getOwnPropertyDescriptor(Object.prototype, "toJSON"); + const originalArrayToJSON = Object.getOwnPropertyDescriptor(Array.prototype, "toJSON"); + let hookCalls = 0; + let redacted: unknown; + + try { + Object.defineProperty(Object.prototype, "toJSON", { + configurable: true, + value() { + hookCalls++; + return { leaked: "synthetic-intrinsic-secret" }; + }, + }); + Object.defineProperty(Array.prototype, "toJSON", { + configurable: true, + value() { + hookCalls++; + return ["synthetic-intrinsic-secret"]; + }, + }); + globalThis.Object = function ReplacementObject() {} as unknown as ObjectConstructor; + globalThis.Array = function ReplacementArray() {} as unknown as ArrayConstructor; + + redacted = redactForSerialization({ apiKey: "synthetic-opaque-credential" }); + } finally { + globalThis.Object = originalObjectConstructor; + globalThis.Array = originalArrayConstructor; + if (originalObjectToJSON) { + Object.defineProperty(Object.prototype, "toJSON", originalObjectToJSON); + } else { + delete (Object.prototype as { toJSON?: unknown }).toJSON; + } + if (originalArrayToJSON) { + Object.defineProperty(Array.prototype, "toJSON", originalArrayToJSON); + } else { + delete (Array.prototype as { toJSON?: unknown }).toJSON; + } + } + + assertEquals(hookCalls, 0); + assertEquals(redacted, { apiKey: REDACTED }); + }); }); describe("sanitizeUrlCredentials", () => { @@ -252,6 +333,266 @@ describe("logger/redact", () => { it("leaves non-URL strings untouched", () => { assertEquals(sanitizeUrlCredentials("just a plain message"), "just a plain message"); }); + + it("keeps benign assignment-shaped words intact", () => { + for ( + const message of [ + "mapping: 4 routes resolved", + "spinner=ready", + "considered: safe", + "residual=small", + "saltiness=balanced", + ] + ) { + assertEquals(sanitizeUrlCredentials(message), message); + } + }); + + it("bounds oversized assignment-key classification and fails closed", () => { + const oversizedKey = `benign_${"segment_".repeat(10_000)}`; + assertEquals( + sanitizeUrlCredentials(`${oversizedKey}=synthetic-opaque-value`), + `${oversizedKey}=${REDACTED}`, + ); + }); + + it("keeps assignment redaction fail-closed after prototype methods are replaced", () => { + const originalIncludes = String.prototype.includes; + const originalSplit = String.prototype.split; + const originalFilter = Array.prototype.filter; + const originalSome = Array.prototype.some; + let sensitive: string; + let benign: string; + + try { + String.prototype.includes = () => false; + String.prototype.split = () => ["mapping"]; + Array.prototype.filter = () => []; + Array.prototype.some = () => false; + + sensitive = sanitizeUrlCredentials("refreshToken=prototype-poison-secret"); + benign = sanitizeUrlCredentials("mapping: 4 routes resolved"); + } finally { + String.prototype.includes = originalIncludes; + String.prototype.split = originalSplit; + Array.prototype.filter = originalFilter; + Array.prototype.some = originalSome; + } + + assertEquals(sensitive, `refreshToken=${REDACTED}`); + assertEquals(benign, "mapping: 4 routes resolved"); + }); + + it("keeps regex sanitization fail-closed after RegExp exec is replaced", () => { + const originalExec = RegExp.prototype.exec; + + try { + RegExp.prototype.exec = () => { + throw new Error("project code replaced RegExp.prototype.exec"); + }; + + assertEquals( + sanitizeUrlCredentials("Using token sk-proj-abc123456789"), + `Using token ${REDACTED}`, + ); + assertEquals( + sanitizeUrlCredentials("https://user:password@example.test/path"), + `https://user:${REDACTED}@example.test/path`, + ); + assertEquals( + sanitizeUrlCredentials("https://example.test/?access_token=secret"), + `https://example.test/?access_token=${REDACTED}`, + ); + assertEquals( + sanitizeUrlCredentials("Bearer opaque-secret"), + `Bearer ${REDACTED}`, + ); + assertEquals( + sanitizeUrlCredentials("refreshToken=prototype-poison-secret"), + `refreshToken=${REDACTED}`, + ); + assertEquals( + sanitizeUrlCredentials("mapping: 4 routes resolved"), + "mapping: 4 routes resolved", + ); + } finally { + RegExp.prototype.exec = originalExec; + } + }); + + it("does not inherit poisoned property-descriptor fields during regex sanitization", () => { + const descriptorFields = [ + "value", + "writable", + "get", + "set", + "enumerable", + "configurable", + ] as const; + const previousDescriptors = descriptorFields.map((field) => + Object.getOwnPropertyDescriptor(Object.prototype, field) + ); + const poisonDescriptors = descriptorFields.map(() => { + const descriptor = Object.create(null) as PropertyDescriptor; + descriptor.configurable = true; + descriptor.get = () => { + throw new Error("descriptor prototype must not be read"); + }; + descriptor.set = () => { + throw new Error("descriptor prototype must not be written"); + }; + return descriptor; + }); + let sanitized: string[] | undefined; + let failure: unknown; + + try { + for (let index = 0; index < descriptorFields.length; index++) { + Object.defineProperty( + Object.prototype, + descriptorFields[index]!, + poisonDescriptors[index]!, + ); + } + sanitized = [ + sanitizeUrlCredentials("Using token sk-proj-abc123456789"), + sanitizeUrlCredentials("https://user:password@example.test/path"), + sanitizeUrlCredentials("https://example.test/?access_token=secret"), + sanitizeUrlCredentials("refreshToken=prototype-poison-secret"), + ]; + } catch (error) { + failure = error; + } finally { + for (const field of descriptorFields) { + Reflect.deleteProperty(Object.prototype, field); + } + for (let index = 0; index < descriptorFields.length; index++) { + const previous = previousDescriptors[index]; + if (previous) Object.defineProperty(Object.prototype, descriptorFields[index]!, previous); + } + } + + if (failure) throw failure; + assertEquals(sanitized, [ + `Using token ${REDACTED}`, + `https://user:${REDACTED}@example.test/path`, + `https://example.test/?access_token=${REDACTED}`, + `refreshToken=${REDACTED}`, + ]); + }); + + it("keeps structured and URL key redaction stable after collection prototypes change", () => { + const originalMapGet = Map.prototype.get; + const originalSetHas = Set.prototype.has; + let structured: unknown; + let url: string; + + try { + Map.prototype.get = () => false; + Set.prototype.has = () => false; + structured = redactForSerialization({ + prototypeMutationApiKeyProbe3325: "synthetic-opaque-credential", + }); + url = sanitizeUrlCredentials( + "https://example.test/callback?code=synthetic-oauth-code", + ); + } finally { + Map.prototype.get = originalMapGet; + Set.prototype.has = originalSetHas; + } + + assertEquals(structured, { + prototypeMutationApiKeyProbe3325: REDACTED, + }); + assertEquals( + url, + `https://example.test/callback?code=${REDACTED}`, + ); + }); + + it("keeps credential redaction stable after string, array, and URL globals change", () => { + const originalIndexOf = String.prototype.indexOf; + const originalStartsWith = String.prototype.startsWith; + const originalSearch = String.prototype.search; + const originalCharCodeAt = String.prototype.charCodeAt; + const originalPush = Array.prototype.push; + const originalPop = Array.prototype.pop; + const originalAt = Array.prototype.at; + const originalDecodeURIComponent = globalThis.decodeURIComponent; + let sanitizedUserinfo = ""; + let sanitizedAssignment = ""; + let sanitizedEncodedParameter = ""; + + try { + String.prototype.indexOf = () => -1; + String.prototype.startsWith = () => false; + String.prototype.search = () => -1; + String.prototype.charCodeAt = () => 0; + Array.prototype.push = () => 0; + Array.prototype.pop = () => undefined; + Array.prototype.at = () => undefined; + globalThis.decodeURIComponent = () => "page"; + + sanitizedUserinfo = sanitizeUrlCredentials( + "https://user:synthetic-password@example.test/path", + ); + sanitizedAssignment = sanitizeUrlCredentials( + "refreshToken='synthetic-token' request continues", + ); + sanitizedEncodedParameter = sanitizeUrlCredentials( + "https://example.test/?access%5Ftoken=synthetic-token&page=2", + ); + } finally { + String.prototype.indexOf = originalIndexOf; + String.prototype.startsWith = originalStartsWith; + String.prototype.search = originalSearch; + String.prototype.charCodeAt = originalCharCodeAt; + Array.prototype.push = originalPush; + Array.prototype.pop = originalPop; + Array.prototype.at = originalAt; + globalThis.decodeURIComponent = originalDecodeURIComponent; + } + + assertEquals( + sanitizedUserinfo, + `https://user:${REDACTED}@example.test/path`, + ); + assertEquals( + sanitizedAssignment, + `refreshToken='${REDACTED}' request continues`, + ); + assertEquals( + sanitizedEncodedParameter, + `https://example.test/?access%5Ftoken=${REDACTED}&page=2`, + ); + }); + + it("keeps composite auth fields and trailing warning text visible", () => { + const warning = + "MCP server started with auth.type='none' (allowUnauthenticated) - all requests accepted"; + + assertEquals(sanitizeUrlCredentials(warning), warning); + assertEquals( + sanitizeUrlCredentials("auth='synthetic-secret' warning remains visible"), + `auth='${REDACTED}' warning remains visible`, + ); + assertEquals( + sanitizeUrlCredentials("authHeader='synthetic-secret' warning remains visible"), + `authHeader='${REDACTED}' warning remains visible`, + ); + assertEquals( + sanitizeUrlCredentials("auth.header='synthetic-secret' warning remains visible"), + `auth.header='${REDACTED}' warning remains visible`, + ); + }); + + it("masks common provider token prefixes without assignment syntax", () => { + const message = "Using token sk-proj-abc123456789"; + const sanitized = sanitizeUrlCredentials(message); + + assertEquals(sanitized.includes("sk-proj-abc123456789"), false); + assertEquals(sanitized, `Using token ${REDACTED}`); + }); }); describe("sanitizeUrlForSpan", () => { diff --git a/src/utils/logger/redact.ts b/src/utils/logger/redact.ts index 47eec2078a..73dccfdfd1 100644 --- a/src/utils/logger/redact.ts +++ b/src/utils/logger/redact.ts @@ -21,18 +21,93 @@ export const REDACTED = "[REDACTED]"; const apply = Reflect.apply; +const arrayPop = Array.prototype.pop; +const arrayPush = Array.prototype.push; const NativeUint32Array = Uint32Array; +const arrayIsArray = Array.isArray; +const arrayPrototype = Array.prototype; +const bigIntToString = BigInt.prototype.toString; +const NativeMap = Map; +const mapDelete = Map.prototype.delete; +const mapGet = Map.prototype.get; +const mapKeys = Map.prototype.keys; +const mapSet = Map.prototype.set; +const objectDefineProperty = Object.defineProperty; +const objectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const objectGetPrototypeOf = Object.getPrototypeOf; +const objectHasOwn = Object.hasOwn; +const objectPrototype = Object.prototype; +const NativeSet = Set; +const nativeDecodeURIComponent = decodeURIComponent; +const NativeURL = URL; +const numberIsFinite = Number.isFinite; +const numberIsInteger = Number.isInteger; const regExpExec = RegExp.prototype.exec; -const regExpReplace = RegExp.prototype[Symbol.replace]; +const regExpGlobalGetter = objectGetOwnPropertyDescriptor(RegExp.prototype, "global")!.get!; +const regExpUnicodeGetter = objectGetOwnPropertyDescriptor(RegExp.prototype, "unicode")!.get!; const stringCharCodeAt = String.prototype.charCodeAt; +const stringIncludes = String.prototype.includes; +const stringIndexOf = String.prototype.indexOf; const stringSlice = String.prototype.slice; +const stringStartsWith = String.prototype.startsWith; const stringToLowerCase = String.prototype.toLowerCase; +const setAdd = Set.prototype.add; +const setDelete = Set.prototype.delete; +const setHas = Set.prototype.has; +const mapIteratorNext = objectGetPrototypeOf(new NativeMap().keys()).next; +const mapSizeGetter = objectGetOwnPropertyDescriptor(Map.prototype, "size")!.get!; +const urlHostGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "host")!.get!; +const urlOriginGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "origin")!.get!; +const urlPasswordGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "password")!.get!; +const urlPathnameGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "pathname")!.get!; +const urlProtocolGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "protocol")!.get!; +const urlUsernameGetter = objectGetOwnPropertyDescriptor(NativeURL.prototype, "username")!.get!; const NON_ALPHANUMERIC_PATTERN = /[^a-z0-9]/g; +const CAMEL_CASE_BOUNDARY_PATTERN = /([a-z0-9])([A-Z])/g; +const ACRONYM_BOUNDARY_PATTERN = /([A-Z])([A-Z][a-z])/g; +const PROVIDER_CREDENTIAL_PATTERN = + /\b(?:sk-[A-Za-z0-9._-]{8,}|gh[po]_[A-Za-z0-9._-]{8,}|xox[baprs]-[A-Za-z0-9._-]{8,}|eyJ[A-Za-z0-9._-]{8,})\b/g; + +function replaceWithCapturedExec( + input: string, + pattern: RegExp, + replacement: string | ((match: RegExpExecArray) => string), +): string { + const global = apply(regExpGlobalGetter, pattern, []) as boolean; + const unicode = apply(regExpUnicodeGetter, pattern, []) as boolean; + let cursor = 0; + let matched = false; + let result = ""; + + pattern.lastIndex = 0; + try { + while (true) { + const match = apply(regExpExec, pattern, [input]) as RegExpExecArray | null; + if (match === null) break; + + const text = match[0]; + const start = match.index; + result += sliceString(input, cursor, start); + result += typeof replacement === "string" ? replacement : replacement(match); + cursor = start + text.length; + matched = true; + + if (!global) break; + if (text.length === 0) { + pattern.lastIndex = advanceStringIndex(input, start, unicode); + } + } + } finally { + pattern.lastIndex = 0; + } + + return matched ? result + sliceString(input, cursor) : input; +} /** Strip all non-alphanumeric characters and lowercase, used for key normalization. */ function normalizeToAlphanumeric(s: string): string { const lowercase = apply(stringToLowerCase, s, []) as string; - return apply(regExpReplace, NON_ALPHANUMERIC_PATTERN, [lowercase, ""]) as string; + return replaceWithCapturedExec(lowercase, NON_ALPHANUMERIC_PATTERN, ""); } const FORWARD_SLASH_CODE_UNIT = 47; @@ -46,6 +121,16 @@ function stringCodeUnitAt(value: string, index: number): number { return apply(stringCharCodeAt, value, [index]) as number; } +function advanceStringIndex(value: string, index: number, unicode: boolean): number { + const next = index + 1; + if (!unicode || next >= value.length) return next; + + const first = stringCodeUnitAt(value, index); + if (first < 0xd800 || first > 0xdbff) return next; + const second = stringCodeUnitAt(value, next); + return second >= 0xdc00 && second <= 0xdfff ? index + 2 : next; +} + function sliceString(value: string, start: number, end?: number): string { return end === undefined ? apply(stringSlice, value, [start]) as string @@ -64,6 +149,25 @@ function isAsciiLetterCodeUnit(codeUnit: number): boolean { return lowercase >= 97 && lowercase <= 122; } +function splitIdentifierTokens(value: string): string[] { + const tokens: string[] = []; + let tokenStart = 0; + + for (let index = 0; index <= value.length; index++) { + const codeUnit = index === value.length ? -1 : stringCodeUnitAt(value, index); + const isIdentifierCodeUnit = (codeUnit >= 97 && codeUnit <= 122) || + (codeUnit >= 48 && codeUnit <= 57); + if (isIdentifierCodeUnit) continue; + + if (index > tokenStart) { + tokens[tokens.length] = sliceString(value, tokenStart, index); + } + tokenStart = index + 1; + } + + return tokens; +} + function isWindowsPath(path: string): boolean { if (path.length >= 2) { const first = stringCodeUnitAt(path, 0); @@ -153,10 +257,9 @@ export function redactPathFromText( * a lowercased, non-alphanumeric-stripped form of the key, so `API-Key`, * `api_key`, and `apiKey` all collapse to `apikey` and match. * - * Deliberately omitted to avoid false positives that swamp real logs: - * - bare `"auth"` (would mask `author`); `authorization`/`authToken` are still - * covered via `authorization`/`token`. - * - short tokens like `"dsn"`/`"sas"` (would mask `feedsNamespace`, etc.). + * Bare `"auth"` is matched separately as an exact normalized key so `author` + * remains visible. Short tokens like `"dsn"`/`"sas"` are deliberately omitted + * to avoid masking keys such as `feedsNamespace`. */ const SENSITIVE_KEY_PATTERNS = [ "password", @@ -170,6 +273,7 @@ const SENSITIVE_KEY_PATTERNS = [ "accesskey", "privatekey", "credential", + "authheader", "authorization", "cookie", "bearer", @@ -189,7 +293,7 @@ const SENSITIVE_KEY_PATTERNS = [ const SENSITIVE_KEY_CACHE_MAX_SIZE = 512; /** Avoid retaining attacker-controlled, oversized property names in the cache. */ const SENSITIVE_KEY_CACHE_MAX_KEY_LENGTH = 128; -const sensitiveKeyCache = new Map(); +const sensitiveKeyCache = new NativeMap(); /** Stop traversing past this depth to keep the pass cheap and stack-safe. */ const MAX_DEPTH = 16; @@ -197,6 +301,10 @@ const MAX_DEPTH = 16; const MAX_CONTAINER_ENTRIES = 1_024; /** Bound aggregate work across an entire redaction call, not per branch. */ const MAX_TRAVERSAL_NODES = 4_096; +/** Bound hostile or cyclic prototype walks while looking for serializers. */ +const MAX_SERIALIZATION_HOOK_PROTOTYPES = 64; +/** Bound the quadratic identifier-token scan used for free-text assignments. */ +const MAX_ASSIGNMENT_KEY_LENGTH = 256; /** * Whether a context key names a credential and should have its value masked. @@ -209,19 +317,24 @@ const MAX_TRAVERSAL_NODES = 4_096; export function isSensitiveKey(key: string): boolean { const cacheable = key.length <= SENSITIVE_KEY_CACHE_MAX_KEY_LENGTH; if (cacheable) { - const cached = sensitiveKeyCache.get(key); + const cached = apply(mapGet, sensitiveKeyCache, [key]) as boolean | undefined; if (cached !== undefined) return cached; } const normalized = normalizeToAlphanumeric(key); - const sensitive = SENSITIVE_KEY_PATTERNS.some((pattern) => normalized.includes(pattern)); + let sensitive = normalized === "auth"; + for (let index = 0; !sensitive && index < SENSITIVE_KEY_PATTERNS.length; index++) { + sensitive = apply(stringIncludes, normalized, [SENSITIVE_KEY_PATTERNS[index]]) as boolean; + } if (cacheable) { - if (sensitiveKeyCache.size >= SENSITIVE_KEY_CACHE_MAX_SIZE) { - const oldestKey = sensitiveKeyCache.keys().next().value as string | undefined; - if (oldestKey !== undefined) sensitiveKeyCache.delete(oldestKey); + const cacheSize = apply(mapSizeGetter, sensitiveKeyCache, []) as number; + if (cacheSize >= SENSITIVE_KEY_CACHE_MAX_SIZE) { + const iterator = apply(mapKeys, sensitiveKeyCache, []) as MapIterator; + const oldestKey = (apply(mapIteratorNext, iterator, []) as IteratorResult).value; + if (oldestKey !== undefined) apply(mapDelete, sensitiveKeyCache, [oldestKey]); } - sensitiveKeyCache.set(key, sensitive); + apply(mapSet, sensitiveKeyCache, [key, sensitive]); } return sensitive; @@ -249,12 +362,50 @@ interface RedactionBudget { */ function classifyArray(value: object): boolean | null { try { - return Array.isArray(value); + return arrayIsArray(value); } catch { return null; } } +function hasSeenSerializationHookOwner(seenOwners: object[], owner: object): boolean { + for (let index = 0; index < seenOwners.length; index++) { + if (seenOwners[index] === owner) return true; + } + return false; +} + +/** + * Read a deliberate serialization hook without consulting hooks installed on + * the intrinsic Object or Array prototypes. Custom and platform prototypes + * such as Date and URL remain supported through data-property methods. + */ +function readSerializationHook(value: object): unknown { + let owner: object | null = value; + const seenOwners: object[] = []; + let prototypesVisited = 0; + while (owner !== null) { + if (owner === objectPrototype || owner === arrayPrototype) return undefined; + if ( + prototypesVisited >= MAX_SERIALIZATION_HOOK_PROTOTYPES || + hasSeenSerializationHookOwner(seenOwners, owner) + ) { + throw new TypeError("serialization hook prototype chain is cyclic or too deep"); + } + prototypesVisited++; + apply(arrayPush, seenOwners, [owner]); + const descriptor = objectGetOwnPropertyDescriptor(owner, "toJSON"); + if (descriptor !== undefined) { + if (!objectHasOwn(descriptor, "value")) { + throw new TypeError("serialization hooks must be data properties"); + } + return descriptor.value; + } + owner = objectGetPrototypeOf(owner); + } + return undefined; +} + function redactValue( value: unknown, depth: number, @@ -269,9 +420,11 @@ function redactValue( budget.remainingNodes--; if (typeof value === "string") return sanitizeUrlCredentials(value); - if (typeof value === "bigint") return mode === "serialization" ? value.toString() : value; + if (typeof value === "bigint") { + return mode === "serialization" ? apply(bigIntToString, value, []) as string : value; + } if (typeof value === "number") { - return mode === "serialization" && !Number.isFinite(value) ? null : value; + return mode === "serialization" && !numberIsFinite(value) ? null : value; } if (typeof value === "boolean" || value === null) return value; if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") { @@ -282,12 +435,12 @@ function redactValue( if (arrayClassification === null) return REDACTED; if (arrayClassification) { - if (depth >= MAX_DEPTH || seen.has(value)) return REDACTED; - seen.add(value); + if (depth >= MAX_DEPTH || apply(setHas, seen, [value])) return REDACTED; + apply(setAdd, seen, [value]); try { const arrayValue = value as unknown[]; const length = arrayValue.length; - if (!Number.isInteger(length) || length < 0 || length > MAX_CONTAINER_ENTRIES) { + if (!numberIsInteger(length) || length < 0 || length > MAX_CONTAINER_ENTRIES) { return REDACTED; } const redacted: unknown[] = mode === "compatible" ? new Array(length) : []; @@ -298,7 +451,7 @@ function redactValue( if (mode === "compatible") { redacted[index] = item; } else { - redacted.push(item); + apply(arrayPush, redacted, [item]); } } return redacted; @@ -307,7 +460,7 @@ function redactValue( // serialized contents unknowable, so the complete array fails closed. return REDACTED; } finally { - seen.delete(value); + apply(setDelete, seen, [value]); } } @@ -319,10 +472,10 @@ function redactValue( // returns an object, array, or scalar, the serialization API redacts *that* // snapshot. The compatibility API keeps scalar serializers such as Date and // URL intact, preserving the established generic return contract. - if (depth >= MAX_DEPTH || seen.has(value)) return REDACTED; + if (depth >= MAX_DEPTH || apply(setHas, seen, [value])) return REDACTED; let toJSON: unknown; try { - toJSON = (value as Record).toJSON; + toJSON = readSerializationHook(value); } catch { // Accessors can throw before a serializer is callable. Never inspect the // raw object after that because its eventual serialization is unknown. @@ -330,7 +483,7 @@ function redactValue( } if (typeof toJSON === "function") { - seen.add(value); + apply(setAdd, seen, [value]); try { const serialized = apply(toJSON, value, []); if (mode === "serialization") { @@ -352,22 +505,22 @@ function redactValue( // skipped) through: fail closed. return REDACTED; } finally { - seen.delete(value); + apply(setDelete, seen, [value]); } } - seen.add(value); + apply(setAdd, seen, [value]); try { const out: Record = {}; const record = value as Record; let propertyCount = 0; for (const key in record) { - if (!Object.hasOwn(record, key)) continue; + if (!objectHasOwn(record, key)) continue; propertyCount++; if (propertyCount > MAX_CONTAINER_ENTRIES) return REDACTED; if (isSensitiveKey(key)) { - Object.defineProperty(out, key, { + objectDefineProperty(out, key, { configurable: true, enumerable: true, value: REDACTED, @@ -382,7 +535,7 @@ function redactValue( if (mode === "serialization" && child === undefined) continue; const redactedChild = redactValue(child, depth + 1, seen, mode, budget); if (budget.exhausted) return REDACTED; - Object.defineProperty(out, key, { + objectDefineProperty(out, key, { configurable: true, enumerable: true, value: redactedChild, @@ -395,7 +548,7 @@ function redactValue( // unredacted object through: fail closed. return REDACTED; } finally { - seen.delete(value); + apply(setDelete, seen, [value]); } } @@ -410,7 +563,7 @@ function redactValue( * functions, symbols, and custom `toJSON` implementations must be normalized. */ export function redactSensitive(context: T): T { - return redactValue(context, 0, new Set(), "compatible", { + return redactValue(context, 0, new NativeSet(), "compatible", { remainingNodes: MAX_TRAVERSAL_NODES, exhausted: false, }) as T; @@ -423,7 +576,7 @@ export function redactSensitive(context: T): T { * closed. Objects with `toJSON` are snapshotted exactly once before redaction. */ export function redactForSerialization(context: unknown): RedactedValue { - return redactValue(context, 0, new Set(), "serialization", { + return redactValue(context, 0, new NativeSet(), "serialization", { remainingNodes: MAX_TRAVERSAL_NODES, exhausted: false, }) as RedactedValue; @@ -457,7 +610,12 @@ const SENSITIVE_URL_PARAMS = [ "x-goog-signature", ] as const; -const NORMALIZED_SENSITIVE_URL_PARAMS = new Set(SENSITIVE_URL_PARAMS.map(normalizeToAlphanumeric)); +const NORMALIZED_SENSITIVE_URL_PARAMS = new NativeSet(); +for (let index = 0; index < SENSITIVE_URL_PARAMS.length; index++) { + apply(setAdd, NORMALIZED_SENSITIVE_URL_PARAMS, [ + normalizeToAlphanumeric(SENSITIVE_URL_PARAMS[index]!), + ]); +} const URL_USERINFO_RE = /(\b[a-z][a-z0-9+.-]*:\/\/|\/\/)([^/?#\s]+)@/gi; const HORIZONTAL_WHITESPACE_URL_USERINFO_RE = @@ -483,7 +641,7 @@ function isHorizontalAssignmentBoundary(character: string): boolean { function isAsciiLetter(character: string | undefined): boolean { if (!character) return false; - const code = character.charCodeAt(0); + const code = stringCodeUnitAt(character, 0); return (code >= 65 && code <= 90) || (code >= 97 && code <= 122); } @@ -493,7 +651,7 @@ function isAssignmentKeyStartCharacter(character: string | undefined): boolean { function isAssignmentKeyCharacter(character: string | undefined): boolean { if (!character) return false; - const code = character.charCodeAt(0); + const code = stringCodeUnitAt(character, 0); return ( isAssignmentKeyStartCharacter(character) || (code >= 48 && code <= 57) || @@ -552,7 +710,7 @@ function assignmentValueEndsAt(input: string, start: number): boolean { function redactAssignmentValue(input: string, start: number): RedactedAssignmentValue { let scanStart = start; let preserveValueQuote = true; - if (input.startsWith(REDACTED, start)) { + if (apply(stringStartsWith, input, [REDACTED, start])) { const markerEnd = start + REDACTED.length; if (assignmentValueEndsAt(input, markerEnd)) { return { @@ -585,6 +743,7 @@ function redactAssignmentValue(input: string, start: number): RedactedAssignment if (character === quote) { if (quoteStart === scanStart && expectedClosings.length === 0) { wrapperQuoteClosed = true; + return { end: index + 1, replacement: replacement() }; } quote = ""; quoteStart = -1; @@ -600,7 +759,7 @@ function redactAssignmentValue(input: string, start: number): RedactedAssignment continue; } if (character === "{" || character === "[") { - expectedClosings.push(character === "{" ? "}" : "]"); + apply(arrayPush, expectedClosings, [character === "{" ? "}" : "]"]); index++; continue; } @@ -608,10 +767,10 @@ function redactAssignmentValue(input: string, start: number): RedactedAssignment expectedClosings.length > 0 && (character === "}" || character === "]") ) { - if (expectedClosings.at(-1) !== character) { + if (expectedClosings[expectedClosings.length - 1] !== character) { return { end: input.length, replacement: replacement() }; } - expectedClosings.pop(); + apply(arrayPop, expectedClosings, []); index++; if ( expectedClosings.length === 0 && @@ -655,7 +814,7 @@ function redactCredentialAssignments( match = apply(regExpExec, prefixPattern, [input]) as RegExpExecArray | null ) { const key = match[keyGroup]!; - if (!isSensitiveKey(key)) continue; + if (!isSensitiveAssignmentKey(key)) continue; const valueStart = prefixPattern.lastIndex; const boundary = urlParameterBoundaryGroup === undefined @@ -664,7 +823,7 @@ function redactCredentialAssignments( const markerEnd = valueStart + REDACTED.length; if ( (boundary === "?" || boundary === "&" || boundary === ";") && - input.startsWith(REDACTED, valueStart) && + apply(stringStartsWith, input, [REDACTED, valueStart]) && input[markerEnd] === "#" ) { // The URL-parameter pass already bounded this credential at the URI @@ -683,19 +842,68 @@ function redactCredentialAssignments( return cursor === 0 ? input : result + sliceString(input, cursor); } +/** + * Classify free-text assignment keys without applying the structured-key + * substring policy to ordinary words. Identifier and camel-case boundaries + * still recognize `refreshToken`, `client_secret`, and `x-api-key`, while + * words such as `mapping` and `considered` stay intact. + */ +function isSensitiveAssignmentKey(key: string): boolean { + // The token-range classifier below is quadratic in the number of identifier + // tokens. Oversized attacker-controlled keys fail closed before that work. + if (key.length > MAX_ASSIGNMENT_KEY_LENGTH) return true; + + const withAcronymBoundaries = replaceWithCapturedExec( + key, + ACRONYM_BOUNDARY_PATTERN, + (match) => `${match[1]} ${match[2]}`, + ); + const withBoundaries = replaceWithCapturedExec( + withAcronymBoundaries, + CAMEL_CASE_BOUNDARY_PATTERN, + (match) => `${match[1]} ${match[2]}`, + ); + const lowercase = apply(stringToLowerCase, withBoundaries, []) as string; + const tokens = splitIdentifierTokens(lowercase); + + for (let start = 0; start < tokens.length; start++) { + if (tokens[start]!.length === 0) continue; + let candidate = ""; + for (let end = start; end < tokens.length; end++) { + const token = tokens[end]!; + if (token.length === 0) continue; + candidate += token; + for (let index = 0; index < SENSITIVE_KEY_PATTERNS.length; index++) { + if (candidate === SENSITIVE_KEY_PATTERNS[index]) return true; + } + } + } + + return tokens.length === 1 && tokens[0] === "auth"; +} + function isStandaloneUrlAuthorityBeforeWhitespace( scheme: string, user: string, password: string, ): boolean { - const whitespaceIndex = password.search(/[ \t]/); + let whitespaceIndex = -1; + for (let index = 0; index < password.length; index++) { + const codeUnit = stringCodeUnitAt(password, index); + if (codeUnit === 0x20 || codeUnit === 0x09) { + whitespaceIndex = index; + break; + } + } if (whitespaceIndex < 0) return false; const authority = `${user}:${sliceString(password, 0, whitespaceIndex)}`; const candidate = scheme === "//" ? `https://${authority}` : `${scheme}${authority}`; try { - const url = new URL(candidate); - return url.username.length === 0 && url.password.length === 0; + const url = new NativeURL(candidate); + const username = apply(urlUsernameGetter, url, []) as string; + const password = apply(urlPasswordGetter, url, []) as string; + return username.length === 0 && password.length === 0; } catch { return false; } @@ -706,7 +914,7 @@ function decodeUrlParameterName(value: string): string { for (let pass = 0; pass < MAX_URL_PARAMETER_DECODE_PASSES; pass++) { let next: string; try { - next = decodeURIComponent(decoded); + next = nativeDecodeURIComponent(decoded); } catch { break; } @@ -722,8 +930,10 @@ function decodeUrlParameterName(value: string): string { * {@link redactSensitive}, which is key-based, this scrubs secrets embedded in * the *value* itself: * - * - URL userinfo: `http://user:pass@host` → `http://user:[REDACTED]@host` - * - sensitive query params: `?access_token=abc` → `?access_token=[REDACTED]` + * - URL userinfo: `https://user:password@example.test/path` -> `https://user:[REDACTED]@example.test/path` + * - sensitive query params: `?access_token=abc` -> `?access_token=[REDACTED]` + * - credential assignments: `refreshToken=abc` -> `refreshToken=[REDACTED]` + * - common provider tokens: `Using token sk-...` -> `Using token [REDACTED]` * * It is intentionally tolerant: it operates on any string (a DSN, a Mongo URI, * an axios error message containing a URL) via regex rather than requiring a @@ -734,10 +944,13 @@ export function sanitizeUrlCredentials(input: string): string { if (typeof input !== "string" || input.length === 0) return input; // 1) userinfo: scheme://user:pass@ → mask the password (and any bare creds). - let out = apply(regExpReplace, URL_USERINFO_RE, [ + let out = replaceWithCapturedExec( input, - (_match: string, scheme: string, userinfo: string) => { - const colon = userinfo.indexOf(":"); + URL_USERINFO_RE, + (match) => { + const scheme = match[1]!; + const userinfo = match[2]!; + const colon = apply(stringIndexOf, userinfo, [":"]) as number; if (colon === -1) { // `scheme://token@host` — the whole userinfo is credential-like. return `${scheme}${REDACTED}@`; @@ -745,70 +958,78 @@ export function sanitizeUrlCredentials(input: string): string { const user = sliceString(userinfo, 0, colon); return `${scheme}${user}:${REDACTED}@`; }, - ]) as string; - out = apply(regExpReplace, HORIZONTAL_WHITESPACE_URL_USERINFO_RE, [ + ); + out = replaceWithCapturedExec( out, - ( - match: string, - scheme: string, - user: string, - password: string, - ) => { + HORIZONTAL_WHITESPACE_URL_USERINFO_RE, + (match) => { + const scheme = match[1]!; + const user = match[2]!; + const password = match[3]!; // Do not reinterpret a complete URL followed later by an email address // on the same line as malformed userinfo. Raw-horizontal-whitespace // recovery is limited to explicit `user:password` shapes whose prefix // cannot already be parsed as a standalone authority. if (isStandaloneUrlAuthorityBeforeWhitespace(scheme, user, password)) { - return match; + return match[0]; } return `${scheme}${user}:${REDACTED}@`; }, - ]) as string; + ); // 2) sensitive query/fragment params: `key=value` → `key=[REDACTED]`. // Match `?key=`, `#key=`, `&key=`, and `;key=` separators and stop at the // next delimiter. OAuth implicit-flow tokens commonly appear after `#`. - out = apply(regExpReplace, /([?#&;])([-a-z0-9_.%\[\]]+)=([^&#;\s]*)/gi, [ + out = replaceWithCapturedExec( out, - (match: string, sep: string, key: string, _val: string) => { + /([?#&;])([-a-z0-9_.%\[\]]+)=([^&#;\s]*)/gi, + (match) => { + const sep = match[1]!; + const key = match[2]!; const decodedKey = decodeUrlParameterName(key); - const sensitive = NORMALIZED_SENSITIVE_URL_PARAMS.has(normalizeToAlphanumeric(decodedKey)) || + const sensitive = apply(setHas, NORMALIZED_SENSITIVE_URL_PARAMS, [ + normalizeToAlphanumeric(decodedKey), + ]) || isSensitiveKey(decodedKey); - return sensitive ? `${sep}${key}=${REDACTED}` : match; + return sensitive ? `${sep}${key}=${REDACTED}` : match[0]; }, - ]) as string; + ); // 3) Cookie header values. // Cookie headers can carry multiple independent credentials separated by // semicolons (and Set-Cookie attributes can contain commas). Mask the entire // header line before the generic assignment scanner can stop at the first // delimiter and expose later values. - out = apply(regExpReplace, /(^|[^a-z0-9_-])((?:set-cookie|cookie)\s*:\s*)[^\r\n]*/gi, [ + out = replaceWithCapturedExec( out, - (_match: string, boundary: string, prefix: string) => `${boundary}${prefix}${REDACTED}`, - ]) as string; + /(^|[^a-z0-9_-])((?:set-cookie|cookie)\s*:\s*)[^\r\n]*/gi, + (match) => `${match[1]}${match[2]}${REDACTED}`, + ); // 4) Header-shaped authorization values and standalone auth schemes. // Authorization schemes are extensible (AWS SigV4, Digest, custom proxy // schemes, and others), so mask the complete line instead of trying to // enumerate schemes or parse their credential-bearing parameters. - out = apply(regExpReplace, /\b(authorization\s*[:=]\s*)[^\r\n]*/gi, [ + out = replaceWithCapturedExec( + out, + /\b(authorization\s*[:=]\s*)[^\r\n]*/gi, + (match) => `${match[1]}${REDACTED}`, + ); + out = replaceWithCapturedExec( out, - (_match: string, prefix: string) => `${prefix}${REDACTED}`, - ]) as string; - out = apply( - regExpReplace, /\b(bearer|basic)(\s+)(?:"[^"\r\n]*"|'[^'\r\n]*'|[a-z0-9._~+/=-]+)/gi, - [ - out, - (_match: string, scheme: string, whitespace: string) => `${scheme}${whitespace}${REDACTED}`, - ], - ) as string; - - // 5) Credential assignments embedded in free-form messages/errors. Match - // generic identifier-shaped keys and delegate classification to the same - // deny-list used for structured context. This keeps JSON snippets, header - // dumps, and ordinary `key=value` text from drifting to a weaker policy. + (match) => `${match[1]}${match[2]}${REDACTED}`, + ); + + // 5) Common provider token shapes can appear as bare values without an + // assignment delimiter, for example `Using token sk-...`. + out = replaceWithCapturedExec(out, PROVIDER_CREDENTIAL_PATTERN, REDACTED); + + // 6) Credential assignments embedded in free-form messages/errors. Match + // generic identifier-shaped keys and apply the same credential vocabulary + // at identifier boundaries. This keeps JSON snippets, header dumps, and + // ordinary `key=value` text from drifting to a weaker policy without + // masking benign words that merely contain a short pattern. // Handle quoted JSON/object keys first and preserve their quoting so the // sanitized text remains intelligible and structurally valid. out = redactCredentialAssignments( @@ -827,19 +1048,19 @@ export function sanitizeUrlCredentials(input: string): string { } function firstUrlDelimiterIndex(input: string): number { - const queryIndex = input.indexOf("?"); - const hashIndex = input.indexOf("#"); + const queryIndex = apply(stringIndexOf, input, ["?"]) as number; + const hashIndex = apply(stringIndexOf, input, ["#"]) as number; if (queryIndex === -1) return hashIndex; if (hashIndex === -1) return queryIndex; - return Math.min(queryIndex, hashIndex); + return queryIndex < hashIndex ? queryIndex : hashIndex; } function sanitizeProtocolRelativeUrlForSpan(input: string): string | null { - if (!input.startsWith("//")) return null; + if (!apply(stringStartsWith, input, ["//"])) return null; try { - const url = new URL(`https:${input}`); - return `//${url.host}${url.pathname}`; + const url = new NativeURL(`https:${input}`); + return `//${apply(urlHostGetter, url, [])}${apply(urlPathnameGetter, url, [])}`; } catch (_) { return null; } @@ -858,17 +1079,21 @@ export function sanitizeUrlForSpan(input: string): string { if (typeof input !== "string" || input.length === 0) return input; try { - const url = new URL(input); - if (url.protocol === "blob:") { + const url = new NativeURL(input); + const protocol = apply(urlProtocolGetter, url, []) as string; + const pathname = apply(urlPathnameGetter, url, []) as string; + const origin = apply(urlOriginGetter, url, []) as string; + if (protocol === "blob:") { try { - const embeddedUrl = new URL(url.pathname); - return embeddedUrl.origin === "null" ? "blob:" : `blob:${embeddedUrl.origin}`; + const embeddedUrl = new NativeURL(pathname); + const embeddedOrigin = apply(urlOriginGetter, embeddedUrl, []) as string; + return embeddedOrigin === "null" ? "blob:" : `blob:${embeddedOrigin}`; } catch (_) { return "blob:"; } } - if (url.origin !== "null") return `${url.origin}${url.pathname}`; - if (/^[a-z][a-z0-9+.-]*:/i.test(input)) return url.protocol; + if (origin !== "null") return `${origin}${pathname}`; + if (apply(regExpExec, /^[a-z][a-z0-9+.-]*:/i, [input]) !== null) return protocol; } catch (_) { // Relative or malformed URL-shaped strings are handled by the fallback. } diff --git a/src/utils/logger/serialization-hostile-fallback.fixture.ts b/src/utils/logger/serialization-hostile-fallback.fixture.ts new file mode 100644 index 0000000000..64fc1c6ccc --- /dev/null +++ b/src/utils/logger/serialization-hostile-fallback.fixture.ts @@ -0,0 +1,30 @@ +const originalObjectValues = Object.values; +const originalObjectToJSON = Object.getOwnPropertyDescriptor(Object.prototype, "toJSON"); + +try { + Object.values = () => { + throw new Error("polluted Object.values"); + }; + Object.defineProperty(Object.prototype, "toJSON", { + configurable: true, + value() { + throw new Error("inherited serializer must not run"); + }, + }); + + const { stringifyRedactedJson } = await import("./serialization.ts"); + const hostileFallback = { + toJSON() { + throw new Error("hostile fallback serializer"); + }, + }; + + console.log(stringifyRedactedJson({ ok: true }, hostileFallback)); +} finally { + Object.values = originalObjectValues; + if (originalObjectToJSON !== undefined) { + Object.defineProperty(Object.prototype, "toJSON", originalObjectToJSON); + } else { + delete (Object.prototype as { toJSON?: unknown }).toJSON; + } +} diff --git a/src/utils/logger/serialization.test.ts b/src/utils/logger/serialization.test.ts new file mode 100644 index 0000000000..c3b8f3ce1d --- /dev/null +++ b/src/utils/logger/serialization.test.ts @@ -0,0 +1,41 @@ +import { assertEquals } from "#veryfront/testing/assert.ts"; +import { describe, it } from "#veryfront/testing/bdd.ts"; + +describe("logger serialization", () => { + it("cannot throw through a hostile redacted JSON fallback", async () => { + const output = await new Deno.Command(Deno.execPath(), { + args: [ + "run", + "--quiet", + new URL("./serialization-hostile-fallback.fixture.ts", import.meta.url).pathname, + ], + cwd: Deno.cwd(), + stdout: "piped", + stderr: "piped", + }).output(); + + const stderr = new TextDecoder().decode(output.stderr); + assertEquals(output.success, true, stderr); + assertEquals(new TextDecoder().decode(output.stdout).trim(), "[REDACTED]"); + }); + + it("does not expose credentials from a fallback component name", async () => { + const output = await new Deno.Command(Deno.execPath(), { + args: [ + "run", + "--quiet", + "--allow-env", + new URL("./logger-hostile-fallback.fixture.ts", import.meta.url).pathname, + ], + cwd: Deno.cwd(), + stdout: "piped", + stderr: "piped", + }).output(); + + const stderr = new TextDecoder().decode(output.stderr); + assertEquals(output.success, true, stderr); + const line = new TextDecoder().decode(output.stdout).trim(); + assertEquals(line.includes("synthetic-component-secret"), false); + assertEquals(JSON.parse(line).component, "token=[REDACTED]"); + }); +}); diff --git a/src/utils/logger/serialization.ts b/src/utils/logger/serialization.ts new file mode 100644 index 0000000000..4e7c497cb7 --- /dev/null +++ b/src/utils/logger/serialization.ts @@ -0,0 +1,90 @@ +import { REDACTED, redactForSerialization } from "./redact.ts"; + +// Capture serialization intrinsics before project code can modify globals. +// Logging and telemetry serialization are safety boundaries and must not become +// throwable or leak data when a tenant mutates Object/Array prototypes. +const arrayIsArray = Array.isArray; +const arrayPrototype = Array.prototype; +const jsonStringify = JSON.stringify; +const objectDefineProperty = Object.defineProperty; +const objectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +const objectGetPrototypeOf = Object.getPrototypeOf; +const objectHasOwn = Object.hasOwn; +const objectPrototype = Object.prototype; +const objectValues = Object.values; +const arrayPrototypeParent = objectGetPrototypeOf(arrayPrototype); +const objectPrototypeParent = objectGetPrototypeOf(objectPrototype); + +function intrinsicSerializationHookMayBePresent(): boolean { + return objectGetOwnPropertyDescriptor(objectPrototype, "toJSON") !== undefined || + objectGetOwnPropertyDescriptor(arrayPrototype, "toJSON") !== undefined || + // A new parent can contribute an inherited hook without changing either + // intrinsic prototype's own descriptor. Treat any chain mutation as hostile. + objectGetPrototypeOf(objectPrototype) !== objectPrototypeParent || + objectGetPrototypeOf(arrayPrototype) !== arrayPrototypeParent; +} + +function blockInheritedSerializationHooks(value: unknown): void { + if (value === null || typeof value !== "object") return; + + if (!objectHasOwn(value, "toJSON")) { + objectDefineProperty(value, "toJSON", { + configurable: false, + enumerable: false, + value: undefined, + writable: false, + }); + } + + if (arrayIsArray(value)) { + for (let index = 0; index < value.length; index++) { + blockInheritedSerializationHooks(value[index]); + } + return; + } + + const values = objectValues(value); + for (let index = 0; index < values.length; index++) { + blockInheritedSerializationHooks(values[index]); + } +} + +function stringifyFallback(fallbackValue: unknown): string { + if (typeof fallbackValue === "string") return fallbackValue; + try { + return jsonStringify(fallbackValue) ?? REDACTED; + } catch { + return REDACTED; + } +} + +export function stringifyRedactedJson( + value: unknown, + fallbackValue: unknown = REDACTED, +): string { + try { + const snapshot = redactForSerialization(value); + if (intrinsicSerializationHookMayBePresent()) { + blockInheritedSerializationHooks(snapshot); + } + return jsonStringify(snapshot) ?? REDACTED; + } catch { + return stringifyFallback(fallbackValue); + } +} + +export function stringifyRedactedAttributeValue( + value: object, + fallbackValue: string = REDACTED, +): string { + try { + const snapshot = redactForSerialization(value); + if (typeof snapshot === "string") return snapshot; + if (intrinsicSerializationHookMayBePresent()) { + blockInheritedSerializationHooks(snapshot); + } + return jsonStringify(snapshot) ?? REDACTED; + } catch { + return fallbackValue; + } +}