@lunora/advisor 1.0.0-alpha.76 → 1.0.0-alpha.77

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.d.mts CHANGED
@@ -1010,9 +1010,10 @@ interface AdvisorProcedureProtection {
1010
1010
  }
1011
1011
  /**
1012
1012
  * One query read discovered in a function body — the input the
1013
- * `filter_without_index` lint consumes. Produced by the codegen feeder (which
1014
- * parses `ctx.db.query("table")…` chains from the AST); runtime callers don't
1015
- * supply it, so the lint simply finds nothing there.
1013
+ * `filter_without_index` / `filter_on_primary_key` / `unbounded_collect` lints
1014
+ * consume. Produced by the codegen feeder (which parses `ctx.db.query("table")…`
1015
+ * chains from the AST); runtime callers don't supply it, so those lints simply
1016
+ * find nothing there.
1016
1017
  */
1017
1018
  interface AdvisorQueryRead {
1018
1019
  /**
@@ -1042,6 +1043,18 @@ interface AdvisorQueryRead {
1042
1043
  line: number;
1043
1044
  /** The queried table; empty when the `query(...)` argument is not a string literal. */
1044
1045
  table: string;
1046
+ /**
1047
+ * The materializing call the chain ends in — `"collect"`, `"take"`,
1048
+ * `"paginate"`, `"first"`, `"unique"`, … — i.e. how much of the narrowed set
1049
+ * the read actually loads.
1050
+ *
1051
+ * `undefined` when the chain reaches no recognised terminal (a reader passed
1052
+ * on, a bare `query(...)`) AND when a feeder predating this field produced
1053
+ * the read. The two are deliberately not distinguished: no consumer could act
1054
+ * on the difference, so the terminal-shaped lints skip the read either way
1055
+ * rather than guessing a terminal.
1056
+ */
1057
+ terminal?: string;
1045
1058
  }
1046
1059
  /**
1047
1060
  * The queue-shaped input the `queue_*` lints consume, produced by the codegen
package/dist/index.d.ts CHANGED
@@ -1010,9 +1010,10 @@ interface AdvisorProcedureProtection {
1010
1010
  }
1011
1011
  /**
1012
1012
  * One query read discovered in a function body — the input the
1013
- * `filter_without_index` lint consumes. Produced by the codegen feeder (which
1014
- * parses `ctx.db.query("table")…` chains from the AST); runtime callers don't
1015
- * supply it, so the lint simply finds nothing there.
1013
+ * `filter_without_index` / `filter_on_primary_key` / `unbounded_collect` lints
1014
+ * consume. Produced by the codegen feeder (which parses `ctx.db.query("table")…`
1015
+ * chains from the AST); runtime callers don't supply it, so those lints simply
1016
+ * find nothing there.
1016
1017
  */
1017
1018
  interface AdvisorQueryRead {
1018
1019
  /**
@@ -1042,6 +1043,18 @@ interface AdvisorQueryRead {
1042
1043
  line: number;
1043
1044
  /** The queried table; empty when the `query(...)` argument is not a string literal. */
1044
1045
  table: string;
1046
+ /**
1047
+ * The materializing call the chain ends in — `"collect"`, `"take"`,
1048
+ * `"paginate"`, `"first"`, `"unique"`, … — i.e. how much of the narrowed set
1049
+ * the read actually loads.
1050
+ *
1051
+ * `undefined` when the chain reaches no recognised terminal (a reader passed
1052
+ * on, a bare `query(...)`) AND when a feeder predating this field produced
1053
+ * the read. The two are deliberately not distinguished: no consumer could act
1054
+ * on the difference, so the terminal-shaped lints skip the read either way
1055
+ * rather than guessing a terminal.
1056
+ */
1057
+ terminal?: string;
1045
1058
  }
1046
1059
  /**
1047
1060
  * The queue-shaped input the `queue_*` lints consume, produced by the codegen
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{dedupeCacheKeys as m}from"./packem_shared/dedupeCacheKeys-7JgxaQWV.mjs";import s from"./packem_shared/constraintValidator-N339mfyx.mjs";import n from"./packem_shared/errorRateOutlier-CgK42sq0.mjs";import p from"./packem_shared/fanOutBreadth-DkloezYX.mjs";import f from"./packem_shared/hotShard-io-eF2Sx.mjs";import l from"./packem_shared/indexUtilization-QfbIH_w4.mjs";import u from"./packem_shared/actionFetchSsrf-BbWxy1Yf.mjs";import d from"./packem_shared/actionWithoutErrorHandling-rhzjCEqm.mjs";import c from"./packem_shared/adminRouteWithoutGuard-BbscPp1m.mjs";import $ from"./packem_shared/aiRawRunEscapeHatch-DfhjYm43.mjs";import h from"./packem_shared/aiRunWithoutLogging-B0K93Pga.mjs";import g from"./packem_shared/aiToolSideEffectPromptInjection-DnWQOoKi.mjs";import b from"./packem_shared/aiUnboundedGenerationPublic-iu7crnSa.mjs";import S from"./packem_shared/allowUnauthenticatedShardAccessEnabled-D4aMNBb0.mjs";import y from"./packem_shared/authApiCallWithoutHeaders-BHkik-EF.mjs";import U from"./packem_shared/authCsrfCheckDisabled-De9ammtV.mjs";import I from"./packem_shared/authEmailVerificationDisabled-BFKdJcx4.mjs";import A from"./packem_shared/authScimWithoutTransactions-DCtLXzks.mjs";import T from"./packem_shared/authSecureCookiesDisabled-B4WIzvMm.mjs";import R from"./packem_shared/authSessionFreshageZero-Bpcaf_Pe.mjs";import W from"./packem_shared/authTrustedOriginsWildcard-DjU9NXEm.mjs";import k from"./packem_shared/browserAllowPrivateTargets-Dxyeh6lg.mjs";import v from"./packem_shared/browserUserUrlWithoutAllowlist-BHBS6b0N.mjs";import w from"./packem_shared/circularFk-ZNjkhLaE.mjs";import x from"./packem_shared/containerInstanceKeyFromUserInput-C8S4T3ty.mjs";import F from"./packem_shared/containerOversizedInstance-B9wW_sU5.mjs";import C from"./packem_shared/containerPublicInternet-g0TRsayn.mjs";import M from"./packem_shared/containerRuntimeEgressRelaxation-BQ2ZD1aV.mjs";import P from"./packem_shared/containerStartEnableInternetOverride-B62WUura.mjs";import N from"./packem_shared/duplicateIndex-DFT7qLQD.mjs";import O from"./packem_shared/emptyIndex-Bp-Y81D0.mjs";import D from"./packem_shared/errorWithoutCatalog-0PbxiZL4.mjs";import E from"./packem_shared/exportSinkMisconfigured-DvpAaa6j.mjs";import G from"./packem_shared/externalSourceIncrementalNoDeletePath-DnoL64YY.mjs";import L from"./packem_shared/externalSourceOnGlobal-IYnALPlO.mjs";import K from"./packem_shared/externalSourceUnscoped-DqBp5jYr.mjs";import _ from"./packem_shared/filterOnPrimaryKey-Db947tQY.mjs";import j from"./packem_shared/filterWithoutIndex-pIpqk-O8.mjs";import q from"./packem_shared/flagGatesSecurityWithUnsafeDefault-DPGFEHTt.mjs";import z from"./packem_shared/geoIndexFieldNotGeopoint-DAGLZtaH.mjs";import H from"./packem_shared/geoIndexUnused-B5Oww9o2.mjs";import V from"./packem_shared/globalTableNearColumnLimit-DBQwb2MS.mjs";import B from"./packem_shared/hardcodedSecret-yu14ITlW.mjs";import Q from"./packem_shared/httpActionMissingAuthGuard-BZDdeW_D.mjs";import Z from"./packem_shared/httpActionResponseHeaderInjection-D063rB7U.mjs";import J from"./packem_shared/hyperdriveOutsideAction-B8ffQwgl.mjs";import X from"./packem_shared/identityUndeclaredClaimTrusted-BYRYGQ74.mjs";import Y from"./packem_shared/imagesUrlSourceFromUserInput-Cy5pc_dA.mjs";import oo from"./packem_shared/indexReferencesUnknownField-BPJjE0zg.mjs";import ro from"./packem_shared/insertManyUnsafeUserData-xLYAkcKj.mjs";import to from"./packem_shared/kvUnscopedUserKeyIdor-B4lGxRxh.mjs";import eo from"./packem_shared/mailInboundDispatchWithoutVerify-BivYJ5F9.mjs";import io from"./packem_shared/mailRecipientFromRequestInput-BR_oSh65.mjs";import ao from"./packem_shared/maskUncoveredPiiColumn-DqPfZM2O.mjs";import mo from"./packem_shared/maskWeakHashStrategyOnPii-DcEwc4Sz.mjs";import so from"./packem_shared/maskedRelationLeakViaWith-CxYk0MDW.mjs";import{i as no}from"./packem_shared/finding-BAVgqnCF.mjs";import po from"./packem_shared/mutatorFullRowReplace-CEgecSGm.mjs";import fo from"./packem_shared/nondeterministicQueryMutation-BZV_biUV.mjs";import lo from"./packem_shared/normalizeIdUsedAsAuthorization-FjISSXJm.mjs";import uo from"./packem_shared/notifyMissingPushConfig-C_GbVw3S.mjs";import co from"./packem_shared/notifySendOutsideAction-B9DcirhJ.mjs";import $o from"./packem_shared/outputProjectionMissingOnPublicRead-9Pm9766s.mjs";import ho from"./packem_shared/ownerFieldFromArgsNotAuth-D4K-_0aD.mjs";import go from"./packem_shared/paymentCreateWithoutAuthorize-CurtqN8K.mjs";import bo from"./packem_shared/paymentWebhookWideTolerance-I7ingfR8.mjs";import So from"./packem_shared/plaintextSecretInWranglerVariables-CmbygN6_.mjs";import yo from"./packem_shared/policyReferencesUnknownTable-D3WelAQa.mjs";import Uo from"./packem_shared/privilegedDispatchUnvalidatedPayload-74GuGye1.mjs";import Io from"./packem_shared/privilegedFanoutFromPublicProcedure-sMsZN4uP.mjs";import Ao from"./packem_shared/procedureWithoutStructuredEvent-l40nx9tX.mjs";import To from"./packem_shared/publicArgumentUsesAny-BzdcoJ9C.mjs";import Ro from"./packem_shared/publicMutationWithoutRatelimit-CozomYXw.mjs";import Wo from"./packem_shared/publicTableRlsOptoutConfusion-CaWtPGAn.mjs";import ko from"./packem_shared/queueWithoutDlq-BLY3iy9Q.mjs";import vo from"./packem_shared/r2sqlOutsideAction-DCMAsAeM.mjs";import wo from"./packem_shared/ratelimitDefaultMemoryStore-mOdI3Jmb.mjs";import xo from"./packem_shared/ratelimitKeySpoofableOrGlobal-DaGId_Fh.mjs";import Fo from"./packem_shared/ratelimitMiddlewareFailOpen-CTWVosEc.mjs";import Co from"./packem_shared/relationReferencesUnknownField-clqyB69Y.mjs";import Mo from"./packem_shared/relationReferencesUnknownTable-EOtKyaHx.mjs";import Po from"./packem_shared/rlsUncoveredTable-DYHgnoos.mjs";import No from"./packem_shared/shapeTargetsGlobalTable-SAlq2N-T.mjs";import Oo from"./packem_shared/shapeUnknownTable-DhvsbEtT.mjs";import Do from"./packem_shared/signupMutationWithoutDisposableGating-Tgv9SbsY.mjs";import Eo from"./packem_shared/softDeleteIncludeDeletedFromArgs-DgxpQHRA.mjs";import Go from"./packem_shared/sqlInjectionRisk-D2NaT-5c.mjs";import Lo from"./packem_shared/storageGenerateUploadUrlNoContentTypePin-C9cjl6Jh.mjs";import Ko from"./packem_shared/storageKeyFromUserArgs-CI-6kyte.mjs";import _o from"./packem_shared/storagePresignedUrlForPrivateContent-j7VhwIdu.mjs";import jo from"./packem_shared/storageUploadWithoutContentTypeAllowlist-oxKLecuf.mjs";import qo from"./packem_shared/storageUploadWithoutMaxSize-DUDxPutk.mjs";import zo from"./packem_shared/tableWithoutInsert-BV7qsPKx.mjs";import Ho from"./packem_shared/ttlFieldNotTimestamp-BFiRkSZD.mjs";import Vo from"./packem_shared/unboundedStringArgument-DN8w1bg-.mjs";import Bo from"./packem_shared/unindexedForeignKey-DK4eWXkb.mjs";import Qo from"./packem_shared/unindexedRelationTarget-B5fFq1E8.mjs";import Zo from"./packem_shared/unrestrictedWhereBranch-YKiydie4.mjs";import Jo from"./packem_shared/userCreatingMutationWithoutCaptcha-DIeHIeTU.mjs";import Xo from"./packem_shared/vectorsNamespaceFromUserInput-Cyq5B2kF.mjs";import Yo from"./packem_shared/workflowDuplicateStepName-cXF6Rpvg.mjs";import or from"./packem_shared/workflowUnknownTarget-BzKlYSCg.mjs";import rr from"./packem_shared/workflowUnused-D9fC9E9b.mjs";import{compareToBaseline as ie,parseAdvisorMap as ae}from"./packem_shared/compareToBaseline-BwXFusQG.mjs";import{gradeFromScore as se}from"./packem_shared/gradeFromScore-DW82YDVY.mjs";import{MAP_VERSION as pe,byCodepoint as fe,scoreAdvisor as le}from"./packem_shared/MAP_VERSION-DxkFCiLX.mjs";import{default as de}from"./packem_shared/classifySensitivity-B1kkm44P.mjs";import{fromServerSchema as $e}from"./packem_shared/fromServerSchema-9aaK1J7g.mjs";const tr={convex:"migrating/from-convex",firebase:"migrating/from-firebase",supabase:"migrating/from-supabase"},i={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=>no(i,{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 \`${tr[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"},er=[oo,Mo,Co,or,Yo,Oo,G,L,K,O,V,D,z,H,E,Ho,w,Bo,Qo,N,zo,rr,ko,_,j,No,po,fo,J,vo,y,yo,Po,ao,mo,F,C,Ro,Zo,Jo,Do,To,Vo,B,Go,c,go,eo,wo,k,Io,Ao,ro,b,u,d,ho,Ko,to,x,$,h,Xo,io,v,Uo,P,M,A,W,U,T,I,R,Y,xo,Wo,S,jo,qo,Lo,_o,Q,Z,Fo,q,g,X,bo,Eo,so,$o,lo,co,uo,So,i],ir=[f,l,s,n,p],ar=[...er,...ir],re=(r,o={})=>{const a=o.lints??ar,t=[];for(const e of a)o.source!==void 0&&e.source!==o.source||t.push(...e.run(r));return m(t)};export{ar as ALL_LINTS,pe as MAP_VERSION,ir as RUNTIME_LINTS,er as STATIC_LINTS,u as actionFetchSsrf,d as actionWithoutErrorHandling,c as adminRouteWithoutGuard,$ as aiRawRunEscapeHatch,h as aiRunWithoutLogging,g as aiToolSideEffectPromptInjection,b as aiUnboundedGenerationPublic,S as allowUnauthenticatedShardAccessEnabled,y as authApiCallWithoutHeaders,U as authCsrfCheckDisabled,I as authEmailVerificationDisabled,A as authScimWithoutTransactions,T as authSecureCookiesDisabled,R as authSessionFreshageZero,W as authTrustedOriginsWildcard,k as browserAllowPrivateTargets,v as browserUserUrlWithoutAllowlist,fe as byCodepoint,w as circularFk,de as classifySensitivity,ie as compareToBaseline,s as constraintValidator,x as containerInstanceKeyFromUserInput,F as containerOversizedInstance,C as containerPublicInternet,M as containerRuntimeEgressRelaxation,P as containerStartEnableInternetOverride,m as dedupeCacheKeys,N as duplicateIndex,O as emptyIndex,n as errorRateOutlier,D as errorWithoutCatalog,E as exportSinkMisconfigured,G as externalSourceIncrementalNoDeletePath,L as externalSourceOnGlobal,K as externalSourceUnscoped,p as fanOutBreadth,_ as filterOnPrimaryKey,j as filterWithoutIndex,q as flagGatesSecurityWithUnsafeDefault,$e as fromServerSchema,z as geoIndexFieldNotGeopoint,H as geoIndexUnused,V as globalTableNearColumnLimit,se as gradeFromScore,B as hardcodedSecret,f as hotShard,Q as httpActionMissingAuthGuard,Z as httpActionResponseHeaderInjection,J as hyperdriveOutsideAction,X as identityUndeclaredClaimTrusted,Y as imagesUrlSourceFromUserInput,oo as indexReferencesUnknownField,l as indexUtilization,ro as insertManyUnsafeUserData,to as kvUnscopedUserKeyIdor,eo as mailInboundDispatchWithoutVerify,io as mailRecipientFromRequestInput,ao as maskUncoveredPiiColumn,mo as maskWeakHashStrategyOnPii,so as maskedRelationLeakViaWith,po as mutatorFullRowReplace,fo as nondeterministicQueryMutation,lo as normalizeIdUsedAsAuthorization,uo as notifyMissingPushConfig,co as notifySendOutsideAction,$o as outputProjectionMissingOnPublicRead,ho as ownerFieldFromArgsNotAuth,ae as parseAdvisorMap,go as paymentCreateWithoutAuthorize,bo as paymentWebhookWideTolerance,So as plaintextSecretInWranglerVariables,yo as policyReferencesUnknownTable,Uo as privilegedDispatchUnvalidatedPayload,Io as privilegedFanoutFromPublicProcedure,Ao as procedureWithoutStructuredEvent,To as publicArgumentUsesAny,Ro as publicMutationWithoutRatelimit,Wo as publicTableRlsOptoutConfusion,ko as queueWithoutDlq,vo as r2sqlOutsideAction,wo as ratelimitDefaultMemoryStore,xo as ratelimitKeySpoofableOrGlobal,Fo as ratelimitMiddlewareFailOpen,Co as relationReferencesUnknownField,Mo as relationReferencesUnknownTable,Po as rlsUncoveredTable,re as runAdvisor,le as scoreAdvisor,No as shapeTargetsGlobalTable,Oo as shapeUnknownTable,Do as signupMutationWithoutDisposableGating,Eo as softDeleteIncludeDeletedFromArgs,Go as sqlInjectionRisk,Lo as storageGenerateUploadUrlNoContentTypePin,Ko as storageKeyFromUserArgs,_o as storagePresignedUrlForPrivateContent,jo as storageUploadWithoutContentTypeAllowlist,qo as storageUploadWithoutMaxSize,zo as tableWithoutInsert,Ho as ttlFieldNotTimestamp,Vo as unboundedStringArgument,Bo as unindexedForeignKey,Qo as unindexedRelationTarget,Zo as unrestrictedWhereBranch,Jo as userCreatingMutationWithoutCaptcha,Xo as vectorsNamespaceFromUserInput,Yo as workflowDuplicateStepName,or as workflowUnknownTarget,rr as workflowUnused};
1
+ import{dedupeCacheKeys as c}from"./packem_shared/dedupeCacheKeys-7JgxaQWV.mjs";import d from"./packem_shared/constraintValidator-N339mfyx.mjs";import u from"./packem_shared/errorRateOutlier-CgK42sq0.mjs";import h from"./packem_shared/fanOutBreadth-DkloezYX.mjs";import $ from"./packem_shared/hotShard-io-eF2Sx.mjs";import g from"./packem_shared/indexUtilization-QfbIH_w4.mjs";import b from"./packem_shared/actionFetchSsrf-BbWxy1Yf.mjs";import y from"./packem_shared/actionWithoutErrorHandling-rhzjCEqm.mjs";import w from"./packem_shared/adminRouteWithoutGuard-BbscPp1m.mjs";import v from"./packem_shared/aiRawRunEscapeHatch-DfhjYm43.mjs";import S from"./packem_shared/aiRunWithoutLogging-B0K93Pga.mjs";import A from"./packem_shared/aiToolSideEffectPromptInjection-DnWQOoKi.mjs";import I from"./packem_shared/aiUnboundedGenerationPublic-iu7crnSa.mjs";import U from"./packem_shared/allowUnauthenticatedShardAccessEnabled-D4aMNBb0.mjs";import R from"./packem_shared/authApiCallWithoutHeaders-BHkik-EF.mjs";import W from"./packem_shared/authCsrfCheckDisabled-De9ammtV.mjs";import k from"./packem_shared/authEmailVerificationDisabled-BFKdJcx4.mjs";import x from"./packem_shared/authScimWithoutTransactions-DCtLXzks.mjs";import T from"./packem_shared/authSecureCookiesDisabled-B4WIzvMm.mjs";import N from"./packem_shared/authSessionFreshageZero-Bpcaf_Pe.mjs";import F from"./packem_shared/authTrustedOriginsWildcard-DjU9NXEm.mjs";import C from"./packem_shared/browserAllowPrivateTargets-Dxyeh6lg.mjs";import M from"./packem_shared/browserUserUrlWithoutAllowlist-BHBS6b0N.mjs";import O from"./packem_shared/circularFk-ZNjkhLaE.mjs";import P from"./packem_shared/containerInstanceKeyFromUserInput-C8S4T3ty.mjs";import D from"./packem_shared/containerOversizedInstance-B9wW_sU5.mjs";import E from"./packem_shared/containerPublicInternet-g0TRsayn.mjs";import q from"./packem_shared/containerRuntimeEgressRelaxation-BQ2ZD1aV.mjs";import L from"./packem_shared/containerStartEnableInternetOverride-B62WUura.mjs";import K from"./packem_shared/duplicateIndex-DFT7qLQD.mjs";import G from"./packem_shared/emptyIndex-Bp-Y81D0.mjs";import _ from"./packem_shared/errorWithoutCatalog-0PbxiZL4.mjs";import j from"./packem_shared/exportSinkMisconfigured-DvpAaa6j.mjs";import z from"./packem_shared/externalSourceIncrementalNoDeletePath-DnoL64YY.mjs";import B from"./packem_shared/externalSourceOnGlobal-IYnALPlO.mjs";import H from"./packem_shared/externalSourceUnscoped-DqBp5jYr.mjs";import V from"./packem_shared/filterOnPrimaryKey-C7SeWewK.mjs";import Q from"./packem_shared/filterWithoutIndex-CyHJwVhc.mjs";import X from"./packem_shared/flagGatesSecurityWithUnsafeDefault-DPGFEHTt.mjs";import Z from"./packem_shared/geoIndexFieldNotGeopoint-DAGLZtaH.mjs";import J from"./packem_shared/geoIndexUnused-B5Oww9o2.mjs";import Y from"./packem_shared/globalTableNearColumnLimit-DBQwb2MS.mjs";import oo from"./packem_shared/hardcodedSecret-yu14ITlW.mjs";import ro from"./packem_shared/httpActionMissingAuthGuard-BZDdeW_D.mjs";import eo from"./packem_shared/httpActionResponseHeaderInjection-D063rB7U.mjs";import to from"./packem_shared/hyperdriveOutsideAction-B8ffQwgl.mjs";import io from"./packem_shared/identityUndeclaredClaimTrusted-BYRYGQ74.mjs";import ao from"./packem_shared/imagesUrlSourceFromUserInput-Cy5pc_dA.mjs";import so from"./packem_shared/indexReferencesUnknownField-EIL4UArJ.mjs";import no from"./packem_shared/insertManyUnsafeUserData-xLYAkcKj.mjs";import mo from"./packem_shared/kvUnscopedUserKeyIdor-B4lGxRxh.mjs";import lo from"./packem_shared/mailInboundDispatchWithoutVerify-BivYJ5F9.mjs";import po from"./packem_shared/mailRecipientFromRequestInput-BR_oSh65.mjs";import fo from"./packem_shared/maskUncoveredPiiColumn-DqPfZM2O.mjs";import co from"./packem_shared/maskWeakHashStrategyOnPii-DcEwc4Sz.mjs";import uo from"./packem_shared/maskedRelationLeakViaWith-CxYk0MDW.mjs";import{i as s}from"./packem_shared/finding-BAVgqnCF.mjs";import ho from"./packem_shared/mutatorFullRowReplace-CEgecSGm.mjs";import $o from"./packem_shared/nondeterministicQueryMutation-BZV_biUV.mjs";import go from"./packem_shared/normalizeIdUsedAsAuthorization-FjISSXJm.mjs";import bo from"./packem_shared/notifyMissingPushConfig-C_GbVw3S.mjs";import yo from"./packem_shared/notifySendOutsideAction-B9DcirhJ.mjs";import wo from"./packem_shared/outputProjectionMissingOnPublicRead-CjLp7ggc.mjs";import vo from"./packem_shared/ownerFieldFromArgsNotAuth-D4K-_0aD.mjs";import So from"./packem_shared/paymentCreateWithoutAuthorize-CurtqN8K.mjs";import Ao from"./packem_shared/paymentWebhookWideTolerance-I7ingfR8.mjs";import Io from"./packem_shared/plaintextSecretInWranglerVariables-CmbygN6_.mjs";import Uo from"./packem_shared/policyReferencesUnknownTable-D3WelAQa.mjs";import Ro from"./packem_shared/privilegedDispatchUnvalidatedPayload-74GuGye1.mjs";import Wo from"./packem_shared/privilegedFanoutFromPublicProcedure-sMsZN4uP.mjs";import ko from"./packem_shared/procedureWithoutStructuredEvent-l40nx9tX.mjs";import xo from"./packem_shared/publicArgumentUsesAny-BzdcoJ9C.mjs";import To from"./packem_shared/publicMutationWithoutRatelimit-DgE1OCbv.mjs";import No from"./packem_shared/publicTableRlsOptoutConfusion-C2s9mIhH.mjs";import Fo from"./packem_shared/queueWithoutDlq-BLY3iy9Q.mjs";import Co from"./packem_shared/r2sqlOutsideAction-DCMAsAeM.mjs";import Mo from"./packem_shared/ratelimitDefaultMemoryStore-mOdI3Jmb.mjs";import Oo from"./packem_shared/ratelimitKeySpoofableOrGlobal-DaGId_Fh.mjs";import Po from"./packem_shared/ratelimitMiddlewareFailOpen-CTWVosEc.mjs";import Do from"./packem_shared/relationReferencesUnknownField-C_3oUop_.mjs";import Eo from"./packem_shared/relationReferencesUnknownTable-EOtKyaHx.mjs";import qo from"./packem_shared/rlsUncoveredTable-DYHgnoos.mjs";import Lo from"./packem_shared/shapeTargetsGlobalTable-SAlq2N-T.mjs";import Ko from"./packem_shared/shapeUnknownTable-DhvsbEtT.mjs";import Go from"./packem_shared/signupMutationWithoutDisposableGating-CqIn86bT.mjs";import _o from"./packem_shared/softDeleteIncludeDeletedFromArgs-DgxpQHRA.mjs";import jo from"./packem_shared/sqlInjectionRisk-D2NaT-5c.mjs";import zo from"./packem_shared/storageGenerateUploadUrlNoContentTypePin-C9cjl6Jh.mjs";import Bo from"./packem_shared/storageKeyFromUserArgs-CI-6kyte.mjs";import Ho from"./packem_shared/storagePresignedUrlForPrivateContent-j7VhwIdu.mjs";import Vo from"./packem_shared/storageUploadWithoutContentTypeAllowlist-oxKLecuf.mjs";import Qo from"./packem_shared/storageUploadWithoutMaxSize-DUDxPutk.mjs";import Xo from"./packem_shared/tableWithoutInsert-BV7qsPKx.mjs";import Zo from"./packem_shared/ttlFieldNotTimestamp-BFiRkSZD.mjs";import{s as Jo,q as Yo}from"./packem_shared/helpers-yHInjqeD.mjs";import or from"./packem_shared/unboundedStringArgument-DN8w1bg-.mjs";import rr from"./packem_shared/unindexedForeignKey-DK4eWXkb.mjs";import er from"./packem_shared/unindexedRelationTarget-B5fFq1E8.mjs";import tr from"./packem_shared/unrestrictedWhereBranch-YKiydie4.mjs";import ir from"./packem_shared/userCreatingMutationWithoutCaptcha-CYgKnJ9j.mjs";import ar from"./packem_shared/vectorsNamespaceFromUserInput-Cyq5B2kF.mjs";import sr from"./packem_shared/workflowDuplicateStepName-cXF6Rpvg.mjs";import nr from"./packem_shared/workflowUnknownTarget-BzKlYSCg.mjs";import mr from"./packem_shared/workflowUnused-D9fC9E9b.mjs";import{compareToBaseline as ut,parseAdvisorMap as ht}from"./packem_shared/compareToBaseline-BwXFusQG.mjs";import{gradeFromScore as gt}from"./packem_shared/gradeFromScore-DW82YDVY.mjs";import{MAP_VERSION as yt,byCodepoint as wt,scoreAdvisor as vt}from"./packem_shared/MAP_VERSION-DxkFCiLX.mjs";import{default as At}from"./packem_shared/classifySensitivity-B1kkm44P.mjs";import{fromServerSchema as Ut}from"./packem_shared/fromServerSchema-9aaK1J7g.mjs";const lr={convex:"migrating/from-convex",firebase:"migrating/from-firebase",supabase:"migrating/from-supabase"},n={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:e=>e.staleMigrationImports===void 0?[]:e.staleMigrationImports.map(o=>s(n,{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 \`${lr[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"},pr=new Map([["global",{level:"WARN",scope:e=>`it reads the whole D1 table "${e}" over a cross-region round trip`}],["root",{level:"WARN",scope:e=>`it loads every row of "${e}" from the root Durable Object's SQLite into memory`}],["shardBy",{level:"INFO",scope:e=>`"${e}" is \`.shardBy()\`, so this collects one shard's rows rather than the whole table — bounded by a single tenant's row count`}]]),fr={level:"WARN",scope:e=>`it loads every row of "${e}"`},m={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:e=>{const o=[],i=Jo(e.schema);for(const r of e.queries??[]){if(r.terminal!=="collect"||r.hasIndex||r.table===""||r.hasFilter)continue;const t=i.get(r.table),{level:a,scope:l}=pr.get(t??"")??fr,p=Yo(r),f=a==="WARN"?` A live subscription over this query records a whole-table dependency, so every write to "${r.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(s(m,{cacheKey:`unbounded_collect:${r.file}:${r.line.toString()}:${r.table}`,detail:`Query on "${r.table}" at ${p} calls .collect() with no index and no filter — ${l(r.table)}.${f}`,level:a,metadata:{exportName:r.exportName,file:r.file,line:r.line,shardKind:t??"unknown",table:r.table}}))}return o},source:"static",title:"Unbounded collect"},cr=[so,Eo,Do,nr,sr,Ko,z,B,H,G,Y,_,Z,J,j,Zo,O,rr,er,K,Xo,mr,Fo,V,Q,m,Lo,ho,$o,to,Co,R,Uo,qo,fo,co,D,E,To,tr,ir,Go,xo,or,oo,jo,w,So,lo,Mo,C,Wo,ko,no,I,b,y,vo,Bo,mo,P,v,S,ar,po,M,Ro,L,q,x,F,W,T,k,N,ao,Oo,No,U,Vo,Qo,zo,Ho,ro,eo,Po,X,A,io,Ao,_o,uo,wo,go,yo,bo,Io,n],dr=[$,g,d,u,h],ur=[...cr,...dr],ft=(e,o={})=>{const i=o.lints??ur,r=[];for(const t of i)o.source!==void 0&&t.source!==o.source||r.push(...t.run(e));return c(r)};export{ur as ALL_LINTS,yt as MAP_VERSION,dr as RUNTIME_LINTS,cr as STATIC_LINTS,b as actionFetchSsrf,y as actionWithoutErrorHandling,w as adminRouteWithoutGuard,v as aiRawRunEscapeHatch,S as aiRunWithoutLogging,A as aiToolSideEffectPromptInjection,I as aiUnboundedGenerationPublic,U as allowUnauthenticatedShardAccessEnabled,R as authApiCallWithoutHeaders,W as authCsrfCheckDisabled,k as authEmailVerificationDisabled,x as authScimWithoutTransactions,T as authSecureCookiesDisabled,N as authSessionFreshageZero,F as authTrustedOriginsWildcard,C as browserAllowPrivateTargets,M as browserUserUrlWithoutAllowlist,wt as byCodepoint,O as circularFk,At as classifySensitivity,ut as compareToBaseline,d as constraintValidator,P as containerInstanceKeyFromUserInput,D as containerOversizedInstance,E as containerPublicInternet,q as containerRuntimeEgressRelaxation,L as containerStartEnableInternetOverride,c as dedupeCacheKeys,K as duplicateIndex,G as emptyIndex,u as errorRateOutlier,_ as errorWithoutCatalog,j as exportSinkMisconfigured,z as externalSourceIncrementalNoDeletePath,B as externalSourceOnGlobal,H as externalSourceUnscoped,h as fanOutBreadth,V as filterOnPrimaryKey,Q as filterWithoutIndex,X as flagGatesSecurityWithUnsafeDefault,Ut as fromServerSchema,Z as geoIndexFieldNotGeopoint,J as geoIndexUnused,Y as globalTableNearColumnLimit,gt as gradeFromScore,oo as hardcodedSecret,$ as hotShard,ro as httpActionMissingAuthGuard,eo as httpActionResponseHeaderInjection,to as hyperdriveOutsideAction,io as identityUndeclaredClaimTrusted,ao as imagesUrlSourceFromUserInput,so as indexReferencesUnknownField,g as indexUtilization,no as insertManyUnsafeUserData,mo as kvUnscopedUserKeyIdor,lo as mailInboundDispatchWithoutVerify,po as mailRecipientFromRequestInput,fo as maskUncoveredPiiColumn,co as maskWeakHashStrategyOnPii,uo as maskedRelationLeakViaWith,ho as mutatorFullRowReplace,$o as nondeterministicQueryMutation,go as normalizeIdUsedAsAuthorization,bo as notifyMissingPushConfig,yo as notifySendOutsideAction,wo as outputProjectionMissingOnPublicRead,vo as ownerFieldFromArgsNotAuth,ht as parseAdvisorMap,So as paymentCreateWithoutAuthorize,Ao as paymentWebhookWideTolerance,Io as plaintextSecretInWranglerVariables,Uo as policyReferencesUnknownTable,Ro as privilegedDispatchUnvalidatedPayload,Wo as privilegedFanoutFromPublicProcedure,ko as procedureWithoutStructuredEvent,xo as publicArgumentUsesAny,To as publicMutationWithoutRatelimit,No as publicTableRlsOptoutConfusion,Fo as queueWithoutDlq,Co as r2sqlOutsideAction,Mo as ratelimitDefaultMemoryStore,Oo as ratelimitKeySpoofableOrGlobal,Po as ratelimitMiddlewareFailOpen,Do as relationReferencesUnknownField,Eo as relationReferencesUnknownTable,qo as rlsUncoveredTable,ft as runAdvisor,vt as scoreAdvisor,Lo as shapeTargetsGlobalTable,Ko as shapeUnknownTable,Go as signupMutationWithoutDisposableGating,_o as softDeleteIncludeDeletedFromArgs,jo as sqlInjectionRisk,zo as storageGenerateUploadUrlNoContentTypePin,Bo as storageKeyFromUserArgs,Ho as storagePresignedUrlForPrivateContent,Vo as storageUploadWithoutContentTypeAllowlist,Qo as storageUploadWithoutMaxSize,Xo as tableWithoutInsert,Zo as ttlFieldNotTimestamp,or as unboundedStringArgument,rr as unindexedForeignKey,er as unindexedRelationTarget,tr as unrestrictedWhereBranch,ir as userCreatingMutationWithoutCaptcha,ar as vectorsNamespaceFromUserInput,sr as workflowDuplicateStepName,nr as workflowUnknownTarget,mr as workflowUnused};
@@ -0,0 +1 @@
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};
@@ -0,0 +1 @@
1
+ import{i as l}from"./finding-BAVgqnCF.mjs";import{s as c,q as u}from"./helpers-yHInjqeD.mjs";const s={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 r=u(e),a=d.get(e.table),o={exportName:e.exportName,file:e.file,line:e.line,shardKind:a??"unknown",table:e.table},n=`filter_without_index:${e.file}:${e.line.toString()}:${e.table}`;if(a==="shardBy"){t.push(l(s,{cacheKey:n,detail:`Query on "${e.table}" at ${r} 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(l(s,{cacheKey:n,detail:`Query on "${e.table}" at ${r} calls .filter() without an index — ${h}.`,metadata:o}))}return t},source:"static",title:"Filter without index"};export{s as default};
@@ -0,0 +1 @@
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 +1 @@
1
- import{i as o}from"./finding-BAVgqnCF.mjs";import{t as d}from"./helpers-DwlvEUCh.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
+ 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 +1 @@
1
- import{i as r}from"./finding-BAVgqnCF.mjs";import{P as l}from"./helpers-DwlvEUCh.mjs";const s={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({ … }))` 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 n=new Map;for(const e of o.schema.tables){const t=e.fields.filter(i=>l.has(i));t.length>0&&n.set(e.name,t)}const a=[];for(const e of o.rawRowReturns){if(e.visibility!=="public"||e.usesOutput||e.usesMask||e.table==="")continue;const t=n.get(e.table);t!==void 0&&a.push(r(s,{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({ … }))\` or mask the PII columns.`,metadata:{columns:t,exportName:e.exportName,file:e.file,line:e.line,table:e.table}}))}return a},source:"static",title:"Public query returns raw rows with PII and no output projection"};export{s as default};
1
+ import{i as r}from"./finding-BAVgqnCF.mjs";import{P as l}from"./helpers-yHInjqeD.mjs";const s={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({ … }))` 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 n=new Map;for(const e of o.schema.tables){const t=e.fields.filter(i=>l.has(i));t.length>0&&n.set(e.name,t)}const a=[];for(const e of o.rawRowReturns){if(e.visibility!=="public"||e.usesOutput||e.usesMask||e.table==="")continue;const t=n.get(e.table);t!==void 0&&a.push(r(s,{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({ … }))\` or mask the PII columns.`,metadata:{columns:t,exportName:e.exportName,file:e.file,line:e.line,table:e.table}}))}return a},source:"static",title:"Public query returns raw rows with PII and no output projection"};export{s as default};
@@ -1 +1 @@
1
- import{i as r}from"./finding-BAVgqnCF.mjs";import{i as a}from"./helpers-DwlvEUCh.mjs";const o=/contact|forgot|login|magic|otp|register|reset|signin|signup|subscribe|verify/iu,s={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(e=>a(e)&&!e.usesRateLimit).map(e=>{const t=o.test(e.exportName);return r(s,{cacheKey:`public_mutation_without_ratelimit:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) has no rate limit${t?" — its name suggests an auth/abuse-sensitive endpoint, so this is high-risk":""}. Add \`.use(rateLimit(...))\` or \`.use(protectPublic({ rateLimit }))\`.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind,sensitive:t}})}),source:"static",title:"Public write without a rate limit"};export{s as default};
1
+ import{i as r}from"./finding-BAVgqnCF.mjs";import{i as a}from"./helpers-yHInjqeD.mjs";const o=/contact|forgot|login|magic|otp|register|reset|signin|signup|subscribe|verify/iu,s={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(e=>a(e)&&!e.usesRateLimit).map(e=>{const t=o.test(e.exportName);return r(s,{cacheKey:`public_mutation_without_ratelimit:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) has no rate limit${t?" — its name suggests an auth/abuse-sensitive endpoint, so this is high-risk":""}. Add \`.use(rateLimit(...))\` or \`.use(protectPublic({ rateLimit }))\`.`,metadata:{exportName:e.exportName,file:e.file,kind:e.kind,sensitive:t}})}),source:"static",title:"Public write without a rate limit"};export{s as default};
@@ -1 +1 @@
1
- import{i as a}from"./finding-BAVgqnCF.mjs";import{o as i}from"./helpers-DwlvEUCh.mjs";const r={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=i(e);t.length!==0&&s.push(a(r,{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{r as default};
1
+ import{i as a}from"./finding-BAVgqnCF.mjs";import{o as i}from"./helpers-yHInjqeD.mjs";const r={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=i(e);t.length!==0&&s.push(a(r,{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{r as default};
@@ -1 +1 @@
1
- import{i as s}from"./finding-BAVgqnCF.mjs";import{t as c}from"./helpers-DwlvEUCh.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
+ 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 +1 @@
1
- import{i as s}from"./finding-BAVgqnCF.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";import{i as o}from"./helpers-DwlvEUCh.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{i as s}from"./finding-BAVgqnCF.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";import{i as o}from"./helpers-yHInjqeD.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 +1 @@
1
- import{i as t}from"./finding-BAVgqnCF.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";import{i as r}from"./helpers-DwlvEUCh.mjs";const s=a=>a.writesUserTable===!0?"writes a user/session table":a.callsMail===!0?"sends mail":a.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",o={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:a=>a.procedureProtections===void 0?[]:a.procedureProtections.filter(e=>r(e)&&(i(e.writesUserTable)||i(e.callsMail))&&!e.usesCaptcha).map(e=>t(o,{cacheKey:`user_creating_mutation_without_captcha:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) ${s(e)} but has no CAPTCHA check. Add \`.use(verifyTurnstile(...))\` 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{o as default};
1
+ import{i as t}from"./finding-BAVgqnCF.mjs";import{m as i}from"./procedure-protections-DRQotu9I.mjs";import{i as r}from"./helpers-yHInjqeD.mjs";const s=a=>a.writesUserTable===!0?"writes a user/session table":a.callsMail===!0?"sends mail":a.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",o={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:a=>a.procedureProtections===void 0?[]:a.procedureProtections.filter(e=>r(e)&&(i(e.writesUserTable)||i(e.callsMail))&&!e.usesCaptcha).map(e=>t(o,{cacheKey:`user_creating_mutation_without_captcha:${e.file}:${e.exportName}`,detail:`Public ${e.kind} \`${e.exportName}\` (${e.file}) ${s(e)} but has no CAPTCHA check. Add \`.use(verifyTurnstile(...))\` 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{o as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/advisor",
3
- "version": "1.0.0-alpha.76",
3
+ "version": "1.0.0-alpha.77",
4
4
  "description": "Schema & query lints (splinter-style advisors) for Lunora, feeding the Studio Advisors view",
5
5
  "keywords": [
6
6
  "advisor",
@@ -1 +0,0 @@
1
- import{i}from"./finding-BAVgqnCF.mjs";const a={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=e.line>0?`${e.file}:${e.line.toString()}`:e.file;return i(a,{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{a as default};
@@ -1 +0,0 @@
1
- import{i as l}from"./finding-BAVgqnCF.mjs";const s={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=new Map(i.schema.tables.map(e=>[e.name,e.shardKind]));for(const e of i.queries??[]){if(!e.hasFilter||e.hasIndex||e.table===""||e.filtersPrimaryKey===!0)continue;const n=e.line>0?`${e.file}:${e.line.toString()}`:e.file,a=d.get(e.table),r={exportName:e.exportName,file:e.file,line:e.line,shardKind:a??"unknown",table:e.table},o=`filter_without_index:${e.file}:${e.line.toString()}:${e.table}`;if(a==="shardBy"){t.push(l(s,{cacheKey:o,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:r}));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(l(s,{cacheKey:o,detail:`Query on "${e.table}" at ${n} calls .filter() without an index — ${h}.`,metadata:r}))}return t},source:"static",title:"Filter without index"};export{s as default};
@@ -1 +0,0 @@
1
- const t=new Set(["accountId","authorId","createdBy","createdById","organizationId","orgId","ownerId","tenantId","updatedBy","userId","workspaceId"]),a=new Set(["address","dateOfBirth","dob","email","firstName","lastName","phone","phoneNumber","socialSecurityNumber","ssn"]),d=new Set(["_creationTime","_id"]),n=e=>e.fields.filter(i=>t.has(i)||a.has(i)),s=e=>new Set([...d,...e.fields]),o=e=>e.visibility==="public"&&(e.kind==="mutation"||e.kind==="action");export{a as P,o as i,n as o,s as t};