@lunora/advisor 1.0.0-alpha.78 → 1.0.0-alpha.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/MAP_VERSION-DEJGm0wI.mjs +1 -0
- package/dist/packem_shared/{actionFetchSsrf-BbWxy1Yf.mjs → actionFetchSsrf-Z61P0o8U.mjs} +1 -1
- package/dist/packem_shared/{actionWithoutErrorHandling-rhzjCEqm.mjs → actionWithoutErrorHandling-4GCBT0_z.mjs} +1 -1
- package/dist/packem_shared/{adminRouteWithoutGuard-BbscPp1m.mjs → adminRouteWithoutGuard-DPE7LuNh.mjs} +1 -1
- package/dist/packem_shared/{aiRawRunEscapeHatch-DfhjYm43.mjs → aiRawRunEscapeHatch-Dq43DVD9.mjs} +1 -1
- package/dist/packem_shared/aiRunWithoutLogging-CvHHtEN9.mjs +1 -0
- package/dist/packem_shared/{aiToolSideEffectPromptInjection-DnWQOoKi.mjs → aiToolSideEffectPromptInjection-K42X3QzJ.mjs} +1 -1
- package/dist/packem_shared/{aiUnboundedGenerationPublic-iu7crnSa.mjs → aiUnboundedGenerationPublic-C17h6sMV.mjs} +1 -1
- package/dist/packem_shared/{allowUnauthenticatedShardAccessEnabled-D4aMNBb0.mjs → allowUnauthenticatedShardAccessEnabled-CjXI-kda.mjs} +1 -1
- package/dist/packem_shared/argument-derived-sink-DLXFC79t.mjs +1 -0
- package/dist/packem_shared/{authApiCallWithoutHeaders-BHkik-EF.mjs → authApiCallWithoutHeaders-C1OOWML5.mjs} +1 -1
- package/dist/packem_shared/{authCsrfCheckDisabled-De9ammtV.mjs → authCsrfCheckDisabled-Dz27bDzp.mjs} +1 -1
- package/dist/packem_shared/{authEmailVerificationDisabled-BFKdJcx4.mjs → authEmailVerificationDisabled-QTDD7TAU.mjs} +1 -1
- package/dist/packem_shared/authScimWithoutTransactions-FqdTmUJs.mjs +1 -0
- package/dist/packem_shared/{authSecureCookiesDisabled-B4WIzvMm.mjs → authSecureCookiesDisabled-CrGYulfJ.mjs} +1 -1
- package/dist/packem_shared/{authSessionFreshageZero-Bpcaf_Pe.mjs → authSessionFreshageZero-yWE6CGYP.mjs} +1 -1
- package/dist/packem_shared/{authTrustedOriginsWildcard-DjU9NXEm.mjs → authTrustedOriginsWildcard-xpeRXfGF.mjs} +1 -1
- package/dist/packem_shared/{browserAllowPrivateTargets-Dxyeh6lg.mjs → browserAllowPrivateTargets-Cj5sizhv.mjs} +1 -1
- package/dist/packem_shared/{browserUserUrlWithoutAllowlist-BHBS6b0N.mjs → browserUserUrlWithoutAllowlist-Wl3xHr7v.mjs} +1 -1
- package/dist/packem_shared/circularFk-DtcWFJxK.mjs +1 -0
- package/dist/packem_shared/classifySensitivity-JnjaTGYi.mjs +1 -0
- package/dist/packem_shared/compareToBaseline-DBgN5YqX.mjs +1 -0
- package/dist/packem_shared/constraintValidator-DoRJD9Is.mjs +1 -0
- package/dist/packem_shared/{containerInstanceKeyFromUserInput-C8S4T3ty.mjs → containerInstanceKeyFromUserInput-uEQQVsEz.mjs} +1 -1
- package/dist/packem_shared/{containerOversizedInstance-B9wW_sU5.mjs → containerOversizedInstance-Bx89uR7E.mjs} +1 -1
- package/dist/packem_shared/{containerPublicInternet-g0TRsayn.mjs → containerPublicInternet-BFfZf_P4.mjs} +1 -1
- package/dist/packem_shared/{containerRuntimeEgressRelaxation-BQ2ZD1aV.mjs → containerRuntimeEgressRelaxation-pkpyXNou.mjs} +1 -1
- package/dist/packem_shared/{containerStartEnableInternetOverride-B62WUura.mjs → containerStartEnableInternetOverride-BBY4PMG1.mjs} +1 -1
- package/dist/packem_shared/dedupeCacheKeys-DtBOHffV.mjs +1 -0
- package/dist/packem_shared/{duplicateIndex-DFT7qLQD.mjs → duplicateIndex-Cip6-Rpu.mjs} +1 -1
- package/dist/packem_shared/{emptyIndex-Bp-Y81D0.mjs → emptyIndex-BnHDcXza.mjs} +1 -1
- package/dist/packem_shared/{errorRateOutlier-CgK42sq0.mjs → errorRateOutlier-pr3_ZKbq.mjs} +1 -1
- package/dist/packem_shared/{errorWithoutCatalog-0PbxiZL4.mjs → errorWithoutCatalog-BTfvaXHR.mjs} +1 -1
- package/dist/packem_shared/{exportSinkMisconfigured-DvpAaa6j.mjs → exportSinkMisconfigured-JfbAx9AI.mjs} +1 -1
- package/dist/packem_shared/{externalSourceIncrementalNoDeletePath-DnoL64YY.mjs → externalSourceIncrementalNoDeletePath-BCzm3HzF.mjs} +1 -1
- package/dist/packem_shared/{externalSourceOnGlobal-IYnALPlO.mjs → externalSourceOnGlobal-CH7xbJ49.mjs} +1 -1
- package/dist/packem_shared/{externalSourceUnscoped-DqBp5jYr.mjs → externalSourceUnscoped-BxU2uSXk.mjs} +1 -1
- package/dist/packem_shared/fanOutBreadth-CBtmZnoh.mjs +1 -0
- package/dist/packem_shared/filterOnPrimaryKey-c37rjqBk.mjs +1 -0
- package/dist/packem_shared/{filterWithoutIndex-CyHJwVhc.mjs → filterWithoutIndex-DXb__LGI.mjs} +1 -1
- package/dist/packem_shared/finding-NrKO8idM.mjs +1 -0
- package/dist/packem_shared/fk-index-DXYS17cc.mjs +1 -0
- package/dist/packem_shared/{flagGatesSecurityWithUnsafeDefault-DPGFEHTt.mjs → flagGatesSecurityWithUnsafeDefault-i-Befg8b.mjs} +1 -1
- package/dist/packem_shared/fromServerSchema-w16riDj2.mjs +1 -0
- package/dist/packem_shared/{geoIndexFieldNotGeopoint-DAGLZtaH.mjs → geoIndexFieldNotGeopoint-D0lOTm-_.mjs} +1 -1
- package/dist/packem_shared/{geoIndexUnused-B5Oww9o2.mjs → geoIndexUnused-D7C9Qr4U.mjs} +1 -1
- package/dist/packem_shared/{globalTableNearColumnLimit-DBQwb2MS.mjs → globalTableNearColumnLimit-BFbBBd6A.mjs} +1 -1
- package/dist/packem_shared/gradeFromScore-KSt58rj1.mjs +1 -0
- package/dist/packem_shared/hardcodedSecret-Bw_4FYrs.mjs +1 -0
- package/dist/packem_shared/helpers-DcWLLrH0.mjs +1 -0
- package/dist/packem_shared/hotShard-BwGYZ3Tq.mjs +1 -0
- package/dist/packem_shared/httpActionMissingAuthGuard-BS3JZgaz.mjs +1 -0
- package/dist/packem_shared/{httpActionResponseHeaderInjection-D063rB7U.mjs → httpActionResponseHeaderInjection-DHnc8c9f.mjs} +1 -1
- package/dist/packem_shared/hyperdriveOutsideAction-CPDdGP2g.mjs +1 -0
- package/dist/packem_shared/{identityUndeclaredClaimTrusted-BYRYGQ74.mjs → identityUndeclaredClaimTrusted-BDFqB7Dw.mjs} +1 -1
- package/dist/packem_shared/{imagesUrlSourceFromUserInput-Cy5pc_dA.mjs → imagesUrlSourceFromUserInput-DizsRh3M.mjs} +1 -1
- package/dist/packem_shared/indexReferencesUnknownField-Dxd5jcVh.mjs +1 -0
- package/dist/packem_shared/{indexUtilization-QfbIH_w4.mjs → indexUtilization-CkVPZcVe.mjs} +1 -1
- package/dist/packem_shared/{insertManyUnsafeUserData-xLYAkcKj.mjs → insertManyUnsafeUserData-DWE8d_DF.mjs} +1 -1
- package/dist/packem_shared/{kvUnscopedUserKeyIdor-B4lGxRxh.mjs → kvUnscopedUserKeyIdor-CIgMPzFj.mjs} +1 -1
- package/dist/packem_shared/mailInboundDispatchWithoutVerify-CWwqXyPX.mjs +1 -0
- package/dist/packem_shared/{mailRecipientFromRequestInput-BR_oSh65.mjs → mailRecipientFromRequestInput-SDCchLP7.mjs} +1 -1
- package/dist/packem_shared/{maskUncoveredPiiColumn-DqPfZM2O.mjs → maskUncoveredPiiColumn-axEka4nd.mjs} +1 -1
- package/dist/packem_shared/{maskWeakHashStrategyOnPii-DcEwc4Sz.mjs → maskWeakHashStrategyOnPii-d9BGImzK.mjs} +1 -1
- package/dist/packem_shared/{maskedRelationLeakViaWith-CxYk0MDW.mjs → maskedRelationLeakViaWith-C8n-Mzb1.mjs} +1 -1
- package/dist/packem_shared/{mutatorFullRowReplace-CEgecSGm.mjs → mutatorFullRowReplace-BzRpZ47r.mjs} +1 -1
- package/dist/packem_shared/{nondeterministicQueryMutation-BZV_biUV.mjs → nondeterministicQueryMutation-sxmixc0l.mjs} +1 -1
- package/dist/packem_shared/{normalizeIdUsedAsAuthorization-FjISSXJm.mjs → normalizeIdUsedAsAuthorization-BXN-Can6.mjs} +1 -1
- package/dist/packem_shared/{notifyMissingPushConfig-C_GbVw3S.mjs → notifyMissingPushConfig-DeQMoNwm.mjs} +1 -1
- package/dist/packem_shared/{notifySendOutsideAction-B9DcirhJ.mjs → notifySendOutsideAction-CU4T16cR.mjs} +1 -1
- package/dist/packem_shared/{outputProjectionMissingOnPublicRead-CjLp7ggc.mjs → outputProjectionMissingOnPublicRead-BN5-uW0p.mjs} +1 -1
- package/dist/packem_shared/{ownerFieldFromArgsNotAuth-D4K-_0aD.mjs → ownerFieldFromArgsNotAuth-CjOSUDKi.mjs} +1 -1
- package/dist/packem_shared/{paymentCreateWithoutAuthorize-CurtqN8K.mjs → paymentCreateWithoutAuthorize-CICGmRFR.mjs} +1 -1
- package/dist/packem_shared/{paymentWebhookWideTolerance-I7ingfR8.mjs → paymentWebhookWideTolerance-B-F5jOeA.mjs} +1 -1
- package/dist/packem_shared/{plaintextSecretInWranglerVariables-CmbygN6_.mjs → plaintextSecretInWranglerVariables-bNkqmNqV.mjs} +1 -1
- package/dist/packem_shared/{policyReferencesUnknownTable-D3WelAQa.mjs → policyReferencesUnknownTable-CIKuRZ5Y.mjs} +1 -1
- package/dist/packem_shared/{privilegedDispatchUnvalidatedPayload-74GuGye1.mjs → privilegedDispatchUnvalidatedPayload-C6Qtc8aT.mjs} +1 -1
- package/dist/packem_shared/{privilegedFanoutFromPublicProcedure-sMsZN4uP.mjs → privilegedFanoutFromPublicProcedure-Coqt23CQ.mjs} +1 -1
- package/dist/packem_shared/procedureWithoutStructuredEvent-CZ_B23nM.mjs +1 -0
- package/dist/packem_shared/{publicArgumentUsesAny-BzdcoJ9C.mjs → publicArgumentUsesAny-BDEvMA8Q.mjs} +1 -1
- package/dist/packem_shared/{publicMutationWithoutRatelimit-DgE1OCbv.mjs → publicMutationWithoutRatelimit-DbczxgpL.mjs} +1 -1
- package/dist/packem_shared/{publicTableRlsOptoutConfusion-C2s9mIhH.mjs → publicTableRlsOptoutConfusion-C9wCtAfg.mjs} +1 -1
- package/dist/packem_shared/{queueWithoutDlq-BLY3iy9Q.mjs → queueWithoutDlq-BvHz3Opg.mjs} +1 -1
- package/dist/packem_shared/{r2sqlOutsideAction-DCMAsAeM.mjs → r2sqlOutsideAction-BdLiLOAX.mjs} +1 -1
- package/dist/packem_shared/ratelimitDefaultMemoryStore-Dj0Kk7t9.mjs +1 -0
- package/dist/packem_shared/ratelimitKeySpoofableOrGlobal-BzDzxnXm.mjs +1 -0
- package/dist/packem_shared/{ratelimitMiddlewareFailOpen-CTWVosEc.mjs → ratelimitMiddlewareFailOpen-iDJq68qx.mjs} +1 -1
- package/dist/packem_shared/relationReferencesUnknownField-BmWWEFsB.mjs +1 -0
- package/dist/packem_shared/{relationReferencesUnknownTable-EOtKyaHx.mjs → relationReferencesUnknownTable-CP4aWtAJ.mjs} +1 -1
- package/dist/packem_shared/{rlsUncoveredTable-DYHgnoos.mjs → rlsUncoveredTable-CZ4ie4gX.mjs} +1 -1
- package/dist/packem_shared/{shapeTargetsGlobalTable-SAlq2N-T.mjs → shapeTargetsGlobalTable-Bu3eEDic.mjs} +1 -1
- package/dist/packem_shared/{shapeUnknownTable-DhvsbEtT.mjs → shapeUnknownTable-CREfNnWi.mjs} +1 -1
- package/dist/packem_shared/{signupMutationWithoutDisposableGating-CqIn86bT.mjs → signupMutationWithoutDisposableGating-D9MFN8QY.mjs} +1 -1
- package/dist/packem_shared/{softDeleteIncludeDeletedFromArgs-DgxpQHRA.mjs → softDeleteIncludeDeletedFromArgs-C7Ugg4zb.mjs} +1 -1
- package/dist/packem_shared/sqlInjectionRisk-CslaRBxz.mjs +1 -0
- package/dist/packem_shared/{storageGenerateUploadUrlNoContentTypePin-C9cjl6Jh.mjs → storageGenerateUploadUrlNoContentTypePin-CTIU_7Fj.mjs} +1 -1
- package/dist/packem_shared/storageKeyFromUserArgs-C-QUcdJ3.mjs +1 -0
- package/dist/packem_shared/{storagePresignedUrlForPrivateContent-j7VhwIdu.mjs → storagePresignedUrlForPrivateContent-yeu1s81k.mjs} +1 -1
- package/dist/packem_shared/{storageUploadWithoutContentTypeAllowlist-oxKLecuf.mjs → storageUploadWithoutContentTypeAllowlist-KSRG81Iu.mjs} +1 -1
- package/dist/packem_shared/{storageUploadWithoutMaxSize-DUDxPutk.mjs → storageUploadWithoutMaxSize-BTiM9Q3A.mjs} +1 -1
- package/dist/packem_shared/tableWithoutInsert-DQ-GxjFF.mjs +1 -0
- package/dist/packem_shared/{ttlFieldNotTimestamp-BFiRkSZD.mjs → ttlFieldNotTimestamp-E14I83wY.mjs} +1 -1
- package/dist/packem_shared/{unboundedStringArgument-DN8w1bg-.mjs → unboundedStringArgument-DzbFJc5q.mjs} +1 -1
- package/dist/packem_shared/{unindexedForeignKey-DK4eWXkb.mjs → unindexedForeignKey-Dypgn8uH.mjs} +1 -1
- package/dist/packem_shared/{unindexedRelationTarget-B5fFq1E8.mjs → unindexedRelationTarget-CSqRJWYZ.mjs} +1 -1
- package/dist/packem_shared/{unrestrictedWhereBranch-YKiydie4.mjs → unrestrictedWhereBranch-CcIBmHik.mjs} +1 -1
- package/dist/packem_shared/{userCreatingMutationWithoutCaptcha-CYgKnJ9j.mjs → userCreatingMutationWithoutCaptcha-lGWw1o2a.mjs} +1 -1
- package/dist/packem_shared/{vectorsNamespaceFromUserInput-Cyq5B2kF.mjs → vectorsNamespaceFromUserInput-CDSOrkyX.mjs} +1 -1
- package/dist/packem_shared/{workflowDuplicateStepName-cXF6Rpvg.mjs → workflowDuplicateStepName-BU4rg5So.mjs} +1 -1
- package/dist/packem_shared/{workflowUnknownTarget-BzKlYSCg.mjs → workflowUnknownTarget-B8H7jwnH.mjs} +1 -1
- package/dist/packem_shared/{workflowUnused-D9fC9E9b.mjs → workflowUnused-BUSOPdHq.mjs} +1 -1
- package/package.json +2 -2
- package/dist/packem_shared/MAP_VERSION-DxkFCiLX.mjs +0 -1
- package/dist/packem_shared/aiRunWithoutLogging-B0K93Pga.mjs +0 -1
- package/dist/packem_shared/argument-derived-sink-BpO3NPPM.mjs +0 -1
- package/dist/packem_shared/authScimWithoutTransactions-DCtLXzks.mjs +0 -1
- package/dist/packem_shared/circularFk-ZNjkhLaE.mjs +0 -1
- package/dist/packem_shared/classifySensitivity-B1kkm44P.mjs +0 -1
- package/dist/packem_shared/compareToBaseline-BwXFusQG.mjs +0 -1
- package/dist/packem_shared/constraintValidator-N339mfyx.mjs +0 -1
- package/dist/packem_shared/dedupeCacheKeys-7JgxaQWV.mjs +0 -1
- package/dist/packem_shared/fanOutBreadth-DkloezYX.mjs +0 -1
- package/dist/packem_shared/filterOnPrimaryKey-C7SeWewK.mjs +0 -1
- package/dist/packem_shared/finding-BAVgqnCF.mjs +0 -1
- package/dist/packem_shared/fk-index-Bmk19RvQ.mjs +0 -1
- package/dist/packem_shared/fromServerSchema-9aaK1J7g.mjs +0 -1
- package/dist/packem_shared/gradeFromScore-DW82YDVY.mjs +0 -1
- package/dist/packem_shared/hardcodedSecret-yu14ITlW.mjs +0 -1
- package/dist/packem_shared/helpers-yHInjqeD.mjs +0 -1
- package/dist/packem_shared/hotShard-io-eF2Sx.mjs +0 -1
- package/dist/packem_shared/httpActionMissingAuthGuard-BZDdeW_D.mjs +0 -1
- package/dist/packem_shared/hyperdriveOutsideAction-B8ffQwgl.mjs +0 -1
- package/dist/packem_shared/indexReferencesUnknownField-EIL4UArJ.mjs +0 -1
- package/dist/packem_shared/mailInboundDispatchWithoutVerify-BivYJ5F9.mjs +0 -1
- package/dist/packem_shared/procedureWithoutStructuredEvent-l40nx9tX.mjs +0 -1
- package/dist/packem_shared/ratelimitDefaultMemoryStore-mOdI3Jmb.mjs +0 -1
- package/dist/packem_shared/ratelimitKeySpoofableOrGlobal-DaGId_Fh.mjs +0 -1
- package/dist/packem_shared/relationReferencesUnknownField-C_3oUop_.mjs +0 -1
- package/dist/packem_shared/sqlInjectionRisk-D2NaT-5c.mjs +0 -1
- package/dist/packem_shared/storageKeyFromUserArgs-CI-6kyte.mjs +0 -1
- package/dist/packem_shared/tableWithoutInsert-BV7qsPKx.mjs +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{e as i}from"./finding-NrKO8idM.mjs";import{m as a}from"./procedure-protections-DRQotu9I.mjs";import{i as s}from"./helpers-DcWLLrH0.mjs";const r=e=>e.writesUserTable===!0?"writes a user/session table":e.callsMail===!0?"sends mail":e.analyzableBody===!1?"may write a user/session table or send mail — its handler body could not be read":"may write a user/session table or send mail — at least one could not be determined",n={categories:["SECURITY"],description:"A public `mutation`/`action` that creates a user/session or sends mail has no CAPTCHA / bot check. Account-creating and mail-sending endpoints are prime automated-abuse targets (credential stuffing, mailbox flooding, disposable-account farming).",facing:"EXTERNAL",level:"WARN",name:"user_creating_mutation_without_captcha",remediation:"Add a server-verified human check: `.use(verifyTurnstile({ secret, token }))` from `@lunora/auth`, or wrap it with `.use(protectPublic({ rateLimit, captcha }))` from `@lunora/server`. Pair with a rate limit for defense in depth.",run:e=>e.procedureProtections===void 0?[]:e.procedureProtections.filter(t=>s(t)&&(a(t.writesUserTable)||a(t.callsMail))&&!t.usesCaptcha).map(t=>i(n,{cacheKey:`user_creating_mutation_without_captcha:${t.file}:${t.exportName}`,detail:`Public ${t.kind} \`${t.exportName}\` (${t.file}) ${r(t)} but has no CAPTCHA check. Add \`.use(verifyTurnstile(...))\` or \`.use(protectPublic({ captcha }))\`.`,metadata:{callsMail:t.callsMail,exportName:t.exportName,file:t.file,kind:t.kind,writesUserTable:t.writesUserTable}})),source:"static",title:"Account-creating / mail-sending write without a CAPTCHA"};export{n as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as
|
|
1
|
+
import{m as r}from"./argument-derived-sink-DLXFC79t.mjs";const o=r({cacheKey:e=>`vectors_namespace_from_user_input:${e.file}:${e.line.toString()}`,categories:["SECURITY"],description:"A `ctx.vectors.query`/`upsert`/`upsertMany` call uses a `namespace` derived from the handler's `args` with no server-side scoping. A Vectorize namespace partitions one index into isolated sub-collections, so an unscoped namespace lets any caller read or poison another tenant's vectors.",detail:e=>`\`ctx.vectors.${e.method}\` in \`${e.exportName}\` (${e.file}:${e.line.toString()}) uses a Vectorize namespace derived from \`args\` with no server-side scoping — any caller can read or poison another tenant's vectors. Derive the namespace from a server-trusted identity (e.g. \`\${ctx.auth.orgId}\`), never from \`args\`.`,facing:"EXTERNAL",getAccesses:e=>e.vectorNamespaceAccesses,level:"WARN",metadata:e=>({exportName:e.exportName,file:e.file,line:e.line,method:e.method}),name:"vectors_namespace_from_user_input",remediation:"Derive the `namespace` from a server-trusted identity (e.g. `${ctx.auth.orgId}`), never from `args`.",title:"Vectorize namespace derived from unscoped user input"});export{o as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{e as l}from"./finding-NrKO8idM.mjs";const p={categories:["SCHEMA"],description:"Two durable steps in this workflow share a name. Cloudflare memoizes a step by its name, so on replay the second call returns the first step's cached result instead of running its body — silently skipping the work without an error.",facing:"INTERNAL",level:"ERROR",name:"workflow_duplicate_step_name",remediation:"Give every `step.do` / `step.sleep` / `step.sleepUntil` / `step.waitForEvent` call in the workflow a unique name. If a step legitimately repeats (e.g. a loop), make the name distinct per iteration by interpolating the item id into the step name.",run:a=>{if(a.workflows===void 0)return[];const o=[];for(const t of a.workflows){const{steps:n}=t;if(n===void 0||n.length===0)continue;const s=new Map,r=new Set;for(const e of n){const i=s.get(e.name);if(i===void 0){s.set(e.name,e.line);continue}r.has(e.name)||(r.add(e.name),o.push(l(p,{cacheKey:`workflow_duplicate_step_name:${t.exportName}:${e.name}`,detail:`Workflow "${t.exportName}" reuses the durable step name "${e.name}" (first at line ${String(i)}, again at line ${String(e.line)}). The second call returns the first's cached result instead of running.`,metadata:{firstLine:i,line:e.line,stepName:e.name,workflow:t.exportName}})))}}return o},source:"static",title:"Duplicate durable step name in workflow"};export{p as default};
|
package/dist/packem_shared/{workflowUnknownTarget-BzKlYSCg.mjs → workflowUnknownTarget-B8H7jwnH.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{e as w}from"./finding-NrKO8idM.mjs";const t={categories:["SCHEMA"],description:'A `ctx.workflows.get("<name>")` call references a workflow that is not declared by any `defineWorkflow` export in `lunora/workflows.ts`. The name is a typo or points at a removed/renamed workflow — the call throws at runtime.',facing:"INTERNAL",level:"ERROR",name:"workflow_unknown_target",remediation:'Fix the workflow name in the `ctx.workflows.get("…")` call, or add the missing `defineWorkflow` export to `lunora/workflows.ts`.',run:e=>{if(e.workflows===void 0||e.workflowCalls===void 0)return[];const r=new Set(e.workflows.map(o=>o.exportName));return e.workflowCalls.filter(o=>o.workflow!==""&&!r.has(o.workflow)).map(o=>w(t,{cacheKey:`workflow_unknown_target:${o.file}:${o.exportName}:${o.workflow}`,detail:`\`ctx.workflows.get("${o.workflow}")\` in "${o.exportName}" (${o.file}) references workflow "${o.workflow}", which is not declared in lunora/workflows.ts.`,metadata:{exportName:o.exportName,file:o.file,line:o.line,workflow:o.workflow}}))},source:"static",title:"Workflow call references unknown workflow"};export{t as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{e as a}from"./finding-NrKO8idM.mjs";const i={categories:["SCHEMA"],description:'No function starts this workflow via `ctx.workflows.get("<name>")`. It may be triggered through a path the advisor can\'t see (the Cloudflare API, a wrangler invocation, a cross-service binding) — or it may be dead code that still deploys as a billable WorkflowEntrypoint.',facing:"INTERNAL",level:"INFO",name:"workflow_unused",remediation:'If the workflow should be triggered in-app, start it from a mutation/action with `ctx.workflows.get("<name>").create({ params })`. If it is started externally or is no longer needed, this advisory can be ignored (or remove the `defineWorkflow` export).',run:o=>{if(o.workflows===void 0)return[];const t=o.workflowCalls??[];if(t.some(e=>e.workflow===""))return[];const r=new Set(t.map(e=>e.workflow));return o.workflows.filter(e=>!r.has(e.exportName)).map(e=>a(i,{cacheKey:`workflow_unused:${e.exportName}`,detail:`No function calls \`ctx.workflows.get("${e.exportName}")\` — workflow "${e.exportName}" is declared but never started in app code.`,metadata:{workflow:e.exportName}}))},source:"static",title:"Workflow is never started"};export{i as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/advisor",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.79",
|
|
4
4
|
"description": "Schema & query lints (splinter-style advisors) for Lunora, feeding the Studio Advisors view",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"advisor",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@lunora/errors": "1.0.0-alpha.21",
|
|
50
|
-
"@lunora/server": "1.0.0-alpha.
|
|
50
|
+
"@lunora/server": "1.0.0-alpha.73"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": "^22.15.0 || >=24.11.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{scoreProcedure as h,procedureWeight as y,coverageFromScore as k,scoreGlobal as N,gradeFromScore as $,projectWeight as b,worstLevel as M,weightFor as j}from"./gradeFromScore-DW82YDVY.mjs";import F from"./classifySensitivity-B1kkm44P.mjs";const S=(e,o)=>e===o?0:e<o?-1:1,w=(e,o)=>S(e.name,o.name),x=(e,o)=>{const t=e[o];return typeof t=="string"?t:void 0},p=(e,o)=>`${e}#${o}`,R=(e,o,t,s)=>e===void 0?{level:o,name:t,occurrences:1,weight:s}:{level:M(e.level,o),name:t,occurrences:e.occurrences+1,weight:Math.max(e.weight,s)},A=(e,o,t)=>{const s=new Map;for(const a of e)s.set(p(a.file,a.exportName),new Map);const c=new Map;for(const a of o){const n=x(a.metadata,"file"),l=x(a.metadata,"exportName"),m=(n!==void 0&&l!==void 0?s.get(p(n,l)):void 0)??c;m.set(a.name,R(m.get(a.name),a.level,a.name,t(a)))}return{byProcedure:new Map([...s].map(([a,n])=>[a,[...n.values()].toSorted(w)])),project:[...c.values()].toSorted(w)}},I=(e,o)=>{const t={clean:0,exempt:0,failing:0,warned:0};let s=o.checks.length;for(const c of e)t[c.coverage]+=1,s+=c.checks.length;return{clean:t.clean,exempt:t.exempt,failing:t.failing,procedures:e.length,rulesFired:s,warned:t.warned}},O=1,W=(e,o,t={})=>{const s=new Set(t.exempt),c=A(e,o,r=>j(r.level)),a=e.map(r=>{const i=p(r.file,r.exportName),v=c.byProcedure.get(i)??[],u=h(v),d=s.has(i)||r.exempt===!0,f=F(r);return{checks:v,coverage:d?"exempt":k(u),...d?{exemptReason:r.exemptReason??""}:{},exportName:r.exportName,file:r.file,id:i,kind:r.kind,score:u,sensitivity:f,visibility:r.visibility,weight:d?0:y(r,f.level)}}).toSorted((r,i)=>S(r.id,i.id)),n={checks:c.project,score:h(c.project)},l=a.filter(r=>r.coverage!=="exempt"),m=l.reduce((r,i)=>r+i.weight,0),g=N([...l,{score:n.score,weight:b(m)}]);return{generatedAt:t.generatedAt??new Date().toISOString(),grade:$(g),procedures:a,project:n,score:g,summary:I(a,n),version:O}};export{O as MAP_VERSION,S as byCodepoint,W as scoreAdvisor};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as n}from"./finding-BAVgqnCF.mjs";const o={categories:["PERFORMANCE"],description:"A procedure runs an AI generation but emits no structured event. Model calls bill per invocation and are not reproducible, so without one there is no way to attribute spend or trace a bad answer back to its prompt.",facing:"INTERNAL",level:"INFO",name:"ai_run_without_logging",remediation:'Emit an event around the generation — `ctx.span("<name>", …)`, or a `ctx.log` line carrying the model and token usage.',run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>e.runsAiGeneration===!0&&e.emitsEvent===!1).map(e=>n(o,{cacheKey:`ai_run_without_logging:${e.file}:${e.exportName}`,detail:`${e.kind} \`${e.exportName}\` (${e.file}) runs an AI generation with no structured event, so its cost and output cannot be attributed later.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind}})),source:"static",title:"AI generation with no structured event"};export{o as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as r}from"./finding-BAVgqnCF.mjs";const s=e=>{const a={categories:e.categories,description:e.description,facing:e.facing,level:e.level,name:e.name,remediation:e.remediation,run:i=>{const n=e.getAccesses(i);return n===void 0||e.suppressWhen?.(i)===!0?[]:n.map(t=>r(a,{cacheKey:e.cacheKey(t),detail:e.detail(t),metadata:e.metadata(t)}))},source:"static",title:e.title};return a};export{s as m};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as t}from"./finding-BAVgqnCF.mjs";const i={categories:["SECURITY"],description:"A `createAuth({...})` call loads `scim()` with a `database` that has no native transactions (`lunoraD1Adapter` / `lunoraAuthAdapter`). `@better-auth/scim` refuses to serve on such an adapter, so every SCIM request fails.",facing:"EXTERNAL",level:"ERROR",name:"auth_scim_without_transactions",remediation:"Move the auth tables somewhere with real transactions: `.auth({ namespace })` with a `LunoraAuthDO` (Durable Object storage), or a Postgres/MySQL kysely dialect over `@lunora/hyperdrive` with `transaction: true`. D1 cannot host SCIM.",run:e=>e.authConfigs===void 0?[]:e.authConfigs.filter(a=>a.analyzable&&a.scimOnNonTransactionalAdapter).map(a=>t(i,{cacheKey:`auth_scim_without_transactions:${a.file}:${a.line.toString()}`,detail:`\`createAuth\` in \`${a.exportName}\` (${a.file}:${a.line.toString()}) loads \`scim()\` on an adapter without native transactions, so the SCIM plugin will refuse to serve and every provisioning request from your IdP fails. Host the auth tables in a Durable Object (\`.auth({ namespace })\`) or on Postgres/MySQL via \`@lunora/hyperdrive\`.`,metadata:{exportName:a.exportName,file:a.file,line:a.line}})),source:"static",title:"SCIM configured on an adapter without transactions"};export{i as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as E}from"./finding-BAVgqnCF.mjs";const S=100,k={categories:["SCHEMA"],description:"A chain of foreign-key relations forms a cycle (e.g. A → B → C → A). Circular FK dependencies can cause unexpected behavior during DELETE operations — a CASCADE chain may loop indefinitely, and even a RESTRICT cycle prevents deletion of any row in the loop without temporarily disabling constraints.",facing:"INTERNAL",level:"WARN",name:"circular_fk",remediation:"Remove or break the cycle by dropping at least one FK relation from the loop. Consider replacing the circular dependency with a nullable FK and explicit application logic, or restructure the schema using a junction table.",run:u=>{const h=[],r=[...new Set(u.schema.tables.map(e=>e.name))].toSorted((e,t)=>e.localeCompare(t)),g=new Map(r.map((e,t)=>[e,t])),s=new Map;for(const e of u.schema.tables)for(const t of e.relations){if(t.kind!=="one"||!g.has(t.table))continue;let n=s.get(e.name);n===void 0&&(n=new Set,s.set(e.name,n)),n.add(t.table)}const i=new Set,c=new Map,d=[],f=new Set;let p=!1;const m=e=>{i.delete(e);const t=c.get(e);if(t!==void 0)for(const n of t)t.delete(n),i.has(n)&&m(n)},w=()=>{const e=[...d];if(e.length<2)return;let t=0;for(let l=1;l<e.length;l+=1)e[l]<e[t]&&(t=l);const n=[...e.slice(t),...e.slice(0,t)],a=n.join("→");if(f.has(a))return;f.add(a);const o=[...n,n[0]].join(" → ");h.push(E(k,{cacheKey:`circular_fk:${a}`,detail:`Circular foreign-key dependency detected: ${o}. This cycle can cause unexpected behavior during DELETE operations.`,metadata:{cycle:n,path:o,tables:n}})),f.size>=S&&(p=!0)},y=(e,t)=>[...s.get(e)??[]].filter(n=>g.get(n)>=t),C=(e,t)=>{for(const n of y(e,t)){let a=c.get(n);a===void 0&&(a=new Set,c.set(n,a)),a.add(e)}},b=(e,t,n)=>{let a=!1;d.push(e),i.add(e);for(const o of y(e,n)){if(p)break;o===t?(w(),a=!0):!i.has(o)&&b(o,t,n)&&(a=!0)}return a?m(e):C(e,n),d.pop(),a};for(let e=0;e<r.length&&!p;e+=1)i.clear(),c.clear(),b(r[e],r[e],e);return h},source:"static",title:"Circular foreign-key dependency"};export{k as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const o=[{fact:e=>e.writesUserTable,reason:"writes an identity table"},{fact:e=>e.callsMail,reason:"sends mail"},{fact:e=>e.fanOut,reason:"fans out to a privileged dispatch surface"},{fact:e=>e.usesInsertManyUnsafe,reason:"bypasses validators with insertManyUnsafe"},{fact:e=>e.unboundedAiGeneration,reason:"runs an unbounded AI generation"}],r="may exhibit sensitive behaviour — its handler body could not be read",i=e=>{const a=[];let n=!1;for(const s of o){const t=s.fact(e);t===!0?a.push(s.reason):t===void 0&&(n=!0)}return n&&a.push(r),{level:a.length===0?"none":"high",reasons:a}};export{i as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{byCodepoint as p}from"./MAP_VERSION-DxkFCiLX.mjs";const l=new Set(["clean","exempt","failing","warned"]),d=(e,r)=>{const t=new Map(e.map(s=>[s.name,s.occurrences]));return r.some(s=>s.occurrences>(t.get(s.name)??0))},i=e=>typeof e=="number"&&Number.isFinite(e),g=e=>{if(typeof e!="object"||e===null)return!1;const r=e;return typeof r.id=="string"&&i(r.score)&&typeof r.coverage=="string"&&l.has(r.coverage)},m=e=>{if(typeof e!="object"||e===null)return!1;const r=e;return Array.isArray(r.checks)&&i(r.score)},v=(e,r)=>{if(r.version!==e.version)return{comparable:!1,reason:"version-mismatch"};const t=new Map(r.procedures.map(o=>[o.id,o])),s=[],n=[],a=[];for(const o of e.procedures){if(o.coverage==="exempt")continue;const c=t.get(o.id);c!==void 0&&c.coverage!=="exempt"&&o.score<c.score&&s.push({after:o.score,before:c.score,id:o.id}),o.coverage==="failing"&&c?.coverage!=="failing"&&n.push(o.id),d(c?.checks??[],o.checks)&&a.push(o.id)}s.sort((o,c)=>p(o.id,c.id)),n.sort(p),a.sort(p);const u=e.score-r.score,f=d(r.project.checks,e.project.checks);return{comparable:!0,dropped:s,newFailing:n,projectRegressed:f,regressed:u<0||s.length>0||n.length>0||a.length>0||f,scoreDelta:u,worsened:a}},y=e=>{if(typeof e!="object"||e===null)return;const r=e;if(!(!i(r.version)||!i(r.score))&&!(!Array.isArray(r.procedures)||!r.procedures.every(t=>g(t)))&&m(r.project))return e};export{v as compareToBaseline,y as parseAdvisorMap};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as u}from"./finding-BAVgqnCF.mjs";const d=5,f=a=>typeof a._id=="string"?a._id:"?",m=(a,t,e)=>a?` (sample capped at ${t.toString()} rows — ${e})`:"",p=(a,t)=>{const e=a.slice(0,d),o=t-e.length,n=e.map(i=>`"${i}"`).join(", ");return o>0?`${n} (+${o.toString()} more)`:n},v=(a,t)=>{if(a==="_id")return t.existingIds;const e=new Set;for(const o of t.rows){const n=o[a];n!=null&&e.add(String(n))}return e},x=(a,t,e,o)=>{if(e.kind!=="one"||o.truncated)return;const n=e.references,i=v(n,o),s=e.field,r=[];for(const g of t.rows){const h=g[s];if(h==null)continue;const $=String(h);i.has($)||r.push(f(g))}if(r.length===0)return;const l=t.truncated,{cap:c}=t,b=n==="_id"?`"${e.table}"`:`"${e.table}"."${n}"`,w=`Table "${t.table}": ${r.length.toString()} sampled row(s) have a dangling FK value in column "${s}" — the referenced ${b} row does not exist. Row ids: ${p(r,r.length)}${m(l,c,"more rows may exist beyond the window")}.`;return u(a,{cacheKey:`constraint_validator:fk:${t.table}:${s}`,detail:w,metadata:{cap:c,column:s,count:r.length,examples:r.slice(0,d),kind:"fk",references:n,referencesTable:e.table,table:t.table,truncated:l}})},S=(a,t,e,o)=>{if(!o)return;const n=[];for(const s of t.rows)s[e]==null&&n.push(f(s));if(n.length===0)return;const i=`Table "${t.table}": ${n.length.toString()} sampled row(s) have a null/missing value in column "${e}", which is declared as non-optional. Row ids: ${p(n,n.length)}${m(t.truncated,t.cap,"more rows may exist beyond the window")}.`;return u(a,{cacheKey:`constraint_validator:null:${t.table}:${e}`,detail:i,metadata:{cap:t.cap,column:e,count:n.length,examples:n.slice(0,d),kind:"null",table:t.table,truncated:t.truncated}})},y=(a,t)=>{const e=[];for(const o of t){const n=a[o];if(n==null)return;e.push(JSON.stringify(n))}return e.join("|")},k=(a,t,e)=>{e.includes(t)||e.push(t),e.push(a)},_=(a,t,e)=>{if(e.kind!=="index"||e.unique!==!0)return;const o=new Map,n=[];for(const s of t.rows){const r=f(s),l=y(s,e.fields);if(l===void 0)continue;const c=o.get(l);c===void 0?o.set(l,r):k(r,c,n)}if(n.length===0)return;const i=`Table "${t.table}": ${n.length.toString()} sampled row(s) share duplicate values on unique index "${e.name}" (${e.fields.join(", ")}). Row ids: ${p(n,n.length)}${m(t.truncated,t.cap,"duplicates beyond the window may exist")}.`;return u(a,{cacheKey:`constraint_validator:unique:${t.table}:${e.name}`,detail:i,metadata:{cap:t.cap,count:n.length,examples:n.slice(0,d),fields:e.fields,index:e.name,kind:"unique",table:t.table,truncated:t.truncated}})},R=(a,t,e,o)=>{const n=o.get(t.table);if(!n)return[];const i=[];for(const s of n.relations){const r=e.get(s.table);if(!r)continue;const l=x(a,t,s,r);l&&i.push(l)}for(const s of n.fields){const r=!n.optionalFields?.has(s),l=S(a,t,s,r);l&&i.push(l)}for(const s of n.indexes){const r=_(a,t,s);r&&i.push(r)}return i},T={categories:["SCHEMA"],description:"Sampled rows violate one or more declared constraints: a foreign-key column references a non-existent row, a non-optional column contains null, or a unique-indexed column has duplicate values. These violations indicate data inserted before a constraint was enforced (e.g. a schema migration, a raw import, or a bug in a past data-migration transform).",facing:"INTERNAL",level:"WARN",name:"constraint_validator",remediation:"Inspect the listed rows and correct or remove the violating values. Run a data migration to backfill nulls, dedup unique violations, or re-link dangling FK references. For FK violations, confirm the target table rows exist before fixing the referencing rows.",run:a=>{if(!a.tableSamples||a.tableSamples.length===0)return[];const t=new Map;for(const o of a.tableSamples)t.set(o.table,o);const e=new Map(a.schema.tables.map(o=>[o.name,o]));return a.tableSamples.flatMap(o=>R(T,o,t,e))},source:"runtime",title:"Constraint violation"};export{T as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const n=a=>{const t=new Map;return a.map(e=>{const c=(t.get(e.cacheKey)??0)+1;return t.set(e.cacheKey,c),c===1?e:{...e,cacheKey:`${e.cacheKey}:${c.toString()}`}})};export{n as dedupeCacheKeys};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as n}from"./finding-BAVgqnCF.mjs";const s=1e3,o=500,d={categories:["PERFORMANCE"],description:"A shard set is wide enough that a cross-shard read over it would approach the per-invocation subrequest ceiling.",facing:"EXTERNAL",level:"WARN",name:"fan_out_breadth",remediation:"Keep reads shard-pinned, or roll cross-shard answers up through a `.global()` table.",run:i=>{const t=(i.shardTraffic??[]).filter(e=>e.requests>0);if(t.length===0)return[];const a=new Map;for(const e of t){const r=e.group??"";a.set(r,(a.get(r)??0)+1)}return[...a].filter(([,e])=>e>=o).map(([e,r])=>n(d,{cacheKey:`fan_out_breadth:${e}`,detail:`${e===""?"This deployment":`Shard group "${e}"`} has ${String(r)} active shards, so a cross-shard read over it would issue ${String(r)} Durable Object subrequests — against a ceiling of ${String(s)} per invocation on the Free plan (higher on Paid, but finite). Shard-pinned reads are unaffected.`,metadata:{freePlanCeiling:s,group:e,shards:r}}))},source:"runtime",title:"Shard set wide enough to strain a cross-shard read"};export{d as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i}from"./finding-BAVgqnCF.mjs";import{q as a}from"./helpers-yHInjqeD.mjs";const l={categories:["PERFORMANCE"],description:"A query filters on `_id`, the primary key. That walks the table comparing every row's id, when `ctx.db.get(id)` fetches the row directly. There is no case where the scan is preferable.",facing:"EXTERNAL",level:"WARN",name:"filter_on_primary_key",remediation:'Replace `ctx.db.query("table").filter((d) => d._id === id).first()` with `ctx.db.get(id)`. Passing a typed `Id<"table">` also narrows the result to that table\'s `Doc`, where the scan form returns the shared row type.',run:t=>(t.queries??[]).filter(e=>e.filtersPrimaryKey===!0&&e.table!=="").map(e=>{const r=a(e);return i(l,{cacheKey:`filter_on_primary_key:${e.file}:${e.line.toString()}:${e.table}`,detail:`Query on "${e.table}" at ${r} filters on \`_id\` — it scans "${e.table}" to find a row \`ctx.db.get(id)\` addresses directly.`,metadata:{exportName:e.exportName,file:e.file,line:e.line,table:e.table}})}),source:"static",title:"Filter on primary key instead of ctx.db.get"};export{l as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const t=(e,i)=>({cacheKey:i.cacheKey,categories:i.categories??e.categories,description:e.description,detail:i.detail,facing:i.facing??e.facing,level:i.level??e.level,metadata:i.metadata,name:e.name,remediation:e.remediation,title:e.title});export{t as i};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const d="_id",i=s=>`by${s.charAt(0).toUpperCase()}${s.slice(1)}`,n=s=>new Set(s.indexes.filter(e=>e.kind==="index").map(e=>e.fields[0]).filter(e=>e!==void 0));export{d as P,n as l,i as s};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const r=t=>({rlsMode:t.rlsMode,tables:Object.entries(t.tables).map(([d,a])=>{const s=[...a.indexes.map(e=>({fields:e.fields,kind:"index",name:e.name,unique:e.unique})),...a.searchIndexes.map(e=>({fields:[e.field,...e.filterFields??[]],kind:"search",name:e.name})),...a.rankIndexes.map(e=>({fields:[...e.sortBy.map(n=>n.field),...e.partitionBy??[]],kind:"rank",name:e.name})),...a.vectorIndexes.map(e=>({fields:[e.field],kind:"vector",name:e.name})),...a.geoIndexes.map(e=>({fields:[e.field],kind:"geo",name:e.name}))],i={},l=new Set;for(const[e,n]of Object.entries(a.shape))if(n.kind==="optional"){const o=n._meta?.inner;i[e]=o?.kind??n.kind,l.add(e)}else i[e]=n.kind,n._meta?.column?.notNull===!1&&l.add(e);return{externallyManaged:a.isExternallyManaged??!1,externalSource:a.externalSource?{hasReconcile:a.externalSource.reconcileEveryMs!==void 0,hasSoftDelete:a.externalSource.softDeleteColumn!==void 0,hasTenantBy:a.externalSource.tenantBy!==void 0,mode:a.externalSource.mode}:void 0,columnKinds:i,fields:Object.keys(a.shape),indexes:s,isPublic:a.isPublic??!1,name:d,optionalFields:l,shardKind:a.shardMode.kind,softDelete:a.softDeleteMode,ttl:a.ttlPolicy,relations:Object.entries(a.relationMap).map(([e,n])=>({field:n.field,kind:n.kind,name:e,onDelete:n.onDelete,references:n.references,table:n.table}))}})});export{r as fromServerSchema};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const s={ERROR:20,INFO:5,WARN:10},i={ERROR:3,INFO:1,WARN:2},n=e=>Number.isFinite(e)&&e>=0?e:0,c=e=>s[e],u=(e,r)=>i[e]>=i[r]?e:r,h=(e,r="none")=>{const o=e.visibility==="public"?2:.5,t=e.kind==="query"?.5:o;return r==="high"?t*2:t},g=e=>Math.max(1,e*.2),d=e=>{const r=e.reduce((o,t)=>o+n(t.weight),0);return Math.min(100,Math.max(0,100-r))},l=e=>{const r=e.reduce((t,a)=>t+a.weight,0);if(r===0)return 100;const o=e.reduce((t,a)=>t+a.score*a.weight,0);return Math.round(o/r)},R=e=>e>=90?"excellent":e>=70?"good":e>=50?"needs-work":"at-risk",m=e=>e===100?"clean":e>=50?"warned":"failing";export{s as DEFAULT_WEIGHT_BY_LEVEL,m as coverageFromScore,R as gradeFromScore,n as normalizeWeight,h as procedureWeight,g as projectWeight,l as scoreGlobal,d as scoreProcedure,c as weightFor,u as worstLevel};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{dedupeCacheKeys as r}from"./dedupeCacheKeys-7JgxaQWV.mjs";import{i}from"./finding-BAVgqnCF.mjs";const a={categories:["SECURITY"],description:"A secret-shaped string literal (live API key, access key, private key, or high-entropy token) is hard-coded in the source. Committed secrets leak via clone/fork/history and force a redeploy to rotate.",facing:"INTERNAL",level:"ERROR",name:"hardcoded_secret",remediation:'Move the secret out of source: set it locally in `.dev.vars` (`lunora env set <NAME> "<value>"`) and in production with `wrangler secret put <NAME>`, then read it at runtime from `env.<NAME>`. Rotate the exposed value — assume it is already compromised.',run:t=>t.secretLiterals===void 0?[]:r(t.secretLiterals.map(e=>i(a,{cacheKey:`hardcoded_secret:${e.file}:${e.line.toString()}:${e.kind}`,detail:`A ${e.kind.replaceAll("_"," ")} (${e.preview}) is hard-coded at ${e.file}:${e.line.toString()}. Move it to \`.dev.vars\` / \`wrangler secret put\` and read it from \`env\`. Rotate the exposed value.`,metadata:{file:e.file,kind:e.kind,line:e.line,preview:e.preview}}))),source:"static",title:"Hard-coded secret in source"};export{a as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const i=new Set(["accountId","authorId","createdBy","createdById","organizationId","orgId","ownerId","tenantId","updatedBy","userId","workspaceId"]),t=new Set(["address","dateOfBirth","dob","email","firstName","lastName","phone","phoneNumber","socialSecurityNumber","ssn"]),n=new Set(["_creationTime","_id"]),s=e=>e.fields.filter(a=>i.has(a)||t.has(a)),d=e=>new Set([...n,...e.fields]),r=e=>e.visibility==="public"&&(e.kind==="mutation"||e.kind==="action"),o=e=>e.line>0?`${e.file}:${e.line.toString()}`:e.file,l=e=>new Map(e.tables.map(a=>[a.name,a.shardKind]));export{t as P,r as i,s as o,o as q,l as s,d as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as h}from"./finding-BAVgqnCF.mjs";const u=.5,c=50,l=o=>{const r=new Map;for(const s of o){const t=s.group??"",e=r.get(t);e===void 0?r.set(t,[s]):e.push(s)}return r},g=(o,r,s)=>{const t=s.reduce((e,a)=>e+a.requests,0);return s.length<2||t<c?[]:s.filter(e=>e.requests/t>=u).map(e=>{const a=e.requests/t,n=e.group!==void 0&&e.group!==""?`"${e.group}" `:"",i=e.shardKey===""?"the root shard":`shard "${e.shardKey}"`,d=Math.round(a*100);return h(o,{cacheKey:`hot_shard:${r}:${e.shardKey}`,detail:`${n}${i} handled ${e.requests.toString()} of ${t.toString()} requests (${d.toString()}%) across ${s.length.toString()} shards — a hot-key skew. Re-shard on a more evenly-distributed key.`,metadata:{group:e.group,requests:e.requests,shardCount:s.length,shardKey:e.shardKey,share:a,totalRequests:t}})})},p={categories:["PERFORMANCE"],description:"One shard is taking a disproportionate share of a sharded function's traffic, so the busy shard becomes a single-DO bottleneck while its siblings sit idle — the hot-key skew sharding is meant to avoid.",facing:"EXTERNAL",level:"WARN",name:"hot_shard",remediation:"Re-shard on a higher-cardinality / more evenly-distributed key, or split the hot entity's state, so request volume spreads across shards instead of concentrating on one.",run:o=>{const r=(o.shardTraffic??[]).filter(s=>s.requests>0);return[...l(r)].flatMap(([s,t])=>g(p,s,t))},source:"runtime",title:"Hot shard"};export{p as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as a}from"./finding-BAVgqnCF.mjs";const n=(t,e)=>t==="httpRoute"&&e?`\`httpRoute\` ${e} handler`:"`httpAction` handler",r={categories:["SECURITY"],description:"An `httpAction`/`httpRoute` handler performs a side effect (`ctx.runMutation`/`ctx.runAction`/a `ctx.db` write) but never reads `ctx.auth` — an unauthenticated HTTP endpoint driving a state change, bypassing the identity/RLS checks that guard the rest of the app.",facing:"EXTERNAL",level:"WARN",name:"http_action_missing_auth_guard",remediation:"Read `ctx.auth` in the handler before the side effect — call `await ctx.auth.getIdentity()` (or check `ctx.auth.userId`) and reject unauthenticated/unauthorized requests, or forward through a `mutation`/`action` whose RLS policies enforce access. If the endpoint is intentionally public (e.g. a signed webhook), verify the provider signature before the write.",run:t=>t.httpActionGuards===void 0?[]:t.httpActionGuards.filter(e=>!e.readsAuth).map(e=>{const i=n(e.kind,e.method);return a(r,{cacheKey:`http_action_missing_auth_guard:${e.file}:${e.line.toString()}`,detail:`${i} \`${e.exportName}\` (${e.file}:${e.line.toString()}) calls \`ctx.${e.sideEffect}\` but never reads \`ctx.auth\` — an anonymous caller can drive this write. Authenticate the request before the side effect.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind,line:e.line,sideEffect:e.sideEffect,...e.method?{method:e.method}:{}}})}),source:"static",title:"Unauthenticated HTTP handler performs a side effect"};export{r as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i}from"./finding-BAVgqnCF.mjs";const a={categories:["SCHEMA"],description:"A `query`/`mutation` handler accesses Hyperdrive via `ctx.sql`. Hyperdrive hits an external database Lunora does not own: queries are non-deterministic (like `fetch`) and external writes are invisible to live queries. `ctx.sql` is available on `ActionCtx` only and must be confined to `action` handlers.",facing:"EXTERNAL",level:"WARN",name:"hyperdrive_outside_action",remediation:"Move the `ctx.sql` access into an `action(...)` (the only context where it is typed), where external I/O is allowed. If a query/mutation needs the data, have the action read it via `ctx.sql` and write a projection into a `defineSchema` DO/D1 table — that write is tracked by live queries, whereas the external DB is not.",run:t=>t.hyperdriveCalls===void 0?[]:t.hyperdriveCalls.map(e=>i(a,{cacheKey:`hyperdrive_outside_action:${e.file}:${e.line.toString()}:${e.callee}`,detail:`\`${e.callee}(…)\` in ${e.exportName} (${e.file}:${e.line.toString()}) runs inside a ${e.kind} handler — Hyperdrive's \`ctx.sql\` is non-deterministic and non-reactive, so it is available only in actions. Move the external SQL into an \`action\` and project the result into a Lunora table if a query/mutation needs it.`,metadata:{callee:e.callee,exportName:e.exportName,file:e.file,kind:e.kind,line:e.line}})),source:"static",title:"Hyperdrive `ctx.sql` used outside an action"};export{a as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as o}from"./finding-BAVgqnCF.mjs";import{t as d}from"./helpers-yHInjqeD.mjs";const r={categories:["SCHEMA"],description:"An index references a column that is not declared on its table. The index can never match, and the typo would otherwise surface only at runtime.",facing:"INTERNAL",level:"ERROR",name:"index_references_unknown_field",remediation:"Fix the column name in the index declaration, or add the column to the table.",run:t=>t.schema.tables.flatMap(e=>{const i=d(e);return e.indexes.flatMap(n=>n.fields.filter(a=>!i.has(a)).map(a=>o(r,{cacheKey:`index_references_unknown_field:${e.name}:${n.name}:${a}`,detail:`Index "${n.name}" on table "${e.name}" references column "${a}", which is not declared on the table.`,metadata:{field:a,index:n.name,indexKind:n.kind,table:e.name}})))}),source:"static",title:"Index references unknown field"};export{r as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i}from"./finding-BAVgqnCF.mjs";const t={categories:["SECURITY"],description:"`createInboundEmailHandler({...})` has no `verify` hook, so it trusts every inbound message — the `from` address is spoofable and DKIM/SPF/DMARC verdicts go unchecked. If the handler dispatches into a Lunora function (admin bearer, RLS disabled), a forged sender can drive privileged writes (a confused-deputy escalation).",facing:"EXTERNAL",level:"ERROR",name:"mail_inbound_dispatch_without_verify",remediation:"Pass a `verify` hook to `createInboundEmailHandler({...})` that rejects the message unless its DKIM/SPF/DMARC authentication verdicts pass and the sender is expected — before any dispatch into a Lunora function. Never trust the `from` header alone.",run:a=>a.configCalls===void 0?[]:a.configCalls.filter(e=>e.callee==="createInboundEmailHandler"&&e.analyzable&&!e.presentKeys.includes("verify")).map(e=>i(t,{cacheKey:`mail_inbound_dispatch_without_verify:${e.file}:${e.line.toString()}`,detail:`\`createInboundEmailHandler({...})\` in ${e.file}:${e.line.toString()} has no \`verify\` hook — it trusts the spoofable \`from\` address and unchecked DKIM/SPF/DMARC verdicts. A forged sender can then reach whatever the handler dispatches to (a Lunora function runs with the admin bearer and RLS disabled). Add a \`verify\` gate that enforces the authentication verdicts before dispatch.`,metadata:{callee:e.callee,file:e.file,line:e.line}})),source:"static",title:"Inbound email handler without a verify hook"};export{t as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i}from"./finding-BAVgqnCF.mjs";const o={categories:["SCHEMA"],description:"A public `mutation`/`action` emits no structured event. When it fails you get a stack trace with no request context — no ids, no tenant, no outcome — so the failure is visible but not searchable.",facing:"INTERNAL",level:"INFO",name:"procedure_without_structured_event",remediation:'Emit one event on the primary path: `ctx.log.info("<verb>", { … })`, or wrap the handler in `ctx.span("<name>", …)` to attach timing too.',run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>e.visibility==="public"&&e.kind!=="query"&&e.emitsEvent===!1).map(e=>i(o,{cacheKey:`procedure_without_structured_event:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) emits no structured event. Add a \`ctx.log\` line or a \`ctx.span\` so a failure carries its request context.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind}})),source:"static",title:"Public write emits no structured event"};export{o as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i}from"./finding-BAVgqnCF.mjs";const a={categories:["SECURITY"],description:"`new RateLimiter({...})` has no `store`, so it falls back to a per-instance in-memory store. On Workers each request may hit a different isolate, so the counter never sums across them — the limit silently under-counts and is effectively a no-op under load, giving false protection against brute-force and cost abuse.",facing:"EXTERNAL",level:"WARN",name:"ratelimit_default_memory_store",remediation:"Pass a shared, durable `store` to `new RateLimiter({...})` (a Durable-Object- or KV-backed store) so every Worker isolate reads and writes the same bucket. The default in-memory store is per-isolate and does not enforce a global limit.",run:t=>t.configCalls===void 0?[]:t.configCalls.filter(e=>e.callee==="RateLimiter"&&e.analyzable&&!e.presentKeys.includes("store")).map(e=>i(a,{cacheKey:`ratelimit_default_memory_store:${e.file}:${e.line.toString()}`,detail:`\`new RateLimiter({...})\` in ${e.file}:${e.line.toString()} has no \`store\` — it uses the per-instance in-memory store, which never sums across Worker isolates, so the limit silently under-counts under load. Pass a Durable-Object- or KV-backed \`store\` so the limit is enforced globally.`,metadata:{callee:e.callee,file:e.file,line:e.line}})),source:"static",title:"RateLimiter using the default in-memory store"};export{a as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i}from"./finding-BAVgqnCF.mjs";const a={categories:["SECURITY"],description:"A `rateLimit`/`dbRateLimit` middleware call's `key` selector is derived from the handler's `args` with no server-side scoping, so an attacker can rotate the key on every request (e.g. by resubmitting a different email/id) and never share a bucket with themselves — the limit never actually limits them.",facing:"EXTERNAL",level:"WARN",name:"ratelimit_key_spoofable_or_global",remediation:'Derive the rate-limit `key` from a server-trusted value — `ctx.auth.userId` for authenticated callers, `ctx.ip` for anonymous ones (e.g. `key: (ctx) => ctx.auth.userId ?? ctx.ip ?? "anon"`) — instead of an `args` field the caller controls.',run:t=>t.ratelimitKeySelectors===void 0?[]:t.ratelimitKeySelectors.map(e=>i(a,{cacheKey:`ratelimit_key_spoofable_or_global:${e.file}:${e.line.toString()}`,detail:`\`${e.callee}(…, "${e.limitName}", { key })\` in \`${e.exportName}\` (${e.file}:${e.line.toString()}) derives its \`key\` from \`args\` with no server-side scoping — an attacker can rotate the key per request and bypass the limit. Derive \`key\` from \`ctx.auth.userId\` / \`ctx.ip\` instead.`,metadata:{callee:e.callee,exportName:e.exportName,file:e.file,limitName:e.limitName,line:e.line}})),source:"static",title:"Rate-limit key derived from spoofable user input"};export{a as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as s}from"./finding-BAVgqnCF.mjs";import{t as c}from"./helpers-yHInjqeD.mjs";const m=(o,i,r,t)=>{const e=o.get(t.table);if(!e)return[];const n=t.kind==="one"?r:e,a=t.kind==="one"?e:r,l=[];return i(n).has(t.field)||l.push({column:t.field,owner:n.name,side:"field"}),i(a).has(t.references)||l.push({column:t.references,owner:a.name,side:"references"}),l},d={categories:["SCHEMA"],description:"A relation references a foreign-key or referenced column that is not declared on its table, so the join can never resolve.",facing:"INTERNAL",level:"ERROR",name:"relation_references_unknown_field",remediation:"Fix the `field` / `references` column name in the relation, or add the missing column.",run:o=>{const i=new Map(o.schema.tables.map(e=>[e.name,e])),r=new Map,t=e=>{let n=r.get(e);return n||(n=c(e),r.set(e,n)),n};return o.schema.tables.flatMap(e=>e.relations.flatMap(n=>m(i,t,e,n).map(a=>s(d,{cacheKey:`relation_references_unknown_field:${e.name}:${n.name}:${a.side}`,detail:`Relation "${n.name}" on table "${e.name}" uses ${a.side} "${a.column}", which is not declared on table "${a.owner}".`,metadata:{column:a.column,owner:a.owner,relation:n.name,side:a.side,table:e.name}}))))},source:"static",title:"Relation references unknown field"};export{d as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i}from"./finding-BAVgqnCF.mjs";const n={categories:["SECURITY"],description:"A `ctx.sql` tagged-template interpolates an unparameterized string-building expression (concatenation or nested template) instead of a bound value — splicing raw text into the query and reopening SQL injection.",facing:"EXTERNAL",level:"ERROR",name:"sql_injection_risk",remediation:"Pass the value through a bound placeholder so the driver parameterizes it — keep request input inside a `ctx.sql` placeholder instead of concatenating it into the query string. Never build SQL text from request input by hand.",run:t=>t.sqlInterpolations===void 0?[]:t.sqlInterpolations.map(e=>i(n,{cacheKey:`sql_injection_risk:${e.file}:${e.line.toString()}`,detail:`\`ctx.sql\` in \`${e.exportName}\` (${e.file}:${e.line.toString()}) interpolates a string-building expression instead of a bound value — a SQL-injection vector. Pass the value through a bound placeholder so the driver parameterizes it.`,metadata:{exportName:e.exportName,file:e.file,line:e.line}})),source:"static",title:"Possible SQL injection in ctx.sql interpolation"};export{n as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i}from"./finding-BAVgqnCF.mjs";const o={categories:["SECURITY"],description:"A `ctx.storage.*` call uses an R2 object key taken directly from the handler's `args` with no server-side scoping. The bucket methods key by the caller-supplied string, so any caller can read, overwrite, or delete another user's object — object-level IDOR.",facing:"EXTERNAL",level:"ERROR",name:"storage_key_from_user_args",remediation:"Prefix the object key with a server-trusted identity (e.g. `${ctx.auth.userId}/…`) or resolve the object through a record the caller is known to own. Never pass request input straight through as an R2 object key.",run:t=>t.storageKeyAccesses===void 0?[]:t.storageKeyAccesses.map(e=>{const r=`\`ctx.storage.*.${e.method}\` in \`${e.exportName}\` (${e.file}:${e.line.toString()})`,s={exportName:e.exportName,file:e.file,line:e.line,method:e.method,visibility:e.visibility??"unknown"};return e.visibility==="internal"?i(o,{cacheKey:`storage_key_from_user_args:${e.file}:${e.line.toString()}`,detail:`${r} uses an object key derived from \`args\` with no server-side scoping. This is expected for an \`internal\` procedure — no caller can reach it directly, so the key is only ever supplied by trusted server code. Audit the PUBLIC procedures that dispatch to it: if one forwards \`args\` straight through, the IDOR is there.`,facing:"INTERNAL",level:"INFO",metadata:s}):i(o,{cacheKey:`storage_key_from_user_args:${e.file}:${e.line.toString()}`,detail:`${r} uses an object key derived from \`args\` with no server-side scoping — any caller can read/overwrite/delete another user's object (IDOR). Prefix the key with a server-trusted identity (e.g. \`\${ctx.auth.userId}/…\`) or resolve the object through an owned record.`,metadata:s})}),source:"static",title:"R2 object key taken directly from user args (IDOR)"};export{o as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i}from"./finding-BAVgqnCF.mjs";const n={categories:["SCHEMA"],description:'No function inserts into this table via `ctx.db.insert("<table>", …)`. It may be read-only by design (seeded by a migration, replicated, or written through a path the advisor can\'t see) — or it may be dead schema.',facing:"INTERNAL",level:"INFO",name:"table_without_insert",remediation:'If the table should be writable, add a mutation that calls `ctx.db.insert("<table>", …)`. If it is read-only or seeded elsewhere, this advisory can be ignored.',run:t=>{if(t.inserts===void 0)return[];const a=new Set(t.inserts.filter(e=>e.table!=="").map(e=>e.table));return t.schema.tables.filter(e=>!a.has(e.name)&&e.externallyManaged!==!0).map(e=>i(n,{cacheKey:`table_without_insert:${e.name}`,detail:`No function calls \`ctx.db.insert("${e.name}", …)\` — table "${e.name}" has no discovered insert path.`,metadata:{table:e.name}}))},source:"static",title:"Table has no insert path"};export{n as default};
|