@lunora/advisor 1.0.0-alpha.7 → 1.0.0-alpha.71

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 (152) hide show
  1. package/LICENSE.md +6 -0
  2. package/README.md +60 -5
  3. package/dist/index.d.mts +4005 -966
  4. package/dist/index.d.ts +4005 -966
  5. package/dist/index.mjs +1 -79
  6. package/dist/packem_shared/MAP_VERSION-DxkFCiLX.mjs +1 -0
  7. package/dist/packem_shared/actionFetchSsrf-BbWxy1Yf.mjs +1 -0
  8. package/dist/packem_shared/actionWithoutErrorHandling-rhzjCEqm.mjs +1 -0
  9. package/dist/packem_shared/adminRouteWithoutGuard-BbscPp1m.mjs +1 -0
  10. package/dist/packem_shared/aiRawRunEscapeHatch-DfhjYm43.mjs +1 -0
  11. package/dist/packem_shared/aiRunWithoutLogging-B0K93Pga.mjs +1 -0
  12. package/dist/packem_shared/aiToolSideEffectPromptInjection-DnWQOoKi.mjs +1 -0
  13. package/dist/packem_shared/aiUnboundedGenerationPublic-iu7crnSa.mjs +1 -0
  14. package/dist/packem_shared/allowUnauthenticatedShardAccessEnabled-D4aMNBb0.mjs +1 -0
  15. package/dist/packem_shared/argument-derived-sink-BpO3NPPM.mjs +1 -0
  16. package/dist/packem_shared/authApiCallWithoutHeaders-BHkik-EF.mjs +1 -0
  17. package/dist/packem_shared/authCsrfCheckDisabled-De9ammtV.mjs +1 -0
  18. package/dist/packem_shared/authEmailVerificationDisabled-BFKdJcx4.mjs +1 -0
  19. package/dist/packem_shared/authScimWithoutTransactions-DCtLXzks.mjs +1 -0
  20. package/dist/packem_shared/authSecureCookiesDisabled-B4WIzvMm.mjs +1 -0
  21. package/dist/packem_shared/authSessionFreshageZero-Bpcaf_Pe.mjs +1 -0
  22. package/dist/packem_shared/authTrustedOriginsWildcard-DjU9NXEm.mjs +1 -0
  23. package/dist/packem_shared/browserAllowPrivateTargets-Dxyeh6lg.mjs +1 -0
  24. package/dist/packem_shared/browserUserUrlWithoutAllowlist-BHBS6b0N.mjs +1 -0
  25. package/dist/packem_shared/circularFk-ZNjkhLaE.mjs +1 -0
  26. package/dist/packem_shared/classifySensitivity-B1kkm44P.mjs +1 -0
  27. package/dist/packem_shared/compareToBaseline-BwXFusQG.mjs +1 -0
  28. package/dist/packem_shared/constraintValidator-N339mfyx.mjs +1 -0
  29. package/dist/packem_shared/containerInstanceKeyFromUserInput-C8S4T3ty.mjs +1 -0
  30. package/dist/packem_shared/containerOversizedInstance-B9wW_sU5.mjs +1 -0
  31. package/dist/packem_shared/containerPublicInternet-g0TRsayn.mjs +1 -0
  32. package/dist/packem_shared/containerRuntimeEgressRelaxation-BQ2ZD1aV.mjs +1 -0
  33. package/dist/packem_shared/containerStartEnableInternetOverride-B62WUura.mjs +1 -0
  34. package/dist/packem_shared/dedupeCacheKeys-7JgxaQWV.mjs +1 -0
  35. package/dist/packem_shared/duplicateIndex-DFT7qLQD.mjs +1 -0
  36. package/dist/packem_shared/emptyIndex-Bp-Y81D0.mjs +1 -0
  37. package/dist/packem_shared/errorRateOutlier-CgK42sq0.mjs +1 -0
  38. package/dist/packem_shared/errorWithoutCatalog-0PbxiZL4.mjs +1 -0
  39. package/dist/packem_shared/exportSinkMisconfigured-DvpAaa6j.mjs +1 -0
  40. package/dist/packem_shared/externalSourceIncrementalNoDeletePath-DnoL64YY.mjs +1 -0
  41. package/dist/packem_shared/externalSourceOnGlobal-IYnALPlO.mjs +1 -0
  42. package/dist/packem_shared/externalSourceUnscoped-DqBp5jYr.mjs +1 -0
  43. package/dist/packem_shared/filterOnPrimaryKey-Db947tQY.mjs +1 -0
  44. package/dist/packem_shared/filterWithoutIndex-pIpqk-O8.mjs +1 -0
  45. package/dist/packem_shared/finding-BAVgqnCF.mjs +1 -0
  46. package/dist/packem_shared/fk-index-Bmk19RvQ.mjs +1 -0
  47. package/dist/packem_shared/flagGatesSecurityWithUnsafeDefault-DPGFEHTt.mjs +1 -0
  48. package/dist/packem_shared/fromServerSchema-9aaK1J7g.mjs +1 -0
  49. package/dist/packem_shared/geoIndexFieldNotGeopoint-DAGLZtaH.mjs +1 -0
  50. package/dist/packem_shared/geoIndexUnused-B5Oww9o2.mjs +1 -0
  51. package/dist/packem_shared/gradeFromScore-DW82YDVY.mjs +1 -0
  52. package/dist/packem_shared/hardcodedSecret-yu14ITlW.mjs +1 -0
  53. package/dist/packem_shared/helpers-DwlvEUCh.mjs +1 -0
  54. package/dist/packem_shared/hotShard-io-eF2Sx.mjs +1 -0
  55. package/dist/packem_shared/httpActionMissingAuthGuard-BZDdeW_D.mjs +1 -0
  56. package/dist/packem_shared/httpActionResponseHeaderInjection-D063rB7U.mjs +1 -0
  57. package/dist/packem_shared/hyperdriveOutsideAction-B8ffQwgl.mjs +1 -0
  58. package/dist/packem_shared/identityUndeclaredClaimTrusted-BYRYGQ74.mjs +1 -0
  59. package/dist/packem_shared/imagesUrlSourceFromUserInput-Cy5pc_dA.mjs +1 -0
  60. package/dist/packem_shared/indexReferencesUnknownField-BPJjE0zg.mjs +1 -0
  61. package/dist/packem_shared/indexUtilization-QfbIH_w4.mjs +1 -0
  62. package/dist/packem_shared/insertManyUnsafeUserData-xLYAkcKj.mjs +1 -0
  63. package/dist/packem_shared/kvUnscopedUserKeyIdor-B4lGxRxh.mjs +1 -0
  64. package/dist/packem_shared/mailInboundDispatchWithoutVerify-BivYJ5F9.mjs +1 -0
  65. package/dist/packem_shared/mailRecipientFromRequestInput-BR_oSh65.mjs +1 -0
  66. package/dist/packem_shared/maskUncoveredPiiColumn-DqPfZM2O.mjs +1 -0
  67. package/dist/packem_shared/maskWeakHashStrategyOnPii-DcEwc4Sz.mjs +1 -0
  68. package/dist/packem_shared/maskedRelationLeakViaWith-CxYk0MDW.mjs +1 -0
  69. package/dist/packem_shared/mutatorFullRowReplace-CEgecSGm.mjs +1 -0
  70. package/dist/packem_shared/nondeterministicQueryMutation-BZV_biUV.mjs +1 -0
  71. package/dist/packem_shared/normalizeIdUsedAsAuthorization-FjISSXJm.mjs +1 -0
  72. package/dist/packem_shared/notifyMissingPushConfig-C_GbVw3S.mjs +1 -0
  73. package/dist/packem_shared/notifySendOutsideAction-B9DcirhJ.mjs +1 -0
  74. package/dist/packem_shared/outputProjectionMissingOnPublicRead-9Pm9766s.mjs +1 -0
  75. package/dist/packem_shared/ownerFieldFromArgsNotAuth-D4K-_0aD.mjs +1 -0
  76. package/dist/packem_shared/paymentCreateWithoutAuthorize-CurtqN8K.mjs +1 -0
  77. package/dist/packem_shared/paymentWebhookWideTolerance-I7ingfR8.mjs +1 -0
  78. package/dist/packem_shared/plaintextSecretInWranglerVariables-CmbygN6_.mjs +1 -0
  79. package/dist/packem_shared/policyReferencesUnknownTable-D3WelAQa.mjs +1 -0
  80. package/dist/packem_shared/privilegedDispatchUnvalidatedPayload-74GuGye1.mjs +1 -0
  81. package/dist/packem_shared/privilegedFanoutFromPublicProcedure-sMsZN4uP.mjs +1 -0
  82. package/dist/packem_shared/procedure-protections-DRQotu9I.mjs +1 -0
  83. package/dist/packem_shared/procedureWithoutStructuredEvent-l40nx9tX.mjs +1 -0
  84. package/dist/packem_shared/publicArgumentUsesAny-BzdcoJ9C.mjs +1 -0
  85. package/dist/packem_shared/publicMutationWithoutRatelimit-CozomYXw.mjs +1 -0
  86. package/dist/packem_shared/publicTableRlsOptoutConfusion-CaWtPGAn.mjs +1 -0
  87. package/dist/packem_shared/queueWithoutDlq-BLY3iy9Q.mjs +1 -0
  88. package/dist/packem_shared/r2sqlOutsideAction-DCMAsAeM.mjs +1 -0
  89. package/dist/packem_shared/ratelimitDefaultMemoryStore-mOdI3Jmb.mjs +1 -0
  90. package/dist/packem_shared/ratelimitKeySpoofableOrGlobal-DaGId_Fh.mjs +1 -0
  91. package/dist/packem_shared/ratelimitMiddlewareFailOpen-CTWVosEc.mjs +1 -0
  92. package/dist/packem_shared/relationReferencesUnknownField-clqyB69Y.mjs +1 -0
  93. package/dist/packem_shared/relationReferencesUnknownTable-EOtKyaHx.mjs +1 -0
  94. package/dist/packem_shared/rlsUncoveredTable-DYHgnoos.mjs +1 -0
  95. package/dist/packem_shared/shapeTargetsGlobalTable-SAlq2N-T.mjs +1 -0
  96. package/dist/packem_shared/shapeUnknownTable-DhvsbEtT.mjs +1 -0
  97. package/dist/packem_shared/signupMutationWithoutDisposableGating-Tgv9SbsY.mjs +1 -0
  98. package/dist/packem_shared/softDeleteIncludeDeletedFromArgs-DgxpQHRA.mjs +1 -0
  99. package/dist/packem_shared/sqlInjectionRisk-D2NaT-5c.mjs +1 -0
  100. package/dist/packem_shared/storageGenerateUploadUrlNoContentTypePin-C9cjl6Jh.mjs +1 -0
  101. package/dist/packem_shared/storageKeyFromUserArgs-CI-6kyte.mjs +1 -0
  102. package/dist/packem_shared/storagePresignedUrlForPrivateContent-j7VhwIdu.mjs +1 -0
  103. package/dist/packem_shared/storageUploadWithoutContentTypeAllowlist-oxKLecuf.mjs +1 -0
  104. package/dist/packem_shared/storageUploadWithoutMaxSize-DUDxPutk.mjs +1 -0
  105. package/dist/packem_shared/tableWithoutInsert-BV7qsPKx.mjs +1 -0
  106. package/dist/packem_shared/ttlFieldNotTimestamp-BFiRkSZD.mjs +1 -0
  107. package/dist/packem_shared/unboundedStringArgument-DN8w1bg-.mjs +1 -0
  108. package/dist/packem_shared/unindexedForeignKey-DK4eWXkb.mjs +1 -0
  109. package/dist/packem_shared/unindexedRelationTarget-B5fFq1E8.mjs +1 -0
  110. package/dist/packem_shared/unrestrictedWhereBranch-YKiydie4.mjs +1 -0
  111. package/dist/packem_shared/userCreatingMutationWithoutCaptcha-DIeHIeTU.mjs +1 -0
  112. package/dist/packem_shared/vectorsNamespaceFromUserInput-Cyq5B2kF.mjs +1 -0
  113. package/dist/packem_shared/workflowDuplicateStepName-cXF6Rpvg.mjs +1 -0
  114. package/dist/packem_shared/workflowUnknownTarget-BzKlYSCg.mjs +1 -0
  115. package/dist/packem_shared/workflowUnused-D9fC9E9b.mjs +1 -0
  116. package/package.json +3 -2
  117. package/dist/packem_shared/AE_METRIC_EVENTS-DexctYv6.mjs +0 -85
  118. package/dist/packem_shared/adminRouteWithoutGuard-UUGBkAjU.mjs +0 -33
  119. package/dist/packem_shared/authApiCallWithoutHeaders-BeJhCZaf.mjs +0 -38
  120. package/dist/packem_shared/circularFk-B2freHrP.mjs +0 -84
  121. package/dist/packem_shared/constraintValidator-Dr9Py3FD.mjs +0 -186
  122. package/dist/packem_shared/containerOversizedInstance-5U1VKPRM.mjs +0 -36
  123. package/dist/packem_shared/containerPublicInternet-CuNerJE5.mjs +0 -30
  124. package/dist/packem_shared/duplicateIndex-BOublMSt.mjs +0 -57
  125. package/dist/packem_shared/emptyIndex-BX8EuEY7.mjs +0 -32
  126. package/dist/packem_shared/filterWithoutIndex-BYVeJaSs.mjs +0 -31
  127. package/dist/packem_shared/finding-Dm_zvzS1.mjs +0 -16
  128. package/dist/packem_shared/fk-index-IUK1ukgs.mjs +0 -7
  129. package/dist/packem_shared/fromServerSchema-DinF1nph.mjs +0 -50
  130. package/dist/packem_shared/hardcodedSecret-W2pz1UZB.mjs +0 -35
  131. package/dist/packem_shared/helpers-DNCkMWZQ.mjs +0 -4
  132. package/dist/packem_shared/hotShard-Ir5D0B6J.mjs +0 -48
  133. package/dist/packem_shared/hyperdriveOutsideAction-BgZqX7Xg.mjs +0 -30
  134. package/dist/packem_shared/indexReferencesUnknownField-DH0_dbUY.mjs +0 -36
  135. package/dist/packem_shared/indexUtilization-B5DMQ3bI.mjs +0 -45
  136. package/dist/packem_shared/maskUncoveredPiiColumn-DjGIPG6M.mjs +0 -61
  137. package/dist/packem_shared/nondeterministicQueryMutation-GXES1fLp.mjs +0 -35
  138. package/dist/packem_shared/policyReferencesUnknownTable-DtaIEovd.mjs +0 -38
  139. package/dist/packem_shared/publicArgumentUsesAny-C71b2NCf.mjs +0 -32
  140. package/dist/packem_shared/publicMutationWithoutRatelimit-xBpJ6GWK.mjs +0 -36
  141. package/dist/packem_shared/r2sqlOutsideAction-CtqxvMuV.mjs +0 -30
  142. package/dist/packem_shared/relationReferencesUnknownField-YznyXt_7.mjs +0 -54
  143. package/dist/packem_shared/relationReferencesUnknownTable-DrorpKYe.mjs +0 -33
  144. package/dist/packem_shared/rlsUncoveredTable-CxEfZ5eZ.mjs +0 -56
  145. package/dist/packem_shared/sqlInjectionRisk-zwytYGLt.mjs +0 -26
  146. package/dist/packem_shared/tableWithoutInsert-CbbaYIP4.mjs +0 -34
  147. package/dist/packem_shared/unboundedStringArgument-DThg2-wt.mjs +0 -32
  148. package/dist/packem_shared/unindexedForeignKey-BgJbKyqK.mjs +0 -45
  149. package/dist/packem_shared/unindexedRelationTarget-D6eyj6Xx.mjs +0 -53
  150. package/dist/packem_shared/userCreatingMutationWithoutCaptcha-CH31YsUZ.mjs +0 -42
  151. package/dist/packem_shared/workflowUnknownTarget-Cdd7WhKQ.mjs +0 -34
  152. package/dist/packem_shared/workflowUnused-D0jHxdz9.mjs +0 -38
@@ -1,186 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const MAX_EXAMPLE_IDS = 5;
4
- const rowId = (row) => typeof row["_id"] === "string" ? row["_id"] : "?";
5
- const truncatedSuffix = (capped, cap, phrase) => capped ? ` (sample capped at ${cap.toString()} rows — ${phrase})` : "";
6
- const formatExamples = (ids, total) => {
7
- const examples = ids.slice(0, MAX_EXAMPLE_IDS);
8
- const more = total - examples.length;
9
- const list = examples.map((id) => `"${id}"`).join(", ");
10
- return more > 0 ? `${list} (+${more.toString()} more)` : list;
11
- };
12
- const checkFkRelation = (lint, sample, relation, targetSample) => {
13
- if (relation.kind !== "one") {
14
- return void 0;
15
- }
16
- const fkColumn = relation.field;
17
- const danglingIds = [];
18
- for (const row of sample.rows) {
19
- const fkValue = row[fkColumn];
20
- if (fkValue === null || fkValue === void 0) {
21
- continue;
22
- }
23
- const fkString = String(fkValue);
24
- if (!targetSample.existingIds.has(fkString)) {
25
- danglingIds.push(rowId(row));
26
- }
27
- }
28
- if (danglingIds.length === 0) {
29
- return void 0;
30
- }
31
- const isTruncated = sample.truncated || targetSample.truncated;
32
- const cap = Math.min(sample.cap, targetSample.cap);
33
- const detail = `Table "${sample.table}": ${danglingIds.length.toString()} sampled row(s) have a dangling FK value in column "${fkColumn}" — the referenced "${relation.table}" row does not exist. Row ids: ${formatExamples(danglingIds, danglingIds.length)}${truncatedSuffix(isTruncated, cap, "more rows may exist beyond the window")}.`;
34
- return emit(lint, {
35
- cacheKey: `constraint_validator:fk:${sample.table}:${fkColumn}`,
36
- detail,
37
- metadata: {
38
- cap,
39
- column: fkColumn,
40
- count: danglingIds.length,
41
- examples: danglingIds.slice(0, MAX_EXAMPLE_IDS),
42
- kind: "fk",
43
- referencesTable: relation.table,
44
- table: sample.table,
45
- truncated: isTruncated
46
- }
47
- });
48
- };
49
- const checkNullField = (lint, sample, field, isRequired) => {
50
- if (!isRequired) {
51
- return void 0;
52
- }
53
- const nullIds = [];
54
- for (const row of sample.rows) {
55
- const value = row[field];
56
- if (value === null || value === void 0) {
57
- nullIds.push(rowId(row));
58
- }
59
- }
60
- if (nullIds.length === 0) {
61
- return void 0;
62
- }
63
- const detail = `Table "${sample.table}": ${nullIds.length.toString()} sampled row(s) have a null/missing value in column "${field}", which is declared as non-optional. Row ids: ${formatExamples(nullIds, nullIds.length)}${truncatedSuffix(sample.truncated, sample.cap, "more rows may exist beyond the window")}.`;
64
- return emit(lint, {
65
- cacheKey: `constraint_validator:null:${sample.table}:${field}`,
66
- detail,
67
- metadata: {
68
- cap: sample.cap,
69
- column: field,
70
- count: nullIds.length,
71
- examples: nullIds.slice(0, MAX_EXAMPLE_IDS),
72
- kind: "null",
73
- table: sample.table,
74
- truncated: sample.truncated
75
- }
76
- });
77
- };
78
- const compositeKey = (row, fields) => {
79
- const parts = [];
80
- for (const field of fields) {
81
- const value = row[field];
82
- if (value === null || value === void 0) {
83
- return void 0;
84
- }
85
- parts.push(JSON.stringify(value));
86
- }
87
- return parts.join("|");
88
- };
89
- const recordDuplicate = (id, existingId, duplicateIds) => {
90
- if (!duplicateIds.includes(existingId)) {
91
- duplicateIds.push(existingId);
92
- }
93
- duplicateIds.push(id);
94
- };
95
- const checkUniqueIndex = (lint, sample, index) => {
96
- if (index.kind !== "index" || index.unique !== true) {
97
- return void 0;
98
- }
99
- const seen = /* @__PURE__ */ new Map();
100
- const duplicateIds = [];
101
- for (const row of sample.rows) {
102
- const id = rowId(row);
103
- const key = compositeKey(row, index.fields);
104
- if (key === void 0) {
105
- continue;
106
- }
107
- const existingId = seen.get(key);
108
- if (existingId === void 0) {
109
- seen.set(key, id);
110
- } else {
111
- recordDuplicate(id, existingId, duplicateIds);
112
- }
113
- }
114
- if (duplicateIds.length === 0) {
115
- return void 0;
116
- }
117
- const detail = `Table "${sample.table}": ${duplicateIds.length.toString()} sampled row(s) share duplicate values on unique index "${index.name}" (${index.fields.join(", ")}). Row ids: ${formatExamples(duplicateIds, duplicateIds.length)}${truncatedSuffix(sample.truncated, sample.cap, "duplicates beyond the window may exist")}.`;
118
- return emit(lint, {
119
- cacheKey: `constraint_validator:unique:${sample.table}:${index.name}`,
120
- detail,
121
- metadata: {
122
- cap: sample.cap,
123
- count: duplicateIds.length,
124
- examples: duplicateIds.slice(0, MAX_EXAMPLE_IDS),
125
- fields: index.fields,
126
- index: index.name,
127
- kind: "unique",
128
- table: sample.table,
129
- truncated: sample.truncated
130
- }
131
- });
132
- };
133
- const checkTable = (lint, sample, sampleByTable, tableByName) => {
134
- const schemaTable = tableByName.get(sample.table);
135
- if (!schemaTable) {
136
- return [];
137
- }
138
- const findings = [];
139
- for (const relation of schemaTable.relations) {
140
- const targetSample = sampleByTable.get(relation.table);
141
- if (!targetSample) {
142
- continue;
143
- }
144
- const f = checkFkRelation(lint, sample, relation, targetSample);
145
- if (f) {
146
- findings.push(f);
147
- }
148
- }
149
- for (const field of schemaTable.fields) {
150
- const isRequired = !schemaTable.optionalFields?.has(field);
151
- const f = checkNullField(lint, sample, field, isRequired);
152
- if (f) {
153
- findings.push(f);
154
- }
155
- }
156
- for (const index of schemaTable.indexes) {
157
- const f = checkUniqueIndex(lint, sample, index);
158
- if (f) {
159
- findings.push(f);
160
- }
161
- }
162
- return findings;
163
- };
164
- const constraintValidator = {
165
- categories: ["SCHEMA"],
166
- description: "Sampled rows violate one or more declared constraints: a foreign-key column references a non-existent row, a non-optional column contains null, or a unique-indexed column has duplicate values. These violations indicate data inserted before a constraint was enforced (e.g. a schema migration, a raw import, or a bug in a past data-migration transform).",
167
- facing: "INTERNAL",
168
- level: "WARN",
169
- name: "constraint_validator",
170
- remediation: "Inspect the listed rows and correct or remove the violating values. Run a data migration to backfill nulls, dedup unique violations, or re-link dangling FK references. For FK violations, confirm the target table rows exist before fixing the referencing rows.",
171
- run: (context) => {
172
- if (!context.tableSamples || context.tableSamples.length === 0) {
173
- return [];
174
- }
175
- const sampleByTable = /* @__PURE__ */ new Map();
176
- for (const sample of context.tableSamples) {
177
- sampleByTable.set(sample.table, sample);
178
- }
179
- const tableByName = new Map(context.schema.tables.map((table) => [table.name, table]));
180
- return context.tableSamples.flatMap((sample) => checkTable(constraintValidator, sample, sampleByTable, tableByName));
181
- },
182
- source: "runtime",
183
- title: "Constraint violation"
184
- };
185
-
186
- export { constraintValidator as default };
@@ -1,36 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const LARGE_NAMED = /* @__PURE__ */ new Set(["standard-3", "standard-4"]);
4
- const VCPU_THRESHOLD = 2;
5
- const MEMORY_MIB_THRESHOLD = 4096;
6
- const isOversized = (instanceType) => typeof instanceType === "string" ? LARGE_NAMED.has(instanceType) : (instanceType.vcpu ?? 0) > VCPU_THRESHOLD || (instanceType.memoryMib ?? 0) > MEMORY_MIB_THRESHOLD;
7
- const containerOversizedInstance = {
8
- categories: ["PERFORMANCE"],
9
- description: "A container is declared on a large instance type, whose provisioned memory and disk are billed for the lifetime of every running instance.",
10
- facing: "INTERNAL",
11
- level: "INFO",
12
- name: "container_oversized_instance",
13
- remediation: "Confirm the workload needs this size; a smaller instanceType (lite/basic/standard-1) costs less while idle-but-running.",
14
- run: (context) => {
15
- const findings = [];
16
- for (const container of context.containers ?? []) {
17
- const { instanceType } = container;
18
- if (instanceType === void 0 || !isOversized(instanceType)) {
19
- continue;
20
- }
21
- const size = typeof instanceType === "string" ? instanceType : JSON.stringify(instanceType);
22
- findings.push(
23
- emit(containerOversizedInstance, {
24
- cacheKey: `container_oversized_instance:${container.exportName}`,
25
- detail: `Container "${container.exportName}" uses a large instance type (${size}); its memory + disk are billed while any instance runs.`,
26
- metadata: { container: container.exportName, instanceType }
27
- })
28
- );
29
- }
30
- return findings;
31
- },
32
- source: "static",
33
- title: "Oversized container instance"
34
- };
35
-
36
- export { containerOversizedInstance as default };
@@ -1,30 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const containerPublicInternet = {
4
- categories: ["SECURITY"],
5
- description: "A container leaves outbound internet access at the default (enabled), which is billed per GB of egress and widens the attack surface.",
6
- facing: "INTERNAL",
7
- level: "INFO",
8
- name: "container_public_internet",
9
- remediation: "Set `enableInternet: false` on the container if it doesn't call external services, or `true` to opt in deliberately.",
10
- run: (context) => {
11
- const findings = [];
12
- for (const container of context.containers ?? []) {
13
- if (container.enableInternet !== void 0) {
14
- continue;
15
- }
16
- findings.push(
17
- emit(containerPublicInternet, {
18
- cacheKey: `container_public_internet:${container.exportName}`,
19
- detail: `Container "${container.exportName}" doesn't set enableInternet, so outbound internet is on by default (egress is billed).`,
20
- metadata: { container: container.exportName }
21
- })
22
- );
23
- }
24
- return findings;
25
- },
26
- source: "static",
27
- title: "Container egress enabled by default"
28
- };
29
-
30
- export { containerPublicInternet as default };
@@ -1,57 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const isLeadingPrefix = (a, b) => {
4
- if (a.length > b.length) {
5
- return false;
6
- }
7
- return a.every((field, position) => field === b[position]);
8
- };
9
- const duplicateIndex = {
10
- categories: ["PERFORMANCE"],
11
- description: "A secondary index is redundant because another index already covers every lookup it serves (its columns are a leading prefix of the other's). The redundant index costs storage and is maintained on every write for no read benefit.",
12
- facing: "INTERNAL",
13
- level: "INFO",
14
- name: "duplicate_index",
15
- remediation: "Drop the redundant index; the covering index already serves its lookups.",
16
- run: (context) => {
17
- const findings = [];
18
- for (const table of context.schema.tables) {
19
- const secondary = table.indexes.filter((index) => index.kind === "index");
20
- for (const candidate of secondary) {
21
- if (candidate.unique === true) {
22
- continue;
23
- }
24
- const cover = secondary.find((other) => {
25
- if (other === candidate) {
26
- return false;
27
- }
28
- if (!isLeadingPrefix(candidate.fields, other.fields)) {
29
- return false;
30
- }
31
- const sameLength = candidate.fields.length === other.fields.length;
32
- return sameLength ? candidate.name > other.name : true;
33
- });
34
- if (!cover) {
35
- continue;
36
- }
37
- findings.push(
38
- emit(duplicateIndex, {
39
- cacheKey: `duplicate_index:${table.name}:${candidate.name}`,
40
- detail: `Index "${candidate.name}" on table "${table.name}" (${candidate.fields.join(", ")}) is redundant — index "${cover.name}" (${cover.fields.join(", ")}) already covers its lookups.`,
41
- metadata: {
42
- coveredBy: { fields: cover.fields, name: cover.name },
43
- fields: candidate.fields,
44
- index: candidate.name,
45
- table: table.name
46
- }
47
- })
48
- );
49
- }
50
- }
51
- return findings;
52
- },
53
- source: "static",
54
- title: "Duplicate / redundant index"
55
- };
56
-
57
- export { duplicateIndex as default };
@@ -1,32 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const emptyIndex = {
4
- categories: ["SCHEMA"],
5
- description: "An index declares no columns, so it indexes nothing and can never narrow a read.",
6
- facing: "INTERNAL",
7
- level: "WARN",
8
- name: "empty_index",
9
- remediation: "Give the index its columns, or remove it.",
10
- run: (context) => {
11
- const findings = [];
12
- for (const table of context.schema.tables) {
13
- for (const index of table.indexes) {
14
- if (index.kind !== "index" || index.fields.length > 0) {
15
- continue;
16
- }
17
- findings.push(
18
- emit(emptyIndex, {
19
- cacheKey: `empty_index:${table.name}:${index.name}`,
20
- detail: `Index "${index.name}" on table "${table.name}" declares no columns.`,
21
- metadata: { index: index.name, table: table.name }
22
- })
23
- );
24
- }
25
- }
26
- return findings;
27
- },
28
- source: "static",
29
- title: "Empty index"
30
- };
31
-
32
- export { emptyIndex as default };
@@ -1,31 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const filterWithoutIndex = {
4
- categories: ["PERFORMANCE"],
5
- 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.",
6
- facing: "EXTERNAL",
7
- level: "WARN",
8
- name: "filter_without_index",
9
- remediation: 'Narrow the read with `.withIndex("name", (q) => q.eq(...))` first, then `.filter()` only for what the index cannot express.',
10
- run: (context) => {
11
- const findings = [];
12
- for (const read of context.queries ?? []) {
13
- if (!read.hasFilter || read.hasIndex || read.table === "") {
14
- continue;
15
- }
16
- const location = read.line > 0 ? `${read.file}:${read.line.toString()}` : read.file;
17
- findings.push(
18
- emit(filterWithoutIndex, {
19
- cacheKey: `filter_without_index:${read.file}:${read.line.toString()}:${read.table}`,
20
- detail: `Query on "${read.table}" at ${location} calls .filter() without an index — it loads every row of "${read.table}" and filters in memory.`,
21
- metadata: { file: read.file, line: read.line, table: read.table }
22
- })
23
- );
24
- }
25
- return findings;
26
- },
27
- source: "static",
28
- title: "Filter without index"
29
- };
30
-
31
- export { filterWithoutIndex as default };
@@ -1,16 +0,0 @@
1
- const emit = (lint, occurrence) => {
2
- return {
3
- cacheKey: occurrence.cacheKey,
4
- categories: occurrence.categories ?? lint.categories,
5
- description: lint.description,
6
- detail: occurrence.detail,
7
- facing: occurrence.facing ?? lint.facing,
8
- level: occurrence.level ?? lint.level,
9
- metadata: occurrence.metadata,
10
- name: lint.name,
11
- remediation: lint.remediation,
12
- title: lint.title
13
- };
14
- };
15
-
16
- export { emit as e };
@@ -1,7 +0,0 @@
1
- const PRIMARY_KEY = "_id";
2
- const suggestIndexName = (field) => `by${field.charAt(0).toUpperCase()}${field.slice(1)}`;
3
- const leadingIndexedColumns = (table) => new Set(
4
- table.indexes.filter((index) => index.kind === "index").map((index) => index.fields[0]).filter((field) => field !== void 0)
5
- );
6
-
7
- export { PRIMARY_KEY as P, leadingIndexedColumns as l, suggestIndexName as s };
@@ -1,50 +0,0 @@
1
- const fromServerSchema = (schema) => {
2
- return {
3
- tables: Object.entries(schema.tables).map(([name, table]) => {
4
- const indexes = [
5
- ...table.indexes.map((index) => {
6
- return { fields: index.fields, kind: "index", name: index.name, unique: index.unique };
7
- }),
8
- ...table.searchIndexes.map((index) => {
9
- return { fields: [index.field, ...index.filterFields ?? []], kind: "search", name: index.name };
10
- }),
11
- ...table.rankIndexes.map((index) => {
12
- return { fields: [...index.sortBy.map((key) => key.field), ...index.partitionBy ?? []], kind: "rank", name: index.name };
13
- }),
14
- ...table.vectorIndexes.map((index) => {
15
- return { fields: [index.field], kind: "vector", name: index.name };
16
- })
17
- ];
18
- const optionalFields = /* @__PURE__ */ new Set();
19
- for (const [fieldName, validator] of Object.entries(table.shape)) {
20
- if (validator.kind === "optional") {
21
- optionalFields.add(fieldName);
22
- } else {
23
- const column = validator._meta?.column;
24
- if (column?.notNull === false) {
25
- optionalFields.add(fieldName);
26
- }
27
- }
28
- }
29
- return {
30
- externallyManaged: table.isExternallyManaged ?? false,
31
- fields: Object.keys(table.shape),
32
- indexes,
33
- name,
34
- optionalFields,
35
- relations: Object.entries(table.relationMap).map(([accessor, relation]) => {
36
- return {
37
- field: relation.field,
38
- kind: relation.kind,
39
- name: accessor,
40
- onDelete: relation.onDelete,
41
- references: relation.references,
42
- table: relation.table
43
- };
44
- })
45
- };
46
- })
47
- };
48
- };
49
-
50
- export { fromServerSchema };
@@ -1,35 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const hardcodedSecret = {
4
- categories: ["SECURITY"],
5
- description: "A secret-shaped string literal (live API key, access key, private key, or high-entropy token) is hard-coded in the source. Committed secrets leak via clone/fork/history and force a redeploy to rotate.",
6
- facing: "INTERNAL",
7
- level: "ERROR",
8
- name: "hardcoded_secret",
9
- remediation: 'Move the secret out of source: set it locally in `.dev.vars` (`lunora env set <NAME> "<value>"`) and in production with `wrangler secret put <NAME>`, then read it at runtime from `env.<NAME>`. Rotate the exposed value — assume it is already compromised.',
10
- run: (context) => {
11
- if (context.secretLiterals === void 0) {
12
- return [];
13
- }
14
- const findings = [];
15
- const occurrenceCount = /* @__PURE__ */ new Map();
16
- for (const secret of context.secretLiterals) {
17
- const baseKey = `${secret.file}:${secret.line.toString()}:${secret.kind}`;
18
- const occurrence = (occurrenceCount.get(baseKey) ?? 0) + 1;
19
- occurrenceCount.set(baseKey, occurrence);
20
- const occurrenceSuffix = occurrence > 1 ? `:${occurrence.toString()}` : "";
21
- findings.push(
22
- emit(hardcodedSecret, {
23
- cacheKey: `hardcoded_secret:${baseKey}${occurrenceSuffix}`,
24
- detail: `A ${secret.kind.replaceAll("_", " ")} (${secret.preview}) is hard-coded at ${secret.file}:${secret.line.toString()}. Move it to \`.dev.vars\` / \`wrangler secret put\` and read it from \`env\`. Rotate the exposed value.`,
25
- metadata: { file: secret.file, kind: secret.kind, line: secret.line, preview: secret.preview }
26
- })
27
- );
28
- }
29
- return findings;
30
- },
31
- source: "static",
32
- title: "Hard-coded secret in source"
33
- };
34
-
35
- export { hardcodedSecret as default };
@@ -1,4 +0,0 @@
1
- const SYSTEM_FIELDS = /* @__PURE__ */ new Set(["_creationTime", "_id"]);
2
- const tableColumnSet = (table) => /* @__PURE__ */ new Set([...SYSTEM_FIELDS, ...table.fields]);
3
-
4
- export { tableColumnSet as t };
@@ -1,48 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const HOT_SHARE_THRESHOLD = 0.5;
4
- const MIN_TOTAL_REQUESTS = 50;
5
- const hotShard = {
6
- categories: ["PERFORMANCE"],
7
- description: "One shard is taking a disproportionate share of a sharded function's traffic, so the busy shard becomes a single-DO bottleneck while its siblings sit idle — the hot-key skew sharding is meant to avoid.",
8
- facing: "EXTERNAL",
9
- level: "WARN",
10
- name: "hot_shard",
11
- remediation: "Re-shard on a higher-cardinality / more evenly-distributed key, or split the hot entity's state, so request volume spreads across shards instead of concentrating on one.",
12
- run: (context) => {
13
- const active = (context.shardTraffic ?? []).filter((shard) => shard.requests > 0);
14
- const totalRequests = active.reduce((sum, shard) => sum + shard.requests, 0);
15
- if (active.length < 2 || totalRequests < MIN_TOTAL_REQUESTS) {
16
- return [];
17
- }
18
- const findings = [];
19
- for (const shard of active) {
20
- const share = shard.requests / totalRequests;
21
- if (share < HOT_SHARE_THRESHOLD) {
22
- continue;
23
- }
24
- const scope = shard.group !== void 0 && shard.group !== "" ? `"${shard.group}" ` : "";
25
- const label = shard.shardKey === "" ? "the root shard" : `shard "${shard.shardKey}"`;
26
- const percent = Math.round(share * 100);
27
- findings.push(
28
- emit(hotShard, {
29
- cacheKey: `hot_shard:${shard.group ?? ""}:${shard.shardKey}`,
30
- detail: `${scope}${label} handled ${shard.requests.toString()} of ${totalRequests.toString()} requests (${percent.toString()}%) across ${active.length.toString()} shards — a hot-key skew. Re-shard on a more evenly-distributed key.`,
31
- metadata: {
32
- group: shard.group,
33
- requests: shard.requests,
34
- shardCount: active.length,
35
- shardKey: shard.shardKey,
36
- share,
37
- totalRequests
38
- }
39
- })
40
- );
41
- }
42
- return findings;
43
- },
44
- source: "runtime",
45
- title: "Hot shard"
46
- };
47
-
48
- export { hotShard as default };
@@ -1,30 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const hyperdriveOutsideAction = {
4
- categories: ["SCHEMA"],
5
- description: "A `query`/`mutation` handler accesses Hyperdrive via `ctx.sql`. Hyperdrive hits an external database Lunora does not own: queries are non-deterministic (like `fetch`) and external writes are invisible to live queries. `ctx.sql` is available on `ActionCtx` only and must be confined to `action` handlers.",
6
- facing: "EXTERNAL",
7
- level: "WARN",
8
- name: "hyperdrive_outside_action",
9
- remediation: "Move the `ctx.sql` access into an `action(...)` (the only context where it is typed), where external I/O is allowed. If a query/mutation needs the data, have the action read it via `ctx.sql` and write a projection into a `defineSchema` DO/D1 table — that write is tracked by live queries, whereas the external DB is not.",
10
- run: (context) => {
11
- if (context.hyperdriveCalls === void 0) {
12
- return [];
13
- }
14
- const findings = [];
15
- for (const call of context.hyperdriveCalls) {
16
- findings.push(
17
- emit(hyperdriveOutsideAction, {
18
- cacheKey: `hyperdrive_outside_action:${call.file}:${call.line.toString()}:${call.callee}`,
19
- detail: `\`${call.callee}(…)\` in ${call.exportName} (${call.file}:${call.line.toString()}) runs inside a ${call.kind} handler — Hyperdrive's \`ctx.sql\` is non-deterministic and non-reactive, so it is available only in actions. Move the external SQL into an \`action\` and project the result into a Lunora table if a query/mutation needs it.`,
20
- metadata: { callee: call.callee, exportName: call.exportName, file: call.file, kind: call.kind, line: call.line }
21
- })
22
- );
23
- }
24
- return findings;
25
- },
26
- source: "static",
27
- title: "Hyperdrive `ctx.sql` used outside an action"
28
- };
29
-
30
- export { hyperdriveOutsideAction as default };
@@ -1,36 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
- import { t as tableColumnSet } from './helpers-DNCkMWZQ.mjs';
3
-
4
- const indexReferencesUnknownField = {
5
- categories: ["SCHEMA"],
6
- 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.",
7
- facing: "INTERNAL",
8
- level: "ERROR",
9
- name: "index_references_unknown_field",
10
- remediation: "Fix the column name in the index declaration, or add the column to the table.",
11
- run: (context) => {
12
- const findings = [];
13
- for (const table of context.schema.tables) {
14
- const columns = tableColumnSet(table);
15
- for (const index of table.indexes) {
16
- for (const field of index.fields) {
17
- if (columns.has(field)) {
18
- continue;
19
- }
20
- findings.push(
21
- emit(indexReferencesUnknownField, {
22
- cacheKey: `index_references_unknown_field:${table.name}:${index.name}:${field}`,
23
- detail: `Index "${index.name}" on table "${table.name}" references column "${field}", which is not declared on the table.`,
24
- metadata: { field, index: index.name, indexKind: index.kind, table: table.name }
25
- })
26
- );
27
- }
28
- }
29
- }
30
- return findings;
31
- },
32
- source: "static",
33
- title: "Index references unknown field"
34
- };
35
-
36
- export { indexReferencesUnknownField as default };
@@ -1,45 +0,0 @@
1
- import { e as emit } from './finding-Dm_zvzS1.mjs';
2
-
3
- const HOT_SCAN_THRESHOLD = 25;
4
- const indexUtilization = {
5
- categories: ["PERFORMANCE"],
6
- description: "Recorded reads show an index the workload doesn't pay for: either a declared index no read ever used (dead overhead on every write) or a table read hot with no index at all (a repeated full scan that degrades as it grows).",
7
- facing: "INTERNAL",
8
- level: "INFO",
9
- name: "index_utilization",
10
- remediation: "Drop a dead index so writes stop maintaining it; add an index covering the hot full-scanned table's read predicate so the read stops scanning every row.",
11
- run: (context) => {
12
- const findings = [];
13
- for (const hit of context.indexHits ?? []) {
14
- if (hit.reads > 0) {
15
- continue;
16
- }
17
- findings.push(
18
- emit(indexUtilization, {
19
- cacheKey: `index_utilization:dead_index:${hit.table}:${hit.index}`,
20
- detail: `Index "${hit.index}" on table "${hit.table}" has recorded no reads since its counter was created — it's dead overhead: every write maintains it and nothing reads through it.`,
21
- metadata: { index: hit.index, kind: "dead_index", reads: hit.reads, table: hit.table }
22
- })
23
- );
24
- }
25
- for (const scan of context.tableScans ?? []) {
26
- if (scan.scans < HOT_SCAN_THRESHOLD) {
27
- continue;
28
- }
29
- findings.push(
30
- emit(indexUtilization, {
31
- cacheKey: `index_utilization:hot_scan:${scan.table}`,
32
- detail: `Table "${scan.table}" has been full-scanned ${scan.scans.toString()} times (cumulative) with no index — a repeated full scan that degrades linearly as "${scan.table}" grows. Add an index covering the read predicate.`,
33
- facing: "EXTERNAL",
34
- level: "WARN",
35
- metadata: { kind: "hot_scan", scans: scan.scans, table: scan.table }
36
- })
37
- );
38
- }
39
- return findings;
40
- },
41
- source: "runtime",
42
- title: "Index utilization"
43
- };
44
-
45
- export { indexUtilization as default };