oauthlint 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -63,8 +63,9 @@ oauthlint gives you is the work most people never do:
63
63
  ([validation report](https://oauthlint.dev/VALIDATION)).
64
64
  - **One coherent product across every language it covers.** Same concepts, same ID scheme,
65
65
  same docs — not a patchwork of community rules with mismatched styles.
66
- - **Every finding teaches.** All 90 rules link to a fix page with CWE and OWASP
67
- mappings. It's a lesson, not a grep hit.
66
+ - **Every finding teaches.** The 100+ rules across JS/TS · Python · Go · Java
67
+ · Rust — each link to a fix page with CWE and OWASP mappings. It's a lesson,
68
+ not a grep hit.
68
69
  - **The angle the registry doesn't have:** oauthlint specifically targets the
69
70
  auth bugs AI coding tools ship on repeat, encoded in each rule's
70
71
  `llm-prevalence` metadata and measured by a [reproducible benchmark](https://github.com/Auspeo/oauthlint/tree/main/benchmark).
@@ -98,8 +99,49 @@ npx oauthlint scan --diff
98
99
 
99
100
  # scan only git-staged files (ideal in a pre-commit hook)
100
101
  npx oauthlint scan --staged
102
+
103
+ # adopt on a large existing codebase: snapshot today's findings…
104
+ npx oauthlint baseline ./src
105
+ # …then only get alerted on NEW findings from here on
106
+ npx oauthlint scan ./src --baseline --fail-on HIGH
107
+ ```
108
+
109
+ ## Adopting on an existing codebase (baseline)
110
+
111
+ Turning a linter on for the first time on a large repo usually drowns you in
112
+ pre-existing findings. A **baseline** snapshots the findings that exist *today*
113
+ so `scan` only alerts on **new** ones — letting you adopt OAuthLint without
114
+ fixing everything up front.
115
+
116
+ ```bash
117
+ # 1. capture the current findings into .oauthlint-baseline.json (commit this file)
118
+ oauthlint baseline ./src
119
+
120
+ # 2. from now on, scan reports only findings NOT in the baseline
121
+ oauthlint scan ./src --baseline --fail-on HIGH
101
122
  ```
102
123
 
124
+ - **`oauthlint baseline [paths...]`** scans and writes
125
+ `.oauthlint-baseline.json` (override with `-o, --output <file>`). It prints how
126
+ many findings were baselined. Commit the file so the whole team shares it.
127
+ - **`oauthlint scan --baseline [file]`** runs a normal scan, then suppresses any
128
+ finding already in the baseline and reports only the rest. `--fail-on` and the
129
+ exit code consider **only the new findings**. With no value it defaults to
130
+ `.oauthlint-baseline.json`; a missing baseline file is a **clear error**
131
+ (exit `2`), never silently treated as empty.
132
+
133
+ **How a finding is fingerprinted.** Each finding gets a stable fingerprint —
134
+ `sha256(oauthlintRuleId + repo-relative path + a whitespace-normalised snapshot
135
+ of the matched code)`. Crucially the **raw line number is not part of the hash**,
136
+ so moving a finding up or down (adding imports, reformatting) keeps it
137
+ baselined, while **changing the flagged code** surfaces it as new. The same
138
+ fingerprint occurring multiple times in one file is disambiguated by a
139
+ deterministic occurrence index. Paths are normalised to repo/cwd-relative
140
+ (POSIX separators) so a baseline is portable across machines, and unreadable
141
+ files (deleted/binary) degrade gracefully instead of crashing. The baseline
142
+ JSON is small, sorted and human-diffable (a `version`, a `generatedAt`, and the
143
+ fingerprint list).
144
+
103
145
  ## Incremental scanning
104
146
 
105
147
  For pre-commit hooks and editor integrations, scanning the whole tree on every
@@ -154,11 +196,15 @@ oauthlint scan --format sarif Emit SARIF for GitHub Code Scanning
154
196
  oauthlint scan --severity HIGH Only show findings ≥ HIGH
155
197
  oauthlint scan --fail-on off Never fail the build (CI dry-run)
156
198
  oauthlint scan --fix Auto-apply safe fixes
199
+ oauthlint scan --baseline Report only findings NOT in the baseline
200
+ oauthlint baseline [paths...] Snapshot current findings to a baseline file
201
+ oauthlint baseline -o <file> Write the baseline to a custom path
157
202
  oauthlint list List every shipped rule
158
203
  oauthlint list --json Same, as JSON
159
204
  oauthlint init Generate .oauthlintrc.yml at cwd
160
205
  oauthlint init --force Overwrite an existing config
161
206
  oauthlint doctor Check your setup (Semgrep, config, …)
207
+ oauthlint --no-update-check Skip the "update available" check (any command)
162
208
  ```
163
209
 
164
210
  ## Use it in CI
@@ -182,8 +228,20 @@ jobs:
182
228
 
183
229
  Or just fail the build on HIGH findings: `npx oauthlint scan ./src --fail-on HIGH`.
184
230
 
185
- > A dedicated **GitHub Action** and a **VS Code extension** (inline diagnostics +
186
- > Quick Fix suppressions) are on the way — follow the [repo](https://github.com/Auspeo/oauthlint) for releases.
231
+ ### GitHub Action
232
+
233
+ Prefer not to wire up the CLI by hand? The **[GitHub Action](https://github.com/Auspeo/oauthlint/tree/main/action)** wraps it for you — it's Docker-based, so it runs in any repo regardless of language:
234
+
235
+ ```yaml
236
+ - uses: Auspeo/oauthlint/action@v1
237
+ with:
238
+ severity: HIGH
239
+ fail-on: HIGH
240
+ ```
241
+
242
+ ### VS Code extension
243
+
244
+ Catch findings as you type. Install **[oauthlint](https://marketplace.visualstudio.com/items?itemName=auspeo.oauthlint-vscode)** (`auspeo.oauthlint-vscode`) from the VS Code Marketplace for inline diagnostics on save, plus Quick Fix suppressions.
187
245
 
188
246
  ## What it catches
189
247
 
@@ -233,9 +291,26 @@ the next reviewer needs to see exactly which lines opted out, and why.
233
291
  |:----:|------|
234
292
  | `0` | No finding at or above the `--fail-on` threshold |
235
293
  | `1` | At least one **HIGH** finding |
236
- | `2` | At least one **CRITICAL** finding, or a scan whose output could not be parsed (it never silently exits clean) |
294
+ | `2` | At least one **CRITICAL** finding, a scan whose output could not be parsed (it never silently exits clean), or a missing/malformed `--baseline` file |
237
295
  | `127` | Semgrep is not installed |
238
296
 
297
+ > With `--baseline`, the `--fail-on` gate and these exit codes consider only the
298
+ > **new** (non-baselined) findings.
299
+
300
+ ## Stay up to date
301
+
302
+ oauthlint occasionally prints a short *"Update available"* notice to **stderr**
303
+ when a newer version is on npm. It's designed to stay out of your way:
304
+
305
+ - It's **non-blocking** and checks npm at most **once a day** (the result is
306
+ cached), so it never slows a scan down.
307
+ - It writes to **stderr only** and is **silent** under `--json` / `--format
308
+ sarif`, when output is piped, in CI, and offline — so it can never corrupt a
309
+ machine-readable report or nag automated runs.
310
+
311
+ To turn it off entirely, pass `--no-update-check` or set the standard
312
+ `NO_UPDATE_NOTIFIER=1` environment variable.
313
+
239
314
  ## License
240
315
 
241
316
  MIT — see [LICENSE](https://github.com/Auspeo/oauthlint/blob/main/LICENSE).
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAuErD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyEpC,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAwHrD"}
package/dist/cli.js CHANGED
@@ -3,10 +3,12 @@ import { dirname, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { Command } from 'commander';
5
5
  import pc from 'picocolors';
6
+ import { runBaseline } from './commands/baseline.js';
6
7
  import { runDoctor } from './commands/doctor.js';
7
8
  import { runInit } from './commands/init.js';
8
9
  import { runList } from './commands/list.js';
9
10
  import { runScan } from './commands/scan.js';
11
+ import { maybeNotifyUpdate } from './core/update-notifier.js';
10
12
  import { SEVERITIES } from './types.js';
11
13
  /**
12
14
  * Exit only once buffered stdout/stderr have drained.
@@ -28,6 +30,21 @@ async function exitAfterFlush(code) {
28
30
  await Promise.all([flush(process.stdout), flush(process.stderr)]);
29
31
  process.exit(code);
30
32
  }
33
+ /**
34
+ * Print the "update available" notice (when allowed) AFTER the command's normal
35
+ * output, then exit once everything is flushed. The notifier is fire-and-forget
36
+ * and self-suppressing: it never blocks, never touches stdout, and is silent
37
+ * under `--json`/`--format sarif`, in CI, when piped, when `NO_UPDATE_NOTIFIER`
38
+ * is set, or when `--no-update-check` is passed.
39
+ */
40
+ async function finishWithNotice(code, ctx) {
41
+ await maybeNotifyUpdate({
42
+ currentVersion: ctx.version,
43
+ machineReadable: ctx.machineReadable,
44
+ disabled: !ctx.updateCheck,
45
+ });
46
+ return exitAfterFlush(code);
47
+ }
31
48
  async function readPackageVersion() {
32
49
  const here = dirname(fileURLToPath(import.meta.url));
33
50
  // dist/cli.js → ../package.json (when published)
@@ -53,7 +70,11 @@ export async function buildProgram() {
53
70
  program
54
71
  .name('oauthlint')
55
72
  .description('OAuthLint — catch the OAuth/OIDC/JWT anti-patterns AI coding tools systematically produce.')
56
- .version(version, '-v, --version');
73
+ .version(version, '-v, --version')
74
+ .option('--no-update-check', 'Do not check npm for a newer oauthlint version');
75
+ // Resolve the global update-check opt-out once, lazily, from the root program.
76
+ // Commander sets `updateCheck: false` when `--no-update-check` is given.
77
+ const updateCheckEnabled = () => program.opts().updateCheck !== false;
57
78
  program
58
79
  .command('scan')
59
80
  .argument('[paths...]', 'One or more files/directories to scan', ['.'])
@@ -66,6 +87,7 @@ export async function buildProgram() {
66
87
  .option('--staged', 'Scan only git-staged files (useful for pre-commit hooks)')
67
88
  .option('--rules-dir <path>', 'Override the bundled rules directory')
68
89
  .option('--fix', 'Apply auto-fixes (rewrites source in place where possible)')
90
+ .option('--baseline [file]', 'Suppress findings already in a baseline file; report only NEW findings (default: .oauthlint-baseline.json)')
69
91
  .action(async (paths, opts) => {
70
92
  const code = await runScan({
71
93
  paths,
@@ -77,29 +99,65 @@ export async function buildProgram() {
77
99
  failOn: opts.failOn,
78
100
  rulesDir: opts.rulesDir,
79
101
  fix: opts.fix,
102
+ baseline: opts.baseline,
103
+ });
104
+ const machineReadable = opts.json === true || opts.format === 'json' || opts.format === 'sarif';
105
+ await finishWithNotice(code, { version, machineReadable, updateCheck: updateCheckEnabled() });
106
+ });
107
+ program
108
+ .command('baseline')
109
+ .argument('[paths...]', 'One or more files/directories to scan', ['.'])
110
+ .description('Scan and write a baseline of current findings (for adopting on an existing codebase)')
111
+ .option('-o, --output <file>', 'Where to write the baseline JSON', '.oauthlint-baseline.json')
112
+ .option('--rules-dir <path>', 'Override the bundled rules directory')
113
+ .action(async (paths, opts) => {
114
+ const code = await runBaseline({
115
+ paths,
116
+ output: opts.output,
117
+ rulesDir: opts.rulesDir,
118
+ });
119
+ // baseline writes a JSON file but its stdout is a human summary.
120
+ await finishWithNotice(code, {
121
+ version,
122
+ machineReadable: false,
123
+ updateCheck: updateCheckEnabled(),
80
124
  });
81
- await exitAfterFlush(code);
82
125
  });
83
126
  program
84
127
  .command('list')
85
128
  .description('List every rule the current install ships with')
86
129
  .option('--json', 'Emit JSON instead of pretty output')
87
130
  .action(async (opts) => {
88
- await exitAfterFlush(await runList({ json: opts.json }));
131
+ const code = await runList({ json: opts.json });
132
+ await finishWithNotice(code, {
133
+ version,
134
+ machineReadable: opts.json === true,
135
+ updateCheck: updateCheckEnabled(),
136
+ });
89
137
  });
90
138
  program
91
139
  .command('init')
92
140
  .description('Generate a .oauthlintrc.yml at the current directory')
93
141
  .option('-f, --force', 'Overwrite an existing config file')
94
142
  .action(async (opts) => {
95
- await exitAfterFlush(await runInit({ cwd: process.cwd(), force: opts.force }));
143
+ const code = await runInit({ cwd: process.cwd(), force: opts.force });
144
+ await finishWithNotice(code, {
145
+ version,
146
+ machineReadable: false,
147
+ updateCheck: updateCheckEnabled(),
148
+ });
96
149
  });
97
150
  program
98
151
  .command('doctor')
99
152
  .description('Diagnose your OAuthLint install (Node, Semgrep, rule pack)')
100
153
  .option('--json', 'Emit JSON instead of pretty output')
101
154
  .action(async (opts) => {
102
- await exitAfterFlush(await runDoctor({ json: opts.json }));
155
+ const code = await runDoctor({ json: opts.json });
156
+ await finishWithNotice(code, {
157
+ version,
158
+ machineReadable: opts.json === true,
159
+ updateCheck: updateCheckEnabled(),
160
+ });
103
161
  });
104
162
  program.showHelpAfterError(pc.dim('(run `oauthlint --help` for available commands)'));
105
163
  return program;
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAmB,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAqB,MAAM,YAAY,CAAC;AAM3D;;;;;;;;GAQG;AACH,KAAK,UAAU,cAAc,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,CAAC,CAAqB,EAAiB,EAAE,CACrD,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAClB,IAAI,CAAC,CAAC,cAAc,KAAK,CAAC,EAAE,CAAC;YAC3B,GAAG,EAAE,CAAC;YACN,OAAO;QACT,CAAC;QACD,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IACL,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,gBAAgB,CAC7B,IAAY,EACZ,GAAwE;IAExE,MAAM,iBAAiB,CAAC;QACtB,cAAc,EAAE,GAAG,CAAC,OAAO;QAC3B,eAAe,EAAE,GAAG,CAAC,eAAe;QACpC,QAAQ,EAAE,CAAC,GAAG,CAAC,WAAW;KAC3B,CAAC,CAAC;IACH,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,KAAK,UAAU,kBAAkB;IAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,iDAAiD;IACjD,8DAA8D;IAC9D,KAAK,MAAM,SAAS,IAAI;QACtB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC;QACnC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC;KAC1C,EAAE,CAAC;QACF,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAY,CAAC;YACrE,IAAI,GAAG,CAAC,OAAO;gBAAE,OAAO,GAAG,CAAC,OAAO,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,cAAc;QAChB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,MAAM,kBAAkB,EAAE,CAAC;IAE3C,OAAO;SACJ,IAAI,CAAC,WAAW,CAAC;SACjB,WAAW,CACV,4FAA4F,CAC7F;SACA,OAAO,CAAC,OAAO,EAAE,eAAe,CAAC;SACjC,MAAM,CAAC,mBAAmB,EAAE,gDAAgD,CAAC,CAAC;IAEjF,+EAA+E;IAC/E,yEAAyE;IACzE,MAAM,kBAAkB,GAAG,GAAY,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,KAAK,KAAK,CAAC;IAE/E,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CAAC,YAAY,EAAE,uCAAuC,EAAE,CAAC,GAAG,CAAC,CAAC;SACtE,WAAW,CAAC,sDAAsD,CAAC;SACnE,MAAM,CAAC,QAAQ,EAAE,wCAAwC,CAAC;SAC1D,MAAM,CAAC,gBAAgB,EAAE,sCAAsC,EAAE,WAAW,CAAC;SAC7E,MAAM,CAAC,oBAAoB,EAAE,oCAAoC,EAAE,aAAa,CAAC;SACjF,MAAM,CACL,mBAAmB,EACnB,uDAAuD,EACvD,WAAW,CACZ;SACA,MAAM,CACL,cAAc,EACd,oFAAoF,CACrF;SACA,MAAM,CAAC,UAAU,EAAE,0DAA0D,CAAC;SAC9E,MAAM,CAAC,oBAAoB,EAAE,sCAAsC,CAAC;SACpE,MAAM,CAAC,OAAO,EAAE,4DAA4D,CAAC;SAC7E,MAAM,CACL,mBAAmB,EACnB,4GAA4G,CAC7G;SACA,MAAM,CAAC,KAAK,EAAE,KAAe,EAAE,IAAoB,EAAE,EAAE;QACtD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC;YACzB,KAAK;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;QACH,MAAM,eAAe,GACnB,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC;QAC1E,MAAM,gBAAgB,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;IAChG,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,UAAU,CAAC;SACnB,QAAQ,CAAC,YAAY,EAAE,uCAAuC,EAAE,CAAC,GAAG,CAAC,CAAC;SACtE,WAAW,CACV,sFAAsF,CACvF;SACA,MAAM,CAAC,qBAAqB,EAAE,kCAAkC,EAAE,0BAA0B,CAAC;SAC7F,MAAM,CAAC,oBAAoB,EAAE,sCAAsC,CAAC;SACpE,MAAM,CAAC,KAAK,EAAE,KAAe,EAAE,IAAwB,EAAE,EAAE;QAC1D,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC;YAC7B,KAAK;YACL,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;QACH,iEAAiE;QACjE,MAAM,gBAAgB,CAAC,IAAI,EAAE;YAC3B,OAAO;YACP,eAAe,EAAE,KAAK;YACtB,WAAW,EAAE,kBAAkB,EAAE;SAClC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,gDAAgD,CAAC;SAC7D,MAAM,CAAC,QAAQ,EAAE,oCAAoC,CAAC;SACtD,MAAM,CAAC,KAAK,EAAE,IAAwB,EAAE,EAAE;QACzC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,gBAAgB,CAAC,IAAI,EAAE;YAC3B,OAAO;YACP,eAAe,EAAE,IAAI,CAAC,IAAI,KAAK,IAAI;YACnC,WAAW,EAAE,kBAAkB,EAAE;SAClC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,sDAAsD,CAAC;SACnE,MAAM,CAAC,aAAa,EAAE,mCAAmC,CAAC;SAC1D,MAAM,CAAC,KAAK,EAAE,IAAyB,EAAE,EAAE;QAC1C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACtE,MAAM,gBAAgB,CAAC,IAAI,EAAE;YAC3B,OAAO;YACP,eAAe,EAAE,KAAK;YACtB,WAAW,EAAE,kBAAkB,EAAE;SAClC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,4DAA4D,CAAC;SACzE,MAAM,CAAC,QAAQ,EAAE,oCAAoC,CAAC;SACtD,MAAM,CAAC,KAAK,EAAE,IAAwB,EAAE,EAAE;QACzC,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,gBAAgB,CAAC,IAAI,EAAE;YAC3B,OAAO;YACP,eAAe,EAAE,IAAI,CAAC,IAAI,KAAK,IAAI;YACnC,WAAW,EAAE,kBAAkB,EAAE;SAClC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,OAAO,CAAC,kBAAkB,CAAC,EAAE,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC,CAAC;IACtF,OAAO,OAAO,CAAC;AACjB,CAAC;AAsBD,SAAS,aAAa,CAAC,CAAS;IAC9B,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAkB,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,uBAAuB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AACrD,SAAS,WAAW,CAAC,CAAS;IAC5B,MAAM,KAAK,GAAG,CAAC,CAAC,WAAW,EAAgB,CAAC;IAC5C,IAAI,CAAE,OAA6B,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,uBAAuB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { SemgrepAdapter } from '../adapters/semgrep.js';
2
+ export interface BaselineCommandOptions {
3
+ /** Path(s) to scan when capturing the baseline. Defaults to `['.']`. */
4
+ paths?: string[];
5
+ /** Where to write the baseline JSON. Defaults to `.oauthlint-baseline.json`. */
6
+ output?: string;
7
+ rulesDir?: string;
8
+ /** Used by tests to inject a mock adapter. */
9
+ adapter?: SemgrepAdapter;
10
+ /** Used by tests to capture output. */
11
+ stream?: NodeJS.WritableStream;
12
+ /** Override the directory git/relative paths resolve from (defaults to cwd). */
13
+ cwd?: string;
14
+ }
15
+ /**
16
+ * `oauthlint baseline [paths...]` — scan the codebase and write a baseline file
17
+ * capturing the CURRENT findings by stable fingerprint, so a later
18
+ * `scan --baseline` only surfaces NEW findings.
19
+ */
20
+ export declare function runBaseline(opts: BaselineCommandOptions): Promise<number>;
21
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,cAAc,EAGf,MAAM,wBAAwB,CAAC;AAMhC,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,gFAAgF;IAChF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+C/E"}
@@ -0,0 +1,50 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { RULES_ROOT } from 'oauthlint-rules';
4
+ import pc from 'picocolors';
5
+ import { SemgrepAdapter, SemgrepNotInstalledError, SemgrepOutputError, } from '../adapters/semgrep.js';
6
+ import { DEFAULT_BASELINE_FILE, buildBaseline, serialiseBaseline } from '../core/baseline.js';
7
+ import { loadConfig } from '../core/config.js';
8
+ import { applySuppressions } from '../core/suppress.js';
9
+ /**
10
+ * `oauthlint baseline [paths...]` — scan the codebase and write a baseline file
11
+ * capturing the CURRENT findings by stable fingerprint, so a later
12
+ * `scan --baseline` only surfaces NEW findings.
13
+ */
14
+ export async function runBaseline(opts) {
15
+ const cwd = opts.cwd ?? process.cwd();
16
+ const config = await loadConfig(cwd);
17
+ const rulesDir = opts.rulesDir ?? config.customRulesDir ?? RULES_ROOT;
18
+ const stream = opts.stream ?? process.stdout;
19
+ const errStream = opts.stream ?? process.stderr;
20
+ const requested = opts.paths?.length ? opts.paths : ['.'];
21
+ const targets = requested.map((p) => resolve(cwd, p));
22
+ const adapter = opts.adapter ?? new SemgrepAdapter({ configPath: rulesDir });
23
+ let result;
24
+ try {
25
+ result = await adapter.scan(targets);
26
+ }
27
+ catch (err) {
28
+ if (err instanceof SemgrepNotInstalledError) {
29
+ errStream.write(`${err.message}\n`);
30
+ return 127;
31
+ }
32
+ if (err instanceof SemgrepOutputError) {
33
+ errStream.write(`${err.message}\n`);
34
+ return 2;
35
+ }
36
+ throw err;
37
+ }
38
+ // Honour inline suppression directives so the baseline mirrors what `scan`
39
+ // would actually report (we don't want to baseline already-suppressed noise).
40
+ const { kept } = await applySuppressions(result.findings);
41
+ const findings = kept;
42
+ const outputPath = resolve(cwd, opts.output ?? DEFAULT_BASELINE_FILE);
43
+ const baseline = await buildBaseline(findings, cwd);
44
+ await writeFile(outputPath, serialiseBaseline(baseline), 'utf8');
45
+ const n = baseline.findings.length;
46
+ stream.write(pc.green(`✓ Baselined ${n} finding${n === 1 ? '' : 's'} → ${opts.output ?? DEFAULT_BASELINE_FILE}\n`));
47
+ stream.write(pc.dim(`Future runs of ${pc.bold('oauthlint scan --baseline')} will report only NEW findings.\n`));
48
+ return 0;
49
+ }
50
+ //# sourceMappingURL=baseline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/commands/baseline.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,13 @@ export interface ScanCommandOptions {
30
30
  * ship a `fix:` template. Currently the cookie-* rules.
31
31
  */
32
32
  fix?: boolean;
33
+ /**
34
+ * Suppress findings already captured in a baseline file, reporting only NEW
35
+ * findings. A bare `true` uses the default `.oauthlint-baseline.json`; a
36
+ * string is treated as an explicit baseline path. A missing file is a clear
37
+ * error, never silently an empty allow-list.
38
+ */
39
+ baseline?: string | boolean;
33
40
  /** Used by tests to inject a mock adapter. */
34
41
  adapter?: SemgrepAdapter;
35
42
  /** Used by tests to capture output. */
@@ -1 +1 @@
1
- {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,cAAc,EAGf,MAAM,wBAAwB,CAAC;AAOhC,OAAO,KAAK,EAAW,YAAY,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,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;IAC/B,gFAAgF;IAChF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoHvE"}
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,cAAc,EAGf,MAAM,wBAAwB,CAAC;AAchC,OAAO,KAAK,EAAW,YAAY,EAAE,MAAM,aAAa,CAAC;AAEzD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,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;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,gFAAgF;IAChF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuJvE"}
@@ -1,6 +1,7 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { RULES_ROOT } from 'oauthlint-rules';
3
3
  import { SemgrepAdapter, SemgrepNotInstalledError, SemgrepOutputError, } from '../adapters/semgrep.js';
4
+ import { BaselineNotFoundError, BaselineParseError, DEFAULT_BASELINE_FILE, loadBaseline, partitionByBaseline, } from '../core/baseline.js';
4
5
  import { GitError, resolveDiffFiles, resolveStagedFiles } from '../core/changed-files.js';
5
6
  import { loadConfig } from '../core/config.js';
6
7
  import { Reporter } from '../core/reporter.js';
@@ -15,6 +16,22 @@ export async function runScan(opts) {
15
16
  const format = opts.format ?? (opts.json ? 'json' : 'pretty');
16
17
  const stream = opts.stream ?? process.stdout;
17
18
  const errStream = opts.stream ?? process.stderr;
19
+ // Load the baseline up front so a missing/malformed file fails fast — before
20
+ // we pay for a full scan — with a clear error rather than a silent no-op.
21
+ let baseline = null;
22
+ if (opts.baseline !== undefined && opts.baseline !== false) {
23
+ const baselinePath = resolve(cwd, typeof opts.baseline === 'string' ? opts.baseline : DEFAULT_BASELINE_FILE);
24
+ try {
25
+ baseline = await loadBaseline(baselinePath);
26
+ }
27
+ catch (err) {
28
+ if (err instanceof BaselineNotFoundError || err instanceof BaselineParseError) {
29
+ errStream.write(`${err.message}\n`);
30
+ return 2;
31
+ }
32
+ throw err;
33
+ }
34
+ }
18
35
  // Resolve the set of targets to hand Semgrep. Incremental flags (--diff /
19
36
  // --staged) win and narrow the scan to changed files only; otherwise we scan
20
37
  // the explicit path args (default ['.']).
@@ -99,6 +116,14 @@ export async function runScan(opts) {
99
116
  if (minSeverity) {
100
117
  findings = findings.filter((f) => meetsThreshold(f.severity, minSeverity));
101
118
  }
119
+ // Baseline suppression: drop findings whose fingerprint is already recorded,
120
+ // so only genuinely NEW findings are reported and gate the exit code.
121
+ let baselinedCount = 0;
122
+ if (baseline) {
123
+ const { newFindings, baselined } = await partitionByBaseline(findings, baseline, cwd);
124
+ findings = newFindings;
125
+ baselinedCount = baselined.length;
126
+ }
102
127
  if (format === 'sarif') {
103
128
  const sarif = await toSarif({ ...result, findings });
104
129
  stream.write(`${JSON.stringify(sarif, null, 2)}\n`);
@@ -112,6 +137,9 @@ export async function runScan(opts) {
112
137
  if (suppressed.length > 0 && format === 'pretty') {
113
138
  stream.write(`\nℹ ${suppressed.length} finding${suppressed.length === 1 ? '' : 's'} suppressed via inline directives.\n`);
114
139
  }
140
+ if (baselinedCount > 0 && format === 'pretty') {
141
+ stream.write(`\nℹ ${baselinedCount} finding${baselinedCount === 1 ? '' : 's'} already in the baseline (reporting NEW findings only).\n`);
142
+ }
115
143
  }
116
144
  if (failOn === 'off')
117
145
  return 0;
@@ -1 +1 @@
1
- {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ import type { Finding } from '../types.js';
2
+ /**
3
+ * Baseline support: capture the CURRENT set of findings so a team can adopt
4
+ * OAuthLint on a large existing codebase and only be alerted on NEW issues.
5
+ *
6
+ * The hard requirement is a fingerprint that is stable across edits that move
7
+ * a finding around without changing it: inserting an import, reformatting a
8
+ * block, or shifting code down N lines must NOT resurface a baselined finding,
9
+ * while genuinely changing the flagged code MUST surface it as new.
10
+ *
11
+ * Fingerprint = sha256 of three parts joined by NULs:
12
+ * 1. the rule identity — `oauthlintRuleId` when present, else `ruleId`
13
+ * 2. the repo/cwd-relative file path (POSIX separators, so it's portable)
14
+ * 3. a NORMALISED snapshot of the matched source (the `startLine..endLine`
15
+ * span, whitespace-collapsed) — explicitly NOT the raw line numbers
16
+ *
17
+ * When the file can't be read (deleted, binary, permission error) we fall back
18
+ * to the trimmed `message` as the snapshot so a fingerprint can still be
19
+ * produced — degraded but deterministic, never a crash.
20
+ *
21
+ * Identical fingerprints within one file (e.g. the same anti-pattern repeated)
22
+ * are disambiguated by a stable per-file occurrence index, assigned in source
23
+ * order, so each physical finding maps to a distinct baseline entry.
24
+ */
25
+ export declare const BASELINE_VERSION: 1;
26
+ export declare const DEFAULT_BASELINE_FILE = ".oauthlint-baseline.json";
27
+ /** A single baselined finding, keyed by its stable fingerprint. */
28
+ export interface BaselineEntry {
29
+ /** Stable fingerprint (see module docs). Survives line shifts. */
30
+ fingerprint: string;
31
+ /** Rule identity used in the hash — for human-diffability of the file. */
32
+ ruleId: string;
33
+ /** Repo/cwd-relative path used in the hash — for human-diffability. */
34
+ filePath: string;
35
+ }
36
+ export interface BaselineFile {
37
+ version: typeof BASELINE_VERSION;
38
+ generatedAt: string;
39
+ /** Sorted, deterministic list of baselined fingerprints. */
40
+ findings: BaselineEntry[];
41
+ }
42
+ /** In-memory baseline: just the set of fingerprints we suppress against. */
43
+ export interface Baseline {
44
+ fingerprints: Set<string>;
45
+ }
46
+ export declare class BaselineNotFoundError extends Error {
47
+ constructor(path: string);
48
+ }
49
+ export declare class BaselineParseError extends Error {
50
+ constructor(path: string, detail: string);
51
+ }
52
+ /**
53
+ * Turn a finding's `filePath` into a portable, repo/cwd-relative path with
54
+ * POSIX separators. Absolute paths are made relative to `cwd`; paths that are
55
+ * already relative are normalised as-is. We never let an absolute path leak
56
+ * into a fingerprint, so a baseline is portable across machines/checkouts.
57
+ */
58
+ export declare function toRelativePath(filePath: string, cwd: string): string;
59
+ /**
60
+ * Compute the stable fingerprint for a finding (without the occurrence index).
61
+ * Two findings of the same rule on the same (relative) file with the same
62
+ * normalised snippet share this value — they're disambiguated by the index
63
+ * added in {@link fingerprintFindings}.
64
+ */
65
+ export declare function baseFingerprint(finding: Finding, cwd: string, cache: Map<string, string[] | null>): Promise<string>;
66
+ /** A finding paired with its final, occurrence-disambiguated fingerprint. */
67
+ export interface FingerprintedFinding {
68
+ finding: Finding;
69
+ fingerprint: string;
70
+ }
71
+ /**
72
+ * Fingerprint a list of findings deterministically. Findings are processed in
73
+ * a stable order, and identical base fingerprints within the run get an
74
+ * occurrence index (`<base>:<n>`) so repeated anti-patterns each get a unique
75
+ * key. The same input list always yields the same fingerprints.
76
+ */
77
+ export declare function fingerprintFindings(findings: Finding[], cwd: string): Promise<FingerprintedFinding[]>;
78
+ /**
79
+ * Build a serialisable baseline file from a set of findings. The entry list is
80
+ * sorted so the JSON is stable and human-diffable across runs.
81
+ */
82
+ export declare function buildBaseline(findings: Finding[], cwd: string): Promise<BaselineFile>;
83
+ /** Serialise a baseline file to pretty JSON (trailing newline for clean diffs). */
84
+ export declare function serialiseBaseline(baseline: BaselineFile): string;
85
+ /**
86
+ * Load a baseline from disk into an in-memory fingerprint set.
87
+ *
88
+ * @throws BaselineNotFoundError when the file is absent — deliberately NOT
89
+ * treated as an empty allow-list, so a misspelt `--baseline path` fails loud
90
+ * instead of silently suppressing nothing (or everything).
91
+ * @throws BaselineParseError when the file is present but malformed.
92
+ */
93
+ export declare function loadBaseline(path: string): Promise<Baseline>;
94
+ /**
95
+ * Split findings into those already present in the baseline (suppressed) and
96
+ * genuinely new ones, by fingerprinting and matching against the baseline set.
97
+ */
98
+ export declare function partitionByBaseline(findings: Finding[], baseline: Baseline, cwd: string): Promise<{
99
+ newFindings: Finding[];
100
+ baselined: Finding[];
101
+ }>;
102
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/core/baseline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,gBAAgB,EAAG,CAAU,CAAC;AAC3C,eAAO,MAAM,qBAAqB,6BAA6B,CAAC;AAEhE,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,gBAAgB,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,4EAA4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC3B;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,IAAI,EAAE,MAAM;CAMzB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAIzC;AAYD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpE;AAuDD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,GAClC,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAuBjC;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAkB3F;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAEhE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8BlE;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,WAAW,EAAE,OAAO,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC,CAS3D"}
@@ -0,0 +1,223 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { isAbsolute, relative, resolve } from 'node:path';
4
+ /**
5
+ * Baseline support: capture the CURRENT set of findings so a team can adopt
6
+ * OAuthLint on a large existing codebase and only be alerted on NEW issues.
7
+ *
8
+ * The hard requirement is a fingerprint that is stable across edits that move
9
+ * a finding around without changing it: inserting an import, reformatting a
10
+ * block, or shifting code down N lines must NOT resurface a baselined finding,
11
+ * while genuinely changing the flagged code MUST surface it as new.
12
+ *
13
+ * Fingerprint = sha256 of three parts joined by NULs:
14
+ * 1. the rule identity — `oauthlintRuleId` when present, else `ruleId`
15
+ * 2. the repo/cwd-relative file path (POSIX separators, so it's portable)
16
+ * 3. a NORMALISED snapshot of the matched source (the `startLine..endLine`
17
+ * span, whitespace-collapsed) — explicitly NOT the raw line numbers
18
+ *
19
+ * When the file can't be read (deleted, binary, permission error) we fall back
20
+ * to the trimmed `message` as the snapshot so a fingerprint can still be
21
+ * produced — degraded but deterministic, never a crash.
22
+ *
23
+ * Identical fingerprints within one file (e.g. the same anti-pattern repeated)
24
+ * are disambiguated by a stable per-file occurrence index, assigned in source
25
+ * order, so each physical finding maps to a distinct baseline entry.
26
+ */
27
+ export const BASELINE_VERSION = 1;
28
+ export const DEFAULT_BASELINE_FILE = '.oauthlint-baseline.json';
29
+ export class BaselineNotFoundError extends Error {
30
+ constructor(path) {
31
+ super(`Baseline file not found: ${path}\nCreate one first with: oauthlint baseline\n(an absent baseline is treated as an error, not an empty allow-list, so a typo never silently disables suppression).`);
32
+ this.name = 'BaselineNotFoundError';
33
+ }
34
+ }
35
+ export class BaselineParseError extends Error {
36
+ constructor(path, detail) {
37
+ super(`Could not parse baseline file ${path}: ${detail}`);
38
+ this.name = 'BaselineParseError';
39
+ }
40
+ }
41
+ /** Collapse runs of whitespace and trim, so reformatting doesn't churn fingerprints. */
42
+ function normaliseSnippet(text) {
43
+ return text.replace(/\s+/g, ' ').trim();
44
+ }
45
+ /** Rule identity that goes into the hash — prefer the stable OAuthLint id. */
46
+ function ruleIdentity(finding) {
47
+ return finding.oauthlintRuleId ?? finding.ruleId;
48
+ }
49
+ /**
50
+ * Turn a finding's `filePath` into a portable, repo/cwd-relative path with
51
+ * POSIX separators. Absolute paths are made relative to `cwd`; paths that are
52
+ * already relative are normalised as-is. We never let an absolute path leak
53
+ * into a fingerprint, so a baseline is portable across machines/checkouts.
54
+ */
55
+ export function toRelativePath(filePath, cwd) {
56
+ const rel = isAbsolute(filePath) ? relative(cwd, filePath) : filePath;
57
+ return rel.split('\\').join('/');
58
+ }
59
+ /**
60
+ * Read the matched source span for a finding and return it normalised. On any
61
+ * read/decoding problem, fall back to the finding's message so we still get a
62
+ * deterministic value rather than throwing.
63
+ */
64
+ async function snapshotFor(finding, cwd, cache) {
65
+ const abs = isAbsolute(finding.filePath) ? finding.filePath : resolve(cwd, finding.filePath);
66
+ let lines = cache.get(abs);
67
+ if (lines === undefined) {
68
+ lines = await readLinesSafe(abs);
69
+ cache.set(abs, lines);
70
+ }
71
+ if (lines === null) {
72
+ // Unreadable (missing/binary/permission) — degrade to the message text.
73
+ return normaliseSnippet(finding.message);
74
+ }
75
+ // Finding lines are 1-based and inclusive. Clamp into range defensively in
76
+ // case the file changed between scan and fingerprinting.
77
+ const start = Math.max(1, finding.startLine);
78
+ const end = Math.max(start, finding.endLine);
79
+ const span = lines.slice(start - 1, end).join('\n');
80
+ const normalised = normaliseSnippet(span);
81
+ // If the span is empty (out-of-range lines after a shrink), fall back so the
82
+ // fingerprint is never the hash of an empty string for every such finding.
83
+ return normalised || normaliseSnippet(finding.message);
84
+ }
85
+ /** Read a file as lines, returning null when it's missing or looks binary. */
86
+ async function readLinesSafe(absPath) {
87
+ let buf;
88
+ try {
89
+ buf = await readFile(absPath);
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ // Treat NUL bytes as the binary signal (cheap, matches common heuristics).
95
+ if (buf.includes(0))
96
+ return null;
97
+ return buf.toString('utf8').split('\n');
98
+ }
99
+ function hash(parts) {
100
+ const h = createHash('sha256');
101
+ // NUL separator can't appear in any of the parts (path/id are text, the
102
+ // snippet has NULs stripped by the binary check), so there's no collision
103
+ // between e.g. ["ab","c"] and ["a","bc"].
104
+ h.update(parts.join('\0'));
105
+ return h.digest('hex');
106
+ }
107
+ /**
108
+ * Compute the stable fingerprint for a finding (without the occurrence index).
109
+ * Two findings of the same rule on the same (relative) file with the same
110
+ * normalised snippet share this value — they're disambiguated by the index
111
+ * added in {@link fingerprintFindings}.
112
+ */
113
+ export async function baseFingerprint(finding, cwd, cache) {
114
+ const rel = toRelativePath(finding.filePath, cwd);
115
+ const snippet = await snapshotFor(finding, cwd, cache);
116
+ return hash([ruleIdentity(finding), rel, snippet]);
117
+ }
118
+ /**
119
+ * Fingerprint a list of findings deterministically. Findings are processed in
120
+ * a stable order, and identical base fingerprints within the run get an
121
+ * occurrence index (`<base>:<n>`) so repeated anti-patterns each get a unique
122
+ * key. The same input list always yields the same fingerprints.
123
+ */
124
+ export async function fingerprintFindings(findings, cwd) {
125
+ const cache = new Map();
126
+ // Stable processing order so occurrence indices are reproducible regardless
127
+ // of the adapter's emission order: by relative path, then line, then rule.
128
+ const ordered = [...findings]
129
+ .map((finding) => ({ finding, rel: toRelativePath(finding.filePath, cwd) }))
130
+ .sort((a, b) => a.rel.localeCompare(b.rel) ||
131
+ a.finding.startLine - b.finding.startLine ||
132
+ a.finding.endLine - b.finding.endLine ||
133
+ ruleIdentity(a.finding).localeCompare(ruleIdentity(b.finding)));
134
+ const seen = new Map();
135
+ const out = [];
136
+ for (const { finding } of ordered) {
137
+ const base = await baseFingerprint(finding, cwd, cache);
138
+ const n = seen.get(base) ?? 0;
139
+ seen.set(base, n + 1);
140
+ out.push({ finding, fingerprint: `${base}:${n}` });
141
+ }
142
+ return out;
143
+ }
144
+ /**
145
+ * Build a serialisable baseline file from a set of findings. The entry list is
146
+ * sorted so the JSON is stable and human-diffable across runs.
147
+ */
148
+ export async function buildBaseline(findings, cwd) {
149
+ const fingerprinted = await fingerprintFindings(findings, cwd);
150
+ const entries = fingerprinted.map(({ finding, fingerprint }) => ({
151
+ fingerprint,
152
+ ruleId: ruleIdentity(finding),
153
+ filePath: toRelativePath(finding.filePath, cwd),
154
+ }));
155
+ entries.sort((a, b) => a.filePath.localeCompare(b.filePath) ||
156
+ a.ruleId.localeCompare(b.ruleId) ||
157
+ a.fingerprint.localeCompare(b.fingerprint));
158
+ return {
159
+ version: BASELINE_VERSION,
160
+ generatedAt: new Date().toISOString(),
161
+ findings: entries,
162
+ };
163
+ }
164
+ /** Serialise a baseline file to pretty JSON (trailing newline for clean diffs). */
165
+ export function serialiseBaseline(baseline) {
166
+ return `${JSON.stringify(baseline, null, 2)}\n`;
167
+ }
168
+ /**
169
+ * Load a baseline from disk into an in-memory fingerprint set.
170
+ *
171
+ * @throws BaselineNotFoundError when the file is absent — deliberately NOT
172
+ * treated as an empty allow-list, so a misspelt `--baseline path` fails loud
173
+ * instead of silently suppressing nothing (or everything).
174
+ * @throws BaselineParseError when the file is present but malformed.
175
+ */
176
+ export async function loadBaseline(path) {
177
+ let raw;
178
+ try {
179
+ raw = await readFile(path, 'utf8');
180
+ }
181
+ catch (err) {
182
+ if (err.code === 'ENOENT') {
183
+ throw new BaselineNotFoundError(path);
184
+ }
185
+ throw err;
186
+ }
187
+ let parsed;
188
+ try {
189
+ parsed = JSON.parse(raw);
190
+ }
191
+ catch (err) {
192
+ throw new BaselineParseError(path, err instanceof Error ? err.message : String(err));
193
+ }
194
+ if (typeof parsed !== 'object' ||
195
+ parsed === null ||
196
+ !Array.isArray(parsed.findings)) {
197
+ throw new BaselineParseError(path, 'expected an object with a "findings" array');
198
+ }
199
+ const fingerprints = new Set();
200
+ for (const entry of parsed.findings) {
201
+ if (entry && typeof entry.fingerprint === 'string') {
202
+ fingerprints.add(entry.fingerprint);
203
+ }
204
+ }
205
+ return { fingerprints };
206
+ }
207
+ /**
208
+ * Split findings into those already present in the baseline (suppressed) and
209
+ * genuinely new ones, by fingerprinting and matching against the baseline set.
210
+ */
211
+ export async function partitionByBaseline(findings, baseline, cwd) {
212
+ const fingerprinted = await fingerprintFindings(findings, cwd);
213
+ const newFindings = [];
214
+ const baselined = [];
215
+ for (const { finding, fingerprint } of fingerprinted) {
216
+ if (baseline.fingerprints.has(fingerprint))
217
+ baselined.push(finding);
218
+ else
219
+ newFindings.push(finding);
220
+ }
221
+ return { newFindings, baselined };
222
+ }
223
+ //# sourceMappingURL=baseline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/core/baseline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ export interface NotifierCache {
2
+ /** Epoch ms of the last successful registry check. */
3
+ lastCheck: number;
4
+ /** The latest version string the registry reported, or null on failure. */
5
+ latestVersion: string | null;
6
+ }
7
+ export interface UpdateNotifierOptions {
8
+ /** Currently-installed version (from package.json). */
9
+ currentVersion: string;
10
+ /** True when the command emits machine-readable output (json/sarif). */
11
+ machineReadable?: boolean;
12
+ /** Explicit opt-out (e.g. `--no-update-check` or config). */
13
+ disabled?: boolean;
14
+ /** Streams/env are injected so tests stay deterministic and offline. */
15
+ stderr?: NodeJS.WriteStream & {
16
+ isTTY?: boolean;
17
+ };
18
+ env?: NodeJS.ProcessEnv;
19
+ /** Override the cache file location (tests). */
20
+ cachePath?: string;
21
+ /** Inject the registry fetch + clock (tests). */
22
+ fetchLatest?: (name: string, timeoutMs: number) => Promise<string | null>;
23
+ now?: () => number;
24
+ }
25
+ /**
26
+ * Decide whether a notice may be shown at all, independent of versions.
27
+ * Cheap, synchronous, and the single source of truth for suppression.
28
+ */
29
+ export declare function shouldCheckForUpdate(opts: {
30
+ machineReadable?: boolean;
31
+ disabled?: boolean;
32
+ stderr?: {
33
+ isTTY?: boolean;
34
+ };
35
+ env?: NodeJS.ProcessEnv;
36
+ }): boolean;
37
+ /** Default cache location: XDG_CACHE_HOME, else ~/.cache. */
38
+ export declare function defaultCachePath(env?: NodeJS.ProcessEnv): string;
39
+ /**
40
+ * Fetch the latest published version from the npm registry.
41
+ * Hits the lightweight dist-tags endpoint, validates the response, and returns
42
+ * null on any error/timeout. Nothing from the response is ever executed.
43
+ */
44
+ export declare function fetchLatestVersion(name: string, timeoutMs?: number): Promise<string | null>;
45
+ /**
46
+ * Run the update check and, when appropriate, print a single notice to stderr.
47
+ * Designed to be awaited right before the process exits so the notice appears
48
+ * AFTER the command's normal output and never interleaves.
49
+ *
50
+ * It resolves to the notice string it printed (or null), which is handy for
51
+ * tests; production callers can ignore the return value.
52
+ */
53
+ export declare function maybeNotifyUpdate(opts: UpdateNotifierOptions): Promise<string | null>;
54
+ export declare function isValidVersion(v: string): boolean;
55
+ /**
56
+ * Compare two semver strings. Returns >0 if a>b, <0 if a<b, 0 if equal.
57
+ * Honours prerelease precedence (1.0.0-rc < 1.0.0) per the semver spec's
58
+ * common cases. Invalid input sorts as "not greater" so we never nag wrongly.
59
+ */
60
+ export declare function compareSemver(a: string, b: string): number;
61
+ //# sourceMappingURL=update-notifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-notifier.d.ts","sourceRoot":"","sources":["../../src/core/update-notifier.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IACpC,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,GAAG;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,GAAG,OAAO,CAYV;AAiBD,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG7E;AAkCD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAA2B,GACrC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsDxB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4C3F;AAgBD,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CA6B1D"}
@@ -0,0 +1,267 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { request as httpsRequest } from 'node:https';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import pc from 'picocolors';
6
+ /**
7
+ * A minimal, dependency-free "newer version available" notifier — the same
8
+ * idea as npm/eslint, kept deliberately small so it adds no transitive
9
+ * supply-chain surface (we don't pull in the `update-notifier` tree).
10
+ *
11
+ * Hard guarantees:
12
+ * - NEVER blocks or delays the command. The check is fire-and-forget against a
13
+ * ~24h cache; the network call has a short timeout and ALL errors are
14
+ * swallowed (offline is completely silent).
15
+ * - Writes ONLY to stderr, never stdout — so `--json` / `--format sarif`
16
+ * piping is never corrupted.
17
+ * - Suppressed whenever output is machine-readable, stderr isn't a TTY, a CI
18
+ * env is detected, `NO_UPDATE_NOTIFIER` is set, or the user opted out.
19
+ */
20
+ /** Package name on the npm registry. */
21
+ const PACKAGE_NAME = 'oauthlint';
22
+ /** How long a cached registry answer is trusted before we re-check. */
23
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h
24
+ /** Network timeout — short, so a slow/blocked registry never stalls the CLI. */
25
+ const NETWORK_TIMEOUT_MS = 2500;
26
+ /**
27
+ * Decide whether a notice may be shown at all, independent of versions.
28
+ * Cheap, synchronous, and the single source of truth for suppression.
29
+ */
30
+ export function shouldCheckForUpdate(opts) {
31
+ const env = opts.env ?? process.env;
32
+ if (opts.disabled)
33
+ return false;
34
+ if (opts.machineReadable)
35
+ return false;
36
+ // Explicit kill-switch honoured by the whole ecosystem.
37
+ if (env.NO_UPDATE_NOTIFIER)
38
+ return false;
39
+ // Any common CI signal — never nag automated runs.
40
+ if (isCI(env))
41
+ return false;
42
+ // Piped / redirected stderr: the notice would land in a log or get mangled.
43
+ const stderr = opts.stderr ?? process.stderr;
44
+ if (!stderr.isTTY)
45
+ return false;
46
+ return true;
47
+ }
48
+ function isCI(env) {
49
+ return Boolean(env.CI ||
50
+ env.CONTINUOUS_INTEGRATION ||
51
+ env.BUILD_NUMBER ||
52
+ env.GITHUB_ACTIONS ||
53
+ env.GITLAB_CI ||
54
+ env.CIRCLECI ||
55
+ env.TRAVIS ||
56
+ env.JENKINS_URL ||
57
+ env.TEAMCITY_VERSION ||
58
+ env.BITBUCKET_BUILD_NUMBER);
59
+ }
60
+ /** Default cache location: XDG_CACHE_HOME, else ~/.cache. */
61
+ export function defaultCachePath(env = process.env) {
62
+ const base = env.XDG_CACHE_HOME?.trim() || join(homedir(), '.cache');
63
+ return join(base, 'oauthlint', 'update-check.json');
64
+ }
65
+ async function readCache(path) {
66
+ try {
67
+ const raw = await readFile(path, 'utf8');
68
+ const parsed = JSON.parse(raw);
69
+ if (parsed &&
70
+ typeof parsed === 'object' &&
71
+ typeof parsed.lastCheck === 'number') {
72
+ const c = parsed;
73
+ const v = c.latestVersion;
74
+ // Validate the cached version: only accept null or a sane semver-ish
75
+ // string. Never trust arbitrary content (defends against a tampered file).
76
+ if (v === null || (typeof v === 'string' && isValidVersion(v))) {
77
+ return { lastCheck: c.lastCheck, latestVersion: v };
78
+ }
79
+ }
80
+ }
81
+ catch {
82
+ /* missing/corrupt cache → treat as no cache */
83
+ }
84
+ return null;
85
+ }
86
+ async function writeCache(path, cache) {
87
+ try {
88
+ await mkdir(dirname(path), { recursive: true });
89
+ await writeFile(path, JSON.stringify(cache), 'utf8');
90
+ }
91
+ catch {
92
+ /* best-effort: a read-only home must never break the command */
93
+ }
94
+ }
95
+ /**
96
+ * Fetch the latest published version from the npm registry.
97
+ * Hits the lightweight dist-tags endpoint, validates the response, and returns
98
+ * null on any error/timeout. Nothing from the response is ever executed.
99
+ */
100
+ export function fetchLatestVersion(name, timeoutMs = NETWORK_TIMEOUT_MS) {
101
+ return new Promise((resolve) => {
102
+ let settled = false;
103
+ const done = (v) => {
104
+ if (settled)
105
+ return;
106
+ settled = true;
107
+ resolve(v);
108
+ };
109
+ const req = httpsRequest({
110
+ method: 'GET',
111
+ hostname: 'registry.npmjs.org',
112
+ path: `/-/package/${encodeURIComponent(name)}/dist-tags`,
113
+ headers: { accept: 'application/json' },
114
+ timeout: timeoutMs,
115
+ }, (res) => {
116
+ if (res.statusCode !== 200) {
117
+ res.resume();
118
+ done(null);
119
+ return;
120
+ }
121
+ let body = '';
122
+ let size = 0;
123
+ res.setEncoding('utf8');
124
+ res.on('data', (chunk) => {
125
+ size += chunk.length;
126
+ // Cap the response we'll buffer — defensive against a hostile/huge body.
127
+ if (size > 64 * 1024) {
128
+ req.destroy();
129
+ done(null);
130
+ return;
131
+ }
132
+ body += chunk;
133
+ });
134
+ res.on('end', () => {
135
+ try {
136
+ const json = JSON.parse(body);
137
+ const latest = json.latest;
138
+ done(typeof latest === 'string' && isValidVersion(latest) ? latest : null);
139
+ }
140
+ catch {
141
+ done(null);
142
+ }
143
+ });
144
+ });
145
+ req.on('timeout', () => {
146
+ req.destroy();
147
+ done(null);
148
+ });
149
+ req.on('error', () => done(null));
150
+ req.end();
151
+ });
152
+ }
153
+ /**
154
+ * Run the update check and, when appropriate, print a single notice to stderr.
155
+ * Designed to be awaited right before the process exits so the notice appears
156
+ * AFTER the command's normal output and never interleaves.
157
+ *
158
+ * It resolves to the notice string it printed (or null), which is handy for
159
+ * tests; production callers can ignore the return value.
160
+ */
161
+ export async function maybeNotifyUpdate(opts) {
162
+ const env = opts.env ?? process.env;
163
+ const stderr = opts.stderr ?? process.stderr;
164
+ if (!shouldCheckForUpdate({
165
+ machineReadable: opts.machineReadable,
166
+ disabled: opts.disabled,
167
+ stderr,
168
+ env,
169
+ })) {
170
+ return null;
171
+ }
172
+ // A bad/dev version (e.g. '0.0.0') can't be meaningfully compared.
173
+ if (!isValidVersion(opts.currentVersion))
174
+ return null;
175
+ const now = opts.now ?? Date.now;
176
+ const cachePath = opts.cachePath ?? defaultCachePath(env);
177
+ const fetchLatest = opts.fetchLatest ?? fetchLatestVersion;
178
+ try {
179
+ const cache = await readCache(cachePath);
180
+ let latest = cache?.latestVersion ?? null;
181
+ const fresh = cache !== null && now() - cache.lastCheck < CHECK_INTERVAL_MS;
182
+ if (!fresh) {
183
+ // Cache is stale or absent → check the registry, then persist the answer
184
+ // (and the timestamp) so we don't hit the network again for ~24h.
185
+ latest = await fetchLatest(PACKAGE_NAME, NETWORK_TIMEOUT_MS);
186
+ await writeCache(cachePath, { lastCheck: now(), latestVersion: latest });
187
+ }
188
+ if (!latest || !isValidVersion(latest))
189
+ return null;
190
+ if (compareSemver(latest, opts.currentVersion) <= 0)
191
+ return null;
192
+ const notice = formatNotice(opts.currentVersion, latest);
193
+ stderr.write(`${notice}\n`);
194
+ return notice;
195
+ }
196
+ catch {
197
+ // Belt-and-braces: nothing here may ever surface to the user.
198
+ return null;
199
+ }
200
+ }
201
+ function formatNotice(current, latest) {
202
+ const headline = `${pc.dim('Update available')} ${pc.dim(current)} ${pc.dim('→')} ${pc.green(latest)}`;
203
+ const upgrade = `Run ${pc.cyan('npm i -g oauthlint')} ${pc.dim('(or')} ${pc.cyan('npx oauthlint@latest')}${pc.dim(')')}`;
204
+ const optOut = pc.dim('Disable: --no-update-check or NO_UPDATE_NOTIFIER=1');
205
+ return `\n${headline}\n${upgrade}\n${optOut}`;
206
+ }
207
+ // ---------------------------------------------------------------------------
208
+ // Semver: a tiny, self-contained comparator. Enough for "is latest > current",
209
+ // including basic prerelease ordering, without a dependency.
210
+ // ---------------------------------------------------------------------------
211
+ const VERSION_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
212
+ export function isValidVersion(v) {
213
+ return VERSION_RE.test(v.trim());
214
+ }
215
+ /**
216
+ * Compare two semver strings. Returns >0 if a>b, <0 if a<b, 0 if equal.
217
+ * Honours prerelease precedence (1.0.0-rc < 1.0.0) per the semver spec's
218
+ * common cases. Invalid input sorts as "not greater" so we never nag wrongly.
219
+ */
220
+ export function compareSemver(a, b) {
221
+ const pa = parseVersion(a);
222
+ const pb = parseVersion(b);
223
+ if (!pa || !pb)
224
+ return 0;
225
+ for (let i = 0; i < 3; i++) {
226
+ if (pa.main[i] !== pb.main[i])
227
+ return pa.main[i] - pb.main[i];
228
+ }
229
+ // Equal core. A version WITH a prerelease is lower than one without.
230
+ if (pa.pre.length === 0 && pb.pre.length === 0)
231
+ return 0;
232
+ if (pa.pre.length === 0)
233
+ return 1; // a is a release, b is a prerelease
234
+ if (pb.pre.length === 0)
235
+ return -1; // a is a prerelease, b is a release
236
+ const len = Math.max(pa.pre.length, pb.pre.length);
237
+ for (let i = 0; i < len; i++) {
238
+ const x = pa.pre[i];
239
+ const y = pb.pre[i];
240
+ if (x === undefined)
241
+ return -1; // shorter prerelease set has lower precedence
242
+ if (y === undefined)
243
+ return 1;
244
+ if (x === y)
245
+ continue;
246
+ const xn = /^\d+$/.test(x);
247
+ const yn = /^\d+$/.test(y);
248
+ if (xn && yn)
249
+ return Number(x) - Number(y);
250
+ if (xn)
251
+ return -1; // numeric identifiers are lower than alphanumeric
252
+ if (yn)
253
+ return 1;
254
+ return x < y ? -1 : 1; // lexical ASCII order
255
+ }
256
+ return 0;
257
+ }
258
+ function parseVersion(v) {
259
+ const m = VERSION_RE.exec(v.trim());
260
+ if (!m)
261
+ return null;
262
+ return {
263
+ main: [Number(m[1]), Number(m[2]), Number(m[3])],
264
+ pre: m[4] ? m[4].split('.') : [],
265
+ };
266
+ }
267
+ //# sourceMappingURL=update-notifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-notifier.js","sourceRoot":"","sources":["../../src/core/update-notifier.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { buildProgram } from './cli.js';
2
2
  export { runScan, type ScanCommandOptions } from './commands/scan.js';
3
+ export { runBaseline, type BaselineCommandOptions } from './commands/baseline.js';
4
+ export { BASELINE_VERSION, DEFAULT_BASELINE_FILE, type Baseline, type BaselineEntry, type BaselineFile, BaselineNotFoundError, BaselineParseError, buildBaseline, fingerprintFindings, loadBaseline, partitionByBaseline, serialiseBaseline, } from './core/baseline.js';
3
5
  export { runList, type ListOptions } from './commands/list.js';
4
6
  export { runInit, type InitOptions } from './commands/init.js';
5
7
  export { runDoctor, type DoctorOptions } from './commands/doctor.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,eAAe,EACf,OAAO,EACP,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,eAAe,EACf,OAAO,EACP,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  export { buildProgram } from './cli.js';
2
2
  export { runScan } from './commands/scan.js';
3
+ export { runBaseline } from './commands/baseline.js';
4
+ export { BASELINE_VERSION, DEFAULT_BASELINE_FILE, BaselineNotFoundError, BaselineParseError, buildBaseline, fingerprintFindings, loadBaseline, partitionByBaseline, serialiseBaseline, } from './core/baseline.js';
3
5
  export { runList } from './commands/list.js';
4
6
  export { runInit } from './commands/init.js';
5
7
  export { runDoctor } from './commands/doctor.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,OAAO,EAA2B,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAoB,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAoB,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAsB,MAAM,sBAAsB,CAAC;AACrE,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAwB,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,OAAO,EAA2B,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,WAAW,EAA+B,MAAM,wBAAwB,CAAC;AAClF,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EAIrB,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAoB,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAoB,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAsB,MAAM,sBAAsB,CAAC;AACrE,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAwB,MAAM,oBAAoB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,YAAY,GACb,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oauthlint",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Catch the OAuth/OIDC/JWT anti-patterns AI coding tools systematically produce. CLI wrapper around the oauthlint-rules Semgrep rule pack.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -47,7 +47,7 @@
47
47
  "picocolors": "1.1.1",
48
48
  "yaml": "2.9.0",
49
49
  "zod": "3.24.1",
50
- "oauthlint-rules": "0.2.3"
50
+ "oauthlint-rules": "0.2.4"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "22.10.5",