oauthlint 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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/bin/oauthlint.js +5 -0
  4. package/dist/adapters/semgrep.d.ts +36 -0
  5. package/dist/adapters/semgrep.d.ts.map +1 -0
  6. package/dist/adapters/semgrep.js +156 -0
  7. package/dist/adapters/semgrep.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +103 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands/doctor.d.ts +8 -0
  13. package/dist/commands/doctor.d.ts.map +1 -0
  14. package/dist/commands/doctor.js +69 -0
  15. package/dist/commands/doctor.js.map +1 -0
  16. package/dist/commands/init.d.ts +9 -0
  17. package/dist/commands/init.d.ts.map +1 -0
  18. package/dist/commands/init.js +51 -0
  19. package/dist/commands/init.js.map +1 -0
  20. package/dist/commands/list.d.ts +6 -0
  21. package/dist/commands/list.d.ts.map +1 -0
  22. package/dist/commands/list.js +32 -0
  23. package/dist/commands/list.js.map +1 -0
  24. package/dist/commands/scan.d.ts +24 -0
  25. package/dist/commands/scan.d.ts.map +1 -0
  26. package/dist/commands/scan.js +73 -0
  27. package/dist/commands/scan.js.map +1 -0
  28. package/dist/core/config.d.ts +4 -0
  29. package/dist/core/config.d.ts.map +1 -0
  30. package/dist/core/config.js +40 -0
  31. package/dist/core/config.js.map +1 -0
  32. package/dist/core/reporter.d.ts +17 -0
  33. package/dist/core/reporter.d.ts.map +1 -0
  34. package/dist/core/reporter.js +111 -0
  35. package/dist/core/reporter.js.map +1 -0
  36. package/dist/core/sarif.d.ts +69 -0
  37. package/dist/core/sarif.d.ts.map +1 -0
  38. package/dist/core/sarif.js +115 -0
  39. package/dist/core/sarif.js.map +1 -0
  40. package/dist/core/severity.d.ts +23 -0
  41. package/dist/core/severity.d.ts.map +1 -0
  42. package/dist/core/severity.js +42 -0
  43. package/dist/core/severity.js.map +1 -0
  44. package/dist/core/suppress.d.ts +40 -0
  45. package/dist/core/suppress.d.ts.map +1 -0
  46. package/dist/core/suppress.js +81 -0
  47. package/dist/core/suppress.js.map +1 -0
  48. package/dist/index.d.ts +13 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +12 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/types.d.ts +42 -0
  53. package/dist/types.d.ts.map +1 -0
  54. package/dist/types.js +17 -0
  55. package/dist/types.js.map +1 -0
  56. package/package.json +71 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAA4B,MAAM,wBAAwB,CAAC;AAMlF,OAAO,KAAK,EAAW,YAAY,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gDAAgD;IAChD,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,MAAM,CAAC,EAAE,YAAY,GAAG,KAAK,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,8CAA8C;IAC9C,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0DvE"}
@@ -0,0 +1,73 @@
1
+ import { resolve } from 'node:path';
2
+ import { RULES_ROOT } from 'oauthlint-rules';
3
+ import { SemgrepAdapter, SemgrepNotInstalledError } from '../adapters/semgrep.js';
4
+ import { loadConfig } from '../core/config.js';
5
+ import { Reporter } from '../core/reporter.js';
6
+ import { toSarif } from '../core/sarif.js';
7
+ import { exitCodeFor, highestSeverity, meetsThreshold } from '../core/severity.js';
8
+ import { applySuppressions } from '../core/suppress.js';
9
+ export async function runScan(opts) {
10
+ const target = resolve(opts.path);
11
+ const config = await loadConfig(target);
12
+ const rulesDir = opts.rulesDir ?? config.customRulesDir ?? RULES_ROOT;
13
+ const failOn = opts.failOn ?? config.failOn ?? 'HIGH';
14
+ const format = opts.format ?? (opts.json ? 'json' : 'pretty');
15
+ const stream = opts.stream ?? process.stdout;
16
+ const reporter = new Reporter({ json: format === 'json', stream });
17
+ if (format === 'pretty')
18
+ reporter.reportStart(target, await countRuleFiles(rulesDir));
19
+ const adapter = opts.adapter ?? new SemgrepAdapter({ configPath: rulesDir });
20
+ let result;
21
+ try {
22
+ result = await adapter.scan(target, { applyFixes: opts.fix });
23
+ }
24
+ catch (err) {
25
+ if (err instanceof SemgrepNotInstalledError) {
26
+ (opts.stream ?? process.stderr).write(`${err.message}\n`);
27
+ return 127;
28
+ }
29
+ throw err;
30
+ }
31
+ // Apply inline suppression directives first (they're source-truth)
32
+ // and then the severity filter on whatever survives.
33
+ let findings = result.findings;
34
+ const { kept, suppressed } = await applySuppressions(findings);
35
+ findings = kept;
36
+ const minSeverity = opts.severity;
37
+ if (minSeverity) {
38
+ findings = findings.filter((f) => meetsThreshold(f.severity, minSeverity));
39
+ }
40
+ if (format === 'sarif') {
41
+ const sarif = await toSarif({ ...result, findings });
42
+ stream.write(`${JSON.stringify(sarif, null, 2)}\n`);
43
+ }
44
+ else {
45
+ reporter.reportResult({ ...result, findings });
46
+ if (opts.fix && format === 'pretty') {
47
+ const fixed = findings.filter((f) => f.ruleId.startsWith('auth.cookie.')).length;
48
+ stream.write(`\n🛠 Auto-fix applied where possible — re-run \`oauthlint scan\` to confirm.${fixed > 0 ? ` (${fixed} cookie-* finding${fixed === 1 ? '' : 's'} eligible.)` : ''}\n`);
49
+ }
50
+ if (suppressed.length > 0 && format === 'pretty') {
51
+ stream.write(`\nℹ ${suppressed.length} finding${suppressed.length === 1 ? '' : 's'} suppressed via inline directives.\n`);
52
+ }
53
+ }
54
+ if (failOn === 'off')
55
+ return 0;
56
+ const worst = highestSeverity(findings);
57
+ if (worst === null)
58
+ return 0;
59
+ if (!meetsThreshold(worst, failOn))
60
+ return 0;
61
+ return exitCodeFor(worst);
62
+ }
63
+ async function countRuleFiles(dir) {
64
+ try {
65
+ const { loadAllRules } = await import('oauthlint-rules');
66
+ const rules = await loadAllRules(dir);
67
+ return rules.length;
68
+ }
69
+ catch {
70
+ return 0;
71
+ }
72
+ }
73
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type OAuthLintConfig } from '../types.js';
2
+ export declare const DEFAULT_CONFIG: OAuthLintConfig;
3
+ export declare function loadConfig(cwd: string): Promise<OAuthLintConfig>;
4
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAc,MAAM,aAAa,CAAC;AAa/D,eAAO,MAAM,cAAc,EAAE,eAE5B,CAAC;AAEF,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAwBtE"}
@@ -0,0 +1,40 @@
1
+ import { cosmiconfig } from 'cosmiconfig';
2
+ import { z } from 'zod';
3
+ import { SEVERITIES } from '../types.js';
4
+ const SeverityValue = z.enum(SEVERITIES);
5
+ const ConfigSchema = z.object({
6
+ version: z.number().default(1),
7
+ include: z.array(z.string()).optional(),
8
+ exclude: z.array(z.string()).optional(),
9
+ rules: z.record(z.union([z.literal('off'), z.literal('warn'), SeverityValue])).optional(),
10
+ customRulesDir: z.string().optional(),
11
+ failOn: z.union([SeverityValue, z.literal('off')]).default('HIGH'),
12
+ });
13
+ export const DEFAULT_CONFIG = {
14
+ failOn: 'HIGH',
15
+ };
16
+ export async function loadConfig(cwd) {
17
+ const explorer = cosmiconfig('oauthlint', {
18
+ searchPlaces: [
19
+ '.oauthlintrc',
20
+ '.oauthlintrc.json',
21
+ '.oauthlintrc.yml',
22
+ '.oauthlintrc.yaml',
23
+ 'oauthlint.config.js',
24
+ 'oauthlint.config.mjs',
25
+ 'oauthlint.config.cjs',
26
+ 'package.json',
27
+ ],
28
+ });
29
+ const result = await explorer.search(cwd);
30
+ if (!result || result.isEmpty)
31
+ return DEFAULT_CONFIG;
32
+ const parsed = ConfigSchema.safeParse(result.config);
33
+ if (!parsed.success) {
34
+ throw new Error(`Invalid oauthlint config in ${result.filepath}:\n${parsed.error.issues
35
+ .map((i) => ` - ${i.path.join('.')}: ${i.message}`)
36
+ .join('\n')}`);
37
+ }
38
+ return parsed.data;
39
+ }
40
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAwB,UAAU,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAEzC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACvC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACvC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACzF,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;CACnE,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAoB;IAC7C,MAAM,EAAE,MAAM;CACf,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,EAAE;QACxC,YAAY,EAAE;YACZ,cAAc;YACd,mBAAmB;YACnB,kBAAkB;YAClB,mBAAmB;YACnB,qBAAqB;YACrB,sBAAsB;YACtB,sBAAsB;YACtB,cAAc;SACf;KACF,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,cAAc,CAAC;IACrD,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,+BAA+B,MAAM,CAAC,QAAQ,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM;aACpE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aACnD,IAAI,CAAC,IAAI,CAAC,EAAE,CAChB,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { ScanResult } from '../types.js';
2
+ export interface ReporterOptions {
3
+ /** When true, emit machine-readable JSON instead of pretty output. */
4
+ json?: boolean;
5
+ /** Stream to write to (defaults to process.stdout). */
6
+ stream?: NodeJS.WritableStream;
7
+ }
8
+ export declare class Reporter {
9
+ private readonly json;
10
+ private readonly stream;
11
+ constructor(opts?: ReporterOptions);
12
+ reportStart(target: string, ruleCount: number): void;
13
+ reportResult(result: ScanResult): void;
14
+ private printFinding;
15
+ private line;
16
+ }
17
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../src/core/reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAW,UAAU,EAAgB,MAAM,aAAa,CAAC;AAUrE,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAU;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAEnC,IAAI,GAAE,eAAoB;IAKtC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAOpD,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAiFtC,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,IAAI;CAGb"}
@@ -0,0 +1,111 @@
1
+ import pc from 'picocolors';
2
+ const SEVERITY_COLOR = {
3
+ CRITICAL: (s) => pc.bgRed(pc.white(s)),
4
+ HIGH: pc.red,
5
+ MEDIUM: pc.yellow,
6
+ LOW: pc.cyan,
7
+ INFO: pc.gray,
8
+ };
9
+ export class Reporter {
10
+ json;
11
+ stream;
12
+ constructor(opts = {}) {
13
+ this.json = opts.json ?? false;
14
+ this.stream = opts.stream ?? process.stdout;
15
+ }
16
+ reportStart(target, ruleCount) {
17
+ if (this.json)
18
+ return;
19
+ this.line(pc.dim(`OAuthLint — scanning ${pc.bold(target)}`));
20
+ this.line(pc.dim(`Loaded ${ruleCount} rules`));
21
+ this.line('');
22
+ }
23
+ reportResult(result) {
24
+ if (this.json) {
25
+ const payload = {
26
+ schemaVersion: 'oauthlint-v1',
27
+ scannedFiles: result.scannedFiles,
28
+ durationMs: result.durationMs,
29
+ semgrepVersion: result.semgrepVersion,
30
+ errors: result.errors,
31
+ findings: result.findings.map((f) => ({
32
+ ruleId: f.ruleId,
33
+ oauthlintRuleId: f.oauthlintRuleId,
34
+ severity: f.severity,
35
+ filePath: f.filePath,
36
+ startLine: f.startLine,
37
+ endLine: f.endLine,
38
+ message: f.message,
39
+ docUrl: f.docUrl,
40
+ cwe: f.cwe,
41
+ llmPrevalence: f.llmPrevalence,
42
+ })),
43
+ };
44
+ this.line(JSON.stringify(payload, null, 2));
45
+ return;
46
+ }
47
+ const sep = pc.dim('━'.repeat(60));
48
+ if (result.findings.length === 0) {
49
+ this.line(pc.green('✓ No auth issues found.'));
50
+ this.line(pc.dim(`Scanned ${result.scannedFiles} file${result.scannedFiles === 1 ? '' : 's'} in ${result.durationMs}ms.`));
51
+ return;
52
+ }
53
+ const counts = countBySeverity(result.findings);
54
+ const summary = [
55
+ counts.CRITICAL ? pc.red(`${counts.CRITICAL} critical`) : null,
56
+ counts.HIGH ? pc.red(`${counts.HIGH} high`) : null,
57
+ counts.MEDIUM ? pc.yellow(`${counts.MEDIUM} medium`) : null,
58
+ counts.LOW ? pc.cyan(`${counts.LOW} low`) : null,
59
+ counts.INFO ? pc.gray(`${counts.INFO} info`) : null,
60
+ ]
61
+ .filter((x) => x !== null)
62
+ .join(pc.dim(' · '));
63
+ this.line(sep);
64
+ this.line(pc.bold(`Found ${result.findings.length} issue${result.findings.length === 1 ? '' : 's'}: `) +
65
+ summary);
66
+ this.line('');
67
+ const sorted = [...result.findings].sort((a, b) => {
68
+ const order = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO'];
69
+ return order.indexOf(a.severity) - order.indexOf(b.severity);
70
+ });
71
+ for (const f of sorted) {
72
+ this.printFinding(f);
73
+ }
74
+ this.line(sep);
75
+ this.line(pc.dim(`Scanned ${result.scannedFiles} files in ${result.durationMs}ms${result.semgrepVersion ? ` · semgrep ${result.semgrepVersion}` : ''}`));
76
+ if (result.errors.length > 0) {
77
+ this.line(pc.yellow(`⚠ ${result.errors.length} semgrep error${result.errors.length === 1 ? '' : 's'}:`));
78
+ for (const e of result.errors) {
79
+ this.line(pc.dim(` · ${e}`));
80
+ }
81
+ }
82
+ }
83
+ printFinding(f) {
84
+ const tag = SEVERITY_COLOR[f.severity](` ${f.severity} `);
85
+ const id = pc.bold(f.ruleId);
86
+ this.line(`${tag} ${id}${f.oauthlintRuleId ? pc.dim(` (${f.oauthlintRuleId})`) : ''}`);
87
+ this.line(pc.dim(` ${f.filePath}:${f.startLine}`));
88
+ const firstLine = f.message.split('\n')[0]?.trim() ?? '';
89
+ if (firstLine)
90
+ this.line(pc.dim(' → ') + firstLine);
91
+ if (f.docUrl)
92
+ this.line(pc.dim(` 📖 ${f.docUrl}`));
93
+ this.line('');
94
+ }
95
+ line(s = '') {
96
+ this.stream.write(`${s}\n`);
97
+ }
98
+ }
99
+ function countBySeverity(findings) {
100
+ const counts = {
101
+ CRITICAL: 0,
102
+ HIGH: 0,
103
+ MEDIUM: 0,
104
+ LOW: 0,
105
+ INFO: 0,
106
+ };
107
+ for (const f of findings)
108
+ counts[f.severity]++;
109
+ return counts;
110
+ }
111
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../src/core/reporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import type { ScanResult } from '../types.js';
2
+ /**
3
+ * Convert OAuthLint scan results into SARIF v2.1.0.
4
+ *
5
+ * Spec: https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html
6
+ *
7
+ * GitHub Code Scanning consumes SARIF directly via the
8
+ * `github/codeql-action/upload-sarif` action, which is the integration
9
+ * path most enterprise teams want.
10
+ */
11
+ interface SarifReport {
12
+ $schema: string;
13
+ version: string;
14
+ runs: SarifRun[];
15
+ }
16
+ interface SarifRun {
17
+ tool: {
18
+ driver: SarifDriver;
19
+ };
20
+ results: SarifResult[];
21
+ }
22
+ interface SarifDriver {
23
+ name: string;
24
+ version: string;
25
+ informationUri: string;
26
+ rules: SarifRule[];
27
+ }
28
+ interface SarifRule {
29
+ id: string;
30
+ name: string;
31
+ shortDescription: {
32
+ text: string;
33
+ };
34
+ fullDescription: {
35
+ text: string;
36
+ };
37
+ helpUri?: string;
38
+ defaultConfiguration: {
39
+ level: 'note' | 'warning' | 'error';
40
+ };
41
+ properties: {
42
+ tags: string[];
43
+ precision?: string;
44
+ security_severity?: string;
45
+ };
46
+ }
47
+ interface SarifResult {
48
+ ruleId: string;
49
+ level: 'note' | 'warning' | 'error';
50
+ message: {
51
+ text: string;
52
+ };
53
+ locations: SarifLocation[];
54
+ properties?: Record<string, unknown>;
55
+ }
56
+ interface SarifLocation {
57
+ physicalLocation: {
58
+ artifactLocation: {
59
+ uri: string;
60
+ };
61
+ region: {
62
+ startLine: number;
63
+ endLine: number;
64
+ };
65
+ };
66
+ }
67
+ export declare function toSarif(result: ScanResult): Promise<SarifReport>;
68
+ export {};
69
+ //# sourceMappingURL=sarif.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif.d.ts","sourceRoot":"","sources":["../../src/core/sarif.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAW,UAAU,EAAgB,MAAM,aAAa,CAAC;AAErE;;;;;;;;GAQG;AAEH,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,EAAE,CAAC;CAClB;AAED,UAAU,QAAQ;IAChB,IAAI,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,CAAC;IAC9B,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,eAAe,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAA;KAAE,CAAC;IAC9D,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAChF;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACpC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,UAAU,aAAa;IACrB,gBAAgB,EAAE;QAChB,gBAAgB,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;QAClC,MAAM,EAAE;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;KAChD,CAAC;CACH;AAoCD,wBAAsB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAyBtE"}
@@ -0,0 +1,115 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const SARIF_LEVEL = {
5
+ INFO: 'note',
6
+ LOW: 'note',
7
+ MEDIUM: 'warning',
8
+ HIGH: 'error',
9
+ CRITICAL: 'error',
10
+ };
11
+ // GitHub uses a 0.0–10.0 numeric "security severity" to colour the
12
+ // inline annotations in the Security tab.
13
+ const SECURITY_SEVERITY = {
14
+ INFO: '1.0',
15
+ LOW: '3.0',
16
+ MEDIUM: '5.5',
17
+ HIGH: '7.5',
18
+ CRITICAL: '9.5',
19
+ };
20
+ async function readToolVersion() {
21
+ const here = dirname(fileURLToPath(import.meta.url));
22
+ for (const candidate of [
23
+ resolve(here, '..', '..', 'package.json'),
24
+ resolve(here, '..', '..', '..', 'package.json'),
25
+ ]) {
26
+ try {
27
+ const pkg = JSON.parse(await readFile(candidate, 'utf8'));
28
+ if (pkg.version)
29
+ return pkg.version;
30
+ }
31
+ catch {
32
+ /* try next */
33
+ }
34
+ }
35
+ return '0.0.0';
36
+ }
37
+ export async function toSarif(result) {
38
+ const version = await readToolVersion();
39
+ const ruleIndex = new Map();
40
+ for (const f of result.findings) {
41
+ if (ruleIndex.has(f.ruleId))
42
+ continue;
43
+ ruleIndex.set(f.ruleId, buildRule(f));
44
+ }
45
+ return {
46
+ $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
47
+ version: '2.1.0',
48
+ runs: [
49
+ {
50
+ tool: {
51
+ driver: {
52
+ name: 'OAuthLint',
53
+ version,
54
+ informationUri: 'https://oauthlint.dev',
55
+ rules: [...ruleIndex.values()],
56
+ },
57
+ },
58
+ results: result.findings.map((f) => buildResult(f)),
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ function buildRule(f) {
64
+ const tags = ['security', 'oauthlint'];
65
+ if (f.cwe)
66
+ tags.push(`external/cwe/${f.cwe.toLowerCase()}`);
67
+ if (f.llmPrevalence)
68
+ tags.push(`llm-prevalence/${f.llmPrevalence.toLowerCase()}`);
69
+ return {
70
+ id: f.ruleId,
71
+ name: f.ruleId,
72
+ shortDescription: { text: f.message.split('\n')[0]?.trim() ?? f.ruleId },
73
+ fullDescription: { text: f.message.trim() },
74
+ helpUri: f.docUrl,
75
+ defaultConfiguration: { level: SARIF_LEVEL[f.severity] },
76
+ properties: {
77
+ tags,
78
+ precision: 'medium',
79
+ security_severity: SECURITY_SEVERITY[f.severity],
80
+ },
81
+ };
82
+ }
83
+ function buildResult(f) {
84
+ return {
85
+ ruleId: f.ruleId,
86
+ level: SARIF_LEVEL[f.severity],
87
+ message: { text: f.message.split('\n')[0]?.trim() ?? f.ruleId },
88
+ locations: [
89
+ {
90
+ physicalLocation: {
91
+ artifactLocation: { uri: relativise(f.filePath) },
92
+ region: { startLine: f.startLine, endLine: f.endLine },
93
+ },
94
+ },
95
+ ],
96
+ properties: {
97
+ oauthlintRuleId: f.oauthlintRuleId,
98
+ severity: f.severity,
99
+ llmPrevalence: f.llmPrevalence,
100
+ },
101
+ };
102
+ }
103
+ function relativise(filePath) {
104
+ // GitHub Code Scanning wants paths relative to the repo root. Strip
105
+ // the cwd if present; otherwise leave the path alone (caller responsibility).
106
+ const cwd = process.cwd();
107
+ if (filePath.startsWith(`${cwd}/`)) {
108
+ return filePath.slice(cwd.length + 1);
109
+ }
110
+ if (filePath.startsWith(cwd)) {
111
+ return filePath.slice(cwd.length).replace(/^\/+/, '');
112
+ }
113
+ return filePath;
114
+ }
115
+ //# sourceMappingURL=sarif.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../src/core/sarif.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type SeverityName } from '../types.js';
2
+ /**
3
+ * Lower index = less severe. Used for "fail if any finding ≥ threshold".
4
+ */
5
+ export declare function severityRank(s: SeverityName): number;
6
+ /**
7
+ * Return true if `actual` meets or exceeds the `threshold` severity.
8
+ */
9
+ export declare function meetsThreshold(actual: SeverityName, threshold: SeverityName): boolean;
10
+ /**
11
+ * Map our 5-level scale to a process exit code:
12
+ * - 0 → no finding worth blocking on
13
+ * - 1 → at least one HIGH
14
+ * - 2 → at least one CRITICAL
15
+ */
16
+ export declare function exitCodeFor(highest: SeverityName | null): number;
17
+ /**
18
+ * Find the highest severity in a list of findings.
19
+ */
20
+ export declare function highestSeverity(findings: {
21
+ severity: SeverityName;
22
+ }[]): SeverityName | null;
23
+ //# sourceMappingURL=severity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"severity.d.ts","sourceRoot":"","sources":["../../src/core/severity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5D;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAGpD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,GAAG,OAAO,CAErF;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,CAKhE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE;IAAE,QAAQ,EAAE,YAAY,CAAA;CAAE,EAAE,GAAG,YAAY,GAAG,IAAI,CAQ3F"}
@@ -0,0 +1,42 @@
1
+ import { SEVERITIES } from '../types.js';
2
+ /**
3
+ * Lower index = less severe. Used for "fail if any finding ≥ threshold".
4
+ */
5
+ export function severityRank(s) {
6
+ const idx = SEVERITIES.indexOf(s);
7
+ return idx === -1 ? 0 : idx;
8
+ }
9
+ /**
10
+ * Return true if `actual` meets or exceeds the `threshold` severity.
11
+ */
12
+ export function meetsThreshold(actual, threshold) {
13
+ return severityRank(actual) >= severityRank(threshold);
14
+ }
15
+ /**
16
+ * Map our 5-level scale to a process exit code:
17
+ * - 0 → no finding worth blocking on
18
+ * - 1 → at least one HIGH
19
+ * - 2 → at least one CRITICAL
20
+ */
21
+ export function exitCodeFor(highest) {
22
+ if (highest === null)
23
+ return 0;
24
+ if (severityRank(highest) >= severityRank('CRITICAL'))
25
+ return 2;
26
+ if (severityRank(highest) >= severityRank('HIGH'))
27
+ return 1;
28
+ return 0;
29
+ }
30
+ /**
31
+ * Find the highest severity in a list of findings.
32
+ */
33
+ export function highestSeverity(findings) {
34
+ let highest = null;
35
+ for (const f of findings) {
36
+ if (!highest || severityRank(f.severity) > severityRank(highest)) {
37
+ highest = f.severity;
38
+ }
39
+ }
40
+ return highest;
41
+ }
42
+ //# sourceMappingURL=severity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"severity.js","sourceRoot":"","sources":["../../src/core/severity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,aAAa,CAAC;AAE5D;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,CAAe;IAC1C,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAClC,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,MAAoB,EAAE,SAAuB;IAC1E,OAAO,YAAY,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,SAAS,CAAC,CAAC;AACzD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA4B;IACtD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,YAAY,CAAC,UAAU,CAAC;QAAE,OAAO,CAAC,CAAC;IAChE,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC;QAAE,OAAO,CAAC,CAAC;IAC5D,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,QAAsC;IACpE,IAAI,OAAO,GAAwB,IAAI,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,IAAI,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;YACjE,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { Finding } from '../types.js';
2
+ /**
3
+ * Inline suppression directives recognised by OAuthLint.
4
+ *
5
+ * `oauthlint-disable-next-line <rule-id> [-- <reason>]`
6
+ * Suppress findings on the immediately following line. The rule id is
7
+ * required (we don't support blanket "disable everything" because it
8
+ * silently hides future findings — bad smell). The reason is optional
9
+ * but encouraged.
10
+ *
11
+ * `oauthlint-disable-line <rule-id> [-- <reason>]`
12
+ * Same, but applies to the current line (the line the comment is on).
13
+ *
14
+ * `oauthlint-disable-file <rule-id> [-- <reason>]`
15
+ * Disable the rule for the entire file. Goes anywhere in the file.
16
+ *
17
+ * Glob `*` is accepted as a rule id placeholder for the wildcard case,
18
+ * but ONLY for `oauthlint-disable-next-line` / `disable-line` (not
19
+ * disable-file) so that wholesale silencing requires an explicit
20
+ * line-by-line decision.
21
+ */
22
+ interface SuppressionMaps {
23
+ /** rule id (or '*') → set of zero-based line numbers to suppress */
24
+ byLine: Map<string, Set<number>>;
25
+ /** rule ids (or '*') suppressed for the whole file */
26
+ fileWide: Set<string>;
27
+ }
28
+ export declare function loadSuppressionMap(filePath: string): Promise<SuppressionMaps>;
29
+ export declare function parseSuppressionsFromSource(source: string): SuppressionMaps;
30
+ export declare function isSuppressed(finding: Finding, maps: SuppressionMaps): boolean;
31
+ /**
32
+ * Apply suppression to a list of findings. Caches the parsed directive
33
+ * map per-file so we only read each file once.
34
+ */
35
+ export declare function applySuppressions(findings: Finding[]): Promise<{
36
+ kept: Finding[];
37
+ suppressed: Finding[];
38
+ }>;
39
+ export {};
40
+ //# sourceMappingURL=suppress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppress.d.ts","sourceRoot":"","sources":["../../src/core/suppress.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,UAAU,eAAe;IACvB,oEAAoE;IACpE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IACjC,sDAAsD;IACtD,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB;AAID,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAQnF;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAgC3E;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAY7E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACpE,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,UAAU,EAAE,OAAO,EAAE,CAAC;CACvB,CAAC,CAkBD"}