@lunora/advisor 1.0.0-alpha.106 → 1.0.0-alpha.107

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.
Files changed (25) hide show
  1. package/dist/index.d.mts +5 -3
  2. package/dist/index.d.ts +5 -3
  3. package/dist/index.mjs +1 -1
  4. package/dist/packem_shared/{filterOnPrimaryKey-COQRpmnu.mjs → filterOnPrimaryKey-jmz_ApDa.mjs} +1 -1
  5. package/dist/packem_shared/{filterWithoutIndex-Cf-tXY_A.mjs → filterWithoutIndex-9kAguEVb.mjs} +1 -1
  6. package/dist/packem_shared/flagGatesSecurityWithUnsafeDefault-20ccteHn.mjs +1 -0
  7. package/dist/packem_shared/geoIndexFieldNotGeopoint-Cd5mywEg.mjs +1 -0
  8. package/dist/packem_shared/helpers-BCqZbKga.mjs +1 -0
  9. package/dist/packem_shared/{indexReferencesUnknownField-B_c3o9QR.mjs → indexReferencesUnknownField-DiC2WN4V.mjs} +1 -1
  10. package/dist/packem_shared/{maskWeakHashStrategyOnPii-BC1n0F4-.mjs → maskWeakHashStrategyOnPii-DXhCr75r.mjs} +1 -1
  11. package/dist/packem_shared/{outputProjectionMissingOnPublicRead-DhiA3mpG.mjs → outputProjectionMissingOnPublicRead-HwweA4NI.mjs} +1 -1
  12. package/dist/packem_shared/publicMutationWithoutRatelimit-cDJph2zk.mjs +1 -0
  13. package/dist/packem_shared/{publicTableRlsOptoutConfusion-Dz7lbKKe.mjs → publicTableRlsOptoutConfusion-B4zbtE9m.mjs} +1 -1
  14. package/dist/packem_shared/ratelimitMiddlewareFailOpen-eOeyAyC-.mjs +1 -0
  15. package/dist/packem_shared/{relationReferencesUnknownField-D8Qyth_P.mjs → relationReferencesUnknownField-GpJPLGDE.mjs} +1 -1
  16. package/dist/packem_shared/{signupMutationWithoutDisposableGating-BIsSLyhJ.mjs → signupMutationWithoutDisposableGating-BMM2vTNu.mjs} +1 -1
  17. package/dist/packem_shared/ttlFieldNotTimestamp-CixnZ0Ii.mjs +1 -0
  18. package/dist/packem_shared/{userCreatingMutationWithoutCaptcha-BYzcTLE2.mjs → userCreatingMutationWithoutCaptcha-CGpMm45R.mjs} +1 -1
  19. package/package.json +2 -2
  20. package/dist/packem_shared/flagGatesSecurityWithUnsafeDefault-i-Befg8b.mjs +0 -1
  21. package/dist/packem_shared/geoIndexFieldNotGeopoint-D0lOTm-_.mjs +0 -1
  22. package/dist/packem_shared/helpers-CwSEZdku.mjs +0 -1
  23. package/dist/packem_shared/publicMutationWithoutRatelimit-Dsd4cSPD.mjs +0 -1
  24. package/dist/packem_shared/ratelimitMiddlewareFailOpen-iDJq68qx.mjs +0 -1
  25. package/dist/packem_shared/ttlFieldNotTimestamp-E14I83wY.mjs +0 -1
package/dist/index.d.mts CHANGED
@@ -2955,7 +2955,9 @@ declare const filterWithoutIndex: Lint;
2955
2955
  * a protection (`enforce*`/`rls*`/`gate*`/`lockdown*`) and defaults `false`, or
2956
2956
  * names a permission/bypass (`allow*`/`permit*`/`bypass*`) and defaults `true`,
2957
2957
  * a flag-backend outage silently disables the protection or grants the
2958
- * permission for every request.
2958
+ * permission for every request. A negating token in the key
2959
+ * (`disable*`/`*Disabled`/`skip*`/`no*`) inverts that — see
2960
+ * {@link safeDefaultFor}.
2959
2961
  *
2960
2962
  * Runs only when the codegen feeder supplies flag-default evidence
2961
2963
  * (`context.flagSecurityDefaults`); a runtime caller flags nothing. Deliberately
@@ -3796,8 +3798,8 @@ declare const ratelimitKeySpoofableOrGlobal: Lint;
3796
3798
  * Runs only when the codegen feeder supplies fail-open-guard evidence
3797
3799
  * (`context.failOpenGuards`); a runtime caller flags nothing. Deliberately narrow
3798
3800
  * — fires only when the options literal provably set `failOpen: true` AND the
3799
- * guarded procedure's export name or rate-limit `name` matches an auth/payment
3800
- * token, keeping the false-positive rate low. One finding per guard.
3801
+ * guarded procedure's export name or rate-limit `name` carries an auth/payment
3802
+ * WORD, keeping the false-positive rate low. One finding per guard.
3801
3803
  */
3802
3804
  declare const ratelimitMiddlewareFailOpen: Lint;
3803
3805
  /**
package/dist/index.d.ts CHANGED
@@ -2955,7 +2955,9 @@ declare const filterWithoutIndex: Lint;
2955
2955
  * a protection (`enforce*`/`rls*`/`gate*`/`lockdown*`) and defaults `false`, or
2956
2956
  * names a permission/bypass (`allow*`/`permit*`/`bypass*`) and defaults `true`,
2957
2957
  * a flag-backend outage silently disables the protection or grants the
2958
- * permission for every request.
2958
+ * permission for every request. A negating token in the key
2959
+ * (`disable*`/`*Disabled`/`skip*`/`no*`) inverts that — see
2960
+ * {@link safeDefaultFor}.
2959
2961
  *
2960
2962
  * Runs only when the codegen feeder supplies flag-default evidence
2961
2963
  * (`context.flagSecurityDefaults`); a runtime caller flags nothing. Deliberately
@@ -3796,8 +3798,8 @@ declare const ratelimitKeySpoofableOrGlobal: Lint;
3796
3798
  * Runs only when the codegen feeder supplies fail-open-guard evidence
3797
3799
  * (`context.failOpenGuards`); a runtime caller flags nothing. Deliberately narrow
3798
3800
  * — fires only when the options literal provably set `failOpen: true` AND the
3799
- * guarded procedure's export name or rate-limit `name` matches an auth/payment
3800
- * token, keeping the false-positive rate low. One finding per guard.
3801
+ * guarded procedure's export name or rate-limit `name` carries an auth/payment
3802
+ * WORD, keeping the false-positive rate low. One finding per guard.
3801
3803
  */
3802
3804
  declare const ratelimitMiddlewareFailOpen: Lint;
3803
3805
  /**
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{dedupeCacheKeys as c}from"./packem_shared/dedupeCacheKeys-DtBOHffV.mjs";import d from"./packem_shared/fanOutBreadth-CBtmZnoh.mjs";import u from"./packem_shared/hotShard-BwGYZ3Tq.mjs";import h from"./packem_shared/indexUtilization-CkVPZcVe.mjs";import g from"./packem_shared/actionFetchSsrf-Z61P0o8U.mjs";import b from"./packem_shared/actionWithoutErrorHandling-4GCBT0_z.mjs";import y from"./packem_shared/adminRouteWithoutGuard-DPE7LuNh.mjs";import w from"./packem_shared/aiRawRunEscapeHatch-Dq43DVD9.mjs";import S from"./packem_shared/aiRunWithoutLogging-CvHHtEN9.mjs";import v from"./packem_shared/aiToolSideEffectPromptInjection-K42X3QzJ.mjs";import A from"./packem_shared/aiUnboundedGenerationPublic-C17h6sMV.mjs";import U from"./packem_shared/allowUnauthenticatedShardAccessEnabled-BQufF5Kb.mjs";import R from"./packem_shared/authApiCallWithoutHeaders-C1OOWML5.mjs";import W from"./packem_shared/authCsrfCheckDisabled-Dz27bDzp.mjs";import T from"./packem_shared/authEmailVerificationDisabled-QTDD7TAU.mjs";import I from"./packem_shared/authScimWithoutTransactions-FqdTmUJs.mjs";import N from"./packem_shared/authSecureCookiesDisabled-CrGYulfJ.mjs";import k from"./packem_shared/authSessionFreshageZero-yWE6CGYP.mjs";import x from"./packem_shared/authTrustedOriginsWildcard-xpeRXfGF.mjs";import F from"./packem_shared/browserAllowPrivateTargets-Cj5sizhv.mjs";import C from"./packem_shared/browserUserUrlWithoutAllowlist-Wl3xHr7v.mjs";import O from"./packem_shared/circularFk-DtcWFJxK.mjs";import M from"./packem_shared/commitOrderedHardDelete-BPdwOKA7.mjs";import $ from"./packem_shared/containerInstanceKeyFromUserInput-uEQQVsEz.mjs";import D from"./packem_shared/containerOversizedInstance-Bx89uR7E.mjs";import E from"./packem_shared/containerPublicInternet-BFfZf_P4.mjs";import P from"./packem_shared/containerRuntimeEgressRelaxation-pkpyXNou.mjs";import K from"./packem_shared/containerStartEnableInternetOverride-BBY4PMG1.mjs";import q from"./packem_shared/duplicateIndex-Cip6-Rpu.mjs";import L from"./packem_shared/emptyIndex-BnHDcXza.mjs";import G from"./packem_shared/errorWithoutCatalog-BTfvaXHR.mjs";import _ from"./packem_shared/exportSinkMisconfigured-JfbAx9AI.mjs";import z from"./packem_shared/externalSourceIncrementalNoDeletePath-BCzm3HzF.mjs";import H from"./packem_shared/externalSourceOnGlobal-CH7xbJ49.mjs";import B from"./packem_shared/externalSourceUnscoped-BxU2uSXk.mjs";import j from"./packem_shared/filterOnPrimaryKey-COQRpmnu.mjs";import V from"./packem_shared/filterWithoutIndex-Cf-tXY_A.mjs";import Q from"./packem_shared/flagGatesSecurityWithUnsafeDefault-i-Befg8b.mjs";import X from"./packem_shared/flagReadInSubscription-DabIhYGD.mjs";import Z from"./packem_shared/geoIndexFieldNotGeopoint-D0lOTm-_.mjs";import J from"./packem_shared/geoIndexUnused-D7C9Qr4U.mjs";import Y from"./packem_shared/globalTableNearColumnLimit-BFbBBd6A.mjs";import oo from"./packem_shared/hardcodedSecret-Bw_4FYrs.mjs";import eo from"./packem_shared/httpActionMissingAuthGuard-BS3JZgaz.mjs";import ro from"./packem_shared/httpActionResponseHeaderInjection-DHnc8c9f.mjs";import to from"./packem_shared/hyperdriveOutsideAction-CPDdGP2g.mjs";import io from"./packem_shared/identityUndeclaredClaimTrusted-BDFqB7Dw.mjs";import no from"./packem_shared/imagesUrlSourceFromUserInput-DizsRh3M.mjs";import ao from"./packem_shared/indexReferencesUnknownField-B_c3o9QR.mjs";import mo from"./packem_shared/insertManyUnsafeUserData-DWE8d_DF.mjs";import so from"./packem_shared/kvUnscopedUserKeyIdor-CIgMPzFj.mjs";import lo from"./packem_shared/mailInboundDispatchWithoutVerify-CWwqXyPX.mjs";import po from"./packem_shared/mailRecipientFromRequestInput-SDCchLP7.mjs";import fo from"./packem_shared/maskUncoveredPiiColumn-axEka4nd.mjs";import co from"./packem_shared/maskWeakHashStrategyOnPii-BC1n0F4-.mjs";import uo from"./packem_shared/maskedRelationLeakViaWith-CIFij6vE.mjs";import{e as a}from"./packem_shared/finding-NrKO8idM.mjs";import ho from"./packem_shared/mutatorFullRowReplace-BzRpZ47r.mjs";import go from"./packem_shared/nondeterministicQueryMutation-sxmixc0l.mjs";import bo from"./packem_shared/normalizeIdUsedAsAuthorization-BXN-Can6.mjs";import yo from"./packem_shared/notifyMissingPushConfig-DeQMoNwm.mjs";import wo from"./packem_shared/notifySendOutsideAction-CWx7KuJS.mjs";import So from"./packem_shared/outputProjectionMissingOnPublicRead-DhiA3mpG.mjs";import vo from"./packem_shared/ownerFieldFromArgsNotAuth-CjOSUDKi.mjs";import Ao from"./packem_shared/paymentCreateWithoutAuthorize-CICGmRFR.mjs";import Uo from"./packem_shared/paymentWebhookWideTolerance-B-F5jOeA.mjs";import Ro from"./packem_shared/plaintextSecretInWranglerVariables-bNkqmNqV.mjs";import Wo from"./packem_shared/policyReferencesUnknownTable-CIKuRZ5Y.mjs";import To from"./packem_shared/privilegedDispatchUnvalidatedPayload-C6Qtc8aT.mjs";import Io from"./packem_shared/privilegedFanoutFromPublicProcedure-Coqt23CQ.mjs";import No from"./packem_shared/procedureWithoutStructuredEvent-CZ_B23nM.mjs";import ko from"./packem_shared/publicArgumentUsesAny-BDEvMA8Q.mjs";import xo from"./packem_shared/publicMutationWithoutRatelimit-Dsd4cSPD.mjs";import Fo from"./packem_shared/publicTableRlsOptoutConfusion-Dz7lbKKe.mjs";import Co from"./packem_shared/queueWithoutDlq-BvHz3Opg.mjs";import Oo from"./packem_shared/r2sqlOutsideAction-BdLiLOAX.mjs";import Mo from"./packem_shared/ratelimitDefaultMemoryStore-Dj0Kk7t9.mjs";import $o from"./packem_shared/ratelimitKeySpoofableOrGlobal-BzDzxnXm.mjs";import Do from"./packem_shared/ratelimitMiddlewareFailOpen-iDJq68qx.mjs";import Eo from"./packem_shared/relationReferencesUnknownField-D8Qyth_P.mjs";import Po from"./packem_shared/relationReferencesUnknownTable-CP4aWtAJ.mjs";import Ko from"./packem_shared/rlsUncoveredTable-CZ4ie4gX.mjs";import qo from"./packem_shared/shapeTargetsGlobalTable-Bu3eEDic.mjs";import Lo from"./packem_shared/shapeUnknownTable-CREfNnWi.mjs";import Go from"./packem_shared/signupMutationWithoutDisposableGating-BIsSLyhJ.mjs";import _o from"./packem_shared/softDeleteIncludeDeletedFromArgs-C7Ugg4zb.mjs";import zo from"./packem_shared/sqlInjectionRisk-CslaRBxz.mjs";import Ho from"./packem_shared/storageGenerateUploadUrlNoContentTypePin-DBq_DeHO.mjs";import Bo from"./packem_shared/storageKeyFromUserArgs-C-QUcdJ3.mjs";import jo from"./packem_shared/storagePresignedUrlForPrivateContent-DZdUkDdW.mjs";import Vo from"./packem_shared/storageUploadWithoutContentTypeAllowlist-KSRG81Iu.mjs";import Qo from"./packem_shared/storageUploadWithoutMaxSize-BTiM9Q3A.mjs";import Xo from"./packem_shared/tableWithoutInsert-DQ-GxjFF.mjs";import Zo from"./packem_shared/ttlFieldNotTimestamp-E14I83wY.mjs";import{s as Jo,q as Yo}from"./packem_shared/helpers-CwSEZdku.mjs";import oe from"./packem_shared/unboundedStringArgument-jZnCbwB0.mjs";import ee from"./packem_shared/unindexedForeignKey-Dypgn8uH.mjs";import re from"./packem_shared/unindexedRelationTarget-CSqRJWYZ.mjs";import te from"./packem_shared/unrestrictedWhereBranch-CcIBmHik.mjs";import ie from"./packem_shared/userCreatingMutationWithoutCaptcha-BYzcTLE2.mjs";import ne from"./packem_shared/vectorsNamespaceFromUserInput-CDSOrkyX.mjs";import ae from"./packem_shared/workflowDuplicateStepName-BU4rg5So.mjs";import me from"./packem_shared/workflowUnknownTarget-B8H7jwnH.mjs";import se from"./packem_shared/workflowUnused-BUSOPdHq.mjs";import{compareToBaseline as ut,parseAdvisorMap as ht}from"./packem_shared/compareToBaseline-DBgN5YqX.mjs";import{gradeFromScore as bt}from"./packem_shared/gradeFromScore-KSt58rj1.mjs";import{MAP_VERSION as wt,byCodepoint as St,scoreAdvisor as vt}from"./packem_shared/MAP_VERSION-DEJGm0wI.mjs";import{default as Ut}from"./packem_shared/classifySensitivity-JnjaTGYi.mjs";import{fromServerSchema as Wt}from"./packem_shared/fromServerSchema-fAymN8o2.mjs";const le={convex:"migrating/from-convex",firebase:"migrating/from-firebase",supabase:"migrating/from-supabase"},m={categories:["SCHEMA"],description:"A migrated-away platform's SDK is still imported from `lunora/` source. The code compiles and typechecks, so a half-finished port keeps reading from the old platform at runtime and the two stores silently drift apart.",facing:"INTERNAL",level:"WARN",name:"migration_stale_import",remediation:"Replace the call with its Lunora equivalent and drop the dependency. If the import is deliberate — a second data source you still read from — move it out of `lunora/` so it is not mistaken for an unfinished migration.",run:r=>r.staleMigrationImports===void 0?[]:r.staleMigrationImports.map(o=>a(m,{cacheKey:`migration_stale_import:${o.file}:${o.line.toString()}:${o.moduleSpecifier}`,detail:`\`${o.file}\` (line ${o.line.toString()}) still imports \`${o.moduleSpecifier}\`. Finish the port — see \`${le[o.platform]}\` — or move the import out of \`lunora/\` if you genuinely still read from ${o.platform}.`,metadata:{file:o.file,line:o.line,moduleSpecifier:o.moduleSpecifier,platform:o.platform}})),source:"static",title:"Stale migration import"},pe=new Map([["global",{level:"WARN",scope:r=>`it reads the whole D1 table "${r}" over a cross-region round trip`}],["root",{level:"WARN",scope:r=>`it loads every row of "${r}" from the root Durable Object's SQLite into memory`}],["shardBy",{level:"INFO",scope:r=>`"${r}" is \`.shardBy()\`, so this collects one shard's rows rather than the whole table — bounded by a single tenant's row count`}]]),fe={level:"WARN",scope:r=>`it loads every row of "${r}"`},s={categories:["PERFORMANCE"],description:"A query calls `.collect()` with no `.withIndex()` and no `.filter()`, so it materializes every row of the table. Any live subscription over it also re-sends that whole result to every subscribed client on every write to the table.",facing:"EXTERNAL",level:"WARN",name:"unbounded_collect",remediation:'Narrow the read with `.withIndex("name", (q) => q.eq(...))`, cap it with `.take(n)`, or page it with `.paginate(args.paginationOpts)` so neither the scan nor the subscription payload grows with the table.',run:r=>{const o=[],i=Jo(r.schema);for(const e of r.queries??[]){if(e.terminal!=="collect"||e.hasIndex||e.table===""||e.hasFilter)continue;const t=i.get(e.table),{level:n,scope:l}=pe.get(t??"")??fe,p=Yo(e),f=n==="WARN"?` A live subscription over this query records a whole-table dependency, so every write to "${e.table}" re-runs it and re-sends the full result to each subscribed socket.`:" Cap it with `.take(n)` if that count can grow.";o.push(a(s,{cacheKey:`unbounded_collect:${e.file}:${e.line.toString()}:${e.table}`,detail:`Query on "${e.table}" at ${p} calls .collect() with no index and no filter — ${l(e.table)}.${f}`,level:n,metadata:{exportName:e.exportName,file:e.file,line:e.line,shardKind:t??"unknown",table:e.table}}))}return o},source:"static",title:"Unbounded collect"},ce=[ao,Po,Eo,me,ae,Lo,z,H,B,L,Y,G,Z,J,_,Zo,M,O,ee,re,q,Xo,se,Co,j,V,s,qo,ho,go,to,Oo,R,Wo,Ko,fo,co,D,E,xo,te,ie,Go,ko,oe,oo,zo,y,Ao,lo,Mo,F,Io,No,mo,A,g,b,vo,Bo,so,$,w,S,ne,po,C,To,K,P,I,x,W,N,T,k,no,$o,Fo,U,Vo,Qo,Ho,jo,eo,ro,Do,Q,X,v,io,Uo,_o,uo,So,bo,wo,yo,Ro,m],de=[u,h,d],ue=[...ce,...de],ft=(r,o={})=>{const i=o.lints??ue,e=[];for(const t of i)o.source!==void 0&&t.source!==o.source||e.push(...t.run(r));return c(e)};export{ue as ALL_LINTS,wt as MAP_VERSION,de as RUNTIME_LINTS,ce as STATIC_LINTS,g as actionFetchSsrf,b as actionWithoutErrorHandling,y as adminRouteWithoutGuard,w as aiRawRunEscapeHatch,S as aiRunWithoutLogging,v as aiToolSideEffectPromptInjection,A as aiUnboundedGenerationPublic,U as allowUnauthenticatedShardAccessEnabled,R as authApiCallWithoutHeaders,W as authCsrfCheckDisabled,T as authEmailVerificationDisabled,I as authScimWithoutTransactions,N as authSecureCookiesDisabled,k as authSessionFreshageZero,x as authTrustedOriginsWildcard,F as browserAllowPrivateTargets,C as browserUserUrlWithoutAllowlist,St as byCodepoint,O as circularFk,Ut as classifySensitivity,M as commitOrderedHardDelete,ut as compareToBaseline,$ as containerInstanceKeyFromUserInput,D as containerOversizedInstance,E as containerPublicInternet,P as containerRuntimeEgressRelaxation,K as containerStartEnableInternetOverride,c as dedupeCacheKeys,q as duplicateIndex,L as emptyIndex,G as errorWithoutCatalog,_ as exportSinkMisconfigured,z as externalSourceIncrementalNoDeletePath,H as externalSourceOnGlobal,B as externalSourceUnscoped,d as fanOutBreadth,j as filterOnPrimaryKey,V as filterWithoutIndex,Q as flagGatesSecurityWithUnsafeDefault,X as flagReadInSubscription,Wt as fromServerSchema,Z as geoIndexFieldNotGeopoint,J as geoIndexUnused,Y as globalTableNearColumnLimit,bt as gradeFromScore,oo as hardcodedSecret,u as hotShard,eo as httpActionMissingAuthGuard,ro as httpActionResponseHeaderInjection,to as hyperdriveOutsideAction,io as identityUndeclaredClaimTrusted,no as imagesUrlSourceFromUserInput,ao as indexReferencesUnknownField,h as indexUtilization,mo as insertManyUnsafeUserData,so as kvUnscopedUserKeyIdor,lo as mailInboundDispatchWithoutVerify,po as mailRecipientFromRequestInput,fo as maskUncoveredPiiColumn,co as maskWeakHashStrategyOnPii,uo as maskedRelationLeakViaWith,ho as mutatorFullRowReplace,go as nondeterministicQueryMutation,bo as normalizeIdUsedAsAuthorization,yo as notifyMissingPushConfig,wo as notifySendOutsideAction,So as outputProjectionMissingOnPublicRead,vo as ownerFieldFromArgsNotAuth,ht as parseAdvisorMap,Ao as paymentCreateWithoutAuthorize,Uo as paymentWebhookWideTolerance,Ro as plaintextSecretInWranglerVariables,Wo as policyReferencesUnknownTable,To as privilegedDispatchUnvalidatedPayload,Io as privilegedFanoutFromPublicProcedure,No as procedureWithoutStructuredEvent,ko as publicArgumentUsesAny,xo as publicMutationWithoutRatelimit,Fo as publicTableRlsOptoutConfusion,Co as queueWithoutDlq,Oo as r2sqlOutsideAction,Mo as ratelimitDefaultMemoryStore,$o as ratelimitKeySpoofableOrGlobal,Do as ratelimitMiddlewareFailOpen,Eo as relationReferencesUnknownField,Po as relationReferencesUnknownTable,Ko as rlsUncoveredTable,ft as runAdvisor,vt as scoreAdvisor,qo as shapeTargetsGlobalTable,Lo as shapeUnknownTable,Go as signupMutationWithoutDisposableGating,_o as softDeleteIncludeDeletedFromArgs,zo as sqlInjectionRisk,Ho as storageGenerateUploadUrlNoContentTypePin,Bo as storageKeyFromUserArgs,jo as storagePresignedUrlForPrivateContent,Vo as storageUploadWithoutContentTypeAllowlist,Qo as storageUploadWithoutMaxSize,Xo as tableWithoutInsert,Zo as ttlFieldNotTimestamp,oe as unboundedStringArgument,ee as unindexedForeignKey,re as unindexedRelationTarget,te as unrestrictedWhereBranch,ie as userCreatingMutationWithoutCaptcha,ne as vectorsNamespaceFromUserInput,ae as workflowDuplicateStepName,me as workflowUnknownTarget,se as workflowUnused};
1
+ import{dedupeCacheKeys as c}from"./packem_shared/dedupeCacheKeys-DtBOHffV.mjs";import d from"./packem_shared/fanOutBreadth-CBtmZnoh.mjs";import u from"./packem_shared/hotShard-BwGYZ3Tq.mjs";import h from"./packem_shared/indexUtilization-CkVPZcVe.mjs";import g from"./packem_shared/actionFetchSsrf-Z61P0o8U.mjs";import b from"./packem_shared/actionWithoutErrorHandling-4GCBT0_z.mjs";import y from"./packem_shared/adminRouteWithoutGuard-DPE7LuNh.mjs";import w from"./packem_shared/aiRawRunEscapeHatch-Dq43DVD9.mjs";import S from"./packem_shared/aiRunWithoutLogging-CvHHtEN9.mjs";import v from"./packem_shared/aiToolSideEffectPromptInjection-K42X3QzJ.mjs";import A from"./packem_shared/aiUnboundedGenerationPublic-C17h6sMV.mjs";import U from"./packem_shared/allowUnauthenticatedShardAccessEnabled-BQufF5Kb.mjs";import R from"./packem_shared/authApiCallWithoutHeaders-C1OOWML5.mjs";import W from"./packem_shared/authCsrfCheckDisabled-Dz27bDzp.mjs";import T from"./packem_shared/authEmailVerificationDisabled-QTDD7TAU.mjs";import I from"./packem_shared/authScimWithoutTransactions-FqdTmUJs.mjs";import N from"./packem_shared/authSecureCookiesDisabled-CrGYulfJ.mjs";import k from"./packem_shared/authSessionFreshageZero-yWE6CGYP.mjs";import x from"./packem_shared/authTrustedOriginsWildcard-xpeRXfGF.mjs";import F from"./packem_shared/browserAllowPrivateTargets-Cj5sizhv.mjs";import C from"./packem_shared/browserUserUrlWithoutAllowlist-Wl3xHr7v.mjs";import O from"./packem_shared/circularFk-DtcWFJxK.mjs";import M from"./packem_shared/commitOrderedHardDelete-BPdwOKA7.mjs";import $ from"./packem_shared/containerInstanceKeyFromUserInput-uEQQVsEz.mjs";import D from"./packem_shared/containerOversizedInstance-Bx89uR7E.mjs";import E from"./packem_shared/containerPublicInternet-BFfZf_P4.mjs";import P from"./packem_shared/containerRuntimeEgressRelaxation-pkpyXNou.mjs";import K from"./packem_shared/containerStartEnableInternetOverride-BBY4PMG1.mjs";import q from"./packem_shared/duplicateIndex-Cip6-Rpu.mjs";import L from"./packem_shared/emptyIndex-BnHDcXza.mjs";import G from"./packem_shared/errorWithoutCatalog-BTfvaXHR.mjs";import _ from"./packem_shared/exportSinkMisconfigured-JfbAx9AI.mjs";import z from"./packem_shared/externalSourceIncrementalNoDeletePath-BCzm3HzF.mjs";import H from"./packem_shared/externalSourceOnGlobal-CH7xbJ49.mjs";import B from"./packem_shared/externalSourceUnscoped-BxU2uSXk.mjs";import j from"./packem_shared/filterOnPrimaryKey-jmz_ApDa.mjs";import V from"./packem_shared/filterWithoutIndex-9kAguEVb.mjs";import Q from"./packem_shared/flagGatesSecurityWithUnsafeDefault-20ccteHn.mjs";import X from"./packem_shared/flagReadInSubscription-DabIhYGD.mjs";import Z from"./packem_shared/geoIndexFieldNotGeopoint-Cd5mywEg.mjs";import J from"./packem_shared/geoIndexUnused-D7C9Qr4U.mjs";import Y from"./packem_shared/globalTableNearColumnLimit-BFbBBd6A.mjs";import oo from"./packem_shared/hardcodedSecret-Bw_4FYrs.mjs";import eo from"./packem_shared/httpActionMissingAuthGuard-BS3JZgaz.mjs";import ro from"./packem_shared/httpActionResponseHeaderInjection-DHnc8c9f.mjs";import to from"./packem_shared/hyperdriveOutsideAction-CPDdGP2g.mjs";import io from"./packem_shared/identityUndeclaredClaimTrusted-BDFqB7Dw.mjs";import no from"./packem_shared/imagesUrlSourceFromUserInput-DizsRh3M.mjs";import ao from"./packem_shared/indexReferencesUnknownField-DiC2WN4V.mjs";import mo from"./packem_shared/insertManyUnsafeUserData-DWE8d_DF.mjs";import so from"./packem_shared/kvUnscopedUserKeyIdor-CIgMPzFj.mjs";import lo from"./packem_shared/mailInboundDispatchWithoutVerify-CWwqXyPX.mjs";import po from"./packem_shared/mailRecipientFromRequestInput-SDCchLP7.mjs";import fo from"./packem_shared/maskUncoveredPiiColumn-axEka4nd.mjs";import co from"./packem_shared/maskWeakHashStrategyOnPii-DXhCr75r.mjs";import uo from"./packem_shared/maskedRelationLeakViaWith-CIFij6vE.mjs";import{e as a}from"./packem_shared/finding-NrKO8idM.mjs";import ho from"./packem_shared/mutatorFullRowReplace-BzRpZ47r.mjs";import go from"./packem_shared/nondeterministicQueryMutation-sxmixc0l.mjs";import bo from"./packem_shared/normalizeIdUsedAsAuthorization-BXN-Can6.mjs";import yo from"./packem_shared/notifyMissingPushConfig-DeQMoNwm.mjs";import wo from"./packem_shared/notifySendOutsideAction-CWx7KuJS.mjs";import So from"./packem_shared/outputProjectionMissingOnPublicRead-HwweA4NI.mjs";import vo from"./packem_shared/ownerFieldFromArgsNotAuth-CjOSUDKi.mjs";import Ao from"./packem_shared/paymentCreateWithoutAuthorize-CICGmRFR.mjs";import Uo from"./packem_shared/paymentWebhookWideTolerance-B-F5jOeA.mjs";import Ro from"./packem_shared/plaintextSecretInWranglerVariables-bNkqmNqV.mjs";import Wo from"./packem_shared/policyReferencesUnknownTable-CIKuRZ5Y.mjs";import To from"./packem_shared/privilegedDispatchUnvalidatedPayload-C6Qtc8aT.mjs";import Io from"./packem_shared/privilegedFanoutFromPublicProcedure-Coqt23CQ.mjs";import No from"./packem_shared/procedureWithoutStructuredEvent-CZ_B23nM.mjs";import ko from"./packem_shared/publicArgumentUsesAny-BDEvMA8Q.mjs";import xo from"./packem_shared/publicMutationWithoutRatelimit-cDJph2zk.mjs";import Fo from"./packem_shared/publicTableRlsOptoutConfusion-B4zbtE9m.mjs";import Co from"./packem_shared/queueWithoutDlq-BvHz3Opg.mjs";import Oo from"./packem_shared/r2sqlOutsideAction-BdLiLOAX.mjs";import Mo from"./packem_shared/ratelimitDefaultMemoryStore-Dj0Kk7t9.mjs";import $o from"./packem_shared/ratelimitKeySpoofableOrGlobal-BzDzxnXm.mjs";import Do from"./packem_shared/ratelimitMiddlewareFailOpen-eOeyAyC-.mjs";import Eo from"./packem_shared/relationReferencesUnknownField-GpJPLGDE.mjs";import Po from"./packem_shared/relationReferencesUnknownTable-CP4aWtAJ.mjs";import Ko from"./packem_shared/rlsUncoveredTable-CZ4ie4gX.mjs";import qo from"./packem_shared/shapeTargetsGlobalTable-Bu3eEDic.mjs";import Lo from"./packem_shared/shapeUnknownTable-CREfNnWi.mjs";import Go from"./packem_shared/signupMutationWithoutDisposableGating-BMM2vTNu.mjs";import _o from"./packem_shared/softDeleteIncludeDeletedFromArgs-C7Ugg4zb.mjs";import zo from"./packem_shared/sqlInjectionRisk-CslaRBxz.mjs";import Ho from"./packem_shared/storageGenerateUploadUrlNoContentTypePin-DBq_DeHO.mjs";import Bo from"./packem_shared/storageKeyFromUserArgs-C-QUcdJ3.mjs";import jo from"./packem_shared/storagePresignedUrlForPrivateContent-DZdUkDdW.mjs";import Vo from"./packem_shared/storageUploadWithoutContentTypeAllowlist-KSRG81Iu.mjs";import Qo from"./packem_shared/storageUploadWithoutMaxSize-BTiM9Q3A.mjs";import Xo from"./packem_shared/tableWithoutInsert-DQ-GxjFF.mjs";import Zo from"./packem_shared/ttlFieldNotTimestamp-CixnZ0Ii.mjs";import{s as Jo,q as Yo}from"./packem_shared/helpers-BCqZbKga.mjs";import oe from"./packem_shared/unboundedStringArgument-jZnCbwB0.mjs";import ee from"./packem_shared/unindexedForeignKey-Dypgn8uH.mjs";import re from"./packem_shared/unindexedRelationTarget-CSqRJWYZ.mjs";import te from"./packem_shared/unrestrictedWhereBranch-CcIBmHik.mjs";import ie from"./packem_shared/userCreatingMutationWithoutCaptcha-CGpMm45R.mjs";import ne from"./packem_shared/vectorsNamespaceFromUserInput-CDSOrkyX.mjs";import ae from"./packem_shared/workflowDuplicateStepName-BU4rg5So.mjs";import me from"./packem_shared/workflowUnknownTarget-B8H7jwnH.mjs";import se from"./packem_shared/workflowUnused-BUSOPdHq.mjs";import{compareToBaseline as ut,parseAdvisorMap as ht}from"./packem_shared/compareToBaseline-DBgN5YqX.mjs";import{gradeFromScore as bt}from"./packem_shared/gradeFromScore-KSt58rj1.mjs";import{MAP_VERSION as wt,byCodepoint as St,scoreAdvisor as vt}from"./packem_shared/MAP_VERSION-DEJGm0wI.mjs";import{default as Ut}from"./packem_shared/classifySensitivity-JnjaTGYi.mjs";import{fromServerSchema as Wt}from"./packem_shared/fromServerSchema-fAymN8o2.mjs";const le={convex:"migrating/from-convex",firebase:"migrating/from-firebase",supabase:"migrating/from-supabase"},m={categories:["SCHEMA"],description:"A migrated-away platform's SDK is still imported from `lunora/` source. The code compiles and typechecks, so a half-finished port keeps reading from the old platform at runtime and the two stores silently drift apart.",facing:"INTERNAL",level:"WARN",name:"migration_stale_import",remediation:"Replace the call with its Lunora equivalent and drop the dependency. If the import is deliberate — a second data source you still read from — move it out of `lunora/` so it is not mistaken for an unfinished migration.",run:r=>r.staleMigrationImports===void 0?[]:r.staleMigrationImports.map(o=>a(m,{cacheKey:`migration_stale_import:${o.file}:${o.line.toString()}:${o.moduleSpecifier}`,detail:`\`${o.file}\` (line ${o.line.toString()}) still imports \`${o.moduleSpecifier}\`. Finish the port — see \`${le[o.platform]}\` — or move the import out of \`lunora/\` if you genuinely still read from ${o.platform}.`,metadata:{file:o.file,line:o.line,moduleSpecifier:o.moduleSpecifier,platform:o.platform}})),source:"static",title:"Stale migration import"},pe=new Map([["global",{level:"WARN",scope:r=>`it reads the whole D1 table "${r}" over a cross-region round trip`}],["root",{level:"WARN",scope:r=>`it loads every row of "${r}" from the root Durable Object's SQLite into memory`}],["shardBy",{level:"INFO",scope:r=>`"${r}" is \`.shardBy()\`, so this collects one shard's rows rather than the whole table — bounded by a single tenant's row count`}]]),fe={level:"WARN",scope:r=>`it loads every row of "${r}"`},s={categories:["PERFORMANCE"],description:"A query calls `.collect()` with no `.withIndex()` and no `.filter()`, so it materializes every row of the table. Any live subscription over it also re-sends that whole result to every subscribed client on every write to the table.",facing:"EXTERNAL",level:"WARN",name:"unbounded_collect",remediation:'Narrow the read with `.withIndex("name", (q) => q.eq(...))`, cap it with `.take(n)`, or page it with `.paginate(args.paginationOpts)` so neither the scan nor the subscription payload grows with the table.',run:r=>{const o=[],i=Jo(r.schema);for(const e of r.queries??[]){if(e.terminal!=="collect"||e.hasIndex||e.table===""||e.hasFilter)continue;const t=i.get(e.table),{level:n,scope:l}=pe.get(t??"")??fe,p=Yo(e),f=n==="WARN"?` A live subscription over this query records a whole-table dependency, so every write to "${e.table}" re-runs it and re-sends the full result to each subscribed socket.`:" Cap it with `.take(n)` if that count can grow.";o.push(a(s,{cacheKey:`unbounded_collect:${e.file}:${e.line.toString()}:${e.table}`,detail:`Query on "${e.table}" at ${p} calls .collect() with no index and no filter — ${l(e.table)}.${f}`,level:n,metadata:{exportName:e.exportName,file:e.file,line:e.line,shardKind:t??"unknown",table:e.table}}))}return o},source:"static",title:"Unbounded collect"},ce=[ao,Po,Eo,me,ae,Lo,z,H,B,L,Y,G,Z,J,_,Zo,M,O,ee,re,q,Xo,se,Co,j,V,s,qo,ho,go,to,Oo,R,Wo,Ko,fo,co,D,E,xo,te,ie,Go,ko,oe,oo,zo,y,Ao,lo,Mo,F,Io,No,mo,A,g,b,vo,Bo,so,$,w,S,ne,po,C,To,K,P,I,x,W,N,T,k,no,$o,Fo,U,Vo,Qo,Ho,jo,eo,ro,Do,Q,X,v,io,Uo,_o,uo,So,bo,wo,yo,Ro,m],de=[u,h,d],ue=[...ce,...de],ft=(r,o={})=>{const i=o.lints??ue,e=[];for(const t of i)o.source!==void 0&&t.source!==o.source||e.push(...t.run(r));return c(e)};export{ue as ALL_LINTS,wt as MAP_VERSION,de as RUNTIME_LINTS,ce as STATIC_LINTS,g as actionFetchSsrf,b as actionWithoutErrorHandling,y as adminRouteWithoutGuard,w as aiRawRunEscapeHatch,S as aiRunWithoutLogging,v as aiToolSideEffectPromptInjection,A as aiUnboundedGenerationPublic,U as allowUnauthenticatedShardAccessEnabled,R as authApiCallWithoutHeaders,W as authCsrfCheckDisabled,T as authEmailVerificationDisabled,I as authScimWithoutTransactions,N as authSecureCookiesDisabled,k as authSessionFreshageZero,x as authTrustedOriginsWildcard,F as browserAllowPrivateTargets,C as browserUserUrlWithoutAllowlist,St as byCodepoint,O as circularFk,Ut as classifySensitivity,M as commitOrderedHardDelete,ut as compareToBaseline,$ as containerInstanceKeyFromUserInput,D as containerOversizedInstance,E as containerPublicInternet,P as containerRuntimeEgressRelaxation,K as containerStartEnableInternetOverride,c as dedupeCacheKeys,q as duplicateIndex,L as emptyIndex,G as errorWithoutCatalog,_ as exportSinkMisconfigured,z as externalSourceIncrementalNoDeletePath,H as externalSourceOnGlobal,B as externalSourceUnscoped,d as fanOutBreadth,j as filterOnPrimaryKey,V as filterWithoutIndex,Q as flagGatesSecurityWithUnsafeDefault,X as flagReadInSubscription,Wt as fromServerSchema,Z as geoIndexFieldNotGeopoint,J as geoIndexUnused,Y as globalTableNearColumnLimit,bt as gradeFromScore,oo as hardcodedSecret,u as hotShard,eo as httpActionMissingAuthGuard,ro as httpActionResponseHeaderInjection,to as hyperdriveOutsideAction,io as identityUndeclaredClaimTrusted,no as imagesUrlSourceFromUserInput,ao as indexReferencesUnknownField,h as indexUtilization,mo as insertManyUnsafeUserData,so as kvUnscopedUserKeyIdor,lo as mailInboundDispatchWithoutVerify,po as mailRecipientFromRequestInput,fo as maskUncoveredPiiColumn,co as maskWeakHashStrategyOnPii,uo as maskedRelationLeakViaWith,ho as mutatorFullRowReplace,go as nondeterministicQueryMutation,bo as normalizeIdUsedAsAuthorization,yo as notifyMissingPushConfig,wo as notifySendOutsideAction,So as outputProjectionMissingOnPublicRead,vo as ownerFieldFromArgsNotAuth,ht as parseAdvisorMap,Ao as paymentCreateWithoutAuthorize,Uo as paymentWebhookWideTolerance,Ro as plaintextSecretInWranglerVariables,Wo as policyReferencesUnknownTable,To as privilegedDispatchUnvalidatedPayload,Io as privilegedFanoutFromPublicProcedure,No as procedureWithoutStructuredEvent,ko as publicArgumentUsesAny,xo as publicMutationWithoutRatelimit,Fo as publicTableRlsOptoutConfusion,Co as queueWithoutDlq,Oo as r2sqlOutsideAction,Mo as ratelimitDefaultMemoryStore,$o as ratelimitKeySpoofableOrGlobal,Do as ratelimitMiddlewareFailOpen,Eo as relationReferencesUnknownField,Po as relationReferencesUnknownTable,Ko as rlsUncoveredTable,ft as runAdvisor,vt as scoreAdvisor,qo as shapeTargetsGlobalTable,Lo as shapeUnknownTable,Go as signupMutationWithoutDisposableGating,_o as softDeleteIncludeDeletedFromArgs,zo as sqlInjectionRisk,Ho as storageGenerateUploadUrlNoContentTypePin,Bo as storageKeyFromUserArgs,jo as storagePresignedUrlForPrivateContent,Vo as storageUploadWithoutContentTypeAllowlist,Qo as storageUploadWithoutMaxSize,Xo as tableWithoutInsert,Zo as ttlFieldNotTimestamp,oe as unboundedStringArgument,ee as unindexedForeignKey,re as unindexedRelationTarget,te as unrestrictedWhereBranch,ie as userCreatingMutationWithoutCaptcha,ne as vectorsNamespaceFromUserInput,ae as workflowDuplicateStepName,me as workflowUnknownTarget,se as workflowUnused};
@@ -1 +1 @@
1
- import{e as r}from"./finding-NrKO8idM.mjs";import{q as a}from"./helpers-CwSEZdku.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 i=a(e);return r(l,{cacheKey:`filter_on_primary_key:${e.file}:${e.line.toString()}:${e.table}`,detail:`Query on "${e.table}" at ${i} 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
+ import{e as r}from"./finding-NrKO8idM.mjs";import{q as a}from"./helpers-BCqZbKga.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 i=a(e);return r(l,{cacheKey:`filter_on_primary_key:${e.file}:${e.line.toString()}:${e.table}`,detail:`Query on "${e.table}" at ${i} 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 +1 @@
1
- import{e as s}from"./finding-NrKO8idM.mjs";import{s as c,q as f}from"./helpers-CwSEZdku.mjs";const l={categories:["PERFORMANCE"],description:"A query calls `.filter()` without a `.withIndex()` / `.withSearchIndex()`, so it loads every row in the table and filters in memory — a full table scan that gets linearly slower as the table grows.",facing:"EXTERNAL",level:"WARN",name:"filter_without_index",remediation:'Narrow the read with `.withIndex("name", (q) => q.eq(...))` first, then `.filter()` only for what the index cannot express.',run:i=>{const t=[],d=c(i.schema);for(const e of i.queries??[]){if(!e.hasFilter||e.hasIndex||e.table===""||e.filtersPrimaryKey===!0)continue;const n=f(e),a=d.get(e.table),o={exportName:e.exportName,file:e.file,line:e.line,shardKind:a??"unknown",table:e.table},r=`filter_without_index:${e.file}:${e.line.toString()}:${e.table}`;if(a==="shardBy"){t.push(s(l,{cacheKey:r,detail:`Query on "${e.table}" at ${n} calls .filter() without an index. "${e.table}" is \`.shardBy()\`, so the read is already scoped to one shard rather than the whole table — this is bounded by one tenant's row count, not the dataset. Add an index if that per-shard count grows large.`,level:"INFO",metadata:o}));continue}const h=new Map([["global",`it scans the whole D1 table "${e.table}" — unbounded, and the cost is a cross-region round trip`],["root",`it loads every row of "${e.table}" from the root Durable Object's SQLite and filters in memory`]]).get(a??"")??`it loads every row of "${e.table}" and filters in memory`;t.push(s(l,{cacheKey:r,detail:`Query on "${e.table}" at ${n} calls .filter() without an index — ${h}.`,metadata:o}))}return t},source:"static",title:"Filter without index"};export{l as default};
1
+ import{e as s}from"./finding-NrKO8idM.mjs";import{s as c,q as f}from"./helpers-BCqZbKga.mjs";const l={categories:["PERFORMANCE"],description:"A query calls `.filter()` without a `.withIndex()` / `.withSearchIndex()`, so it loads every row in the table and filters in memory — a full table scan that gets linearly slower as the table grows.",facing:"EXTERNAL",level:"WARN",name:"filter_without_index",remediation:'Narrow the read with `.withIndex("name", (q) => q.eq(...))` first, then `.filter()` only for what the index cannot express.',run:i=>{const t=[],d=c(i.schema);for(const e of i.queries??[]){if(!e.hasFilter||e.hasIndex||e.table===""||e.filtersPrimaryKey===!0)continue;const n=f(e),a=d.get(e.table),o={exportName:e.exportName,file:e.file,line:e.line,shardKind:a??"unknown",table:e.table},r=`filter_without_index:${e.file}:${e.line.toString()}:${e.table}`;if(a==="shardBy"){t.push(s(l,{cacheKey:r,detail:`Query on "${e.table}" at ${n} calls .filter() without an index. "${e.table}" is \`.shardBy()\`, so the read is already scoped to one shard rather than the whole table — this is bounded by one tenant's row count, not the dataset. Add an index if that per-shard count grows large.`,level:"INFO",metadata:o}));continue}const h=new Map([["global",`it scans the whole D1 table "${e.table}" — unbounded, and the cost is a cross-region round trip`],["root",`it loads every row of "${e.table}" from the root Durable Object's SQLite and filters in memory`]]).get(a??"")??`it loads every row of "${e.table}" and filters in memory`;t.push(s(l,{cacheKey:r,detail:`Query on "${e.table}" at ${n} calls .filter() without an index — ${h}.`,metadata:o}))}return t},source:"static",title:"Filter without index"};export{l as default};
@@ -0,0 +1 @@
1
+ import{e as i}from"./finding-NrKO8idM.mjs";const n=new Set(["disallow","disallowed","enforce","enforced","enforcement","enforcing","gate","gated","gating","lockdown","rls"]),o=new Set(["allow","allowed","bypass","bypassed","permit","permitted"]),r=new Set(["disable","disabled","no","off","skip","skipped","without"]),f=/(?<=[A-Z])(?=[A-Z][a-z])/gu,d=/(?<=[a-z0-9])(?=[A-Z])/gu,u=/[^a-z0-9]+/u,c=t=>t.replaceAll(f," ").replaceAll(d," ").toLowerCase().split(u).filter(e=>e.length>0),g=t=>{const e=c(t),a=e.some(s=>n.has(s)),l=e.some(s=>o.has(s));return a===l?void 0:e.filter(s=>r.has(s)).length%2===1?!a:a},p={categories:["SECURITY"],description:"A `ctx.flags.boolean(key, default)` read on a security-shaped key has a fail-open default that selects the permissive branch. OpenFeature returns the default when the provider errors, so an outage silently disables a protection (a `false` default on an `enforce`/`rls`/`gate`/`lockdown` key) or grants a permission (a `true` default on an `allow`/`permit`/`bypass` key). A negating token in the key (`disableRls`, `rlsDisabled`, `skipEnforcement`) inverts which default is the safe one.",facing:"EXTERNAL",level:"WARN",name:"flag_gates_security_with_unsafe_default",remediation:"Flip the default so a provider outage fails closed — the finding's own detail names the value to write. The safe default is always the RESTRICTIVE branch, which is not a fixed value: a protection flag (`enforce*`/`rls*`/`gate*`/`lockdown*`/`disallow*`) defaults `true` and a permission/bypass flag (`allow*`/`permit*`/`bypass*`) defaults `false`, but a negated key (`disableRls`, `rlsDisabled`, `skipEnforcement`) inverts both. Never let an unreachable flag backend open access.",run:t=>t.flagSecurityDefaults===void 0?[]:t.flagSecurityDefaults.filter(e=>{const a=g(e.key);return a!==void 0&&a!==e.defaultValue}).map(e=>i(p,{cacheKey:`flag_gates_security_with_unsafe_default:${e.file}:${e.line.toString()}`,detail:`\`ctx.flags.boolean("${e.key}", ${String(e.defaultValue)})\` in \`${e.exportName}\` (${e.file}:${e.line.toString()}) fails open to the permissive branch — a provider outage returns \`${String(e.defaultValue)}\`, ${e.defaultValue?"granting the guarded permission":"disabling the guarded protection"}. Fail closed: default it to \`${String(!e.defaultValue)}\`.`,metadata:{defaultValue:e.defaultValue,exportName:e.exportName,file:e.file,key:e.key,line:e.line}})),source:"static",title:"Security flag fails open to the permissive branch"};export{p as default};
@@ -0,0 +1 @@
1
+ import{e as d}from"./finding-NrKO8idM.mjs";import{c}from"./helpers-BCqZbKga.mjs";const s={categories:["SCHEMA"],description:"A `.geoIndex(name, { field })` points at a column that is not a `v.geoPoint()`, so its geohash companion can't be built and `withGeoIndex(...)` can never match.",facing:"INTERNAL",level:"ERROR",name:"geo_index_field_not_geopoint",remediation:"Point the geo index at a `v.geoPoint()` column, or change the column's type to `v.geoPoint()`.",run:a=>{const t=[];for(const e of a.schema.tables)for(const n of e.indexes){if(n.kind!=="geo")continue;const o=n.fields[0];if(o===void 0)continue;const i=c(e,o);i===void 0||i==="geoPoint"||t.push(d(s,{cacheKey:`geo_index_field_not_geopoint:${e.name}:${n.name}:${o}`,detail:`Geo index "${n.name}" on table "${e.name}" indexes column "${o}", which is a ${i}, not a v.geoPoint().`,metadata:{field:o,index:n.name,kind:i,table:e.name}}))}return t},source:"static",title:"Geo index field is not a geoPoint"};export{s as default};
@@ -0,0 +1 @@
1
+ const o=["address","birthdate","creditCard","dateOfBirth","dob","driversLicense","email","firstName","fullName","lastName","nationalId","passport","phone","phoneNumber","socialSecurity","socialSecurityNumber","ssn","taxId"],I=/[^a-z0-9]/giu,u=/([a-z0-9])([A-Z])/gu,S=/[^a-zA-Z0-9]+/u,n=e=>e.replaceAll(I,"").toLowerCase(),s=e=>e.replaceAll(u,"$1_$2").split(S).filter(t=>t.length>0).map(t=>t.toLowerCase()),h=new Set(o.map(e=>n(e))),N=new Set(o.filter(e=>s(e).length===1).map(e=>e.toLowerCase())),_=new Set(["accountId","authorId","createdBy","createdById","organizationId","orgId","ownerId","tenantId","updatedBy","userId","workspaceId"]),w=e=>h.has(n(e))||s(e).some(t=>N.has(t));new Set(o);const E=(e,t)=>{const a=s(e),r=n(e);return t.some(i=>{if(r===n(i))return!0;const c=s(i);return c.length>0&&a.some((A,l)=>c.every((d,m)=>a[l+m]===d))})},L=(e,t)=>e.columnKinds!==void 0&&Object.hasOwn(e.columnKinds,t)?e.columnKinds[t]:void 0,p=new Set(["_creationTime","_id"]),O=e=>e.fields.filter(t=>_.has(t)||w(t)),P=e=>new Set([...p,...e.fields]),f=e=>e.visibility==="public"&&(e.kind==="mutation"||e.kind==="action"),y=e=>e.line>0?`${e.file}:${e.line.toString()}`:e.file,C=e=>new Map(e.tables.map(t=>[t.name,t.shardKind]));export{f as a,L as c,w as i,E as m,O as o,y as q,C as s,P as t};
@@ -1 +1 @@
1
- import{e as i}from"./finding-NrKO8idM.mjs";import{t as r}from"./helpers-CwSEZdku.mjs";const c={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:a=>a.schema.tables.flatMap(e=>{const o=r(e);return e.indexes.flatMap(n=>n.fields.filter(t=>!o.has(t)).map(t=>i(c,{cacheKey:`index_references_unknown_field:${e.name}:${n.name}:${t}`,detail:`Index "${n.name}" on table "${e.name}" references column "${t}", which is not declared on the table.`,metadata:{field:t,index:n.name,indexKind:n.kind,table:e.name}})))}),source:"static",title:"Index references unknown field"};export{c as default};
1
+ import{e as i}from"./finding-NrKO8idM.mjs";import{t as r}from"./helpers-BCqZbKga.mjs";const c={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:a=>a.schema.tables.flatMap(e=>{const o=r(e);return e.indexes.flatMap(n=>n.fields.filter(t=>!o.has(t)).map(t=>i(c,{cacheKey:`index_references_unknown_field:${e.name}:${n.name}:${t}`,detail:`Index "${n.name}" on table "${e.name}" references column "${t}", which is not declared on the table.`,metadata:{field:t,index:n.name,indexKind:n.kind,table:e.name}})))}),source:"static",title:"Index references unknown field"};export{c as default};
@@ -1 +1 @@
1
- import{e as i}from"./finding-NrKO8idM.mjs";import{i as s}from"./helpers-CwSEZdku.mjs";const o={categories:["SECURITY"],description:'A `mask(policies)` column named like PII (email, SSN, phone, …) uses the `"hash"` strategy. `"hash"` is an unsalted 32-bit FNV-1a digest — brute-force-recoverable and cross-row-correlatable — not a confidentiality control, so it does not actually hide the value.',facing:"EXTERNAL",level:"WARN",name:"mask_weak_hash_strategy_on_pii",remediation:'Use `"redact"` instead of `"hash"` for this column — e.g. `mask({ <table>: { <column>: "redact" } })`. Reserve `"hash"` for columns where a stable, joinable pseudonym is the goal and the value itself is not sensitive.',run:a=>a.maskStrategies===void 0?[]:a.maskStrategies.filter(e=>e.strategy==="hash"&&s(e.column)).map(e=>i(o,{cacheKey:`mask_weak_hash_strategy_on_pii:${e.file}:${e.line.toString()}`,detail:`\`${e.exportName}\` in ${e.file} masks \`${e.table===""?e.column:`${e.table}.${e.column}`}\` with \`"hash"\` — its name suggests PII, and \`"hash"\` is brute-force-recoverable and leaks cross-row equality. Use \`"redact"\` instead.`,metadata:{column:e.column,exportName:e.exportName,file:e.file,table:e.table}})),source:"static",title:'Weak "hash" mask strategy on a PII column'};export{o as default};
1
+ import{e as i}from"./finding-NrKO8idM.mjs";import{i as s}from"./helpers-BCqZbKga.mjs";const o={categories:["SECURITY"],description:'A `mask(policies)` column named like PII (email, SSN, phone, …) uses the `"hash"` strategy. `"hash"` is an unsalted 32-bit FNV-1a digest — brute-force-recoverable and cross-row-correlatable — not a confidentiality control, so it does not actually hide the value.',facing:"EXTERNAL",level:"WARN",name:"mask_weak_hash_strategy_on_pii",remediation:'Use `"redact"` instead of `"hash"` for this column — e.g. `mask({ <table>: { <column>: "redact" } })`. Reserve `"hash"` for columns where a stable, joinable pseudonym is the goal and the value itself is not sensitive.',run:a=>a.maskStrategies===void 0?[]:a.maskStrategies.filter(e=>e.strategy==="hash"&&s(e.column)).map(e=>i(o,{cacheKey:`mask_weak_hash_strategy_on_pii:${e.file}:${e.line.toString()}`,detail:`\`${e.exportName}\` in ${e.file} masks \`${e.table===""?e.column:`${e.table}.${e.column}`}\` with \`"hash"\` — its name suggests PII, and \`"hash"\` is brute-force-recoverable and leaks cross-row equality. Use \`"redact"\` instead.`,metadata:{column:e.column,exportName:e.exportName,file:e.file,table:e.table}})),source:"static",title:'Weak "hash" mask strategy on a PII column'};export{o as default};
@@ -1 +1 @@
1
- import{e as r}from"./finding-NrKO8idM.mjs";import{i as s}from"./helpers-CwSEZdku.mjs";const l={categories:["SECURITY"],description:"A `.public()` `query` returns raw table rows (no `.output(...)` projection, no `.use(mask(...))`) from a table carrying PII-named columns (`email`, `phone`, `ssn`, …). Every column ships to the caller, and a column added to the table later leaks by default.",facing:"EXTERNAL",level:"INFO",name:"output_projection_missing_on_public_read",remediation:"Add an explicit return projection to the public query — `.output(v.object({ … }).strip())` listing only the fields a client needs — or apply a `.use(mask(...))` policy to the PII columns. This makes the exposed shape intentional and stops a newly-added column from leaking through this query by default.",run:o=>{if(o.rawRowReturns===void 0)return[];const i=new Map;for(const e of o.schema.tables){const t=e.fields.filter(a=>s(a));t.length>0&&i.set(e.name,t)}const n=[];for(const e of o.rawRowReturns){if(e.visibility!=="public"||e.usesOutput||e.usesMask||e.table==="")continue;const t=i.get(e.table);t!==void 0&&n.push(r(l,{cacheKey:`output_projection_missing_on_public_read:${e.file}:${e.line.toString()}`,detail:`Public query \`${e.exportName}\` (${e.file}:${e.line.toString()}) returns raw \`${e.table}\` rows with no \`.output(...)\` projection — shipping PII column(s) ${t.join(", ")} to every caller, and any column added to \`${e.table}\` later leaks by default. Project the return with \`.output(v.object({ … }).strip())\` or mask the PII columns.`,metadata:{columns:t,exportName:e.exportName,file:e.file,line:e.line,table:e.table}}))}return n},source:"static",title:"Public query returns raw rows with PII and no output projection"};export{l as default};
1
+ import{e as r}from"./finding-NrKO8idM.mjs";import{i as s}from"./helpers-BCqZbKga.mjs";const l={categories:["SECURITY"],description:"A `.public()` `query` returns raw table rows (no `.output(...)` projection, no `.use(mask(...))`) from a table carrying PII-named columns (`email`, `phone`, `ssn`, …). Every column ships to the caller, and a column added to the table later leaks by default.",facing:"EXTERNAL",level:"INFO",name:"output_projection_missing_on_public_read",remediation:"Add an explicit return projection to the public query — `.output(v.object({ … }).strip())` listing only the fields a client needs — or apply a `.use(mask(...))` policy to the PII columns. This makes the exposed shape intentional and stops a newly-added column from leaking through this query by default.",run:o=>{if(o.rawRowReturns===void 0)return[];const i=new Map;for(const e of o.schema.tables){const t=e.fields.filter(a=>s(a));t.length>0&&i.set(e.name,t)}const n=[];for(const e of o.rawRowReturns){if(e.visibility!=="public"||e.usesOutput||e.usesMask||e.table==="")continue;const t=i.get(e.table);t!==void 0&&n.push(r(l,{cacheKey:`output_projection_missing_on_public_read:${e.file}:${e.line.toString()}`,detail:`Public query \`${e.exportName}\` (${e.file}:${e.line.toString()}) returns raw \`${e.table}\` rows with no \`.output(...)\` projection — shipping PII column(s) ${t.join(", ")} to every caller, and any column added to \`${e.table}\` later leaks by default. Project the return with \`.output(v.object({ … }).strip())\` or mask the PII columns.`,metadata:{columns:t,exportName:e.exportName,file:e.file,line:e.line,table:e.table}}))}return n},source:"static",title:"Public query returns raw rows with PII and no output projection"};export{l as default};
@@ -0,0 +1 @@
1
+ import{e as a}from"./finding-NrKO8idM.mjs";import{a as r,m as s}from"./helpers-BCqZbKga.mjs";const n=["contact","forgot","login","logIn","magic","otp","register","reset","signin","signIn","signup","signUp","subscribe","verify"],o={categories:["SECURITY"],description:"A public `mutation`/`action` has no `rateLimit` middleware. Publicly-callable writes are flood and brute-force targets — an attacker can exhaust writes, mail quota, or credits, or guess credentials on auth-shaped endpoints.",facing:"EXTERNAL",level:"WARN",name:"public_mutation_without_ratelimit",remediation:'Attach a rate limit: `.use(rateLimit(limiter, "<bucket>"))` from `@lunora/ratelimit`, or wrap the recommended public-procedure guards with `.use(protectPublic({ rateLimit, captcha }))` from `@lunora/server`. Genuinely-open writes can be acknowledged by adding a permissive limiter.',run:i=>i.procedureProtections===void 0?[]:i.procedureProtections.filter(t=>r(t)&&!t.usesRateLimit).map(t=>{const e=s(t.exportName,n);return a(o,{cacheKey:`public_mutation_without_ratelimit:${t.file}:${t.exportName}`,detail:`Public ${t.kind} \`${t.exportName}\` (${t.file}) has no rate limit${e?" — its name suggests an auth/abuse-sensitive endpoint, so this is high-risk":""}. Add \`.use(rateLimit(...))\` or \`.use(protectPublic({ rateLimit }))\`.`,metadata:{exportName:t.exportName,file:t.file,kind:t.kind,sensitive:e}})}),source:"static",title:"Public write without a rate limit"};export{o as default};
@@ -1 +1 @@
1
- import{e as i}from"./finding-NrKO8idM.mjs";import{o as a}from"./helpers-CwSEZdku.mjs";const n={categories:["SECURITY"],description:'`.public()` opts a table OUT of the schema\'s `.rls("required")` enforcement — it does not mean the table holds public data. A `.public()` table whose columns look ownership- or PII-shaped (`userId`, `email`, `ssn`, …) is exempt from the row-security guard, not "safe to expose".',facing:"EXTERNAL",level:"WARN",name:"public_table_rls_optout_confusion",remediation:'Double-check this table actually needs the RLS opt-out. If it does not, remove `.public()` so `.rls("required")` covers it like every other table. If it genuinely must stay open (a shared lookup table, for instance), rename or comment it so the exemption is intentional rather than a name-implies-safety mistake, and make sure no procedure trusts `.public()` as an authorization boundary.',run:o=>{if(o.schema.rlsMode!=="required")return[];const s=[];for(const e of o.schema.tables){if(!e.isPublic)continue;const t=a(e);t.length!==0&&s.push(i(n,{cacheKey:`public_table_rls_optout_confusion:${e.name}`,detail:`Table "${e.name}" is \`.public()\` (opted OUT of the schema's \`.rls("required")\` enforcement) but carries ownership/PII-shaped column(s): ${t.join(", ")}. \`.public()\` means "exempt from RLS", not "safe to expose" — confirm this table really needs the opt-out.`,metadata:{columns:t,table:e.name}}))}return s},source:"static",title:"Public table opts out of RLS but carries sensitive columns"};export{n as default};
1
+ import{e as i}from"./finding-NrKO8idM.mjs";import{o as a}from"./helpers-BCqZbKga.mjs";const n={categories:["SECURITY"],description:'`.public()` opts a table OUT of the schema\'s `.rls("required")` enforcement — it does not mean the table holds public data. A `.public()` table whose columns look ownership- or PII-shaped (`userId`, `email`, `ssn`, …) is exempt from the row-security guard, not "safe to expose".',facing:"EXTERNAL",level:"WARN",name:"public_table_rls_optout_confusion",remediation:'Double-check this table actually needs the RLS opt-out. If it does not, remove `.public()` so `.rls("required")` covers it like every other table. If it genuinely must stay open (a shared lookup table, for instance), rename or comment it so the exemption is intentional rather than a name-implies-safety mistake, and make sure no procedure trusts `.public()` as an authorization boundary.',run:o=>{if(o.schema.rlsMode!=="required")return[];const s=[];for(const e of o.schema.tables){if(!e.isPublic)continue;const t=a(e);t.length!==0&&s.push(i(n,{cacheKey:`public_table_rls_optout_confusion:${e.name}`,detail:`Table "${e.name}" is \`.public()\` (opted OUT of the schema's \`.rls("required")\` enforcement) but carries ownership/PII-shaped column(s): ${t.join(", ")}. \`.public()\` means "exempt from RLS", not "safe to expose" — confirm this table really needs the opt-out.`,metadata:{columns:t,table:e.name}}))}return s},source:"static",title:"Public table opts out of RLS but carries sensitive columns"};export{n as default};
@@ -0,0 +1 @@
1
+ import{e as r}from"./finding-NrKO8idM.mjs";import{m as t}from"./helpers-BCqZbKga.mjs";const a=["signin","signIn","signup","signUp","login","logIn","register","reset","password","otp","verify","payment","checkout"],n=i=>t(i.exportName,a)||t(i.limitName,a),l={categories:["SECURITY"],description:"A rate-limit or Turnstile middleware guarding a sensitive auth/payment procedure is configured `failOpen: true`, so a limiter store outage or a failed Turnstile siteverify silently admits every request — turning a transient failure into an unthrottled brute-force / abuse window.",facing:"EXTERNAL",level:"WARN",name:"ratelimit_middleware_fail_open",remediation:"Drop `failOpen: true` (the default is fail-closed) on the rate-limit / Turnstile guard for auth and payment flows, or move the escape hatch to a non-sensitive procedure. Prefer rejecting traffic with a 503 over admitting unlimited unauthenticated attempts when the limiter or siteverify is unavailable.",run:i=>i.failOpenGuards===void 0?[]:i.failOpenGuards.filter(e=>e.failOpen&&n(e)).map(e=>r(l,{cacheKey:`ratelimit_middleware_fail_open:${e.file}:${e.line.toString()}`,detail:`\`${e.callee}(...)\` in \`${e.exportName}\` (${e.file}:${e.line.toString()}) sets \`failOpen: true\` while guarding a sensitive flow — a limiter/siteverify outage then admits every request instead of rejecting it.`,metadata:{callee:e.callee,exportName:e.exportName,file:e.file,limitName:e.limitName,line:e.line}})),source:"static",title:"Fail-open rate-limit / CAPTCHA guard on a sensitive procedure"};export{l as default};
@@ -1 +1 @@
1
- import{e as c}from"./finding-NrKO8idM.mjs";import{t as m}from"./helpers-CwSEZdku.mjs";const f=(o,i,t,a)=>{const e=o.get(a.table);if(!e)return[];const n=a.kind==="one"?t:e,s=a.kind==="one"?e:t,r=[];return i(n).has(a.field)||r.push({column:a.field,owner:n.name,side:"field"}),i(s).has(a.references)||r.push({column:a.references,owner:s.name,side:"references"}),r},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])),t=new Map,a=e=>{let n=t.get(e);return n||(n=m(e),t.set(e,n)),n};return o.schema.tables.flatMap(e=>e.relations.flatMap(n=>f(i,a,e,n).map(s=>c(d,{cacheKey:`relation_references_unknown_field:${e.name}:${n.name}:${s.side}`,detail:`Relation "${n.name}" on table "${e.name}" uses ${s.side} "${s.column}", which is not declared on table "${s.owner}".`,metadata:{column:s.column,owner:s.owner,relation:n.name,side:s.side,table:e.name}}))))},source:"static",title:"Relation references unknown field"};export{d as default};
1
+ import{e as c}from"./finding-NrKO8idM.mjs";import{t as m}from"./helpers-BCqZbKga.mjs";const f=(o,i,t,a)=>{const e=o.get(a.table);if(!e)return[];const n=a.kind==="one"?t:e,s=a.kind==="one"?e:t,r=[];return i(n).has(a.field)||r.push({column:a.field,owner:n.name,side:"field"}),i(s).has(a.references)||r.push({column:a.references,owner:s.name,side:"references"}),r},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])),t=new Map,a=e=>{let n=t.get(e);return n||(n=m(e),t.set(e,n)),n};return o.schema.tables.flatMap(e=>e.relations.flatMap(n=>f(i,a,e,n).map(s=>c(d,{cacheKey:`relation_references_unknown_field:${e.name}:${n.name}:${s.side}`,detail:`Relation "${n.name}" on table "${e.name}" uses ${s.side} "${s.column}", which is not declared on table "${s.owner}".`,metadata:{column:s.column,owner:s.owner,relation:n.name,side:s.side,table:e.name}}))))},source:"static",title:"Relation references unknown field"};export{d as default};
@@ -1 +1 @@
1
- import{e as s}from"./finding-NrKO8idM.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";import{a as o}from"./helpers-CwSEZdku.mjs";const r={categories:["SECURITY"],description:"A public `mutation`/`action` that creates a user/session/account row has no disposable-email gate. Throwaway mailboxes farm free trials, evade bans, and pollute the user table.",facing:"EXTERNAL",level:"WARN",name:"signup_mutation_without_disposable_gating",remediation:"Add `.use(emailGateMiddleware({ email: (ctx) => ctx.args.email }))` from `@lunora/auth/email-guard` to reject disposable domains at signup, or gate better-auth's native signup with `withEmailGate(...)` / `emailGateDatabaseHooks(...)`.",run:a=>{if(a.procedureProtections===void 0)return[];const t=[];for(const e of a.procedureProtections)!o(e)||!i(e.writesUserTable)||e.usesEmailGate||i(e.hasEmailArg)&&t.push(s(r,{cacheKey:`signup_mutation_without_disposable_gating:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) writes a user/session/account table but has no disposable-email gate. Add \`.use(emailGateMiddleware(...))\` from \`@lunora/auth/email-guard\`.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind,writesUserTable:e.writesUserTable}}));return t},source:"static",title:"Account-creating write without a disposable-email gate"};export{r as default};
1
+ import{e as s}from"./finding-NrKO8idM.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";import{a as o}from"./helpers-BCqZbKga.mjs";const r={categories:["SECURITY"],description:"A public `mutation`/`action` that creates a user/session/account row has no disposable-email gate. Throwaway mailboxes farm free trials, evade bans, and pollute the user table.",facing:"EXTERNAL",level:"WARN",name:"signup_mutation_without_disposable_gating",remediation:"Add `.use(emailGateMiddleware({ email: (ctx) => ctx.args.email }))` from `@lunora/auth/email-guard` to reject disposable domains at signup, or gate better-auth's native signup with `withEmailGate(...)` / `emailGateDatabaseHooks(...)`.",run:a=>{if(a.procedureProtections===void 0)return[];const t=[];for(const e of a.procedureProtections)!o(e)||!i(e.writesUserTable)||e.usesEmailGate||i(e.hasEmailArg)&&t.push(s(r,{cacheKey:`signup_mutation_without_disposable_gating:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) writes a user/session/account table but has no disposable-email gate. Add \`.use(emailGateMiddleware(...))\` from \`@lunora/auth/email-guard\`.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind,writesUserTable:e.writesUserTable}}));return t},source:"static",title:"Account-creating write without a disposable-email gate"};export{r as default};
@@ -0,0 +1 @@
1
+ import{e as o}from"./finding-NrKO8idM.mjs";import{c as l}from"./helpers-BCqZbKga.mjs";const m=new Set(["date","number","timestamp"]),s={categories:["SCHEMA"],description:"A `.ttl(field)` policy points at a column that is not an epoch-millisecond timestamp, so the alarm sweep's `field < now` comparison can't decide expiry correctly.",facing:"INTERNAL",level:"ERROR",name:"ttl_field_not_timestamp",remediation:"Point `.ttl(field)` at a `v.timestamp()` / `v.date()` / `v.number()` (epoch-ms) column, or add such a column.",run:n=>{const a=[];for(const e of n.schema.tables){const{ttl:t}=e;if(!t)continue;const i=l(e,t.field);i===void 0||m.has(i)||a.push(o(s,{cacheKey:`ttl_field_not_timestamp:${e.name}:${t.field}`,detail:`Table "${e.name}" declares \`.ttl("${t.field}")\`, but "${t.field}" is a ${i} column, not an epoch-millisecond timestamp.`,metadata:{field:t.field,kind:i,table:e.name}}))}return a},source:"static",title:"TTL field is not a timestamp"};export{s as default};
@@ -1 +1 @@
1
- import{e as i}from"./finding-NrKO8idM.mjs";import{m as a}from"./procedure-protections-DRQotu9I.mjs";import{a as s}from"./helpers-CwSEZdku.mjs";const r=t=>t.writesUserTable===!0?"writes a user/session table":t.callsMail===!0?"sends mail":t.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(verifyTurnstileMiddleware({ secret, token: (c) => c.args.captchaToken }))` from `@lunora/auth`, or wrap it with `.use(protectPublic({ rateLimit, captcha }))` from `@lunora/server`. Pair with a rate limit for defense in depth. Note it is `verifyTurnstileMiddleware`, not the bare `verifyTurnstile` verdict function — that one returns a Promise and verifies nothing when placed in a `.use()` chain.",run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>s(e)&&(a(e.writesUserTable)||a(e.callsMail))&&!e.usesCaptcha).map(e=>i(n,{cacheKey:`user_creating_mutation_without_captcha:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) ${r(e)} but has no CAPTCHA check. Add \`.use(verifyTurnstileMiddleware(...))\` or \`.use(protectPublic({ captcha }))\`.`,metadata:{callsMail:e.callsMail,exportName:e.exportName,file:e.file,kind:e.kind,writesUserTable:e.writesUserTable}})),source:"static",title:"Account-creating / mail-sending write without a CAPTCHA"};export{n as default};
1
+ import{e as i}from"./finding-NrKO8idM.mjs";import{m as a}from"./procedure-protections-DRQotu9I.mjs";import{a as s}from"./helpers-BCqZbKga.mjs";const r=t=>t.writesUserTable===!0?"writes a user/session table":t.callsMail===!0?"sends mail":t.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(verifyTurnstileMiddleware({ secret, token: (c) => c.args.captchaToken }))` from `@lunora/auth`, or wrap it with `.use(protectPublic({ rateLimit, captcha }))` from `@lunora/server`. Pair with a rate limit for defense in depth. Note it is `verifyTurnstileMiddleware`, not the bare `verifyTurnstile` verdict function — that one returns a Promise and verifies nothing when placed in a `.use()` chain.",run:t=>t.procedureProtections===void 0?[]:t.procedureProtections.filter(e=>s(e)&&(a(e.writesUserTable)||a(e.callsMail))&&!e.usesCaptcha).map(e=>i(n,{cacheKey:`user_creating_mutation_without_captcha:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) ${r(e)} but has no CAPTCHA check. Add \`.use(verifyTurnstileMiddleware(...))\` or \`.use(protectPublic({ captcha }))\`.`,metadata:{callsMail:e.callsMail,exportName:e.exportName,file:e.file,kind:e.kind,writesUserTable:e.writesUserTable}})),source:"static",title:"Account-creating / mail-sending write without a CAPTCHA"};export{n as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/advisor",
3
- "version": "1.0.0-alpha.106",
3
+ "version": "1.0.0-alpha.107",
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.30",
50
- "@lunora/server": "1.0.0-alpha.100"
50
+ "@lunora/server": "1.0.0-alpha.101"
51
51
  },
52
52
  "engines": {
53
53
  "node": "^22.15.0 || >=24.11.0"
@@ -1 +0,0 @@
1
- import{e as n}from"./finding-NrKO8idM.mjs";const i=new Set(["enforce","gate","lockdown","rls"]),r=new Set(["allow","bypass","permit"]),o=/(?<=[A-Z])(?=[A-Z][a-z])/gu,f=/(?<=[a-z0-9])(?=[A-Z])/gu,u=/[^a-z0-9]+/u,c=a=>a.replaceAll(o," ").replaceAll(f," ").toLowerCase().split(u).filter(e=>e.length>0),d=a=>{const e=c(a.key),l=e.some(t=>i.has(t)),s=e.some(t=>r.has(t));return l===s?!1:l?!a.defaultValue:a.defaultValue},p={categories:["SECURITY"],description:"A `ctx.flags.boolean(key, default)` read on a security-shaped key has a fail-open default that selects the permissive branch. OpenFeature returns the default when the provider errors, so an outage silently disables a protection (a `false` default on an `enforce`/`rls`/`gate`/`lockdown` key) or grants a permission (a `true` default on an `allow`/`permit`/`bypass` key).",facing:"EXTERNAL",level:"WARN",name:"flag_gates_security_with_unsafe_default",remediation:"Flip the default so a provider outage fails closed: default a protection flag (`enforce*`/`rls*`/`gate*`/`lockdown*`) to `true`, and a permission/bypass flag (`allow*`/`permit*`/`bypass*`) to `false`. The safe default is always the restrictive branch — never let an unreachable flag backend open access.",run:a=>a.flagSecurityDefaults===void 0?[]:a.flagSecurityDefaults.filter(e=>d(e)).map(e=>n(p,{cacheKey:`flag_gates_security_with_unsafe_default:${e.file}:${e.line.toString()}`,detail:`\`ctx.flags.boolean("${e.key}", ${String(e.defaultValue)})\` in \`${e.exportName}\` (${e.file}:${e.line.toString()}) fails open to the permissive branch — a provider outage returns \`${String(e.defaultValue)}\`, ${e.defaultValue?"granting the guarded permission":"disabling the guarded protection"}.`,metadata:{defaultValue:e.defaultValue,exportName:e.exportName,file:e.file,key:e.key,line:e.line}})),source:"static",title:"Security flag fails open to the permissive branch"};export{p as default};
@@ -1 +0,0 @@
1
- import{e as d}from"./finding-NrKO8idM.mjs";const c={categories:["SCHEMA"],description:"A `.geoIndex(name, { field })` points at a column that is not a `v.geoPoint()`, so its geohash companion can't be built and `withGeoIndex(...)` can never match.",facing:"INTERNAL",level:"ERROR",name:"geo_index_field_not_geopoint",remediation:"Point the geo index at a `v.geoPoint()` column, or change the column's type to `v.geoPoint()`.",run:a=>{const t=[];for(const e of a.schema.tables)for(const n of e.indexes){if(n.kind!=="geo")continue;const o=n.fields[0];if(o===void 0)continue;const i=e.columnKinds?.[o];i===void 0||i==="geoPoint"||t.push(d(c,{cacheKey:`geo_index_field_not_geopoint:${e.name}:${n.name}:${o}`,detail:`Geo index "${n.name}" on table "${e.name}" indexes column "${o}", which is a ${i}, not a v.geoPoint().`,metadata:{field:o,index:n.name,kind:i,table:e.name}}))}return t},source:"static",title:"Geo index field is not a geoPoint"};export{c as default};
@@ -1 +0,0 @@
1
- const s=["address","birthdate","creditCard","dateOfBirth","dob","driversLicense","email","firstName","fullName","lastName","nationalId","passport","phone","phoneNumber","socialSecurity","socialSecurityNumber","ssn","taxId"],i=/[^a-z0-9]/giu,o=/([a-z0-9])([A-Z])/gu,l=/[^a-zA-Z0-9]+/u,a=e=>e.replaceAll(i,"").toLowerCase(),n=e=>e.replaceAll(o,"$1_$2").split(l).filter(t=>t.length>0).map(t=>t.toLowerCase()),r=new Set(s.map(e=>a(e))),c=new Set(s.filter(e=>n(e).length===1).map(e=>e.toLowerCase())),d=new Set(["accountId","authorId","createdBy","createdById","organizationId","orgId","ownerId","tenantId","updatedBy","userId","workspaceId"]),I=e=>r.has(a(e))||n(e).some(t=>c.has(t));new Set(s);const S=new Set(["_creationTime","_id"]),m=e=>e.fields.filter(t=>d.has(t)||I(t)),u=e=>new Set([...S,...e.fields]),p=e=>e.visibility==="public"&&(e.kind==="mutation"||e.kind==="action"),N=e=>e.line>0?`${e.file}:${e.line.toString()}`:e.file,_=e=>new Map(e.tables.map(t=>[t.name,t.shardKind]));export{p as a,I as i,m as o,N as q,_ as s,u as t};
@@ -1 +0,0 @@
1
- import{e as a}from"./finding-NrKO8idM.mjs";import{a as r}from"./helpers-CwSEZdku.mjs";const s=/contact|forgot|login|magic|otp|register|reset|signin|signup|subscribe|verify/iu,o={categories:["SECURITY"],description:"A public `mutation`/`action` has no `rateLimit` middleware. Publicly-callable writes are flood and brute-force targets — an attacker can exhaust writes, mail quota, or credits, or guess credentials on auth-shaped endpoints.",facing:"EXTERNAL",level:"WARN",name:"public_mutation_without_ratelimit",remediation:'Attach a rate limit: `.use(rateLimit(limiter, "<bucket>"))` from `@lunora/ratelimit`, or wrap the recommended public-procedure guards with `.use(protectPublic({ rateLimit, captcha }))` from `@lunora/server`. Genuinely-open writes can be acknowledged by adding a permissive limiter.',run:i=>i.procedureProtections===void 0?[]:i.procedureProtections.filter(t=>r(t)&&!t.usesRateLimit).map(t=>{const e=s.test(t.exportName);return a(o,{cacheKey:`public_mutation_without_ratelimit:${t.file}:${t.exportName}`,detail:`Public ${t.kind} \`${t.exportName}\` (${t.file}) has no rate limit${e?" — its name suggests an auth/abuse-sensitive endpoint, so this is high-risk":""}. Add \`.use(rateLimit(...))\` or \`.use(protectPublic({ rateLimit }))\`.`,metadata:{exportName:t.exportName,file:t.file,kind:t.kind,sensitive:e}})}),source:"static",title:"Public write without a rate limit"};export{o as default};
@@ -1 +0,0 @@
1
- import{e as n}from"./finding-NrKO8idM.mjs";const r=["signin","signup","login","register","reset","password","otp","verify","payment","checkout"],l=i=>i.toLowerCase().replaceAll(/[^a-z0-9]+/gu,""),t=i=>{const e=l(i);return r.some(a=>e.includes(a))},s=i=>t(i.exportName)||t(i.limitName),o={categories:["SECURITY"],description:"A rate-limit or Turnstile middleware guarding a sensitive auth/payment procedure is configured `failOpen: true`, so a limiter store outage or a failed Turnstile siteverify silently admits every request — turning a transient failure into an unthrottled brute-force / abuse window.",facing:"EXTERNAL",level:"WARN",name:"ratelimit_middleware_fail_open",remediation:"Drop `failOpen: true` (the default is fail-closed) on the rate-limit / Turnstile guard for auth and payment flows, or move the escape hatch to a non-sensitive procedure. Prefer rejecting traffic with a 503 over admitting unlimited unauthenticated attempts when the limiter or siteverify is unavailable.",run:i=>i.failOpenGuards===void 0?[]:i.failOpenGuards.filter(e=>e.failOpen&&s(e)).map(e=>n(o,{cacheKey:`ratelimit_middleware_fail_open:${e.file}:${e.line.toString()}`,detail:`\`${e.callee}(...)\` in \`${e.exportName}\` (${e.file}:${e.line.toString()}) sets \`failOpen: true\` while guarding a sensitive flow — a limiter/siteverify outage then admits every request instead of rejecting it.`,metadata:{callee:e.callee,exportName:e.exportName,file:e.file,limitName:e.limitName,line:e.line}})),source:"static",title:"Fail-open rate-limit / CAPTCHA guard on a sensitive procedure"};export{o as default};
@@ -1 +0,0 @@
1
- import{e as o}from"./finding-NrKO8idM.mjs";const l=new Set(["date","number","timestamp"]),s={categories:["SCHEMA"],description:"A `.ttl(field)` policy points at a column that is not an epoch-millisecond timestamp, so the alarm sweep's `field < now` comparison can't decide expiry correctly.",facing:"INTERNAL",level:"ERROR",name:"ttl_field_not_timestamp",remediation:"Point `.ttl(field)` at a `v.timestamp()` / `v.date()` / `v.number()` (epoch-ms) column, or add such a column.",run:n=>{const a=[];for(const e of n.schema.tables){const{ttl:t}=e;if(!t)continue;const i=e.columnKinds?.[t.field];i===void 0||l.has(i)||a.push(o(s,{cacheKey:`ttl_field_not_timestamp:${e.name}:${t.field}`,detail:`Table "${e.name}" declares \`.ttl("${t.field}")\`, but "${t.field}" is a ${i} column, not an epoch-millisecond timestamp.`,metadata:{field:t.field,kind:i,table:e.name}}))}return a},source:"static",title:"TTL field is not a timestamp"};export{s as default};