logshield-cli 0.4.0 → 0.4.2
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 +29 -2
- package/README.md +60 -19
- package/dist/cli/index.cjs +149 -26
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.4.2
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- CLI errors are now written to stderr (CI-safe piping)
|
|
8
|
+
- JSON output is newline-terminated
|
|
9
|
+
- URL redaction is no longer overly aggressive; only credentials and sensitive parameters are redacted
|
|
10
|
+
- PASSWORD redaction preserves original delimiter and spacing
|
|
11
|
+
- Improved dry-run reporting consistency
|
|
12
|
+
- Added contract tests for CLI output and URL behavior
|
|
13
|
+
|
|
14
|
+
## v0.4.1
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Prevented secret leakage in `--json` output by removing raw match values from the public result shape
|
|
19
|
+
- Forwarded `--dry-run` into the engine to ensure consistent, future-proof behavior
|
|
20
|
+
|
|
21
|
+
### Improved
|
|
22
|
+
|
|
23
|
+
- Expanded credential detection for common API key variants (`api_key`, `api-key`, `apikey`) and `Authorization: Bearer ...`
|
|
24
|
+
- Hardened AWS secret key strict detection to reduce false positives while keeping strict mode safe
|
|
25
|
+
|
|
26
|
+
### Notes
|
|
27
|
+
|
|
28
|
+
- No breaking changes
|
|
29
|
+
- No new features
|
|
30
|
+
- Stability and safety hardening release
|
|
31
|
+
|
|
3
32
|
## v0.4.0
|
|
4
33
|
|
|
5
34
|
### Changed
|
|
@@ -21,8 +50,6 @@
|
|
|
21
50
|
### Improved
|
|
22
51
|
|
|
23
52
|
- CLI documentation clarity
|
|
24
|
-
- Blog and docs structure consistency
|
|
25
|
-
- Shared `styles.css` and `main.js` across site pages
|
|
26
53
|
|
|
27
54
|
### Notes
|
|
28
55
|
|
package/README.md
CHANGED
|
@@ -1,18 +1,59 @@
|
|
|
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
|
+
```
|
|
15
38
|
|
|
39
|
+
After LogShield, the same logs are safe to share.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## When should I use LogShield?
|
|
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
58
|
echo "email=test@example.com token=sk_live_123" | logshield scan --dry-run
|
|
18
59
|
```
|
|
@@ -35,7 +76,9 @@ echo "email=test@example.com token=sk_live_123" | logshield scan
|
|
|
35
76
|
- Prefer `--dry-run` first in CI to verify you are not over-redacting.
|
|
36
77
|
- Then switch to enforced mode once you are satisfied with the preview.
|
|
37
78
|
|
|
38
|
-
LogShield is a CLI tool that scans logs and redacts **real secrets**
|
|
79
|
+
LogShield is a CLI tool that scans logs and redacts **real secrets**
|
|
80
|
+
(API keys, tokens, credentials) before logs are shared with others,
|
|
81
|
+
AI tools, CI systems, or public channels.
|
|
39
82
|
|
|
40
83
|
It is designed to be **predictable, conservative, and safe for production pipelines**.
|
|
41
84
|
|
|
@@ -148,28 +191,28 @@ If a file is not provided and input is piped, LogShield automatically reads from
|
|
|
148
191
|
|
|
149
192
|
## CLI Flags
|
|
150
193
|
|
|
151
|
-
- `--strict`
|
|
194
|
+
- `--strict`
|
|
152
195
|
Aggressive, security-first redaction
|
|
153
196
|
|
|
154
|
-
- `--stdin`
|
|
197
|
+
- `--stdin`
|
|
155
198
|
Explicitly force reading from STDIN
|
|
156
199
|
|
|
157
|
-
- `--dry-run`
|
|
200
|
+
- `--dry-run`
|
|
158
201
|
Detect sensitive data without modifying output
|
|
159
202
|
|
|
160
|
-
- `--fail-on-detect`
|
|
203
|
+
- `--fail-on-detect`
|
|
161
204
|
Exit with code `1` if any redaction is detected (CI-friendly)
|
|
162
205
|
|
|
163
|
-
- `--summary`
|
|
206
|
+
- `--summary`
|
|
164
207
|
Print a compact redaction summary
|
|
165
208
|
|
|
166
|
-
- `--json`
|
|
209
|
+
- `--json`
|
|
167
210
|
JSON output (cannot be combined with `--dry-run`)
|
|
168
211
|
|
|
169
|
-
- `--version`
|
|
212
|
+
- `--version`
|
|
170
213
|
Print CLI version
|
|
171
214
|
|
|
172
|
-
- `--help`
|
|
215
|
+
- `--help`
|
|
173
216
|
Show help
|
|
174
217
|
|
|
175
218
|
---
|
|
@@ -204,10 +247,10 @@ cat app.log | logshield scan --dry-run
|
|
|
204
247
|
|
|
205
248
|
```
|
|
206
249
|
logshield (dry-run)
|
|
207
|
-
Detected
|
|
208
|
-
OAUTH_ACCESS_TOKEN x1
|
|
250
|
+
Detected 5 redactions:
|
|
209
251
|
AUTH_BEARER x2
|
|
210
252
|
EMAIL x1
|
|
253
|
+
OAUTH_ACCESS_TOKEN x1
|
|
211
254
|
PASSWORD x1
|
|
212
255
|
|
|
213
256
|
No output was modified.
|
|
@@ -319,7 +362,7 @@ logshield scan --json < logs.txt
|
|
|
319
362
|
Notes:
|
|
320
363
|
|
|
321
364
|
- `--json` **cannot** be combined with `--dry-run`
|
|
322
|
-
- Output schema is stable within v0.
|
|
365
|
+
- Output schema is stable within v0.4.x
|
|
323
366
|
|
|
324
367
|
---
|
|
325
368
|
|
|
@@ -370,7 +413,7 @@ Depending on rules and mode:
|
|
|
370
413
|
LogShield guarantees:
|
|
371
414
|
|
|
372
415
|
- Deterministic output
|
|
373
|
-
- Stable behavior within **v0.
|
|
416
|
+
- Stable behavior within **v0.4.x**
|
|
374
417
|
- No runtime dependencies
|
|
375
418
|
- Snapshot-tested and contract-tested
|
|
376
419
|
- No telemetry
|
|
@@ -401,5 +444,3 @@ It is a **last-line safety net**, not a primary defense.
|
|
|
401
444
|
## License
|
|
402
445
|
|
|
403
446
|
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
|
}
|
|
@@ -126,8 +127,7 @@ function applyRules(input, rules, ctx, matches) {
|
|
|
126
127
|
const replaced = rule.replace(match, ctx, groups);
|
|
127
128
|
if (replaced !== match) {
|
|
128
129
|
matches.push({
|
|
129
|
-
rule: rule.name
|
|
130
|
-
value: match
|
|
130
|
+
rule: rule.name
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
if (ctx.dryRun) {
|
|
@@ -185,8 +185,12 @@ var init_tokens = __esm({
|
|
|
185
185
|
},
|
|
186
186
|
{
|
|
187
187
|
name: "EMAIL",
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
// Avoid corrupting URLs with embedded credentials like:
|
|
189
|
+
// https://user:pass@host
|
|
190
|
+
// In those cases, `pass@host` can look like an email.
|
|
191
|
+
// We therefore require a safe delimiter (whitespace/quotes/brackets/`=` or `: `) before the email.
|
|
192
|
+
pattern: /(^|[\s"'\(\[\{<>,;]|=|:\s)([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})/gim,
|
|
193
|
+
replace: (_match, _ctx, groups) => `${groups[0]}<REDACTED_EMAIL>`
|
|
190
194
|
}
|
|
191
195
|
];
|
|
192
196
|
}
|
|
@@ -197,10 +201,15 @@ var credentialRules;
|
|
|
197
201
|
var init_credentials = __esm({
|
|
198
202
|
"src/rules/credentials.ts"() {
|
|
199
203
|
credentialRules = [
|
|
204
|
+
// password=... or password: ...
|
|
200
205
|
{
|
|
201
206
|
name: "PASSWORD",
|
|
202
|
-
|
|
203
|
-
|
|
207
|
+
// Preserve delimiter and spacing so logs remain readable and diff-friendly.
|
|
208
|
+
// Examples:
|
|
209
|
+
// password=secret -> password=<REDACTED_PASSWORD>
|
|
210
|
+
// Password : 123 -> Password : <REDACTED_PASSWORD>
|
|
211
|
+
pattern: /\b(password)(\s*[:=]\s*)([^\s]+)/gi,
|
|
212
|
+
replace: (_match, _ctx, groups) => `${groups[0]}${groups[1]}<REDACTED_PASSWORD>`
|
|
204
213
|
},
|
|
205
214
|
// DB URL credential: postgres://user:pass@host
|
|
206
215
|
{
|
|
@@ -208,17 +217,30 @@ var init_credentials = __esm({
|
|
|
208
217
|
pattern: /\b(postgres|mysql|mongodb):\/\/([^:\s]+):([^@\s]+)@/gi,
|
|
209
218
|
replace: (_match, _ctx, groups) => `${groups[0]}://${groups[1]}:<REDACTED_PASSWORD>@`
|
|
210
219
|
},
|
|
211
|
-
|
|
220
|
+
/**
|
|
221
|
+
* API key (common variants):
|
|
222
|
+
* - apiKey=...
|
|
223
|
+
* - api_key=...
|
|
224
|
+
* - api-key: ...
|
|
225
|
+
* - apikey=...
|
|
226
|
+
* Supports '=' or ':' and optional quotes/spaces.
|
|
227
|
+
*/
|
|
212
228
|
{
|
|
213
229
|
name: "API_KEY",
|
|
214
|
-
pattern: /\
|
|
230
|
+
pattern: /\bapi(?:[_-]?key)\s*[:=]\s*["']?([A-Za-z0-9_\-]{16,})["']?\b/gi,
|
|
215
231
|
replace: () => "<REDACTED_API_KEY>"
|
|
216
232
|
},
|
|
217
233
|
// x-api-key: ....
|
|
218
234
|
{
|
|
219
235
|
name: "API_KEY_HEADER",
|
|
220
|
-
pattern: /\bx-api-key
|
|
236
|
+
pattern: /\bx-api-key\s*:\s*["']?[A-Za-z0-9_\-]{16,}["']?\b/gi,
|
|
221
237
|
replace: () => "x-api-key: <REDACTED_API_KEY>"
|
|
238
|
+
},
|
|
239
|
+
// authorization: Bearer ...
|
|
240
|
+
{
|
|
241
|
+
name: "AUTHORIZATION_BEARER",
|
|
242
|
+
pattern: /\bauthorization\s*:\s*bearer\s+([A-Za-z0-9._\-]{16,})\b/gi,
|
|
243
|
+
replace: () => "authorization: Bearer <REDACTED_TOKEN>"
|
|
222
244
|
}
|
|
223
245
|
];
|
|
224
246
|
}
|
|
@@ -234,9 +256,24 @@ var init_cloud = __esm({
|
|
|
234
256
|
pattern: /\bAKIA[0-9A-Z]{16,20}\b/g,
|
|
235
257
|
replace: (match, { strict }) => strict ? "<REDACTED_AWS_KEY>" : match
|
|
236
258
|
},
|
|
259
|
+
/**
|
|
260
|
+
* Prefer contextual AWS secret key detection first:
|
|
261
|
+
* - AWS_SECRET_ACCESS_KEY=...
|
|
262
|
+
* - aws_secret_access_key: ...
|
|
263
|
+
* - secretAccessKey=...
|
|
264
|
+
*/
|
|
265
|
+
{
|
|
266
|
+
name: "AWS_SECRET_ACCESS_KEY",
|
|
267
|
+
pattern: /\b(?:AWS_SECRET_ACCESS_KEY|aws_secret_access_key|secretAccessKey|awsSecretAccessKey)\s*[:=]\s*["']?([A-Za-z0-9\/+=]{40})["']?\b/g,
|
|
268
|
+
replace: (_match, { strict }, groups) => strict ? "<REDACTED_AWS_SECRET>" : _match.replace(groups[0], "<REDACTED_AWS_SECRET>")
|
|
269
|
+
},
|
|
270
|
+
/**
|
|
271
|
+
* Strict-only broad fallback, but require at least one of / + = inside the 40 chars
|
|
272
|
+
* to reduce false positives on purely alphanumeric 40-char strings.
|
|
273
|
+
*/
|
|
237
274
|
{
|
|
238
275
|
name: "AWS_SECRET_KEY",
|
|
239
|
-
pattern: /\b[A-Za-z0-9\/+=]{40}\b/g,
|
|
276
|
+
pattern: /\b(?=[A-Za-z0-9\/+=]{40}\b)(?=[A-Za-z0-9\/+=]*[\/+=])[A-Za-z0-9\/+=]{40}\b/g,
|
|
240
277
|
replace: (match, { strict }) => strict ? "<REDACTED_AWS_SECRET>" : match
|
|
241
278
|
},
|
|
242
279
|
{
|
|
@@ -288,14 +325,100 @@ var init_creditCard = __esm({
|
|
|
288
325
|
});
|
|
289
326
|
|
|
290
327
|
// src/rules/urls.ts
|
|
291
|
-
|
|
328
|
+
function redactQueryLike(segment) {
|
|
329
|
+
if (segment.length < 2) return segment;
|
|
330
|
+
const prefix = segment[0];
|
|
331
|
+
const raw = segment.slice(1);
|
|
332
|
+
if (!raw.includes("=")) return segment;
|
|
333
|
+
const parts = raw.split("&");
|
|
334
|
+
const redacted = parts.map((p) => {
|
|
335
|
+
const eq = p.indexOf("=");
|
|
336
|
+
if (eq === -1) return p;
|
|
337
|
+
const key = p.slice(0, eq);
|
|
338
|
+
const value = p.slice(eq + 1);
|
|
339
|
+
const normalized = key.trim().toLowerCase();
|
|
340
|
+
if (!SENSITIVE_PARAM_KEYS.has(normalized)) return p;
|
|
341
|
+
if (value.length === 0) return `${key}=`;
|
|
342
|
+
return `${key}=<REDACTED_URL_PARAM>`;
|
|
343
|
+
});
|
|
344
|
+
return `${prefix}${redacted.join("&")}`;
|
|
345
|
+
}
|
|
346
|
+
function redactUrl(match) {
|
|
347
|
+
const schemeIdx = match.indexOf("://");
|
|
348
|
+
if (schemeIdx === -1) return match;
|
|
349
|
+
const scheme = match.slice(0, schemeIdx + 3);
|
|
350
|
+
const rest = match.slice(schemeIdx + 3);
|
|
351
|
+
const authorityEnd = (() => {
|
|
352
|
+
const slash = rest.indexOf("/");
|
|
353
|
+
const q = rest.indexOf("?");
|
|
354
|
+
const h = rest.indexOf("#");
|
|
355
|
+
const candidates = [slash, q, h].filter((i) => i !== -1);
|
|
356
|
+
return candidates.length === 0 ? rest.length : Math.min(...candidates);
|
|
357
|
+
})();
|
|
358
|
+
let authority = rest.slice(0, authorityEnd);
|
|
359
|
+
let tail = rest.slice(authorityEnd);
|
|
360
|
+
const at = authority.lastIndexOf("@");
|
|
361
|
+
if (at !== -1) {
|
|
362
|
+
const userinfo = authority.slice(0, at);
|
|
363
|
+
const host = authority.slice(at + 1);
|
|
364
|
+
const colon = userinfo.indexOf(":");
|
|
365
|
+
if (colon !== -1) {
|
|
366
|
+
const user = userinfo.slice(0, colon);
|
|
367
|
+
authority = `${user}:<REDACTED_PASSWORD>@${host}`;
|
|
368
|
+
} else {
|
|
369
|
+
authority = `<REDACTED_PASSWORD>@${host}`;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const hashIdx = tail.indexOf("#");
|
|
373
|
+
const queryIdx = tail.indexOf("?");
|
|
374
|
+
if (queryIdx !== -1 && (hashIdx === -1 || queryIdx < hashIdx)) {
|
|
375
|
+
const before = tail.slice(0, queryIdx);
|
|
376
|
+
const after = tail.slice(queryIdx);
|
|
377
|
+
const hashInside = after.indexOf("#");
|
|
378
|
+
if (hashInside === -1) {
|
|
379
|
+
tail = `${before}${redactQueryLike(after)}`;
|
|
380
|
+
} else {
|
|
381
|
+
const qPart = after.slice(0, hashInside);
|
|
382
|
+
const hPart = after.slice(hashInside);
|
|
383
|
+
tail = `${before}${redactQueryLike(qPart)}${redactQueryLike(hPart)}`;
|
|
384
|
+
}
|
|
385
|
+
} else if (hashIdx !== -1) {
|
|
386
|
+
const before = tail.slice(0, hashIdx);
|
|
387
|
+
const hPart = tail.slice(hashIdx);
|
|
388
|
+
tail = `${before}${redactQueryLike(hPart)}`;
|
|
389
|
+
}
|
|
390
|
+
return `${scheme}${authority}${tail}`;
|
|
391
|
+
}
|
|
392
|
+
var SENSITIVE_PARAM_KEYS, urlRules;
|
|
292
393
|
var init_urls = __esm({
|
|
293
394
|
"src/rules/urls.ts"() {
|
|
395
|
+
SENSITIVE_PARAM_KEYS = new Set(
|
|
396
|
+
[
|
|
397
|
+
"access_token",
|
|
398
|
+
"token",
|
|
399
|
+
"id_token",
|
|
400
|
+
"refresh_token",
|
|
401
|
+
"auth",
|
|
402
|
+
"authorization",
|
|
403
|
+
"api_key",
|
|
404
|
+
"apikey",
|
|
405
|
+
"api-key",
|
|
406
|
+
"key",
|
|
407
|
+
"secret",
|
|
408
|
+
"password",
|
|
409
|
+
"passwd",
|
|
410
|
+
"signature",
|
|
411
|
+
"sig",
|
|
412
|
+
"session"
|
|
413
|
+
].map((k) => k.toLowerCase())
|
|
414
|
+
);
|
|
294
415
|
urlRules = [
|
|
295
416
|
{
|
|
296
417
|
name: "URL",
|
|
297
|
-
|
|
298
|
-
|
|
418
|
+
// Match HTTP(S) URLs, stopping at whitespace.
|
|
419
|
+
// (Conservative: avoids attempting to be a full RFC URL parser.)
|
|
420
|
+
pattern: /\bhttps?:\/\/[^\s]+/gi,
|
|
421
|
+
replace: (match) => redactUrl(match)
|
|
299
422
|
}
|
|
300
423
|
];
|
|
301
424
|
}
|
|
@@ -389,7 +512,7 @@ var { printSummary: printSummary2 } = (init_summary(), __toCommonJS(summary_expo
|
|
|
389
512
|
var { sanitizeLog: sanitizeLog2 } = (init_sanitizeLog(), __toCommonJS(sanitizeLog_exports));
|
|
390
513
|
var rawArgs = process.argv.slice(2).map((arg) => arg === "-h" ? "--help" : arg);
|
|
391
514
|
function getVersion() {
|
|
392
|
-
return true ? "0.4.
|
|
515
|
+
return true ? "0.4.2" : "unknown";
|
|
393
516
|
}
|
|
394
517
|
function printHelp() {
|
|
395
518
|
process.stdout.write(`Usage: logshield scan [file]
|
|
@@ -410,6 +533,9 @@ Options:
|
|
|
410
533
|
--help Show help
|
|
411
534
|
`);
|
|
412
535
|
}
|
|
536
|
+
function writeErr(message) {
|
|
537
|
+
process.stderr.write(message);
|
|
538
|
+
}
|
|
413
539
|
function parseArgs(args) {
|
|
414
540
|
const flags = /* @__PURE__ */ new Set();
|
|
415
541
|
const positionals = [];
|
|
@@ -447,10 +573,8 @@ function renderDryRunReport(matches) {
|
|
|
447
573
|
process.stdout.write(`Detected ${total} ${label}:
|
|
448
574
|
`);
|
|
449
575
|
for (const { rule, count } of entries) {
|
|
450
|
-
process.stdout.write(
|
|
451
|
-
|
|
452
|
-
`
|
|
453
|
-
);
|
|
576
|
+
process.stdout.write(` ${rule.padEnd(maxLen)} x${count}
|
|
577
|
+
`);
|
|
454
578
|
}
|
|
455
579
|
process.stdout.write("\n");
|
|
456
580
|
process.stdout.write("No output was modified.\n");
|
|
@@ -469,7 +593,7 @@ async function main() {
|
|
|
469
593
|
const { flags, positionals } = parseArgs(rawArgs);
|
|
470
594
|
const command = positionals[0];
|
|
471
595
|
if (command !== "scan") {
|
|
472
|
-
|
|
596
|
+
writeErr("Unknown command\n");
|
|
473
597
|
process.exit(1);
|
|
474
598
|
}
|
|
475
599
|
const file = positionals[1];
|
|
@@ -482,20 +606,20 @@ async function main() {
|
|
|
482
606
|
const stdinAuto = isStdinPiped();
|
|
483
607
|
const useStdin = stdinFlag || stdinAuto;
|
|
484
608
|
if (useStdin && file) {
|
|
485
|
-
|
|
609
|
+
writeErr("Cannot read from both STDIN and file\n");
|
|
486
610
|
process.exit(1);
|
|
487
611
|
}
|
|
488
612
|
if (dryRun && json) {
|
|
489
|
-
|
|
613
|
+
writeErr("--dry-run cannot be used with --json\n");
|
|
490
614
|
process.exit(1);
|
|
491
615
|
}
|
|
492
616
|
if (json && summary) {
|
|
493
|
-
|
|
617
|
+
writeErr("--summary cannot be used with --json\n");
|
|
494
618
|
process.exit(1);
|
|
495
619
|
}
|
|
496
620
|
try {
|
|
497
621
|
const input = await readInput2(useStdin ? void 0 : file);
|
|
498
|
-
const result = sanitizeLog2(input, { strict });
|
|
622
|
+
const result = sanitizeLog2(input, { strict, dryRun });
|
|
499
623
|
if (dryRun) {
|
|
500
624
|
renderDryRunReport(result.matches);
|
|
501
625
|
if (failOnDetect && result.matches.length > 0) {
|
|
@@ -512,8 +636,7 @@ async function main() {
|
|
|
512
636
|
}
|
|
513
637
|
process.exit(0);
|
|
514
638
|
} catch (err) {
|
|
515
|
-
|
|
516
|
-
process.stdout.write("\n");
|
|
639
|
+
writeErr((err?.message || "Unexpected error") + "\n");
|
|
517
640
|
process.exit(2);
|
|
518
641
|
}
|
|
519
642
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logshield-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"build:web": "vite build --outDir dist-web",
|
|
18
18
|
"build:blog": "node scripts/build-blog.js",
|
|
19
19
|
"dev:web": "vite",
|
|
20
|
+
"typecheck": "tsc -p tsconfig.core.json && tsc -p tsconfig.cli.json --noEmit",
|
|
20
21
|
"pretest": "npm run build",
|
|
21
22
|
"test": "vitest",
|
|
22
23
|
"prepublishOnly": "npm run build"
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
"esbuild": "^0.25.0",
|
|
28
29
|
"postcss": "^8.5.6",
|
|
29
30
|
"tailwindcss": "^3.4.19",
|
|
31
|
+
"typescript": "^5.9.3",
|
|
30
32
|
"vitest": "^4.0.0"
|
|
31
33
|
}
|
|
32
34
|
}
|