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 +80 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +63 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/baseline.d.ts +21 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +50 -0
- package/dist/commands/baseline.js.map +1 -0
- package/dist/commands/scan.d.ts +7 -0
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +28 -0
- package/dist/commands/scan.js.map +1 -1
- package/dist/core/baseline.d.ts +102 -0
- package/dist/core/baseline.d.ts.map +1 -0
- package/dist/core/baseline.js +223 -0
- package/dist/core/baseline.js.map +1 -0
- package/dist/core/update-notifier.d.ts +61 -0
- package/dist/core/update-notifier.d.ts.map +1 -0
- package/dist/core/update-notifier.js +267 -0
- package/dist/core/update-notifier.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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.**
|
|
67
|
-
mappings. It's a lesson,
|
|
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
|
-
|
|
186
|
-
|
|
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,
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"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,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,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,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,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -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":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAiBxD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAA4B;IAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC,cAAc,IAAI,UAAU,CAAC;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAEhD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IAEtD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,cAAc,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC7E,IAAI,MAAmD,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,wBAAwB,EAAE,CAAC;YAC5C,SAAS,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YACpC,OAAO,GAAG,CAAC;QACb,CAAC;QACD,IAAI,GAAG,YAAY,kBAAkB,EAAE,CAAC;YACtC,SAAS,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YACpC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,2EAA2E;IAC3E,8EAA8E;IAC9E,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAc,IAAI,CAAC;IAEjC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,IAAI,qBAAqB,CAAC,CAAC;IACtE,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,SAAS,CAAC,UAAU,EAAE,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;IAEjE,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;IACnC,MAAM,CAAC,KAAK,CACV,EAAE,CAAC,KAAK,CACN,eAAe,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,MAAM,IAAI,qBAAqB,IAAI,CAC5F,CACF,CAAC;IACF,MAAM,CAAC,KAAK,CACV,EAAE,CAAC,GAAG,CACJ,kBAAkB,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,mCAAmC,CAC1F,CACF,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC"}
|
package/dist/commands/scan.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/commands/scan.js
CHANGED
|
@@ -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":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;
|
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
+
"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.
|
|
50
|
+
"oauthlint-rules": "0.2.4"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "22.10.5",
|