logshield-cli 0.4.1 → 0.4.3
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/CHANGELOG.md +31 -0
- package/README.md +82 -28
- package/dist/cli/index.cjs +145 -51
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.4.3
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Prevented API key redaction from corrupting header names (`x-api-key`)
|
|
8
|
+
- Preserved key labels when redacting `api_key=...` values
|
|
9
|
+
- Corrected CLI exit code for invalid flag combinations (`--json --dry-run` now exits with code 2)
|
|
10
|
+
|
|
11
|
+
### Improved
|
|
12
|
+
|
|
13
|
+
- Deterministic and aligned `--summary` output (alphabetical, indented)
|
|
14
|
+
- Hardened CLI behavior with end-to-end golden tests
|
|
15
|
+
- Strengthened regression coverage for rule overlap and precedence
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
|
|
19
|
+
- No breaking changes
|
|
20
|
+
- No new features
|
|
21
|
+
- Hardening and correctness release
|
|
22
|
+
|
|
23
|
+
## v0.4.2
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- CLI errors are now written to stderr (CI-safe piping)
|
|
28
|
+
- JSON output is newline-terminated
|
|
29
|
+
- URL redaction is no longer overly aggressive; only credentials and sensitive parameters are redacted
|
|
30
|
+
- PASSWORD redaction preserves original delimiter and spacing
|
|
31
|
+
- Improved dry-run reporting consistency
|
|
32
|
+
- Added contract tests for CLI output and URL behavior
|
|
33
|
+
|
|
3
34
|
## v0.4.1
|
|
4
35
|
|
|
5
36
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,41 +1,90 @@
|
|
|
1
|
-
---
|
|
2
1
|
# LogShield
|
|
3
2
|
|
|
4
3
|
[](https://www.npmjs.com/package/logshield-cli)
|
|
5
4
|
[](https://www.npmjs.com/package/logshield-cli)
|
|
6
5
|
[](https://github.com/afria85/LogShield/actions/workflows/ci.yml)
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Your logs already contain secrets. You just don't see them.
|
|
8
|
+
|
|
9
|
+
LogShield is a small CLI that automatically redacts secrets from logs **before**
|
|
10
|
+
you paste them into CI, GitHub issues, Slack, or send them to third-party support.
|
|
11
|
+
|
|
12
|
+
No configuration. No cloud. Deterministic output.
|
|
13
|
+
|
|
14
|
+
---
|
|
9
15
|
|
|
10
16
|
## Quick start (30 seconds)
|
|
11
17
|
|
|
12
18
|
```bash
|
|
13
|
-
#
|
|
14
|
-
|
|
19
|
+
# Sanitize logs before sharing them
|
|
20
|
+
cat app.log | logshield scan
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Example input**
|
|
24
|
+
|
|
25
|
+
```txt
|
|
26
|
+
POSTGRES_URL=postgres://user:supersecret@db.internal
|
|
27
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
These are typical raw logs -- with secrets -- before you share them.
|
|
31
|
+
|
|
32
|
+
**Output**
|
|
33
|
+
|
|
34
|
+
```txt
|
|
35
|
+
POSTGRES_URL=postgres://user:<REDACTED_PASSWORD>@db.internal
|
|
36
|
+
Authorization: Bearer <REDACTED_TOKEN>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
After LogShield, the same logs are safe to share.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## When should I use LogShield?
|
|
15
44
|
|
|
45
|
+
Use LogShield whenever logs leave your system:
|
|
46
|
+
|
|
47
|
+
- Before pasting logs into CI
|
|
48
|
+
- Before attaching logs to GitHub issues
|
|
49
|
+
- Before sending logs to third-party support
|
|
50
|
+
- Before sharing logs in Slack or email
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Preview before enforcing (dry-run)
|
|
55
|
+
|
|
56
|
+
```bash
|
|
16
57
|
# Preview what would be redacted (does not modify output)
|
|
17
|
-
echo "email=test@example.com
|
|
58
|
+
echo "email=test@example.com Authorization: Bearer abcdefghijklmnop" | logshield scan --dry-run
|
|
18
59
|
```
|
|
19
60
|
|
|
20
61
|
```
|
|
21
62
|
logshield (dry-run)
|
|
22
63
|
Detected 2 redactions:
|
|
23
|
-
|
|
24
|
-
|
|
64
|
+
AUTH_BEARER x1
|
|
65
|
+
EMAIL x1
|
|
25
66
|
|
|
26
67
|
No output was modified.
|
|
27
68
|
Use without --dry-run to apply.
|
|
28
69
|
```
|
|
29
70
|
|
|
71
|
+
Notes:
|
|
72
|
+
|
|
73
|
+
- The report is printed to stdout
|
|
74
|
+
- No log content is echoed
|
|
75
|
+
- Output is deterministic and CI-safe
|
|
76
|
+
|
|
30
77
|
```bash
|
|
31
78
|
# Enforce redaction (sanitized output)
|
|
32
|
-
echo "email=test@example.com
|
|
79
|
+
echo "email=test@example.com Authorization: Bearer abcdefghijklmnop" | logshield scan
|
|
33
80
|
```
|
|
34
81
|
|
|
35
82
|
- Prefer `--dry-run` first in CI to verify you are not over-redacting.
|
|
36
83
|
- Then switch to enforced mode once you are satisfied with the preview.
|
|
37
84
|
|
|
38
|
-
LogShield is a CLI tool that scans logs and redacts **real secrets**
|
|
85
|
+
LogShield is a CLI tool that scans logs and redacts **real secrets**
|
|
86
|
+
(API keys, tokens, credentials) before logs are shared with others,
|
|
87
|
+
AI tools, CI systems, or public channels.
|
|
39
88
|
|
|
40
89
|
It is designed to be **predictable, conservative, and safe for production pipelines**.
|
|
41
90
|
|
|
@@ -97,8 +146,8 @@ Examples:
|
|
|
97
146
|
|
|
98
147
|
```
|
|
99
148
|
<REDACTED_PASSWORD>
|
|
100
|
-
<
|
|
101
|
-
<
|
|
149
|
+
<REDACTED_API_KEY>
|
|
150
|
+
<REDACTED_TOKEN>
|
|
102
151
|
<REDACTED_EMAIL>
|
|
103
152
|
```
|
|
104
153
|
|
|
@@ -148,28 +197,28 @@ If a file is not provided and input is piped, LogShield automatically reads from
|
|
|
148
197
|
|
|
149
198
|
## CLI Flags
|
|
150
199
|
|
|
151
|
-
- `--strict`
|
|
200
|
+
- `--strict`
|
|
152
201
|
Aggressive, security-first redaction
|
|
153
202
|
|
|
154
|
-
- `--stdin`
|
|
203
|
+
- `--stdin`
|
|
155
204
|
Explicitly force reading from STDIN
|
|
156
205
|
|
|
157
|
-
- `--dry-run`
|
|
206
|
+
- `--dry-run`
|
|
158
207
|
Detect sensitive data without modifying output
|
|
159
208
|
|
|
160
|
-
- `--fail-on-detect`
|
|
209
|
+
- `--fail-on-detect`
|
|
161
210
|
Exit with code `1` if any redaction is detected (CI-friendly)
|
|
162
211
|
|
|
163
|
-
- `--summary`
|
|
212
|
+
- `--summary`
|
|
164
213
|
Print a compact redaction summary
|
|
165
214
|
|
|
166
|
-
- `--json`
|
|
215
|
+
- `--json`
|
|
167
216
|
JSON output (cannot be combined with `--dry-run`)
|
|
168
217
|
|
|
169
|
-
- `--version`
|
|
218
|
+
- `--version`
|
|
170
219
|
Print CLI version
|
|
171
220
|
|
|
172
|
-
- `--help`
|
|
221
|
+
- `--help`
|
|
173
222
|
Show help
|
|
174
223
|
|
|
175
224
|
---
|
|
@@ -204,10 +253,10 @@ cat app.log | logshield scan --dry-run
|
|
|
204
253
|
|
|
205
254
|
```
|
|
206
255
|
logshield (dry-run)
|
|
207
|
-
Detected
|
|
208
|
-
|
|
209
|
-
AUTH_BEARER x2
|
|
256
|
+
Detected 4 redactions:
|
|
257
|
+
AUTH_BEARER x1
|
|
210
258
|
EMAIL x1
|
|
259
|
+
OAUTH_ACCESS_TOKEN x1
|
|
211
260
|
PASSWORD x1
|
|
212
261
|
|
|
213
262
|
No output was modified.
|
|
@@ -302,10 +351,16 @@ Example:
|
|
|
302
351
|
|
|
303
352
|
```
|
|
304
353
|
LogShield Summary
|
|
305
|
-
|
|
306
|
-
|
|
354
|
+
API_KEY_HEADER: 1
|
|
355
|
+
PASSWORD: 2
|
|
307
356
|
```
|
|
308
357
|
|
|
358
|
+
Notes:
|
|
359
|
+
|
|
360
|
+
- Sanitized log output is written to stdout
|
|
361
|
+
- The summary is written to stderr
|
|
362
|
+
- Rules are sorted alphabetically
|
|
363
|
+
|
|
309
364
|
---
|
|
310
365
|
|
|
311
366
|
## JSON output
|
|
@@ -319,7 +374,8 @@ logshield scan --json < logs.txt
|
|
|
319
374
|
Notes:
|
|
320
375
|
|
|
321
376
|
- `--json` **cannot** be combined with `--dry-run`
|
|
322
|
-
-
|
|
377
|
+
- Usage errors exit with code `2`
|
|
378
|
+
- Output is always newline-terminated
|
|
323
379
|
|
|
324
380
|
---
|
|
325
381
|
|
|
@@ -370,7 +426,7 @@ Depending on rules and mode:
|
|
|
370
426
|
LogShield guarantees:
|
|
371
427
|
|
|
372
428
|
- Deterministic output
|
|
373
|
-
- Stable behavior within **v0.
|
|
429
|
+
- Stable behavior within **v0.4.x**
|
|
374
430
|
- No runtime dependencies
|
|
375
431
|
- Snapshot-tested and contract-tested
|
|
376
432
|
- No telemetry
|
|
@@ -401,5 +457,3 @@ It is a **last-line safety net**, not a primary defense.
|
|
|
401
457
|
## License
|
|
402
458
|
|
|
403
459
|
Apache-2.0
|
|
404
|
-
|
|
405
|
-
---
|
package/dist/cli/index.cjs
CHANGED
|
@@ -70,7 +70,8 @@ __export(writeOutput_exports, {
|
|
|
70
70
|
});
|
|
71
71
|
function writeOutput(result, opts) {
|
|
72
72
|
if (opts.json) {
|
|
73
|
-
process.stdout.write(JSON.stringify(result)
|
|
73
|
+
process.stdout.write(`${JSON.stringify(result)}
|
|
74
|
+
`);
|
|
74
75
|
} else {
|
|
75
76
|
process.stdout.write(result.output);
|
|
76
77
|
}
|
|
@@ -86,33 +87,25 @@ __export(summary_exports, {
|
|
|
86
87
|
printSummary: () => printSummary
|
|
87
88
|
});
|
|
88
89
|
function printSummary(matches) {
|
|
89
|
-
if (!matches
|
|
90
|
-
process.stderr.write("
|
|
90
|
+
if (!matches.length) {
|
|
91
|
+
process.stderr.write("LogShield Summary\n(no redactions detected)\n");
|
|
91
92
|
return;
|
|
92
93
|
}
|
|
93
|
-
const
|
|
94
|
+
const counts = {};
|
|
94
95
|
for (const m of matches) {
|
|
95
|
-
|
|
96
|
+
counts[m.rule] = (counts[m.rule] ?? 0) + 1;
|
|
96
97
|
}
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const label = total === 1 ? "redaction" : "redactions";
|
|
104
|
-
process.stderr.write(`logshield summary: ${total} ${label}
|
|
98
|
+
const rules = Object.keys(counts).sort((a, b) => a.localeCompare(b));
|
|
99
|
+
const maxNameLen = Math.max(...rules.map((r) => r.length));
|
|
100
|
+
process.stderr.write("LogShield Summary\n");
|
|
101
|
+
for (const rule of rules) {
|
|
102
|
+
const padded = rule.padEnd(maxNameLen, " ");
|
|
103
|
+
process.stderr.write(` ${padded} x${counts[rule]}
|
|
105
104
|
`);
|
|
106
|
-
for (const { rule, count } of entries) {
|
|
107
|
-
process.stderr.write(
|
|
108
|
-
` ${rule.padEnd(maxLen)} x${count}
|
|
109
|
-
`
|
|
110
|
-
);
|
|
111
105
|
}
|
|
112
106
|
}
|
|
113
107
|
var init_summary = __esm({
|
|
114
108
|
"src/cli/summary.ts"() {
|
|
115
|
-
"use strict";
|
|
116
109
|
}
|
|
117
110
|
});
|
|
118
111
|
|
|
@@ -184,8 +177,12 @@ var init_tokens = __esm({
|
|
|
184
177
|
},
|
|
185
178
|
{
|
|
186
179
|
name: "EMAIL",
|
|
187
|
-
|
|
188
|
-
|
|
180
|
+
// Avoid corrupting URLs with embedded credentials like:
|
|
181
|
+
// https://user:pass@host
|
|
182
|
+
// In those cases, `pass@host` can look like an email.
|
|
183
|
+
// We therefore require a safe delimiter (whitespace/quotes/brackets/`=` or `: `) before the email.
|
|
184
|
+
pattern: /(^|[\s"'\(\[\{<>,;]|=|:\s)([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})/gim,
|
|
185
|
+
replace: (_match, _ctx, groups) => `${groups[0]}<REDACTED_EMAIL>`
|
|
189
186
|
}
|
|
190
187
|
];
|
|
191
188
|
}
|
|
@@ -199,8 +196,12 @@ var init_credentials = __esm({
|
|
|
199
196
|
// password=... or password: ...
|
|
200
197
|
{
|
|
201
198
|
name: "PASSWORD",
|
|
202
|
-
|
|
203
|
-
|
|
199
|
+
// Preserve delimiter and spacing so logs remain readable and diff-friendly.
|
|
200
|
+
// Examples:
|
|
201
|
+
// password=secret -> password=<REDACTED_PASSWORD>
|
|
202
|
+
// Password : 123 -> Password : <REDACTED_PASSWORD>
|
|
203
|
+
pattern: /\b(password)(\s*[:=]\s*)([^\s]+)/gi,
|
|
204
|
+
replace: (_match, _ctx, groups) => `${groups[0]}${groups[1]}<REDACTED_PASSWORD>`
|
|
204
205
|
},
|
|
205
206
|
// DB URL credential: postgres://user:pass@host
|
|
206
207
|
{
|
|
@@ -208,6 +209,15 @@ var init_credentials = __esm({
|
|
|
208
209
|
pattern: /\b(postgres|mysql|mongodb):\/\/([^:\s]+):([^@\s]+)@/gi,
|
|
209
210
|
replace: (_match, _ctx, groups) => `${groups[0]}://${groups[1]}:<REDACTED_PASSWORD>@`
|
|
210
211
|
},
|
|
212
|
+
// x-api-key: ....
|
|
213
|
+
// IMPORTANT: this must run BEFORE the generic API_KEY rule. Otherwise the
|
|
214
|
+
// generic API_KEY rule can match the "api-key: <value>" substring first and
|
|
215
|
+
// corrupt the header name (e.g. "x-api-key" -> "x-").
|
|
216
|
+
{
|
|
217
|
+
name: "API_KEY_HEADER",
|
|
218
|
+
pattern: /\bx-api-key\s*:\s*["']?[A-Za-z0-9_\-]{16,}["']?\b/gi,
|
|
219
|
+
replace: () => "x-api-key: <REDACTED_API_KEY>"
|
|
220
|
+
},
|
|
211
221
|
/**
|
|
212
222
|
* API key (common variants):
|
|
213
223
|
* - apiKey=...
|
|
@@ -215,23 +225,19 @@ var init_credentials = __esm({
|
|
|
215
225
|
* - api-key: ...
|
|
216
226
|
* - apikey=...
|
|
217
227
|
* Supports '=' or ':' and optional quotes/spaces.
|
|
228
|
+
*
|
|
229
|
+
* NOTE:
|
|
230
|
+
* Do NOT try to handle "Authorization: Bearer ..." here; that causes overlap
|
|
231
|
+
* with token rules. Token redaction is handled in tokens.ts.
|
|
218
232
|
*/
|
|
219
233
|
{
|
|
220
234
|
name: "API_KEY",
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
pattern: /\bx-api-key\s*:\s*["']?[A-Za-z0-9_\-]{16,}["']?\b/gi,
|
|
228
|
-
replace: () => "x-api-key: <REDACTED_API_KEY>"
|
|
229
|
-
},
|
|
230
|
-
// authorization: Bearer ...
|
|
231
|
-
{
|
|
232
|
-
name: "AUTHORIZATION_BEARER",
|
|
233
|
-
pattern: /\bauthorization\s*:\s*bearer\s+([A-Za-z0-9._\-]{16,})\b/gi,
|
|
234
|
-
replace: () => "authorization: Bearer <REDACTED_TOKEN>"
|
|
235
|
+
// Preserve the key label + delimiter, redact only the value.
|
|
236
|
+
// Examples:
|
|
237
|
+
// api_key=abcdef... -> api_key=<REDACTED_API_KEY>
|
|
238
|
+
// api-key: "abcdef..." -> api-key: "<REDACTED_API_KEY>"
|
|
239
|
+
pattern: /\b(api(?:[_-]?key)\s*[:=]\s*["']?)([A-Za-z0-9_\-]{16,})(["']?)\b/gi,
|
|
240
|
+
replace: (_match, _ctx, groups) => `${groups[0]}<REDACTED_API_KEY>${groups[2]}`
|
|
235
241
|
}
|
|
236
242
|
];
|
|
237
243
|
}
|
|
@@ -316,14 +322,100 @@ var init_creditCard = __esm({
|
|
|
316
322
|
});
|
|
317
323
|
|
|
318
324
|
// src/rules/urls.ts
|
|
319
|
-
|
|
325
|
+
function redactQueryLike(segment) {
|
|
326
|
+
if (segment.length < 2) return segment;
|
|
327
|
+
const prefix = segment[0];
|
|
328
|
+
const raw = segment.slice(1);
|
|
329
|
+
if (!raw.includes("=")) return segment;
|
|
330
|
+
const parts = raw.split("&");
|
|
331
|
+
const redacted = parts.map((p) => {
|
|
332
|
+
const eq = p.indexOf("=");
|
|
333
|
+
if (eq === -1) return p;
|
|
334
|
+
const key = p.slice(0, eq);
|
|
335
|
+
const value = p.slice(eq + 1);
|
|
336
|
+
const normalized = key.trim().toLowerCase();
|
|
337
|
+
if (!SENSITIVE_PARAM_KEYS.has(normalized)) return p;
|
|
338
|
+
if (value.length === 0) return `${key}=`;
|
|
339
|
+
return `${key}=<REDACTED_URL_PARAM>`;
|
|
340
|
+
});
|
|
341
|
+
return `${prefix}${redacted.join("&")}`;
|
|
342
|
+
}
|
|
343
|
+
function redactUrl(match) {
|
|
344
|
+
const schemeIdx = match.indexOf("://");
|
|
345
|
+
if (schemeIdx === -1) return match;
|
|
346
|
+
const scheme = match.slice(0, schemeIdx + 3);
|
|
347
|
+
const rest = match.slice(schemeIdx + 3);
|
|
348
|
+
const authorityEnd = (() => {
|
|
349
|
+
const slash = rest.indexOf("/");
|
|
350
|
+
const q = rest.indexOf("?");
|
|
351
|
+
const h = rest.indexOf("#");
|
|
352
|
+
const candidates = [slash, q, h].filter((i) => i !== -1);
|
|
353
|
+
return candidates.length === 0 ? rest.length : Math.min(...candidates);
|
|
354
|
+
})();
|
|
355
|
+
let authority = rest.slice(0, authorityEnd);
|
|
356
|
+
let tail = rest.slice(authorityEnd);
|
|
357
|
+
const at = authority.lastIndexOf("@");
|
|
358
|
+
if (at !== -1) {
|
|
359
|
+
const userinfo = authority.slice(0, at);
|
|
360
|
+
const host = authority.slice(at + 1);
|
|
361
|
+
const colon = userinfo.indexOf(":");
|
|
362
|
+
if (colon !== -1) {
|
|
363
|
+
const user = userinfo.slice(0, colon);
|
|
364
|
+
authority = `${user}:<REDACTED_PASSWORD>@${host}`;
|
|
365
|
+
} else {
|
|
366
|
+
authority = `<REDACTED_PASSWORD>@${host}`;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const hashIdx = tail.indexOf("#");
|
|
370
|
+
const queryIdx = tail.indexOf("?");
|
|
371
|
+
if (queryIdx !== -1 && (hashIdx === -1 || queryIdx < hashIdx)) {
|
|
372
|
+
const before = tail.slice(0, queryIdx);
|
|
373
|
+
const after = tail.slice(queryIdx);
|
|
374
|
+
const hashInside = after.indexOf("#");
|
|
375
|
+
if (hashInside === -1) {
|
|
376
|
+
tail = `${before}${redactQueryLike(after)}`;
|
|
377
|
+
} else {
|
|
378
|
+
const qPart = after.slice(0, hashInside);
|
|
379
|
+
const hPart = after.slice(hashInside);
|
|
380
|
+
tail = `${before}${redactQueryLike(qPart)}${redactQueryLike(hPart)}`;
|
|
381
|
+
}
|
|
382
|
+
} else if (hashIdx !== -1) {
|
|
383
|
+
const before = tail.slice(0, hashIdx);
|
|
384
|
+
const hPart = tail.slice(hashIdx);
|
|
385
|
+
tail = `${before}${redactQueryLike(hPart)}`;
|
|
386
|
+
}
|
|
387
|
+
return `${scheme}${authority}${tail}`;
|
|
388
|
+
}
|
|
389
|
+
var SENSITIVE_PARAM_KEYS, urlRules;
|
|
320
390
|
var init_urls = __esm({
|
|
321
391
|
"src/rules/urls.ts"() {
|
|
392
|
+
SENSITIVE_PARAM_KEYS = new Set(
|
|
393
|
+
[
|
|
394
|
+
"access_token",
|
|
395
|
+
"token",
|
|
396
|
+
"id_token",
|
|
397
|
+
"refresh_token",
|
|
398
|
+
"auth",
|
|
399
|
+
"authorization",
|
|
400
|
+
"api_key",
|
|
401
|
+
"apikey",
|
|
402
|
+
"api-key",
|
|
403
|
+
"key",
|
|
404
|
+
"secret",
|
|
405
|
+
"password",
|
|
406
|
+
"passwd",
|
|
407
|
+
"signature",
|
|
408
|
+
"sig",
|
|
409
|
+
"session"
|
|
410
|
+
].map((k) => k.toLowerCase())
|
|
411
|
+
);
|
|
322
412
|
urlRules = [
|
|
323
413
|
{
|
|
324
414
|
name: "URL",
|
|
325
|
-
|
|
326
|
-
|
|
415
|
+
// Match HTTP(S) URLs, stopping at whitespace.
|
|
416
|
+
// (Conservative: avoids attempting to be a full RFC URL parser.)
|
|
417
|
+
pattern: /\bhttps?:\/\/[^\s]+/gi,
|
|
418
|
+
replace: (match) => redactUrl(match)
|
|
327
419
|
}
|
|
328
420
|
];
|
|
329
421
|
}
|
|
@@ -417,7 +509,7 @@ var { printSummary: printSummary2 } = (init_summary(), __toCommonJS(summary_expo
|
|
|
417
509
|
var { sanitizeLog: sanitizeLog2 } = (init_sanitizeLog(), __toCommonJS(sanitizeLog_exports));
|
|
418
510
|
var rawArgs = process.argv.slice(2).map((arg) => arg === "-h" ? "--help" : arg);
|
|
419
511
|
function getVersion() {
|
|
420
|
-
return true ? "0.4.
|
|
512
|
+
return true ? "0.4.3" : "unknown";
|
|
421
513
|
}
|
|
422
514
|
function printHelp() {
|
|
423
515
|
process.stdout.write(`Usage: logshield scan [file]
|
|
@@ -438,6 +530,9 @@ Options:
|
|
|
438
530
|
--help Show help
|
|
439
531
|
`);
|
|
440
532
|
}
|
|
533
|
+
function writeErr(message) {
|
|
534
|
+
process.stderr.write(message);
|
|
535
|
+
}
|
|
441
536
|
function parseArgs(args) {
|
|
442
537
|
const flags = /* @__PURE__ */ new Set();
|
|
443
538
|
const positionals = [];
|
|
@@ -482,6 +577,10 @@ function renderDryRunReport(matches) {
|
|
|
482
577
|
process.stdout.write("No output was modified.\n");
|
|
483
578
|
process.stdout.write("Use without --dry-run to apply.\n");
|
|
484
579
|
}
|
|
580
|
+
function exitUsageError(message) {
|
|
581
|
+
writeErr(message.endsWith("\n") ? message : message + "\n");
|
|
582
|
+
process.exit(2);
|
|
583
|
+
}
|
|
485
584
|
async function main() {
|
|
486
585
|
if (rawArgs.length === 0 || rawArgs.includes("--help")) {
|
|
487
586
|
printHelp();
|
|
@@ -495,8 +594,7 @@ async function main() {
|
|
|
495
594
|
const { flags, positionals } = parseArgs(rawArgs);
|
|
496
595
|
const command = positionals[0];
|
|
497
596
|
if (command !== "scan") {
|
|
498
|
-
|
|
499
|
-
process.exit(1);
|
|
597
|
+
exitUsageError("Unknown command");
|
|
500
598
|
}
|
|
501
599
|
const file = positionals[1];
|
|
502
600
|
const strict = flags.has("--strict");
|
|
@@ -508,16 +606,13 @@ async function main() {
|
|
|
508
606
|
const stdinAuto = isStdinPiped();
|
|
509
607
|
const useStdin = stdinFlag || stdinAuto;
|
|
510
608
|
if (useStdin && file) {
|
|
511
|
-
|
|
512
|
-
process.exit(1);
|
|
609
|
+
exitUsageError("Cannot read from both STDIN and file");
|
|
513
610
|
}
|
|
514
611
|
if (dryRun && json) {
|
|
515
|
-
|
|
516
|
-
process.exit(1);
|
|
612
|
+
exitUsageError("--dry-run cannot be used with --json");
|
|
517
613
|
}
|
|
518
614
|
if (json && summary) {
|
|
519
|
-
|
|
520
|
-
process.exit(1);
|
|
615
|
+
exitUsageError("--summary cannot be used with --json");
|
|
521
616
|
}
|
|
522
617
|
try {
|
|
523
618
|
const input = await readInput2(useStdin ? void 0 : file);
|
|
@@ -538,8 +633,7 @@ async function main() {
|
|
|
538
633
|
}
|
|
539
634
|
process.exit(0);
|
|
540
635
|
} catch (err) {
|
|
541
|
-
|
|
542
|
-
process.stdout.write("\n");
|
|
636
|
+
writeErr((err?.message || "Unexpected error") + "\n");
|
|
543
637
|
process.exit(2);
|
|
544
638
|
}
|
|
545
639
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logshield-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"typecheck": "tsc -p tsconfig.core.json && tsc -p tsconfig.cli.json --noEmit",
|
|
21
21
|
"pretest": "npm run build",
|
|
22
22
|
"test": "vitest",
|
|
23
|
-
"
|
|
23
|
+
"prepublish:check": "npm run typecheck && npm test",
|
|
24
|
+
"prepublishOnly": "npm run prepublish:check"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@types/node": "^25.0.3",
|