@lunora/advisor 1.0.0-alpha.2 → 1.0.0-alpha.20
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/LICENSE.md +6 -0
- package/README.md +1 -1
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +2158 -4
- package/dist/index.d.ts +2158 -4
- package/dist/index.mjs +110 -8
- package/dist/packem_shared/{AE_METRIC_EVENTS-DexctYv6.mjs → AE_METRIC_EVENTS-BM14d0lm.mjs} +3 -1
- package/dist/packem_shared/actionFetchSsrf-wbmYgzmz.mjs +26 -0
- package/dist/packem_shared/aiRawRunEscapeHatch-C29jd32J.mjs +26 -0
- package/dist/packem_shared/aiToolSideEffectPromptInjection-BtD3alqB.mjs +33 -0
- package/dist/packem_shared/aiUnboundedGenerationPublic-CbAcSxLB.mjs +26 -0
- package/dist/packem_shared/allowUnauthenticatedShardAccessEnabled-BcASq-jP.mjs +30 -0
- package/dist/packem_shared/argument-derived-sink-C1xTTqAt.mjs +30 -0
- package/dist/packem_shared/authCsrfCheckDisabled-DCf9FSoD.mjs +26 -0
- package/dist/packem_shared/authEmailVerificationDisabled-Dk-Tc5oU.mjs +26 -0
- package/dist/packem_shared/authSecureCookiesDisabled-osJrHW9Y.mjs +26 -0
- package/dist/packem_shared/authSessionFreshageZero-CjufCc_Q.mjs +26 -0
- package/dist/packem_shared/authTrustedOriginsWildcard-ylv4PKzC.mjs +26 -0
- package/dist/packem_shared/browserAllowPrivateTargets-CBvQxLAM.mjs +26 -0
- package/dist/packem_shared/browserUserUrlWithoutAllowlist-CvhA6w59.mjs +26 -0
- package/dist/packem_shared/containerInstanceKeyFromUserInput-BUXj2J4w.mjs +19 -0
- package/dist/packem_shared/containerRuntimeEgressRelaxation-B0LlfmDA.mjs +26 -0
- package/dist/packem_shared/containerStartEnableInternetOverride-DDaHZQ1L.mjs +26 -0
- package/dist/packem_shared/externalSourceOnGlobal-Bg-NfCX9.mjs +30 -0
- package/dist/packem_shared/externalSourceUnscoped-5vT-Bup3.mjs +44 -0
- package/dist/packem_shared/flagGatesSecurityWithUnsafeDefault-BhIs0shr.mjs +41 -0
- package/dist/packem_shared/{fromServerSchema-DinF1nph.mjs → fromServerSchema-BiB6JSiW.mjs} +9 -0
- package/dist/packem_shared/helpers-BySnKhVB.mjs +31 -0
- package/dist/packem_shared/httpActionMissingAuthGuard-CxipddNx.mjs +35 -0
- package/dist/packem_shared/httpActionResponseHeaderInjection-DOFS7pFT.mjs +39 -0
- package/dist/packem_shared/identityUndeclaredClaimTrusted-D8nXV2dd.mjs +32 -0
- package/dist/packem_shared/imagesUrlSourceFromUserInput-YcDQ0b_Y.mjs +19 -0
- package/dist/packem_shared/{indexReferencesUnknownField-DH0_dbUY.mjs → indexReferencesUnknownField-BSWNngxX.mjs} +1 -1
- package/dist/packem_shared/insertManyUnsafeUserData-Dn77XpmX.mjs +26 -0
- package/dist/packem_shared/kvUnscopedUserKeyIdor-YWwmfE8X.mjs +19 -0
- package/dist/packem_shared/mailInboundDispatchWithoutVerify-CHSRA8zz.mjs +28 -0
- package/dist/packem_shared/mailRecipientFromRequestInput-Cka2qu5J.mjs +26 -0
- package/dist/packem_shared/maskWeakHashStrategyOnPii-1c4q8Opf.mjs +35 -0
- package/dist/packem_shared/maskedRelationLeakViaWith-CPI4s0sl.mjs +76 -0
- package/dist/packem_shared/mutatorFullRowReplace-BJnNDaIV.mjs +26 -0
- package/dist/packem_shared/normalizeIdUsedAsAuthorization-BVPtCpzT.mjs +50 -0
- package/dist/packem_shared/outputProjectionMissingOnPublicRead-Bl5IMx0k.mjs +51 -0
- package/dist/packem_shared/ownerFieldFromArgsNotAuth-mOw3hE5z.mjs +26 -0
- package/dist/packem_shared/paymentCreateWithoutAuthorize-BYm4JLxo.mjs +26 -0
- package/dist/packem_shared/paymentWebhookWideTolerance-D9QQJMJq.mjs +35 -0
- package/dist/packem_shared/plaintextSecretInWranglerVariables-NKO4YkKf.mjs +35 -0
- package/dist/packem_shared/privilegedDispatchUnvalidatedPayload-5Forjckt.mjs +37 -0
- package/dist/packem_shared/privilegedFanoutFromPublicProcedure-D3dL01B8.mjs +26 -0
- package/dist/packem_shared/{publicMutationWithoutRatelimit-xBpJ6GWK.mjs → publicMutationWithoutRatelimit-DbIhgi7j.mjs} +2 -2
- package/dist/packem_shared/publicTableRlsOptoutConfusion-Bn71yoD4.mjs +38 -0
- package/dist/packem_shared/queueWithoutDlq-CSkGNb_0.mjs +41 -0
- package/dist/packem_shared/ratelimitDefaultMemoryStore-BISChG5C.mjs +26 -0
- package/dist/packem_shared/ratelimitKeySpoofableOrGlobal-DqlHYQQ3.mjs +26 -0
- package/dist/packem_shared/ratelimitMiddlewareFailOpen-CJgDCaUw.mjs +33 -0
- package/dist/packem_shared/{relationReferencesUnknownField-YznyXt_7.mjs → relationReferencesUnknownField-CjbLScJ1.mjs} +1 -1
- package/dist/packem_shared/shapeTargetsGlobalTable-DHrf4Koi.mjs +34 -0
- package/dist/packem_shared/shapeUnknownTable-C8aDWFoe.mjs +34 -0
- package/dist/packem_shared/softDeleteIncludeDeletedFromArgs-BLqDKrkM.mjs +38 -0
- package/dist/packem_shared/storageGenerateUploadUrlNoContentTypePin-Da4L9Ge8.mjs +26 -0
- package/dist/packem_shared/storageKeyFromUserArgs-B86elJgS.mjs +19 -0
- package/dist/packem_shared/storagePresignedUrlForPrivateContent-yGmb8uqz.mjs +47 -0
- package/dist/packem_shared/storageUploadWithoutContentTypeAllowlist-BV-gF1lT.mjs +27 -0
- package/dist/packem_shared/storageUploadWithoutMaxSize-DpxO59wU.mjs +27 -0
- package/dist/packem_shared/{userCreatingMutationWithoutCaptcha-CH31YsUZ.mjs → userCreatingMutationWithoutCaptcha-2DZtWIPb.mjs} +2 -2
- package/dist/packem_shared/vectorsNamespaceFromUserInput-CQhr5bVn.mjs +19 -0
- package/dist/packem_shared/workflowDuplicateStepName-ioBxPBCy.mjs +48 -0
- package/package.json +4 -3
- package/dist/packem_shared/helpers-DNCkMWZQ.mjs +0 -4
package/dist/index.mjs
CHANGED
|
@@ -1,42 +1,96 @@
|
|
|
1
1
|
import constraintValidator from './packem_shared/constraintValidator-Dr9Py3FD.mjs';
|
|
2
2
|
import hotShard from './packem_shared/hotShard-Ir5D0B6J.mjs';
|
|
3
3
|
import indexUtilization from './packem_shared/indexUtilization-B5DMQ3bI.mjs';
|
|
4
|
+
import actionFetchSsrf from './packem_shared/actionFetchSsrf-wbmYgzmz.mjs';
|
|
4
5
|
import adminRouteWithoutGuard from './packem_shared/adminRouteWithoutGuard-UUGBkAjU.mjs';
|
|
6
|
+
import aiRawRunEscapeHatch from './packem_shared/aiRawRunEscapeHatch-C29jd32J.mjs';
|
|
7
|
+
import aiToolSideEffectPromptInjection from './packem_shared/aiToolSideEffectPromptInjection-BtD3alqB.mjs';
|
|
8
|
+
import aiUnboundedGenerationPublic from './packem_shared/aiUnboundedGenerationPublic-CbAcSxLB.mjs';
|
|
9
|
+
import allowUnauthenticatedShardAccessEnabled from './packem_shared/allowUnauthenticatedShardAccessEnabled-BcASq-jP.mjs';
|
|
5
10
|
import authApiCallWithoutHeaders from './packem_shared/authApiCallWithoutHeaders-BeJhCZaf.mjs';
|
|
11
|
+
import authCsrfCheckDisabled from './packem_shared/authCsrfCheckDisabled-DCf9FSoD.mjs';
|
|
12
|
+
import authEmailVerificationDisabled from './packem_shared/authEmailVerificationDisabled-Dk-Tc5oU.mjs';
|
|
13
|
+
import authSecureCookiesDisabled from './packem_shared/authSecureCookiesDisabled-osJrHW9Y.mjs';
|
|
14
|
+
import authSessionFreshageZero from './packem_shared/authSessionFreshageZero-CjufCc_Q.mjs';
|
|
15
|
+
import authTrustedOriginsWildcard from './packem_shared/authTrustedOriginsWildcard-ylv4PKzC.mjs';
|
|
16
|
+
import browserAllowPrivateTargets from './packem_shared/browserAllowPrivateTargets-CBvQxLAM.mjs';
|
|
17
|
+
import browserUserUrlWithoutAllowlist from './packem_shared/browserUserUrlWithoutAllowlist-CvhA6w59.mjs';
|
|
6
18
|
import circularFk from './packem_shared/circularFk-B2freHrP.mjs';
|
|
19
|
+
import containerInstanceKeyFromUserInput from './packem_shared/containerInstanceKeyFromUserInput-BUXj2J4w.mjs';
|
|
7
20
|
import containerOversizedInstance from './packem_shared/containerOversizedInstance-5U1VKPRM.mjs';
|
|
8
21
|
import containerPublicInternet from './packem_shared/containerPublicInternet-CuNerJE5.mjs';
|
|
22
|
+
import containerRuntimeEgressRelaxation from './packem_shared/containerRuntimeEgressRelaxation-B0LlfmDA.mjs';
|
|
23
|
+
import containerStartEnableInternetOverride from './packem_shared/containerStartEnableInternetOverride-DDaHZQ1L.mjs';
|
|
9
24
|
import duplicateIndex from './packem_shared/duplicateIndex-BOublMSt.mjs';
|
|
10
25
|
import emptyIndex from './packem_shared/emptyIndex-BX8EuEY7.mjs';
|
|
11
26
|
import filterWithoutIndex from './packem_shared/filterWithoutIndex-BYVeJaSs.mjs';
|
|
27
|
+
import flagGatesSecurityWithUnsafeDefault from './packem_shared/flagGatesSecurityWithUnsafeDefault-BhIs0shr.mjs';
|
|
12
28
|
import hardcodedSecret from './packem_shared/hardcodedSecret-W2pz1UZB.mjs';
|
|
29
|
+
import httpActionMissingAuthGuard from './packem_shared/httpActionMissingAuthGuard-CxipddNx.mjs';
|
|
30
|
+
import httpActionResponseHeaderInjection from './packem_shared/httpActionResponseHeaderInjection-DOFS7pFT.mjs';
|
|
13
31
|
import hyperdriveOutsideAction from './packem_shared/hyperdriveOutsideAction-BgZqX7Xg.mjs';
|
|
14
|
-
import
|
|
32
|
+
import identityUndeclaredClaimTrusted from './packem_shared/identityUndeclaredClaimTrusted-D8nXV2dd.mjs';
|
|
33
|
+
import imagesUrlSourceFromUserInput from './packem_shared/imagesUrlSourceFromUserInput-YcDQ0b_Y.mjs';
|
|
34
|
+
import indexReferencesUnknownField from './packem_shared/indexReferencesUnknownField-BSWNngxX.mjs';
|
|
35
|
+
import insertManyUnsafeUserData from './packem_shared/insertManyUnsafeUserData-Dn77XpmX.mjs';
|
|
36
|
+
import kvUnscopedUserKeyIdor from './packem_shared/kvUnscopedUserKeyIdor-YWwmfE8X.mjs';
|
|
37
|
+
import mailInboundDispatchWithoutVerify from './packem_shared/mailInboundDispatchWithoutVerify-CHSRA8zz.mjs';
|
|
38
|
+
import mailRecipientFromRequestInput from './packem_shared/mailRecipientFromRequestInput-Cka2qu5J.mjs';
|
|
15
39
|
import maskUncoveredPiiColumn from './packem_shared/maskUncoveredPiiColumn-DjGIPG6M.mjs';
|
|
40
|
+
import maskWeakHashStrategyOnPii from './packem_shared/maskWeakHashStrategyOnPii-1c4q8Opf.mjs';
|
|
41
|
+
import maskedRelationLeakViaWith from './packem_shared/maskedRelationLeakViaWith-CPI4s0sl.mjs';
|
|
42
|
+
import mutatorFullRowReplace from './packem_shared/mutatorFullRowReplace-BJnNDaIV.mjs';
|
|
16
43
|
import nondeterministicQueryMutation from './packem_shared/nondeterministicQueryMutation-GXES1fLp.mjs';
|
|
44
|
+
import normalizeIdUsedAsAuthorization from './packem_shared/normalizeIdUsedAsAuthorization-BVPtCpzT.mjs';
|
|
45
|
+
import outputProjectionMissingOnPublicRead from './packem_shared/outputProjectionMissingOnPublicRead-Bl5IMx0k.mjs';
|
|
46
|
+
import ownerFieldFromArgsNotAuth from './packem_shared/ownerFieldFromArgsNotAuth-mOw3hE5z.mjs';
|
|
47
|
+
import paymentCreateWithoutAuthorize from './packem_shared/paymentCreateWithoutAuthorize-BYm4JLxo.mjs';
|
|
48
|
+
import paymentWebhookWideTolerance from './packem_shared/paymentWebhookWideTolerance-D9QQJMJq.mjs';
|
|
49
|
+
import plaintextSecretInWranglerVariables from './packem_shared/plaintextSecretInWranglerVariables-NKO4YkKf.mjs';
|
|
17
50
|
import policyReferencesUnknownTable from './packem_shared/policyReferencesUnknownTable-DtaIEovd.mjs';
|
|
51
|
+
import privilegedDispatchUnvalidatedPayload from './packem_shared/privilegedDispatchUnvalidatedPayload-5Forjckt.mjs';
|
|
52
|
+
import privilegedFanoutFromPublicProcedure from './packem_shared/privilegedFanoutFromPublicProcedure-D3dL01B8.mjs';
|
|
18
53
|
import publicArgumentUsesAny from './packem_shared/publicArgumentUsesAny-C71b2NCf.mjs';
|
|
19
|
-
import publicMutationWithoutRatelimit from './packem_shared/publicMutationWithoutRatelimit-
|
|
54
|
+
import publicMutationWithoutRatelimit from './packem_shared/publicMutationWithoutRatelimit-DbIhgi7j.mjs';
|
|
55
|
+
import publicTableRlsOptoutConfusion from './packem_shared/publicTableRlsOptoutConfusion-Bn71yoD4.mjs';
|
|
56
|
+
import queueWithoutDlq from './packem_shared/queueWithoutDlq-CSkGNb_0.mjs';
|
|
20
57
|
import r2sqlOutsideAction from './packem_shared/r2sqlOutsideAction-CtqxvMuV.mjs';
|
|
21
|
-
import
|
|
58
|
+
import ratelimitDefaultMemoryStore from './packem_shared/ratelimitDefaultMemoryStore-BISChG5C.mjs';
|
|
59
|
+
import ratelimitKeySpoofableOrGlobal from './packem_shared/ratelimitKeySpoofableOrGlobal-DqlHYQQ3.mjs';
|
|
60
|
+
import ratelimitMiddlewareFailOpen from './packem_shared/ratelimitMiddlewareFailOpen-CJgDCaUw.mjs';
|
|
61
|
+
import relationReferencesUnknownField from './packem_shared/relationReferencesUnknownField-CjbLScJ1.mjs';
|
|
22
62
|
import relationReferencesUnknownTable from './packem_shared/relationReferencesUnknownTable-DrorpKYe.mjs';
|
|
23
63
|
import rlsUncoveredTable from './packem_shared/rlsUncoveredTable-CxEfZ5eZ.mjs';
|
|
64
|
+
import shapeTargetsGlobalTable from './packem_shared/shapeTargetsGlobalTable-DHrf4Koi.mjs';
|
|
65
|
+
import shapeUnknownTable from './packem_shared/shapeUnknownTable-C8aDWFoe.mjs';
|
|
66
|
+
import softDeleteIncludeDeletedFromArgs from './packem_shared/softDeleteIncludeDeletedFromArgs-BLqDKrkM.mjs';
|
|
24
67
|
import sqlInjectionRisk from './packem_shared/sqlInjectionRisk-zwytYGLt.mjs';
|
|
68
|
+
import storageGenerateUploadUrlNoContentTypePin from './packem_shared/storageGenerateUploadUrlNoContentTypePin-Da4L9Ge8.mjs';
|
|
69
|
+
import storageKeyFromUserArgs from './packem_shared/storageKeyFromUserArgs-B86elJgS.mjs';
|
|
70
|
+
import storagePresignedUrlForPrivateContent from './packem_shared/storagePresignedUrlForPrivateContent-yGmb8uqz.mjs';
|
|
71
|
+
import storageUploadWithoutContentTypeAllowlist from './packem_shared/storageUploadWithoutContentTypeAllowlist-BV-gF1lT.mjs';
|
|
72
|
+
import storageUploadWithoutMaxSize from './packem_shared/storageUploadWithoutMaxSize-DpxO59wU.mjs';
|
|
25
73
|
import tableWithoutInsert from './packem_shared/tableWithoutInsert-CbbaYIP4.mjs';
|
|
26
74
|
import unboundedStringArgument from './packem_shared/unboundedStringArgument-DThg2-wt.mjs';
|
|
27
75
|
import unindexedForeignKey from './packem_shared/unindexedForeignKey-BgJbKyqK.mjs';
|
|
28
76
|
import unindexedRelationTarget from './packem_shared/unindexedRelationTarget-D6eyj6Xx.mjs';
|
|
29
|
-
import userCreatingMutationWithoutCaptcha from './packem_shared/userCreatingMutationWithoutCaptcha-
|
|
77
|
+
import userCreatingMutationWithoutCaptcha from './packem_shared/userCreatingMutationWithoutCaptcha-2DZtWIPb.mjs';
|
|
78
|
+
import vectorsNamespaceFromUserInput from './packem_shared/vectorsNamespaceFromUserInput-CQhr5bVn.mjs';
|
|
79
|
+
import workflowDuplicateStepName from './packem_shared/workflowDuplicateStepName-ioBxPBCy.mjs';
|
|
30
80
|
import workflowUnknownTarget from './packem_shared/workflowUnknownTarget-Cdd7WhKQ.mjs';
|
|
31
81
|
import workflowUnused from './packem_shared/workflowUnused-D0jHxdz9.mjs';
|
|
32
|
-
export { AE_METRIC_EVENTS, loadAnalyticsRuntimeMetrics } from './packem_shared/AE_METRIC_EVENTS-
|
|
33
|
-
export {
|
|
82
|
+
export { AE_METRIC_EVENTS, loadAnalyticsRuntimeMetrics } from './packem_shared/AE_METRIC_EVENTS-BM14d0lm.mjs';
|
|
83
|
+
export { default as externalSourceOnGlobal } from './packem_shared/externalSourceOnGlobal-Bg-NfCX9.mjs';
|
|
84
|
+
export { default as externalSourceUnscoped } from './packem_shared/externalSourceUnscoped-5vT-Bup3.mjs';
|
|
85
|
+
export { fromServerSchema } from './packem_shared/fromServerSchema-BiB6JSiW.mjs';
|
|
34
86
|
|
|
35
87
|
const STATIC_LINTS = [
|
|
36
88
|
indexReferencesUnknownField,
|
|
37
89
|
relationReferencesUnknownTable,
|
|
38
90
|
relationReferencesUnknownField,
|
|
39
91
|
workflowUnknownTarget,
|
|
92
|
+
workflowDuplicateStepName,
|
|
93
|
+
shapeUnknownTable,
|
|
40
94
|
emptyIndex,
|
|
41
95
|
circularFk,
|
|
42
96
|
unindexedForeignKey,
|
|
@@ -44,7 +98,10 @@ const STATIC_LINTS = [
|
|
|
44
98
|
duplicateIndex,
|
|
45
99
|
tableWithoutInsert,
|
|
46
100
|
workflowUnused,
|
|
101
|
+
queueWithoutDlq,
|
|
47
102
|
filterWithoutIndex,
|
|
103
|
+
shapeTargetsGlobalTable,
|
|
104
|
+
mutatorFullRowReplace,
|
|
48
105
|
nondeterministicQueryMutation,
|
|
49
106
|
hyperdriveOutsideAction,
|
|
50
107
|
r2sqlOutsideAction,
|
|
@@ -52,6 +109,7 @@ const STATIC_LINTS = [
|
|
|
52
109
|
policyReferencesUnknownTable,
|
|
53
110
|
rlsUncoveredTable,
|
|
54
111
|
maskUncoveredPiiColumn,
|
|
112
|
+
maskWeakHashStrategyOnPii,
|
|
55
113
|
containerOversizedInstance,
|
|
56
114
|
containerPublicInternet,
|
|
57
115
|
publicMutationWithoutRatelimit,
|
|
@@ -60,7 +118,51 @@ const STATIC_LINTS = [
|
|
|
60
118
|
unboundedStringArgument,
|
|
61
119
|
hardcodedSecret,
|
|
62
120
|
sqlInjectionRisk,
|
|
63
|
-
adminRouteWithoutGuard
|
|
121
|
+
adminRouteWithoutGuard,
|
|
122
|
+
paymentCreateWithoutAuthorize,
|
|
123
|
+
mailInboundDispatchWithoutVerify,
|
|
124
|
+
ratelimitDefaultMemoryStore,
|
|
125
|
+
browserAllowPrivateTargets,
|
|
126
|
+
privilegedFanoutFromPublicProcedure,
|
|
127
|
+
insertManyUnsafeUserData,
|
|
128
|
+
aiUnboundedGenerationPublic,
|
|
129
|
+
actionFetchSsrf,
|
|
130
|
+
ownerFieldFromArgsNotAuth,
|
|
131
|
+
storageKeyFromUserArgs,
|
|
132
|
+
kvUnscopedUserKeyIdor,
|
|
133
|
+
containerInstanceKeyFromUserInput,
|
|
134
|
+
aiRawRunEscapeHatch,
|
|
135
|
+
vectorsNamespaceFromUserInput,
|
|
136
|
+
mailRecipientFromRequestInput,
|
|
137
|
+
browserUserUrlWithoutAllowlist,
|
|
138
|
+
privilegedDispatchUnvalidatedPayload,
|
|
139
|
+
containerStartEnableInternetOverride,
|
|
140
|
+
containerRuntimeEgressRelaxation,
|
|
141
|
+
authTrustedOriginsWildcard,
|
|
142
|
+
authCsrfCheckDisabled,
|
|
143
|
+
authSecureCookiesDisabled,
|
|
144
|
+
authEmailVerificationDisabled,
|
|
145
|
+
authSessionFreshageZero,
|
|
146
|
+
imagesUrlSourceFromUserInput,
|
|
147
|
+
ratelimitKeySpoofableOrGlobal,
|
|
148
|
+
publicTableRlsOptoutConfusion,
|
|
149
|
+
allowUnauthenticatedShardAccessEnabled,
|
|
150
|
+
storageUploadWithoutContentTypeAllowlist,
|
|
151
|
+
storageUploadWithoutMaxSize,
|
|
152
|
+
storageGenerateUploadUrlNoContentTypePin,
|
|
153
|
+
storagePresignedUrlForPrivateContent,
|
|
154
|
+
httpActionMissingAuthGuard,
|
|
155
|
+
httpActionResponseHeaderInjection,
|
|
156
|
+
ratelimitMiddlewareFailOpen,
|
|
157
|
+
flagGatesSecurityWithUnsafeDefault,
|
|
158
|
+
aiToolSideEffectPromptInjection,
|
|
159
|
+
identityUndeclaredClaimTrusted,
|
|
160
|
+
paymentWebhookWideTolerance,
|
|
161
|
+
softDeleteIncludeDeletedFromArgs,
|
|
162
|
+
maskedRelationLeakViaWith,
|
|
163
|
+
outputProjectionMissingOnPublicRead,
|
|
164
|
+
normalizeIdUsedAsAuthorization,
|
|
165
|
+
plaintextSecretInWranglerVariables
|
|
64
166
|
];
|
|
65
167
|
const RUNTIME_LINTS = [hotShard, indexUtilization, constraintValidator];
|
|
66
168
|
const ALL_LINTS = [...STATIC_LINTS, ...RUNTIME_LINTS];
|
|
@@ -76,4 +178,4 @@ const runAdvisor = (context, options = {}) => {
|
|
|
76
178
|
return findings;
|
|
77
179
|
};
|
|
78
180
|
|
|
79
|
-
export { ALL_LINTS, RUNTIME_LINTS, STATIC_LINTS, adminRouteWithoutGuard, authApiCallWithoutHeaders, circularFk, constraintValidator, containerOversizedInstance, containerPublicInternet, duplicateIndex, emptyIndex, filterWithoutIndex, hardcodedSecret, hotShard, hyperdriveOutsideAction, indexReferencesUnknownField, indexUtilization, maskUncoveredPiiColumn, nondeterministicQueryMutation, policyReferencesUnknownTable, publicArgumentUsesAny, publicMutationWithoutRatelimit, r2sqlOutsideAction, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, sqlInjectionRisk, tableWithoutInsert, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, userCreatingMutationWithoutCaptcha, workflowUnknownTarget, workflowUnused };
|
|
181
|
+
export { ALL_LINTS, RUNTIME_LINTS, STATIC_LINTS, actionFetchSsrf, adminRouteWithoutGuard, aiRawRunEscapeHatch, aiToolSideEffectPromptInjection, aiUnboundedGenerationPublic, allowUnauthenticatedShardAccessEnabled, authApiCallWithoutHeaders, authCsrfCheckDisabled, authEmailVerificationDisabled, authSecureCookiesDisabled, authSessionFreshageZero, authTrustedOriginsWildcard, browserAllowPrivateTargets, browserUserUrlWithoutAllowlist, circularFk, constraintValidator, containerInstanceKeyFromUserInput, containerOversizedInstance, containerPublicInternet, containerRuntimeEgressRelaxation, containerStartEnableInternetOverride, duplicateIndex, emptyIndex, filterWithoutIndex, flagGatesSecurityWithUnsafeDefault, hardcodedSecret, hotShard, httpActionMissingAuthGuard, httpActionResponseHeaderInjection, hyperdriveOutsideAction, identityUndeclaredClaimTrusted, imagesUrlSourceFromUserInput, indexReferencesUnknownField, indexUtilization, insertManyUnsafeUserData, kvUnscopedUserKeyIdor, mailInboundDispatchWithoutVerify, mailRecipientFromRequestInput, maskUncoveredPiiColumn, maskWeakHashStrategyOnPii, maskedRelationLeakViaWith, mutatorFullRowReplace, nondeterministicQueryMutation, normalizeIdUsedAsAuthorization, outputProjectionMissingOnPublicRead, ownerFieldFromArgsNotAuth, paymentCreateWithoutAuthorize, paymentWebhookWideTolerance, plaintextSecretInWranglerVariables, policyReferencesUnknownTable, privilegedDispatchUnvalidatedPayload, privilegedFanoutFromPublicProcedure, publicArgumentUsesAny, publicMutationWithoutRatelimit, publicTableRlsOptoutConfusion, queueWithoutDlq, r2sqlOutsideAction, ratelimitDefaultMemoryStore, ratelimitKeySpoofableOrGlobal, ratelimitMiddlewareFailOpen, relationReferencesUnknownField, relationReferencesUnknownTable, rlsUncoveredTable, runAdvisor, shapeTargetsGlobalTable, shapeUnknownTable, softDeleteIncludeDeletedFromArgs, sqlInjectionRisk, storageGenerateUploadUrlNoContentTypePin, storageKeyFromUserArgs, storagePresignedUrlForPrivateContent, storageUploadWithoutContentTypeAllowlist, storageUploadWithoutMaxSize, tableWithoutInsert, unboundedStringArgument, unindexedForeignKey, unindexedRelationTarget, userCreatingMutationWithoutCaptcha, vectorsNamespaceFromUserInput, workflowDuplicateStepName, workflowUnknownTarget, workflowUnused };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
|
|
1
3
|
const DATASET_NAME_PATTERN = /^[\w.-]+$/u;
|
|
2
4
|
const AE_METRIC_EVENTS = {
|
|
3
5
|
/** `lunora.index.hit` — one row per `(table, index)` use. `blob2`=table, `blob3`=index. */
|
|
@@ -22,7 +24,7 @@ const toText = (value) => {
|
|
|
22
24
|
};
|
|
23
25
|
const assertDataset = (dataset) => {
|
|
24
26
|
if (!DATASET_NAME_PATTERN.test(dataset)) {
|
|
25
|
-
throw new
|
|
27
|
+
throw new LunoraError("INTERNAL", `@lunora/advisor: invalid Analytics Engine dataset name "${dataset}" — expected a bare table identifier.`);
|
|
26
28
|
}
|
|
27
29
|
};
|
|
28
30
|
const sqlString = (value) => `'${value.replaceAll("\\", "\\\\").replaceAll("'", "''")}'`;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const actionFetchSsrf = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "An action's `ctx.fetch(url)` fetches a URL derived from the handler's `args`. `ctx.fetch` applies no host allowlist, so request-controlled URLs let a caller reach cloud metadata endpoints or internal services — server-side request forgery.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "ERROR",
|
|
8
|
+
name: "action_fetch_ssrf",
|
|
9
|
+
remediation: "Validate the URL against an allowlist of expected hosts before fetching, and reject private, loopback, and link-local addresses (notably `169.254.169.254`). Never pass request input straight to `ctx.fetch`.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.argumentDerivedFetches === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.argumentDerivedFetches.map(
|
|
15
|
+
(fetchCall) => emit(actionFetchSsrf, {
|
|
16
|
+
cacheKey: `action_fetch_ssrf:${fetchCall.file}:${fetchCall.line.toString()}`,
|
|
17
|
+
detail: `\`ctx.fetch\` in \`${fetchCall.exportName}\` (${fetchCall.file}:${fetchCall.line.toString()}) fetches a URL derived from \`args\` — a server-side request forgery vector. Validate the URL against a host allowlist and reject private/link-local targets before fetching.`,
|
|
18
|
+
metadata: { exportName: fetchCall.exportName, file: fetchCall.file, line: fetchCall.line }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "Possible SSRF from arg-derived ctx.fetch URL"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { actionFetchSsrf as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const aiRawRunEscapeHatch = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "A `ctx.ai.run(model, …)` call selects its model from the handler's `args` with no server-side scoping. `ctx.ai.run` is the raw Workers AI binding passthrough, so an arg-derived model id lets any caller pick an arbitrary model — bypassing the typed `ctx.ai.model(...)` + AI-SDK layer's cap/schema.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "WARN",
|
|
8
|
+
name: "ai_raw_run_escape_hatch",
|
|
9
|
+
remediation: `Select the model from a fixed server-side allowlist — never from \`args\`. Prefer the typed \`ctx.ai.model(...)\` + \`generateText\`/\`streamText\` path with an output cap over the raw \`ctx.ai.run\` escape hatch.`,
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.aiRawRuns === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.aiRawRuns.map(
|
|
15
|
+
(access) => emit(aiRawRunEscapeHatch, {
|
|
16
|
+
cacheKey: `ai_raw_run_escape_hatch:${access.file}:${access.line.toString()}`,
|
|
17
|
+
detail: `\`ctx.ai.run\` in \`${access.exportName}\` (${access.file}:${access.line.toString()}) selects its model from \`args\` with no server-side scoping — any caller can pick an arbitrary model, bypassing the typed AI-SDK layer's cap/schema. Select the model from a fixed server-side allowlist instead.`,
|
|
18
|
+
metadata: { exportName: access.exportName, file: access.file, line: access.line }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "Possible arbitrary model selection from arg-derived ctx.ai.run"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { aiRawRunEscapeHatch as default };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const aiToolSideEffectPromptInjection = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "A `generateText` / `streamText` call whose prompt/messages carry user-supplied text and whose model-callable `tools` perform a privileged side effect (DB write, `ctx.run` dispatch, or outbound send). An injected instruction in the user text can steer the model into firing that action — the confused-deputy prompt-injection hazard.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "WARN",
|
|
8
|
+
name: "ai_tool_side_effect_prompt_injection",
|
|
9
|
+
remediation: "Don't let a model with user-derived input directly trigger a privileged action. Have the tool return a proposal the server validates and authorizes before executing, gate the side effect behind an explicit human/step confirmation, or keep side-effecting tools out of any generation whose prompt includes untrusted input.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.aiToolSideEffects === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.aiToolSideEffects.filter((row) => row.userInputDerived).map((row) => {
|
|
15
|
+
const location = `\`${row.exportName}\` (${row.file}:${row.line.toString()})`;
|
|
16
|
+
return emit(aiToolSideEffectPromptInjection, {
|
|
17
|
+
cacheKey: `ai_tool_side_effect_prompt_injection:${row.file}:${row.line.toString()}`,
|
|
18
|
+
detail: `\`${row.method}\` in ${location} feeds user-derived input to a model whose tools reach \`${row.sideEffect}\`. An injected instruction can steer the model into firing that side effect.`,
|
|
19
|
+
metadata: {
|
|
20
|
+
exportName: row.exportName,
|
|
21
|
+
file: row.file,
|
|
22
|
+
line: row.line,
|
|
23
|
+
method: row.method,
|
|
24
|
+
sideEffect: row.sideEffect
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
source: "static",
|
|
30
|
+
title: "AI tool side effect reachable via prompt injection"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { aiToolSideEffectPromptInjection as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const aiUnboundedGenerationPublic = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "A public procedure runs an AI generation (`generateText`/`streamText`/`generateObject`/`streamObject`) with no `maxOutputTokens` bound. Generation bills per output token, so an anonymous caller can request arbitrarily long completions in a loop — a denial-of-wallet vector that also ties up worker time on long streams.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "WARN",
|
|
8
|
+
name: "ai_unbounded_generation_public",
|
|
9
|
+
remediation: "Cap the completion with `maxOutputTokens` in the generation config, and rate-limit the public entry point (`.use(rateLimit(...))`). For expensive models, prefer running generation from an internal, authenticated path rather than exposing it directly to anonymous callers.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.procedureProtections === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.procedureProtections.filter((procedure) => procedure.unboundedAiGeneration && procedure.visibility === "public").map(
|
|
15
|
+
(procedure) => emit(aiUnboundedGenerationPublic, {
|
|
16
|
+
cacheKey: `ai_unbounded_generation_public:${procedure.file}:${procedure.exportName}`,
|
|
17
|
+
detail: `\`${procedure.exportName}\` (${procedure.file}) is public and runs an AI generation with no \`maxOutputTokens\` bound — an anonymous caller can drive unbounded, billable completions in a loop. Add a \`maxOutputTokens\` cap and rate-limit the entry point.`,
|
|
18
|
+
metadata: { exportName: procedure.exportName, file: procedure.file, kind: procedure.kind }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "Public procedure runs unbounded AI generation (no maxOutputTokens)"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { aiUnboundedGenerationPublic as default };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const allowUnauthenticatedShardAccessEnabled = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "`.extend(() => ({ allowUnauthenticatedShardAccess: true }))` disables the fail-closed default that denies an unauthenticated shard lookup. Combined with a schema that doesn't enforce `.rls(\"required\")` everywhere (or that leaves a table `.public()`), an unauthenticated caller can shard-hop into another tenant's rows with no row-security guard behind the door.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "WARN",
|
|
8
|
+
name: "allow_unauthenticated_shard_access_enabled",
|
|
9
|
+
remediation: "Prefer `authorizeShard` / `authorizeFanOut` (which take precedence over `allowUnauthenticatedShardAccess` and let you allow specific unauthenticated cases) over the blanket opt-out. If the app genuinely needs open shard access, enforce `.rls(\"required\")` on the schema with no `.public()` tables so a missing identity still can't read another tenant's rows.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.configCalls === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const hasRlsGap = context.schema.rlsMode !== "required" || context.schema.tables.some((table) => table.isPublic);
|
|
15
|
+
if (!hasRlsGap) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return context.configCalls.filter((call) => call.callee === "extend" && call.trueKeys.includes("allowUnauthenticatedShardAccess")).map(
|
|
19
|
+
(call) => emit(allowUnauthenticatedShardAccessEnabled, {
|
|
20
|
+
cacheKey: `allow_unauthenticated_shard_access_enabled:${call.file}:${call.line.toString()}`,
|
|
21
|
+
detail: `\`.extend(...)\` in ${call.file}:${call.line.toString()} sets \`allowUnauthenticatedShardAccess: true\`, and the schema has an RLS gap (no \`.rls("required")\`, or a \`.public()\` table) — an unauthenticated caller can shard-hop into another tenant's rows with no row-security guard behind the door.`,
|
|
22
|
+
metadata: { callee: call.callee, file: call.file, line: call.line }
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
},
|
|
26
|
+
source: "static",
|
|
27
|
+
title: "Unauthenticated shard access enabled on an RLS-gapped schema"
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { allowUnauthenticatedShardAccessEnabled as default };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const makeArgumentDerivedSinkLint = (config) => {
|
|
4
|
+
const lint = {
|
|
5
|
+
categories: config.categories,
|
|
6
|
+
description: config.description,
|
|
7
|
+
facing: config.facing,
|
|
8
|
+
level: config.level,
|
|
9
|
+
name: config.name,
|
|
10
|
+
remediation: config.remediation,
|
|
11
|
+
run: (context) => {
|
|
12
|
+
const accesses = config.getAccesses(context);
|
|
13
|
+
if (accesses === void 0 || config.suppressWhen?.(context) === true) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return accesses.map(
|
|
17
|
+
(access) => emit(lint, {
|
|
18
|
+
cacheKey: config.cacheKey(access),
|
|
19
|
+
detail: config.detail(access),
|
|
20
|
+
metadata: config.metadata(access)
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
source: "static",
|
|
25
|
+
title: config.title
|
|
26
|
+
};
|
|
27
|
+
return lint;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { makeArgumentDerivedSinkLint as m };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const authCsrfCheckDisabled = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "A `createAuth({...})` call sets `advanced.disableCSRFCheck: true`, turning off better-auth's origin validation for state-changing auth requests — a cross-site request riding the ambient auth cookie is processed like a same-origin one.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "ERROR",
|
|
8
|
+
name: "auth_csrf_check_disabled",
|
|
9
|
+
remediation: "Remove `advanced.disableCSRFCheck: true` from `createAuth({...})` and rely on `trustedOrigins` for origin validation instead of disabling the check.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.authConfigs === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.authConfigs.filter((config) => config.analyzable && config.disableCsrfCheck).map(
|
|
15
|
+
(config) => emit(authCsrfCheckDisabled, {
|
|
16
|
+
cacheKey: `auth_csrf_check_disabled:${config.file}:${config.line.toString()}`,
|
|
17
|
+
detail: `\`createAuth\` in \`${config.exportName}\` (${config.file}:${config.line.toString()}) sets \`advanced.disableCSRFCheck: true\`, turning off origin validation for state-changing auth requests. Remove the flag and rely on \`trustedOrigins\` instead.`,
|
|
18
|
+
metadata: { exportName: config.exportName, file: config.file, line: config.line }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "createAuth CSRF check disabled"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { authCsrfCheckDisabled as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const authEmailVerificationDisabled = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "A `createAuth({...})` call enables `emailAndPassword` with no `requireEmailVerification: true`. better-auth defaults email verification off, so an account is usable before the caller has proven ownership of the email address.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "WARN",
|
|
8
|
+
name: "auth_email_verification_disabled",
|
|
9
|
+
remediation: "Set `emailAndPassword.requireEmailVerification: true` on `createAuth({...})` so a new account can't act until its email address is verified.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.authConfigs === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.authConfigs.filter((config) => config.analyzable && config.emailPasswordEnabled && !config.requireEmailVerification).map(
|
|
15
|
+
(config) => emit(authEmailVerificationDisabled, {
|
|
16
|
+
cacheKey: `auth_email_verification_disabled:${config.file}:${config.line.toString()}`,
|
|
17
|
+
detail: `\`createAuth\` in \`${config.exportName}\` (${config.file}:${config.line.toString()}) enables \`emailAndPassword\` with no \`requireEmailVerification: true\`, so an account is usable before its email is proven. Set \`emailAndPassword.requireEmailVerification: true\`.`,
|
|
18
|
+
metadata: { exportName: config.exportName, file: config.file, line: config.line }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "createAuth email verification disabled"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { authEmailVerificationDisabled as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const authSecureCookiesDisabled = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "A `createAuth({...})` call sets `advanced.useSecureCookies: false`, overriding Lunora's secure-by-default posture — the session cookie ships without `Secure` even on an HTTPS deployment, so it can be sent over a plaintext connection.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "ERROR",
|
|
8
|
+
name: "auth_secure_cookies_disabled",
|
|
9
|
+
remediation: "Remove `advanced.useSecureCookies: false` from `createAuth({...})` — let Lunora's secure-by-default posture set the `Secure` cookie attribute for an HTTPS deployment.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.authConfigs === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.authConfigs.filter((config) => config.analyzable && config.secureCookiesDisabled).map(
|
|
15
|
+
(config) => emit(authSecureCookiesDisabled, {
|
|
16
|
+
cacheKey: `auth_secure_cookies_disabled:${config.file}:${config.line.toString()}`,
|
|
17
|
+
detail: `\`createAuth\` in \`${config.exportName}\` (${config.file}:${config.line.toString()}) sets \`advanced.useSecureCookies: false\`, so the session cookie ships without \`Secure\` even on an HTTPS deployment. Remove the override and let Lunora's secure-by-default posture apply.`,
|
|
18
|
+
metadata: { exportName: config.exportName, file: config.file, line: config.line }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "createAuth secure cookies disabled"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { authSecureCookiesDisabled as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const authSessionFreshageZero = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "A `createAuth({...})` call sets `session.freshAge: 0`, disabling better-auth's recent-reauth check for sensitive operations — a long-lived stolen session can perform them without proving the caller still controls the credentials.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "WARN",
|
|
8
|
+
name: "auth_session_freshage_zero",
|
|
9
|
+
remediation: "Remove `session.freshAge: 0` from `createAuth({...})` (or set it to a real window) so sensitive operations require a recently re-authenticated session.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.authConfigs === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.authConfigs.filter((config) => config.analyzable && config.sessionFreshAgeZero).map(
|
|
15
|
+
(config) => emit(authSessionFreshageZero, {
|
|
16
|
+
cacheKey: `auth_session_freshage_zero:${config.file}:${config.line.toString()}`,
|
|
17
|
+
detail: `\`createAuth\` in \`${config.exportName}\` (${config.file}:${config.line.toString()}) sets \`session.freshAge: 0\`, disabling the recent-reauth check for sensitive operations. Remove the override or set a real window.`,
|
|
18
|
+
metadata: { exportName: config.exportName, file: config.file, line: config.line }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "createAuth session freshAge zero"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { authSessionFreshageZero as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const authTrustedOriginsWildcard = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "A `createAuth({...})` call's `trustedOrigins` array contains a `\"*\"` entry, disabling better-auth's CSRF/origin validation entirely — any origin is accepted for state-changing auth requests.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "ERROR",
|
|
8
|
+
name: "auth_trusted_origins_wildcard",
|
|
9
|
+
remediation: 'List the specific origins your deployment serves in `trustedOrigins` instead of `"*"` — a wildcard disables origin validation for every caller.',
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.authConfigs === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.authConfigs.filter((config) => config.analyzable && config.trustedOriginsWildcard).map(
|
|
15
|
+
(config) => emit(authTrustedOriginsWildcard, {
|
|
16
|
+
cacheKey: `auth_trusted_origins_wildcard:${config.file}:${config.line.toString()}`,
|
|
17
|
+
detail: `\`createAuth\` in \`${config.exportName}\` (${config.file}:${config.line.toString()}) sets \`trustedOrigins\` to include \`"*"\`, disabling CSRF/origin validation for every request. List the specific origins your deployment serves instead.`,
|
|
18
|
+
metadata: { exportName: config.exportName, file: config.file, line: config.line }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "createAuth trustedOrigins wildcard"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { authTrustedOriginsWildcard as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const browserAllowPrivateTargets = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "`createBrowser({ allowPrivateTargets: true })` disables the private/internal-IP SSRF guard, letting a navigated URL reach cloud metadata endpoints, internal services, or loopback. Combined with a request-supplied URL this is a server-side request forgery path.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "ERROR",
|
|
8
|
+
name: "browser_allow_private_targets",
|
|
9
|
+
remediation: "Remove `allowPrivateTargets: true` from `createBrowser({...})` so the private/internal-address guard stays on. If a specific internal host is genuinely required, pin it with an `allowedHosts` allowlist instead of opening all private targets.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.configCalls === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.configCalls.filter((call) => call.callee === "createBrowser" && call.trueKeys.includes("allowPrivateTargets")).map(
|
|
15
|
+
(call) => emit(browserAllowPrivateTargets, {
|
|
16
|
+
cacheKey: `browser_allow_private_targets:${call.file}:${call.line.toString()}`,
|
|
17
|
+
detail: `\`createBrowser({ allowPrivateTargets: true })\` in ${call.file}:${call.line.toString()} disables the private/internal-address SSRF guard — a navigated URL can then reach cloud metadata, internal services, or loopback. Remove the override, or pin the specific host with \`allowedHosts\` instead.`,
|
|
18
|
+
metadata: { callee: call.callee, file: call.file, line: call.line }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "Browser with private-target SSRF guard disabled"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { browserAllowPrivateTargets as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { m as makeArgumentDerivedSinkLint } from './argument-derived-sink-C1xTTqAt.mjs';
|
|
2
|
+
|
|
3
|
+
const browserUserUrlWithoutAllowlist = makeArgumentDerivedSinkLint({
|
|
4
|
+
cacheKey: (access) => `browser_user_url_without_allowlist:${access.file}:${access.line.toString()}`,
|
|
5
|
+
categories: ["SECURITY"],
|
|
6
|
+
description: "A `ctx.browser.<method>(url, …)` call navigates to a URL derived from the handler's `args` with no server-side scoping, and no `createBrowser` allowlist contains it. The default guard blocks private targets but not arbitrary public URLs, so any caller can make the headless browser an open-proxy / SSRF tool (fetch arbitrary third-party URLs, DNS-rebind to internal hosts).",
|
|
7
|
+
detail: (access) => `\`ctx.browser.${access.method}\` in \`${access.exportName}\` (${access.file}:${access.line.toString()}) navigates to a URL derived from \`args\` with no server-side scoping, and no \`createBrowser\` allowlist contains it — the default guard blocks private targets but not arbitrary public URLs, so any caller can turn the headless browser into an open-proxy / SSRF tool. Pin \`allowedHosts\` (and/or \`resolveDns\`) on \`createBrowser({...})\`, and derive the URL from server-trusted state where possible.`,
|
|
8
|
+
facing: "EXTERNAL",
|
|
9
|
+
getAccesses: (context) => context.browserUrlAccesses,
|
|
10
|
+
level: "WARN",
|
|
11
|
+
metadata: (access) => {
|
|
12
|
+
return { exportName: access.exportName, file: access.file, line: access.line, method: access.method };
|
|
13
|
+
},
|
|
14
|
+
name: "browser_user_url_without_allowlist",
|
|
15
|
+
remediation: "Pin the browser with an `allowedHosts` allowlist (and/or `resolveDns`) on `createBrowser({...})`, and derive the navigation URL from server-trusted state where possible rather than passing `args` straight to `ctx.browser`.",
|
|
16
|
+
// A `createBrowser` hardened with an `allowedHosts` allowlist or a pinned
|
|
17
|
+
// `resolveDns` contains the SSRF surface — suppress every finding when one is
|
|
18
|
+
// visible. Only an analyzable (non-spread, static object-literal) config call
|
|
19
|
+
// counts; an opaque config could set the key elsewhere but can't be relied on.
|
|
20
|
+
suppressWhen: (context) => (context.configCalls ?? []).some(
|
|
21
|
+
(call) => call.callee === "createBrowser" && call.analyzable && (call.presentKeys.includes("allowedHosts") || call.presentKeys.includes("resolveDns"))
|
|
22
|
+
),
|
|
23
|
+
title: "Browser navigates to arg-derived URL with no allowlist"
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export { browserUserUrlWithoutAllowlist as default };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { m as makeArgumentDerivedSinkLint } from './argument-derived-sink-C1xTTqAt.mjs';
|
|
2
|
+
|
|
3
|
+
const containerInstanceKeyFromUserInput = makeArgumentDerivedSinkLint({
|
|
4
|
+
cacheKey: (access) => `container_instance_key_from_user_input:${access.file}:${access.line.toString()}`,
|
|
5
|
+
categories: ["SECURITY"],
|
|
6
|
+
description: "A `ctx.containers.<name>.get` call routes to a container instance using a key derived from the handler's `args` with no server-side scoping. Any caller can supply another tenant's key and reach that tenant's container instance — a cross-tenant insecure direct object reference (IDOR).",
|
|
7
|
+
detail: (access) => `\`ctx.containers.*.${access.method}\` in \`${access.exportName}\` (${access.file}:${access.line.toString()}) routes to a container instance using a key derived from \`args\` with no server-side scoping — any caller can reach another tenant's container (IDOR). Derive the key from a server-trusted identity (e.g. \`\${ctx.auth.userId}\`).`,
|
|
8
|
+
facing: "EXTERNAL",
|
|
9
|
+
getAccesses: (context) => context.containerKeyAccesses,
|
|
10
|
+
level: "WARN",
|
|
11
|
+
metadata: (access) => {
|
|
12
|
+
return { exportName: access.exportName, file: access.file, line: access.line, method: access.method };
|
|
13
|
+
},
|
|
14
|
+
name: "container_instance_key_from_user_input",
|
|
15
|
+
remediation: `Derive the container instance key from a server-trusted identity (e.g. \`\${ctx.auth.userId}\`) or a record the caller owns — never pass request input straight to \`ctx.containers.<name>.get\`.`,
|
|
16
|
+
title: "Possible cross-tenant IDOR from arg-derived unscoped container key"
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export { containerInstanceKeyFromUserInput as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { e as emit } from './finding-Dm_zvzS1.mjs';
|
|
2
|
+
|
|
3
|
+
const containerRuntimeEgressRelaxation = {
|
|
4
|
+
categories: ["SECURITY"],
|
|
5
|
+
description: "A runtime `.egress.allow(...)` / `.egress.deny(...)` / `.egress.setAllowed(...)` call mutates the container's egress firewall at runtime, re-opening a network surface the static analysis assumes is locked to the `defineContainer` declaration.",
|
|
6
|
+
facing: "EXTERNAL",
|
|
7
|
+
level: "WARN",
|
|
8
|
+
name: "container_runtime_egress_relaxation",
|
|
9
|
+
remediation: "Prefer declaring the egress policy statically in `lunora/containers.ts` where it is auditable. When a runtime mutation is unavoidable, scope it behind a server-trusted check and keep the change narrow (a specific host, not a broad allow) rather than an unconditional relaxation.",
|
|
10
|
+
run: (context) => {
|
|
11
|
+
if (context.containerOverrides === void 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
return context.containerOverrides.filter((override) => override.kind === "egress_relaxation").map(
|
|
15
|
+
(override) => emit(containerRuntimeEgressRelaxation, {
|
|
16
|
+
cacheKey: `container_runtime_egress_relaxation:${override.file}:${override.line.toString()}`,
|
|
17
|
+
detail: `\`${override.exportName}\` calls \`.egress.${override.detail}(...)\` at runtime, mutating the container's egress firewall.`,
|
|
18
|
+
metadata: { exportName: override.exportName, file: override.file, line: override.line, method: override.detail }
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
source: "static",
|
|
23
|
+
title: "Runtime egress mutation relaxes the container firewall"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { containerRuntimeEgressRelaxation as default };
|