oauthlint-rules 0.1.0

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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +3 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/loader.d.ts +35 -0
  8. package/dist/loader.d.ts.map +1 -0
  9. package/dist/loader.js +72 -0
  10. package/dist/loader.js.map +1 -0
  11. package/dist/manifest.d.ts +10 -0
  12. package/dist/manifest.d.ts.map +1 -0
  13. package/dist/manifest.js +10 -0
  14. package/dist/manifest.js.map +1 -0
  15. package/dist/schema.d.ts +425 -0
  16. package/dist/schema.d.ts.map +1 -0
  17. package/dist/schema.js +71 -0
  18. package/dist/schema.js.map +1 -0
  19. package/package.json +61 -0
  20. package/rules/cookie/long-lived.yml +42 -0
  21. package/rules/cookie/no-httponly.yml +54 -0
  22. package/rules/cookie/no-samesite.yml +56 -0
  23. package/rules/cookie/no-secure.yml +58 -0
  24. package/rules/cors/wildcard-with-credentials.yml +56 -0
  25. package/rules/flow/insecure-random.yml +56 -0
  26. package/rules/flow/no-rate-limit.yml +39 -0
  27. package/rules/flow/password-min-length.yml +44 -0
  28. package/rules/flow/password-plaintext.yml +82 -0
  29. package/rules/flow/timing-unsafe-compare.yml +103 -0
  30. package/rules/jwt/alg-none.yml +46 -0
  31. package/rules/jwt/algorithm-confusion.yml +67 -0
  32. package/rules/jwt/in-url.yml +33 -0
  33. package/rules/jwt/localstorage.yml +39 -0
  34. package/rules/jwt/no-audience.yml +49 -0
  35. package/rules/jwt/no-expiration.yml +45 -0
  36. package/rules/jwt/no-issuer.yml +40 -0
  37. package/rules/jwt/weak-secret.yml +56 -0
  38. package/rules/oauth/broad-scope.yml +39 -0
  39. package/rules/oauth/hardcoded-secret.yml +41 -0
  40. package/rules/oauth/implicit-flow.yml +36 -0
  41. package/rules/oauth/long-token-lifetime.yml +57 -0
  42. package/rules/oauth/no-pkce.yml +50 -0
  43. package/rules/oauth/no-state-validation.yml +42 -0
  44. package/rules/oauth/no-state.yml +41 -0
  45. package/rules/oauth/open-redirect-callback.yml +47 -0
  46. package/rules/oauth/wildcard-redirect.yml +45 -0
  47. package/rules/secret/provider-key.yml +44 -0
  48. package/rules/session/id-in-url.yml +30 -0
  49. package/rules/session/no-regeneration.yml +53 -0
@@ -0,0 +1,425 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * OAuthLint rule severity levels.
4
+ * Mapped to Semgrep severities and used for exit code decisions.
5
+ */
6
+ export declare const SeveritySchema: z.ZodEnum<["INFO", "WARNING", "ERROR"]>;
7
+ export type Severity = z.infer<typeof SeveritySchema>;
8
+ /**
9
+ * OAuthLint-specific metadata attached to each rule.
10
+ * The `llm-prevalence` tag is core to our positioning: it indicates
11
+ * how often this anti-pattern appears in code produced by LLMs (Cursor,
12
+ * Claude Code, Copilot, Gemini Code Assist).
13
+ */
14
+ export declare const OAuthLintMetadataSchema: z.ZodObject<{
15
+ 'oauthlint-rule-id': z.ZodString;
16
+ 'oauthlint-doc-url': z.ZodString;
17
+ category: z.ZodLiteral<"security">;
18
+ cwe: z.ZodOptional<z.ZodString>;
19
+ owasp: z.ZodOptional<z.ZodString>;
20
+ 'llm-prevalence': z.ZodEnum<["HIGH", "MEDIUM", "LOW"]>;
21
+ references: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
22
+ technology: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ 'oauthlint-rule-id': string;
25
+ 'oauthlint-doc-url': string;
26
+ category: "security";
27
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
28
+ cwe?: string | undefined;
29
+ owasp?: string | undefined;
30
+ references?: string[] | undefined;
31
+ technology?: string[] | undefined;
32
+ }, {
33
+ 'oauthlint-rule-id': string;
34
+ 'oauthlint-doc-url': string;
35
+ category: "security";
36
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
37
+ cwe?: string | undefined;
38
+ owasp?: string | undefined;
39
+ references?: string[] | undefined;
40
+ technology?: string[] | undefined;
41
+ }>;
42
+ export type OAuthLintMetadata = z.infer<typeof OAuthLintMetadataSchema>;
43
+ export declare const RuleSchema: z.ZodEffects<z.ZodObject<{
44
+ id: z.ZodString;
45
+ languages: z.ZodArray<z.ZodString, "many">;
46
+ severity: z.ZodEnum<["INFO", "WARNING", "ERROR"]>;
47
+ message: z.ZodString;
48
+ metadata: z.ZodObject<{
49
+ 'oauthlint-rule-id': z.ZodString;
50
+ 'oauthlint-doc-url': z.ZodString;
51
+ category: z.ZodLiteral<"security">;
52
+ cwe: z.ZodOptional<z.ZodString>;
53
+ owasp: z.ZodOptional<z.ZodString>;
54
+ 'llm-prevalence': z.ZodEnum<["HIGH", "MEDIUM", "LOW"]>;
55
+ references: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
56
+ technology: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
57
+ }, "strip", z.ZodTypeAny, {
58
+ 'oauthlint-rule-id': string;
59
+ 'oauthlint-doc-url': string;
60
+ category: "security";
61
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
62
+ cwe?: string | undefined;
63
+ owasp?: string | undefined;
64
+ references?: string[] | undefined;
65
+ technology?: string[] | undefined;
66
+ }, {
67
+ 'oauthlint-rule-id': string;
68
+ 'oauthlint-doc-url': string;
69
+ category: "security";
70
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
71
+ cwe?: string | undefined;
72
+ owasp?: string | undefined;
73
+ references?: string[] | undefined;
74
+ technology?: string[] | undefined;
75
+ }>;
76
+ pattern: z.ZodOptional<z.ZodType<unknown, z.ZodTypeDef, unknown>>;
77
+ 'pattern-either': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
78
+ 'pattern-not': z.ZodOptional<z.ZodType<unknown, z.ZodTypeDef, unknown>>;
79
+ 'pattern-inside': z.ZodOptional<z.ZodType<unknown, z.ZodTypeDef, unknown>>;
80
+ 'pattern-regex': z.ZodOptional<z.ZodString>;
81
+ 'pattern-either-regex': z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
82
+ patterns: z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
83
+ fix: z.ZodOptional<z.ZodString>;
84
+ mode: z.ZodOptional<z.ZodEnum<["search", "taint"]>>;
85
+ 'pattern-sources': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
86
+ 'pattern-sinks': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
87
+ 'pattern-sanitizers': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
88
+ 'pattern-propagators': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
89
+ }, "strip", z.ZodTypeAny, {
90
+ message: string;
91
+ id: string;
92
+ languages: string[];
93
+ severity: "INFO" | "WARNING" | "ERROR";
94
+ metadata: {
95
+ 'oauthlint-rule-id': string;
96
+ 'oauthlint-doc-url': string;
97
+ category: "security";
98
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
99
+ cwe?: string | undefined;
100
+ owasp?: string | undefined;
101
+ references?: string[] | undefined;
102
+ technology?: string[] | undefined;
103
+ };
104
+ pattern?: unknown;
105
+ 'pattern-either'?: unknown[] | undefined;
106
+ 'pattern-not'?: unknown;
107
+ 'pattern-inside'?: unknown;
108
+ 'pattern-regex'?: string | undefined;
109
+ 'pattern-either-regex'?: string[] | undefined;
110
+ patterns?: unknown[] | undefined;
111
+ fix?: string | undefined;
112
+ mode?: "search" | "taint" | undefined;
113
+ 'pattern-sources'?: unknown[] | undefined;
114
+ 'pattern-sinks'?: unknown[] | undefined;
115
+ 'pattern-sanitizers'?: unknown[] | undefined;
116
+ 'pattern-propagators'?: unknown[] | undefined;
117
+ }, {
118
+ message: string;
119
+ id: string;
120
+ languages: string[];
121
+ severity: "INFO" | "WARNING" | "ERROR";
122
+ metadata: {
123
+ 'oauthlint-rule-id': string;
124
+ 'oauthlint-doc-url': string;
125
+ category: "security";
126
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
127
+ cwe?: string | undefined;
128
+ owasp?: string | undefined;
129
+ references?: string[] | undefined;
130
+ technology?: string[] | undefined;
131
+ };
132
+ pattern?: unknown;
133
+ 'pattern-either'?: unknown[] | undefined;
134
+ 'pattern-not'?: unknown;
135
+ 'pattern-inside'?: unknown;
136
+ 'pattern-regex'?: string | undefined;
137
+ 'pattern-either-regex'?: string[] | undefined;
138
+ patterns?: unknown[] | undefined;
139
+ fix?: string | undefined;
140
+ mode?: "search" | "taint" | undefined;
141
+ 'pattern-sources'?: unknown[] | undefined;
142
+ 'pattern-sinks'?: unknown[] | undefined;
143
+ 'pattern-sanitizers'?: unknown[] | undefined;
144
+ 'pattern-propagators'?: unknown[] | undefined;
145
+ }>, {
146
+ message: string;
147
+ id: string;
148
+ languages: string[];
149
+ severity: "INFO" | "WARNING" | "ERROR";
150
+ metadata: {
151
+ 'oauthlint-rule-id': string;
152
+ 'oauthlint-doc-url': string;
153
+ category: "security";
154
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
155
+ cwe?: string | undefined;
156
+ owasp?: string | undefined;
157
+ references?: string[] | undefined;
158
+ technology?: string[] | undefined;
159
+ };
160
+ pattern?: unknown;
161
+ 'pattern-either'?: unknown[] | undefined;
162
+ 'pattern-not'?: unknown;
163
+ 'pattern-inside'?: unknown;
164
+ 'pattern-regex'?: string | undefined;
165
+ 'pattern-either-regex'?: string[] | undefined;
166
+ patterns?: unknown[] | undefined;
167
+ fix?: string | undefined;
168
+ mode?: "search" | "taint" | undefined;
169
+ 'pattern-sources'?: unknown[] | undefined;
170
+ 'pattern-sinks'?: unknown[] | undefined;
171
+ 'pattern-sanitizers'?: unknown[] | undefined;
172
+ 'pattern-propagators'?: unknown[] | undefined;
173
+ }, {
174
+ message: string;
175
+ id: string;
176
+ languages: string[];
177
+ severity: "INFO" | "WARNING" | "ERROR";
178
+ metadata: {
179
+ 'oauthlint-rule-id': string;
180
+ 'oauthlint-doc-url': string;
181
+ category: "security";
182
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
183
+ cwe?: string | undefined;
184
+ owasp?: string | undefined;
185
+ references?: string[] | undefined;
186
+ technology?: string[] | undefined;
187
+ };
188
+ pattern?: unknown;
189
+ 'pattern-either'?: unknown[] | undefined;
190
+ 'pattern-not'?: unknown;
191
+ 'pattern-inside'?: unknown;
192
+ 'pattern-regex'?: string | undefined;
193
+ 'pattern-either-regex'?: string[] | undefined;
194
+ patterns?: unknown[] | undefined;
195
+ fix?: string | undefined;
196
+ mode?: "search" | "taint" | undefined;
197
+ 'pattern-sources'?: unknown[] | undefined;
198
+ 'pattern-sinks'?: unknown[] | undefined;
199
+ 'pattern-sanitizers'?: unknown[] | undefined;
200
+ 'pattern-propagators'?: unknown[] | undefined;
201
+ }>;
202
+ export type Rule = z.infer<typeof RuleSchema>;
203
+ export declare const RuleFileSchema: z.ZodObject<{
204
+ rules: z.ZodArray<z.ZodEffects<z.ZodObject<{
205
+ id: z.ZodString;
206
+ languages: z.ZodArray<z.ZodString, "many">;
207
+ severity: z.ZodEnum<["INFO", "WARNING", "ERROR"]>;
208
+ message: z.ZodString;
209
+ metadata: z.ZodObject<{
210
+ 'oauthlint-rule-id': z.ZodString;
211
+ 'oauthlint-doc-url': z.ZodString;
212
+ category: z.ZodLiteral<"security">;
213
+ cwe: z.ZodOptional<z.ZodString>;
214
+ owasp: z.ZodOptional<z.ZodString>;
215
+ 'llm-prevalence': z.ZodEnum<["HIGH", "MEDIUM", "LOW"]>;
216
+ references: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
217
+ technology: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
218
+ }, "strip", z.ZodTypeAny, {
219
+ 'oauthlint-rule-id': string;
220
+ 'oauthlint-doc-url': string;
221
+ category: "security";
222
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
223
+ cwe?: string | undefined;
224
+ owasp?: string | undefined;
225
+ references?: string[] | undefined;
226
+ technology?: string[] | undefined;
227
+ }, {
228
+ 'oauthlint-rule-id': string;
229
+ 'oauthlint-doc-url': string;
230
+ category: "security";
231
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
232
+ cwe?: string | undefined;
233
+ owasp?: string | undefined;
234
+ references?: string[] | undefined;
235
+ technology?: string[] | undefined;
236
+ }>;
237
+ pattern: z.ZodOptional<z.ZodType<unknown, z.ZodTypeDef, unknown>>;
238
+ 'pattern-either': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
239
+ 'pattern-not': z.ZodOptional<z.ZodType<unknown, z.ZodTypeDef, unknown>>;
240
+ 'pattern-inside': z.ZodOptional<z.ZodType<unknown, z.ZodTypeDef, unknown>>;
241
+ 'pattern-regex': z.ZodOptional<z.ZodString>;
242
+ 'pattern-either-regex': z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
243
+ patterns: z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
244
+ fix: z.ZodOptional<z.ZodString>;
245
+ mode: z.ZodOptional<z.ZodEnum<["search", "taint"]>>;
246
+ 'pattern-sources': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
247
+ 'pattern-sinks': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
248
+ 'pattern-sanitizers': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
249
+ 'pattern-propagators': z.ZodOptional<z.ZodArray<z.ZodType<unknown, z.ZodTypeDef, unknown>, "many">>;
250
+ }, "strip", z.ZodTypeAny, {
251
+ message: string;
252
+ id: string;
253
+ languages: string[];
254
+ severity: "INFO" | "WARNING" | "ERROR";
255
+ metadata: {
256
+ 'oauthlint-rule-id': string;
257
+ 'oauthlint-doc-url': string;
258
+ category: "security";
259
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
260
+ cwe?: string | undefined;
261
+ owasp?: string | undefined;
262
+ references?: string[] | undefined;
263
+ technology?: string[] | undefined;
264
+ };
265
+ pattern?: unknown;
266
+ 'pattern-either'?: unknown[] | undefined;
267
+ 'pattern-not'?: unknown;
268
+ 'pattern-inside'?: unknown;
269
+ 'pattern-regex'?: string | undefined;
270
+ 'pattern-either-regex'?: string[] | undefined;
271
+ patterns?: unknown[] | undefined;
272
+ fix?: string | undefined;
273
+ mode?: "search" | "taint" | undefined;
274
+ 'pattern-sources'?: unknown[] | undefined;
275
+ 'pattern-sinks'?: unknown[] | undefined;
276
+ 'pattern-sanitizers'?: unknown[] | undefined;
277
+ 'pattern-propagators'?: unknown[] | undefined;
278
+ }, {
279
+ message: string;
280
+ id: string;
281
+ languages: string[];
282
+ severity: "INFO" | "WARNING" | "ERROR";
283
+ metadata: {
284
+ 'oauthlint-rule-id': string;
285
+ 'oauthlint-doc-url': string;
286
+ category: "security";
287
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
288
+ cwe?: string | undefined;
289
+ owasp?: string | undefined;
290
+ references?: string[] | undefined;
291
+ technology?: string[] | undefined;
292
+ };
293
+ pattern?: unknown;
294
+ 'pattern-either'?: unknown[] | undefined;
295
+ 'pattern-not'?: unknown;
296
+ 'pattern-inside'?: unknown;
297
+ 'pattern-regex'?: string | undefined;
298
+ 'pattern-either-regex'?: string[] | undefined;
299
+ patterns?: unknown[] | undefined;
300
+ fix?: string | undefined;
301
+ mode?: "search" | "taint" | undefined;
302
+ 'pattern-sources'?: unknown[] | undefined;
303
+ 'pattern-sinks'?: unknown[] | undefined;
304
+ 'pattern-sanitizers'?: unknown[] | undefined;
305
+ 'pattern-propagators'?: unknown[] | undefined;
306
+ }>, {
307
+ message: string;
308
+ id: string;
309
+ languages: string[];
310
+ severity: "INFO" | "WARNING" | "ERROR";
311
+ metadata: {
312
+ 'oauthlint-rule-id': string;
313
+ 'oauthlint-doc-url': string;
314
+ category: "security";
315
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
316
+ cwe?: string | undefined;
317
+ owasp?: string | undefined;
318
+ references?: string[] | undefined;
319
+ technology?: string[] | undefined;
320
+ };
321
+ pattern?: unknown;
322
+ 'pattern-either'?: unknown[] | undefined;
323
+ 'pattern-not'?: unknown;
324
+ 'pattern-inside'?: unknown;
325
+ 'pattern-regex'?: string | undefined;
326
+ 'pattern-either-regex'?: string[] | undefined;
327
+ patterns?: unknown[] | undefined;
328
+ fix?: string | undefined;
329
+ mode?: "search" | "taint" | undefined;
330
+ 'pattern-sources'?: unknown[] | undefined;
331
+ 'pattern-sinks'?: unknown[] | undefined;
332
+ 'pattern-sanitizers'?: unknown[] | undefined;
333
+ 'pattern-propagators'?: unknown[] | undefined;
334
+ }, {
335
+ message: string;
336
+ id: string;
337
+ languages: string[];
338
+ severity: "INFO" | "WARNING" | "ERROR";
339
+ metadata: {
340
+ 'oauthlint-rule-id': string;
341
+ 'oauthlint-doc-url': string;
342
+ category: "security";
343
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
344
+ cwe?: string | undefined;
345
+ owasp?: string | undefined;
346
+ references?: string[] | undefined;
347
+ technology?: string[] | undefined;
348
+ };
349
+ pattern?: unknown;
350
+ 'pattern-either'?: unknown[] | undefined;
351
+ 'pattern-not'?: unknown;
352
+ 'pattern-inside'?: unknown;
353
+ 'pattern-regex'?: string | undefined;
354
+ 'pattern-either-regex'?: string[] | undefined;
355
+ patterns?: unknown[] | undefined;
356
+ fix?: string | undefined;
357
+ mode?: "search" | "taint" | undefined;
358
+ 'pattern-sources'?: unknown[] | undefined;
359
+ 'pattern-sinks'?: unknown[] | undefined;
360
+ 'pattern-sanitizers'?: unknown[] | undefined;
361
+ 'pattern-propagators'?: unknown[] | undefined;
362
+ }>, "many">;
363
+ }, "strip", z.ZodTypeAny, {
364
+ rules: {
365
+ message: string;
366
+ id: string;
367
+ languages: string[];
368
+ severity: "INFO" | "WARNING" | "ERROR";
369
+ metadata: {
370
+ 'oauthlint-rule-id': string;
371
+ 'oauthlint-doc-url': string;
372
+ category: "security";
373
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
374
+ cwe?: string | undefined;
375
+ owasp?: string | undefined;
376
+ references?: string[] | undefined;
377
+ technology?: string[] | undefined;
378
+ };
379
+ pattern?: unknown;
380
+ 'pattern-either'?: unknown[] | undefined;
381
+ 'pattern-not'?: unknown;
382
+ 'pattern-inside'?: unknown;
383
+ 'pattern-regex'?: string | undefined;
384
+ 'pattern-either-regex'?: string[] | undefined;
385
+ patterns?: unknown[] | undefined;
386
+ fix?: string | undefined;
387
+ mode?: "search" | "taint" | undefined;
388
+ 'pattern-sources'?: unknown[] | undefined;
389
+ 'pattern-sinks'?: unknown[] | undefined;
390
+ 'pattern-sanitizers'?: unknown[] | undefined;
391
+ 'pattern-propagators'?: unknown[] | undefined;
392
+ }[];
393
+ }, {
394
+ rules: {
395
+ message: string;
396
+ id: string;
397
+ languages: string[];
398
+ severity: "INFO" | "WARNING" | "ERROR";
399
+ metadata: {
400
+ 'oauthlint-rule-id': string;
401
+ 'oauthlint-doc-url': string;
402
+ category: "security";
403
+ 'llm-prevalence': "HIGH" | "MEDIUM" | "LOW";
404
+ cwe?: string | undefined;
405
+ owasp?: string | undefined;
406
+ references?: string[] | undefined;
407
+ technology?: string[] | undefined;
408
+ };
409
+ pattern?: unknown;
410
+ 'pattern-either'?: unknown[] | undefined;
411
+ 'pattern-not'?: unknown;
412
+ 'pattern-inside'?: unknown;
413
+ 'pattern-regex'?: string | undefined;
414
+ 'pattern-either-regex'?: string[] | undefined;
415
+ patterns?: unknown[] | undefined;
416
+ fix?: string | undefined;
417
+ mode?: "search" | "taint" | undefined;
418
+ 'pattern-sources'?: unknown[] | undefined;
419
+ 'pattern-sinks'?: unknown[] | undefined;
420
+ 'pattern-sanitizers'?: unknown[] | undefined;
421
+ 'pattern-propagators'?: unknown[] | undefined;
422
+ }[];
423
+ }>;
424
+ export type RuleFile = z.infer<typeof RuleFileSchema>;
425
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,cAAc,yCAAuC,CAAC;AACnE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAclC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAUxE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuCpB,CAAC;AAEJ,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEzB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
package/dist/schema.js ADDED
@@ -0,0 +1,71 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * OAuthLint rule severity levels.
4
+ * Mapped to Semgrep severities and used for exit code decisions.
5
+ */
6
+ export const SeveritySchema = z.enum(['INFO', 'WARNING', 'ERROR']);
7
+ /**
8
+ * OAuthLint-specific metadata attached to each rule.
9
+ * The `llm-prevalence` tag is core to our positioning: it indicates
10
+ * how often this anti-pattern appears in code produced by LLMs (Cursor,
11
+ * Claude Code, Copilot, Gemini Code Assist).
12
+ */
13
+ export const OAuthLintMetadataSchema = z.object({
14
+ 'oauthlint-rule-id': z.string().regex(/^AUTH-[A-Z]+-\d{3}$/, {
15
+ message: 'oauthlint-rule-id must match AUTH-<CATEGORY>-<NNN>',
16
+ }),
17
+ 'oauthlint-doc-url': z.string().url(),
18
+ category: z.literal('security'),
19
+ cwe: z
20
+ .string()
21
+ .regex(/^CWE-\d+$/)
22
+ .optional(),
23
+ owasp: z.string().optional(),
24
+ 'llm-prevalence': z.enum(['HIGH', 'MEDIUM', 'LOW']),
25
+ references: z.array(z.string().url()).optional(),
26
+ technology: z.array(z.string()).optional(),
27
+ });
28
+ /**
29
+ * Semgrep rule shape (subset we actually use).
30
+ * Semgrep accepts many shapes; we only validate the fields we rely on.
31
+ */
32
+ const PatternEntrySchema = z.lazy(() => z.union([z.string(), z.record(z.string(), z.unknown()), z.array(PatternEntrySchema)]));
33
+ export const RuleSchema = z
34
+ .object({
35
+ id: z.string().regex(/^auth\.[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*$/, {
36
+ message: 'Rule id must follow auth.<category>.<name> (kebab-case)',
37
+ }),
38
+ languages: z.array(z.string()).min(1),
39
+ severity: SeveritySchema,
40
+ message: z.string().min(20, 'Message must be at least 20 characters'),
41
+ metadata: OAuthLintMetadataSchema,
42
+ pattern: PatternEntrySchema.optional(),
43
+ 'pattern-either': z.array(PatternEntrySchema).optional(),
44
+ 'pattern-not': PatternEntrySchema.optional(),
45
+ 'pattern-inside': PatternEntrySchema.optional(),
46
+ 'pattern-regex': z.string().optional(),
47
+ 'pattern-either-regex': z.array(z.string()).optional(),
48
+ patterns: z.array(PatternEntrySchema).optional(),
49
+ fix: z.string().optional(),
50
+ // Taint-mode rules (Semgrep dataflow).
51
+ mode: z.enum(['search', 'taint']).optional(),
52
+ 'pattern-sources': z.array(PatternEntrySchema).optional(),
53
+ 'pattern-sinks': z.array(PatternEntrySchema).optional(),
54
+ 'pattern-sanitizers': z.array(PatternEntrySchema).optional(),
55
+ 'pattern-propagators': z.array(PatternEntrySchema).optional(),
56
+ })
57
+ .refine((rule) => rule.pattern !== undefined ||
58
+ rule['pattern-either'] !== undefined ||
59
+ rule.patterns !== undefined ||
60
+ rule['pattern-regex'] !== undefined ||
61
+ rule['pattern-either-regex'] !== undefined ||
62
+ // Taint mode defines sources + sinks instead of a top-level pattern.
63
+ (rule.mode === 'taint' &&
64
+ rule['pattern-sources'] !== undefined &&
65
+ rule['pattern-sinks'] !== undefined), {
66
+ message: 'Rule must define at least one of: pattern, pattern-either, patterns, pattern-regex, or be a taint rule with pattern-sources + pattern-sinks',
67
+ });
68
+ export const RuleFileSchema = z.object({
69
+ rules: z.array(RuleSchema).min(1),
70
+ });
71
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "oauthlint-rules",
3
+ "version": "0.1.0",
4
+ "description": "Semgrep rules catching the OAuth/OIDC/JWT anti-patterns that AI coding tools systematically produce.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "rules",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./rules/*": "./rules/*",
20
+ "./manifest": "./dist/manifest.js"
21
+ },
22
+ "keywords": [
23
+ "oauthlint",
24
+ "semgrep",
25
+ "oauth",
26
+ "oidc",
27
+ "jwt",
28
+ "security",
29
+ "sast",
30
+ "auth",
31
+ "ai-code"
32
+ ],
33
+ "dependencies": {
34
+ "yaml": "2.6.1",
35
+ "zod": "3.24.1"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "22.10.5",
39
+ "fast-glob": "3.3.3",
40
+ "typescript": "5.7.3",
41
+ "vitest": "2.1.9"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/Auspeo/oauthlint.git",
49
+ "directory": "rules"
50
+ },
51
+ "homepage": "https://oauthlint.dev",
52
+ "bugs": "https://github.com/Auspeo/oauthlint/issues",
53
+ "license": "MIT",
54
+ "author": "Maurice Anney <webofboss@gmail.com>",
55
+ "scripts": {
56
+ "build": "tsc",
57
+ "test": "vitest",
58
+ "test:run": "vitest run",
59
+ "typecheck": "tsc --noEmit"
60
+ }
61
+ }
@@ -0,0 +1,42 @@
1
+ rules:
2
+ - id: auth.cookie.long-lived
3
+ languages:
4
+ - javascript
5
+ - typescript
6
+ severity: INFO
7
+ message: |
8
+ An auth-looking cookie is being set with a `maxAge` greater than
9
+ 30 days (the threshold is 30 × 24 × 60 × 60 × 1000 = 2_592_000_000
10
+ milliseconds). Long-lived session cookies expand the blast radius
11
+ of any single token theft and bypass server-side revocation if
12
+ the application doesn't validate freshness on every request.
13
+
14
+ Prefer short-lived access cookies (15-60 min) paired with a
15
+ separate refresh token rotation flow. If you really need a "remember
16
+ me" cookie, scope it tightly (`SameSite=Strict`, dedicated path) and
17
+ back it with a server-side allowlist you can revoke.
18
+ pattern-either:
19
+ - patterns:
20
+ - pattern: '$RES.cookie($NAME, $VAL, $OPTS)'
21
+ - metavariable-regex:
22
+ metavariable: $NAME
23
+ regex: ^(['"])(?:[a-zA-Z0-9_-]*(?:session|sid|sess|auth|token|jwt|refresh|access|remember)[a-zA-Z0-9_-]*)\1$
24
+ - metavariable-pattern:
25
+ metavariable: $OPTS
26
+ patterns:
27
+ - pattern: '{..., maxAge: $MS, ...}'
28
+ - metavariable-comparison:
29
+ metavariable: $MS
30
+ comparison: $MS > 2592000000
31
+ metadata:
32
+ oauthlint-rule-id: AUTH-COOKIE-004
33
+ oauthlint-doc-url: https://oauthlint.dev/rules/cookie-long-lived
34
+ category: security
35
+ cwe: CWE-613
36
+ owasp: API2:2023
37
+ llm-prevalence: MEDIUM
38
+ technology:
39
+ - express
40
+ - fastify
41
+ references:
42
+ - https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
@@ -0,0 +1,54 @@
1
+ rules:
2
+ - id: auth.cookie.no-httponly
3
+ languages:
4
+ - javascript
5
+ - typescript
6
+ severity: WARNING
7
+ message: |
8
+ A session/auth cookie is being set WITHOUT the `HttpOnly` flag.
9
+ Any XSS that lands on a page in the same origin will be able to
10
+ read this cookie via `document.cookie` and exfiltrate the session.
11
+
12
+ Set `{ httpOnly: true }` on every authentication cookie. If a
13
+ front-end framework genuinely needs to read it from JS, that is a
14
+ design problem — server-side state is the right answer.
15
+ pattern-either:
16
+ - patterns:
17
+ - pattern: $RES.cookie($NAME, $VAL, $OPTS)
18
+ - metavariable-regex:
19
+ metavariable: $NAME
20
+ regex: ^(['"])(?:[a-zA-Z0-9_-]*(?:session|sid|sess|auth|token|jwt|refresh|access)[a-zA-Z0-9_-]*)\1$
21
+ - metavariable-pattern:
22
+ metavariable: $OPTS
23
+ patterns:
24
+ - pattern-not: '{..., httpOnly: true, ...}'
25
+ - pattern-not: '{..., httpOnly: $X, ...}'
26
+ - pattern: '{...}'
27
+ # HttpOnly explicitly disabled — the exact bug, must fire.
28
+ - patterns:
29
+ - pattern: $RES.cookie($NAME, $VAL, $OPTS)
30
+ - metavariable-regex:
31
+ metavariable: $NAME
32
+ regex: ^(['"])(?:[a-zA-Z0-9_-]*(?:session|sid|sess|auth|token|jwt|refresh|access)[a-zA-Z0-9_-]*)\1$
33
+ - metavariable-pattern:
34
+ metavariable: $OPTS
35
+ pattern: '{..., httpOnly: false, ...}'
36
+ # 2-arg form has no options at all → no HttpOnly (parity with no-secure).
37
+ - patterns:
38
+ - pattern: $RES.cookie($NAME, $VAL)
39
+ - metavariable-regex:
40
+ metavariable: $NAME
41
+ regex: ^(['"])(?:[a-zA-Z0-9_-]*(?:session|sid|sess|auth|token|jwt|refresh|access)[a-zA-Z0-9_-]*)\1$
42
+ fix: '$RES.cookie($NAME, $VAL, { ...$OPTS, httpOnly: true })'
43
+ metadata:
44
+ oauthlint-rule-id: AUTH-COOKIE-002
45
+ oauthlint-doc-url: https://oauthlint.dev/rules/cookie-no-httponly
46
+ category: security
47
+ cwe: CWE-1004
48
+ owasp: API8:2023
49
+ llm-prevalence: HIGH
50
+ technology:
51
+ - express
52
+ - fastify
53
+ references:
54
+ - https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.6