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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maurice Anney
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # oauthlint
2
+
3
+ > Catch the OAuth/OIDC/JWT anti-patterns AI coding tools systematically produce.
4
+
5
+ CLI wrapper around [`oauthlint-rules`](../oauthlint-rules) — a Semgrep
6
+ rule pack curated by an IAM engineer.
7
+
8
+ ## Quick start
9
+
10
+ ```bash
11
+ npx oauthlint scan ./src
12
+ ```
13
+
14
+ You need [Semgrep](https://semgrep.dev/docs/getting-started/) installed
15
+ (`pipx install semgrep` or `brew install semgrep`). The CLI invokes it
16
+ under the hood and normalises the output for humans and CI.
17
+
18
+ ## Commands
19
+
20
+ ```
21
+ oauthlint scan [path] Scan a directory (default: current dir)
22
+ oauthlint scan --json Emit machine-readable JSON
23
+ oauthlint scan --severity HIGH Only show findings ≥ HIGH
24
+ oauthlint scan --fail-on off Never fail the build (CI dry-run)
25
+ oauthlint list List every shipped rule
26
+ oauthlint list --json Same, as JSON
27
+ oauthlint init Generate .oauthlintrc.yml at cwd
28
+ oauthlint init --force Overwrite an existing config
29
+ ```
30
+
31
+ ## Exit codes
32
+
33
+ | Code | When |
34
+ |------|------|
35
+ | 0 | No findings ≥ `failOn` threshold |
36
+ | 1 | At least one HIGH finding |
37
+ | 2 | At least one CRITICAL finding |
38
+ | 127 | Semgrep is not installed |
39
+
40
+ ## Configuration
41
+
42
+ Create a `.oauthlintrc.yml` at your repo root with `oauthlint init`. The
43
+ file follows the schema in [`src/core/config.ts`](./src/core/config.ts).
44
+
45
+ ```yaml
46
+ version: 1
47
+ include:
48
+ - "src/**/*.{ts,tsx,js,jsx}"
49
+ exclude:
50
+ - "**/*.test.ts"
51
+ rules:
52
+ auth.cookie.no-samesite: warn
53
+ auth.session.id-in-url: off
54
+ failOn: HIGH
55
+ ```
56
+
57
+ ## License
58
+
59
+ MIT — see [LICENSE](../../LICENSE).
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { buildProgram } from '../dist/cli.js';
3
+
4
+ const program = await buildProgram();
5
+ await program.parseAsync(process.argv);
@@ -0,0 +1,36 @@
1
+ import { type ScanResult } from '../types.js';
2
+ export declare class SemgrepNotInstalledError extends Error {
3
+ constructor();
4
+ }
5
+ export interface SemgrepAdapterOptions {
6
+ /** Override path to the semgrep binary (defaults to `semgrep` on PATH). */
7
+ binary?: string;
8
+ /** Override the rule config — usually a directory of YAML rules. */
9
+ configPath: string;
10
+ /** Working directory to run semgrep from (the target to scan). */
11
+ cwd?: string;
12
+ }
13
+ export declare class SemgrepAdapter {
14
+ private readonly binary;
15
+ private readonly configPath;
16
+ private readonly cwd?;
17
+ constructor(opts: SemgrepAdapterOptions);
18
+ /**
19
+ * Run semgrep against `targetPath`, returning a normalised scan result.
20
+ * When `applyFixes` is true, Semgrep rewrites the source files in place
21
+ * using each rule's `fix:` template. The returned report reflects what
22
+ * was matched BEFORE the rewrite — callers should re-scan if they want
23
+ * a clean post-fix report.
24
+ *
25
+ * @throws SemgrepNotInstalledError if the binary cannot be found.
26
+ */
27
+ scan(targetPath: string, options?: {
28
+ applyFixes?: boolean;
29
+ }): Promise<ScanResult>;
30
+ /**
31
+ * Check whether Semgrep is callable at all. Used by `oauthlint doctor`.
32
+ */
33
+ getVersion(): Promise<string | null>;
34
+ }
35
+ export declare function normaliseRuleId(rawId: string): string;
36
+ //# sourceMappingURL=semgrep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semgrep.d.ts","sourceRoot":"","sources":["../../src/adapters/semgrep.ts"],"names":[],"mappings":"AACA,OAAO,EAAsC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AA6BlF,qBAAa,wBAAyB,SAAQ,KAAK;;CASlD;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;gBAElB,IAAI,EAAE,qBAAqB;IAMvC;;;;;;;;OAQG;IACG,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IA2D3F;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAS3C;AAwCD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGrD"}
@@ -0,0 +1,156 @@
1
+ import { ExecaError, execa } from 'execa';
2
+ import { SEMGREP_SEVERITY_MAP } from '../types.js';
3
+ export class SemgrepNotInstalledError extends Error {
4
+ constructor() {
5
+ super('Semgrep is not installed.\n' +
6
+ 'Install it with: pipx install semgrep (or) brew install semgrep\n' +
7
+ 'See https://semgrep.dev/docs/getting-started/ for more options.');
8
+ this.name = 'SemgrepNotInstalledError';
9
+ }
10
+ }
11
+ export class SemgrepAdapter {
12
+ binary;
13
+ configPath;
14
+ cwd;
15
+ constructor(opts) {
16
+ this.binary = opts.binary ?? 'semgrep';
17
+ this.configPath = opts.configPath;
18
+ this.cwd = opts.cwd;
19
+ }
20
+ /**
21
+ * Run semgrep against `targetPath`, returning a normalised scan result.
22
+ * When `applyFixes` is true, Semgrep rewrites the source files in place
23
+ * using each rule's `fix:` template. The returned report reflects what
24
+ * was matched BEFORE the rewrite — callers should re-scan if they want
25
+ * a clean post-fix report.
26
+ *
27
+ * @throws SemgrepNotInstalledError if the binary cannot be found.
28
+ */
29
+ async scan(targetPath, options = {}) {
30
+ const start = Date.now();
31
+ const args = [
32
+ 'scan',
33
+ '--config',
34
+ this.configPath,
35
+ '--json',
36
+ '--quiet',
37
+ '--no-git-ignore',
38
+ '--metrics=off',
39
+ ];
40
+ if (options.applyFixes)
41
+ args.push('--autofix');
42
+ args.push(targetPath);
43
+ let result;
44
+ try {
45
+ result = await execa(this.binary, args, {
46
+ cwd: this.cwd,
47
+ reject: false,
48
+ // Semgrep exits non-zero when findings are present; we don't want that to throw.
49
+ stdout: 'pipe',
50
+ stderr: 'pipe',
51
+ });
52
+ }
53
+ catch (err) {
54
+ if (isSpawnFailure(err)) {
55
+ throw new SemgrepNotInstalledError();
56
+ }
57
+ throw err;
58
+ }
59
+ // Even with reject:false, execa surfaces spawn failures via the result object.
60
+ if (isSpawnFailure(result)) {
61
+ throw new SemgrepNotInstalledError();
62
+ }
63
+ let parsed;
64
+ try {
65
+ const stdout = typeof result.stdout === 'string' ? result.stdout : '';
66
+ parsed = JSON.parse(stdout || '{}');
67
+ }
68
+ catch {
69
+ return {
70
+ findings: [],
71
+ scannedFiles: 0,
72
+ durationMs: Date.now() - start,
73
+ semgrepVersion: null,
74
+ errors: ['semgrep output was not valid JSON'],
75
+ };
76
+ }
77
+ const findings = (parsed.results ?? []).map(toFinding);
78
+ return {
79
+ findings,
80
+ scannedFiles: parsed.paths?.scanned?.length ?? 0,
81
+ durationMs: Date.now() - start,
82
+ semgrepVersion: parsed.version ?? null,
83
+ errors: (parsed.errors ?? []).map((e) => e.long_msg ?? e.message ?? 'unknown semgrep error'),
84
+ };
85
+ }
86
+ /**
87
+ * Check whether Semgrep is callable at all. Used by `oauthlint doctor`.
88
+ */
89
+ async getVersion() {
90
+ try {
91
+ const { stdout } = await execa(this.binary, ['--version'], { reject: false });
92
+ return stdout.trim() || null;
93
+ }
94
+ catch (err) {
95
+ if (isSpawnFailure(err))
96
+ return null;
97
+ throw err;
98
+ }
99
+ }
100
+ }
101
+ function toFinding(r) {
102
+ const rawSeverity = (r.extra.severity ?? 'INFO').toUpperCase();
103
+ const severity = SEMGREP_SEVERITY_MAP[rawSeverity] ?? 'MEDIUM';
104
+ return {
105
+ ruleId: normaliseRuleId(r.check_id),
106
+ oauthlintRuleId: r.extra.metadata?.['oauthlint-rule-id'],
107
+ severity,
108
+ filePath: r.path,
109
+ startLine: r.start.line,
110
+ endLine: r.end.line,
111
+ message: (r.extra.message ?? '').trim(),
112
+ docUrl: r.extra.metadata?.['oauthlint-doc-url'],
113
+ cwe: r.extra.metadata?.cwe,
114
+ llmPrevalence: r.extra.metadata?.['llm-prevalence'],
115
+ };
116
+ }
117
+ /**
118
+ * Strip the file-path prefix Semgrep adds when loading rules from a
119
+ * directory. Semgrep encodes the relative path into `check_id`, so a
120
+ * rule defined as `auth.jwt.alg-none` in
121
+ * `packages/oauthlint-rules/rules/jwt/alg-none.yml` is reported as
122
+ * `packages.oauthlint-rules.rules.jwt.auth.jwt.alg-none`.
123
+ *
124
+ * OAuthLint rule ids always have the shape `auth.<category>.<name>`, so we
125
+ * pull out the trailing match. The regex is deliberately strict: it has to
126
+ * land at the END of the string so we don't accidentally truncate
127
+ * `auth.oauth.hardcoded-secret` into `auth.hardcoded-secret` when the
128
+ * path itself contains the substring `auth.` more than once (e.g.
129
+ * `rules.oauth.auth.oauth.hardcoded-secret`, where `oauth` ends with
130
+ * "auth" before the dot).
131
+ *
132
+ * Custom (non-OAuthLint) rule ids that don't fit the shape are returned
133
+ * unchanged.
134
+ */
135
+ const OAUTHLINT_ID_RE = /auth\.[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*$/;
136
+ export function normaliseRuleId(rawId) {
137
+ const match = rawId.match(OAUTHLINT_ID_RE);
138
+ return match ? match[0] : rawId;
139
+ }
140
+ function isSpawnFailure(err) {
141
+ // Direct throw path (when execa is configured to reject).
142
+ if (err instanceof ExecaError) {
143
+ return err.code === 'ENOENT';
144
+ }
145
+ // reject:false path — execa returns a result object that exposes `failed`
146
+ // plus `code` / `cause` indicating ENOENT when the binary is missing.
147
+ if (typeof err === 'object' && err !== null) {
148
+ const r = err;
149
+ if (r.code === 'ENOENT')
150
+ return true;
151
+ if (r.cause?.code === 'ENOENT')
152
+ return true;
153
+ }
154
+ return false;
155
+ }
156
+ //# sourceMappingURL=semgrep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semgrep.js","sourceRoot":"","sources":["../../src/adapters/semgrep.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function buildProgram(): Promise<Command>;
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BpC,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAgErD"}
package/dist/cli.js ADDED
@@ -0,0 +1,103 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { Command } from 'commander';
5
+ import pc from 'picocolors';
6
+ import { runDoctor } from './commands/doctor.js';
7
+ import { runInit } from './commands/init.js';
8
+ import { runList } from './commands/list.js';
9
+ import { runScan } from './commands/scan.js';
10
+ import { SEVERITIES } from './types.js';
11
+ async function readPackageVersion() {
12
+ const here = dirname(fileURLToPath(import.meta.url));
13
+ // dist/cli.js → ../package.json (when published)
14
+ // src/cli.ts → ../package.json (when running via tsx/vitest)
15
+ for (const candidate of [
16
+ resolve(here, '..', 'package.json'),
17
+ resolve(here, '..', '..', 'package.json'),
18
+ ]) {
19
+ try {
20
+ const pkg = JSON.parse(await readFile(candidate, 'utf8'));
21
+ if (pkg.version)
22
+ return pkg.version;
23
+ }
24
+ catch {
25
+ /* try next */
26
+ }
27
+ }
28
+ return '0.0.0';
29
+ }
30
+ export async function buildProgram() {
31
+ const program = new Command();
32
+ const version = await readPackageVersion();
33
+ program
34
+ .name('oauthlint')
35
+ .description('OAuthLint — catch the OAuth/OIDC/JWT anti-patterns AI coding tools systematically produce.')
36
+ .version(version, '-v, --version');
37
+ program
38
+ .command('scan')
39
+ .argument('[path]', 'Path to scan', '.')
40
+ .description('Scan a directory for auth misconfigurations')
41
+ .option('--json', 'Emit JSON (shortcut for --format json)')
42
+ .option('--format <fmt>', 'Output format: pretty | json | sarif', parseFormat)
43
+ .option('--severity <level>', 'Only emit findings ≥ this severity', parseSeverity)
44
+ .option('--fail-on <level>', 'Process exits non-zero if any finding ≥ this severity', parseFailOn)
45
+ .option('--rules-dir <path>', 'Override the bundled rules directory')
46
+ .option('--fix', 'Apply auto-fixes (rewrites source in place where possible)')
47
+ .action(async (path, opts) => {
48
+ const code = await runScan({
49
+ path,
50
+ json: opts.json,
51
+ format: opts.format,
52
+ severity: opts.severity,
53
+ failOn: opts.failOn,
54
+ rulesDir: opts.rulesDir,
55
+ fix: opts.fix,
56
+ });
57
+ process.exit(code);
58
+ });
59
+ program
60
+ .command('list')
61
+ .description('List every rule the current install ships with')
62
+ .option('--json', 'Emit JSON instead of pretty output')
63
+ .action(async (opts) => {
64
+ process.exit(await runList({ json: opts.json }));
65
+ });
66
+ program
67
+ .command('init')
68
+ .description('Generate a .oauthlintrc.yml at the current directory')
69
+ .option('-f, --force', 'Overwrite an existing config file')
70
+ .action(async (opts) => {
71
+ process.exit(await runInit({ cwd: process.cwd(), force: opts.force }));
72
+ });
73
+ program
74
+ .command('doctor')
75
+ .description('Diagnose your OAuthLint install (Node, Semgrep, rule pack)')
76
+ .option('--json', 'Emit JSON instead of pretty output')
77
+ .action(async (opts) => {
78
+ process.exit(await runDoctor({ json: opts.json }));
79
+ });
80
+ program.showHelpAfterError(pc.dim('(run `oauthlint --help` for available commands)'));
81
+ return program;
82
+ }
83
+ function parseSeverity(v) {
84
+ const upper = v.toUpperCase();
85
+ if (!SEVERITIES.includes(upper)) {
86
+ throw new Error(`Unknown severity "${v}". Expected one of: ${SEVERITIES.join(', ')}`);
87
+ }
88
+ return upper;
89
+ }
90
+ function parseFailOn(v) {
91
+ if (v === 'off')
92
+ return 'off';
93
+ return parseSeverity(v);
94
+ }
95
+ const FORMATS = ['pretty', 'json', 'sarif'];
96
+ function parseFormat(v) {
97
+ const lower = v.toLowerCase();
98
+ if (!FORMATS.includes(lower)) {
99
+ throw new Error(`Unknown format "${v}". Expected one of: ${FORMATS.join(', ')}`);
100
+ }
101
+ return lower;
102
+ }
103
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface DoctorOptions {
2
+ binary?: string;
3
+ stream?: NodeJS.WritableStream;
4
+ /** When true, emit JSON instead of pretty output. */
5
+ json?: boolean;
6
+ }
7
+ export declare function runDoctor(opts?: DoctorOptions): Promise<number>;
8
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,qDAAqD;IACrD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAQD,wBAAsB,SAAS,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDzE"}
@@ -0,0 +1,69 @@
1
+ import { buildManifest } from 'oauthlint-rules';
2
+ import pc from 'picocolors';
3
+ import { SemgrepAdapter } from '../adapters/semgrep.js';
4
+ export async function runDoctor(opts = {}) {
5
+ const out = opts.stream ?? process.stdout;
6
+ const checks = [];
7
+ checks.push({
8
+ name: 'Node.js runtime',
9
+ status: nodeOk() ? 'ok' : 'warn',
10
+ details: `${process.version} (≥ v20 required)`,
11
+ });
12
+ const adapter = new SemgrepAdapter({ configPath: 'unused', binary: opts.binary });
13
+ const semgrepVersion = await adapter.getVersion();
14
+ if (semgrepVersion) {
15
+ checks.push({
16
+ name: 'Semgrep CLI',
17
+ status: 'ok',
18
+ details: semgrepVersion,
19
+ });
20
+ }
21
+ else {
22
+ checks.push({
23
+ name: 'Semgrep CLI',
24
+ status: 'fail',
25
+ details: 'not found on PATH — install with `pipx install semgrep` or `brew install semgrep`',
26
+ });
27
+ }
28
+ try {
29
+ const manifest = await buildManifest();
30
+ checks.push({
31
+ name: 'OAuthLint rule pack',
32
+ status: manifest.length >= 20 ? 'ok' : 'warn',
33
+ details: `${manifest.length} rules loaded`,
34
+ });
35
+ }
36
+ catch (err) {
37
+ checks.push({
38
+ name: 'OAuthLint rule pack',
39
+ status: 'fail',
40
+ details: `failed to load: ${err.message}`,
41
+ });
42
+ }
43
+ if (opts.json) {
44
+ out.write(`${JSON.stringify({ checks }, null, 2)}\n`);
45
+ }
46
+ else {
47
+ out.write(pc.bold('OAuthLint — environment check\n'));
48
+ out.write(`${pc.dim('─'.repeat(60))}\n`);
49
+ for (const c of checks) {
50
+ out.write(`${badge(c.status)} ${c.name.padEnd(20, ' ')} ${pc.dim(c.details)}\n`);
51
+ }
52
+ }
53
+ return checks.some((c) => c.status === 'fail') ? 1 : 0;
54
+ }
55
+ function nodeOk() {
56
+ const major = Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10);
57
+ return major >= 20;
58
+ }
59
+ function badge(status) {
60
+ switch (status) {
61
+ case 'ok':
62
+ return pc.green(' ✓ ');
63
+ case 'warn':
64
+ return pc.yellow(' ! ');
65
+ case 'fail':
66
+ return pc.red(' ✗ ');
67
+ }
68
+ }
69
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface InitOptions {
2
+ cwd: string;
3
+ force?: boolean;
4
+ stream?: NodeJS.WritableStream;
5
+ }
6
+ export declare function runInit(opts: InitOptions): Promise<number>;
7
+ export declare const __TEMPLATE = "# OAuthLint config\n# Documentation: https://oauthlint.dev/docs/config\nversion: 1\n\n# Files to scan\ninclude:\n - \"src/**/*.{ts,tsx,js,jsx,mjs,cjs}\"\nexclude:\n - \"**/*.test.{ts,tsx,js,jsx}\"\n - \"**/*.spec.{ts,tsx,js,jsx}\"\n - \"node_modules/**\"\n - \"dist/**\"\n - \"build/**\"\n\n# Per-rule overrides:\n# off \u2192 disable the rule\n# warn \u2192 emit but don't affect exit code\n# <SEV> \u2192 override severity (INFO | LOW | MEDIUM | HIGH | CRITICAL)\n# rules:\n# auth.cookie.no-samesite: warn\n# auth.session.id-in-url: off\n\n# Custom rules directory (in addition to the bundled rules)\n# customRulesDir: ./security/oauthlint-rules\n\n# Exit code policy: fail when any finding \u2265 this severity is present.\n# Use \"off\" to never fail the run.\nfailOn: HIGH\n";
8
+ export declare const __join: (...paths: string[]) => string;
9
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAWhE;AAGD,eAAO,MAAM,UAAU,syBAAW,CAAC;AAGnC,eAAO,MAAM,MAAM,gCAAO,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { join, resolve } from 'node:path';
4
+ import pc from 'picocolors';
5
+ const TEMPLATE = `# OAuthLint config
6
+ # Documentation: https://oauthlint.dev/docs/config
7
+ version: 1
8
+
9
+ # Files to scan
10
+ include:
11
+ - "src/**/*.{ts,tsx,js,jsx,mjs,cjs}"
12
+ exclude:
13
+ - "**/*.test.{ts,tsx,js,jsx}"
14
+ - "**/*.spec.{ts,tsx,js,jsx}"
15
+ - "node_modules/**"
16
+ - "dist/**"
17
+ - "build/**"
18
+
19
+ # Per-rule overrides:
20
+ # off → disable the rule
21
+ # warn → emit but don't affect exit code
22
+ # <SEV> → override severity (INFO | LOW | MEDIUM | HIGH | CRITICAL)
23
+ # rules:
24
+ # auth.cookie.no-samesite: warn
25
+ # auth.session.id-in-url: off
26
+
27
+ # Custom rules directory (in addition to the bundled rules)
28
+ # customRulesDir: ./security/oauthlint-rules
29
+
30
+ # Exit code policy: fail when any finding ≥ this severity is present.
31
+ # Use "off" to never fail the run.
32
+ failOn: HIGH
33
+ `;
34
+ export async function runInit(opts) {
35
+ const out = opts.stream ?? process.stdout;
36
+ const target = resolve(opts.cwd, '.oauthlintrc.yml');
37
+ if (existsSync(target) && !opts.force) {
38
+ out.write(pc.yellow(`✗ ${target} already exists — pass --force to overwrite.\n`));
39
+ return 1;
40
+ }
41
+ await writeFile(target, TEMPLATE, 'utf8');
42
+ out.write(pc.green(`✓ Wrote ${target}\n`));
43
+ out.write(pc.dim(`Next: run ${pc.bold('npx oauthlint scan .')} to try it.\n`));
44
+ return 0;
45
+ }
46
+ // Exported for tests so we can assert on the template content directly.
47
+ export const __TEMPLATE = TEMPLATE;
48
+ // Keep `join` available for downstream tests that want to construct paths
49
+ // without re-importing node:path.
50
+ export const __join = join;
51
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BhB,CAAC;AAQF,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAiB;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAC1C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACrD,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACtC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,MAAM,gDAAgD,CAAC,CAAC,CAAC;QAClF,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,MAAM,IAAI,CAAC,CAAC,CAAC;IAC3C,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,sBAAsB,CAAC,eAAe,CAAC,CAAC,CAAC;IAC/E,OAAO,CAAC,CAAC;AACX,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAC;AACnC,0EAA0E;AAC1E,kCAAkC;AAClC,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC"}
@@ -0,0 +1,6 @@
1
+ export interface ListOptions {
2
+ json?: boolean;
3
+ stream?: NodeJS.WritableStream;
4
+ }
5
+ export declare function runList(opts?: ListOptions): Promise<number>;
6
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC;AAED,wBAAsB,OAAO,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBrE"}
@@ -0,0 +1,32 @@
1
+ import { buildManifest } from 'oauthlint-rules';
2
+ import pc from 'picocolors';
3
+ export async function runList(opts = {}) {
4
+ const out = opts.stream ?? process.stdout;
5
+ const manifest = await buildManifest();
6
+ if (opts.json) {
7
+ out.write(`${JSON.stringify(manifest, null, 2)}\n`);
8
+ return 0;
9
+ }
10
+ out.write(pc.bold(`OAuthLint — ${manifest.length} rules\n`));
11
+ out.write(`${pc.dim('─'.repeat(70))}\n`);
12
+ for (const m of manifest) {
13
+ const sev = badge(m.severity);
14
+ const llm = m.llmPrevalence === 'HIGH' ? pc.magenta('LLM↑') : pc.dim(' ');
15
+ const id = pc.bold(m.id.padEnd(38, ' '));
16
+ out.write(`${sev} ${llm} ${id} ${pc.dim(m.oauthlintId)}\n`);
17
+ if (m.description)
18
+ out.write(pc.dim(` ${m.description}\n`));
19
+ }
20
+ return 0;
21
+ }
22
+ function badge(sev) {
23
+ switch (sev) {
24
+ case 'ERROR':
25
+ return pc.red(' ERR ');
26
+ case 'WARNING':
27
+ return pc.yellow(' WRN ');
28
+ default:
29
+ return pc.gray(' INF ');
30
+ }
31
+ }
32
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,MAAM,YAAY,CAAC;AAO5B,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAoB,EAAE;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAC1C,MAAM,QAAQ,GAAG,MAAM,aAAa,EAAE,CAAC;IAEvC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QACpD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,eAAe,QAAQ,CAAC,MAAM,UAAU,CAAC,CAAC,CAAC;IAC7D,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,CAAC,CAAC,aAAa,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC9E,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;QACzC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC5D,IAAI,CAAC,CAAC,WAAW;YAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,KAAK,CAAC,GAAW;IACxB,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,OAAO,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACzB,KAAK,SAAS;YACZ,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5B;YACE,OAAO,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { SemgrepAdapter } from '../adapters/semgrep.js';
2
+ import type { SeverityName } from '../types.js';
3
+ export type ScanFormat = 'pretty' | 'json' | 'sarif';
4
+ export interface ScanCommandOptions {
5
+ path: string;
6
+ /** Legacy boolean — equivalent to `format: 'json'`. */
7
+ json?: boolean;
8
+ /** Output format. Overrides `json` when set. */
9
+ format?: ScanFormat;
10
+ severity?: SeverityName;
11
+ failOn?: SeverityName | 'off';
12
+ rulesDir?: string;
13
+ /**
14
+ * Apply auto-fixes (rewrites source files in place) for rules that
15
+ * ship a `fix:` template. Currently the cookie-* rules.
16
+ */
17
+ fix?: boolean;
18
+ /** Used by tests to inject a mock adapter. */
19
+ adapter?: SemgrepAdapter;
20
+ /** Used by tests to capture output. */
21
+ stream?: NodeJS.WritableStream;
22
+ }
23
+ export declare function runScan(opts: ScanCommandOptions): Promise<number>;
24
+ //# sourceMappingURL=scan.d.ts.map