logshield-cli 0.4.2 → 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 +20 -0
- package/README.md +25 -12
- package/dist/cli/index.cjs +38 -41
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
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
|
+
|
|
3
23
|
## v0.4.2
|
|
4
24
|
|
|
5
25
|
### Fixed
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/logshield-cli)
|
|
5
5
|
[](https://github.com/afria85/LogShield/actions/workflows/ci.yml)
|
|
6
6
|
|
|
7
|
-
Your logs already contain secrets. You just don
|
|
7
|
+
Your logs already contain secrets. You just don't see them.
|
|
8
8
|
|
|
9
9
|
LogShield is a small CLI that automatically redacts secrets from logs **before**
|
|
10
10
|
you paste them into CI, GitHub issues, Slack, or send them to third-party support.
|
|
@@ -55,22 +55,28 @@ Use LogShield whenever logs leave your system:
|
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
57
|
# Preview what would be redacted (does not modify output)
|
|
58
|
-
echo "email=test@example.com
|
|
58
|
+
echo "email=test@example.com Authorization: Bearer abcdefghijklmnop" | logshield scan --dry-run
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
```
|
|
62
62
|
logshield (dry-run)
|
|
63
63
|
Detected 2 redactions:
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
AUTH_BEARER x1
|
|
65
|
+
EMAIL x1
|
|
66
66
|
|
|
67
67
|
No output was modified.
|
|
68
68
|
Use without --dry-run to apply.
|
|
69
69
|
```
|
|
70
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
|
+
|
|
71
77
|
```bash
|
|
72
78
|
# Enforce redaction (sanitized output)
|
|
73
|
-
echo "email=test@example.com
|
|
79
|
+
echo "email=test@example.com Authorization: Bearer abcdefghijklmnop" | logshield scan
|
|
74
80
|
```
|
|
75
81
|
|
|
76
82
|
- Prefer `--dry-run` first in CI to verify you are not over-redacting.
|
|
@@ -140,8 +146,8 @@ Examples:
|
|
|
140
146
|
|
|
141
147
|
```
|
|
142
148
|
<REDACTED_PASSWORD>
|
|
143
|
-
<
|
|
144
|
-
<
|
|
149
|
+
<REDACTED_API_KEY>
|
|
150
|
+
<REDACTED_TOKEN>
|
|
145
151
|
<REDACTED_EMAIL>
|
|
146
152
|
```
|
|
147
153
|
|
|
@@ -247,8 +253,8 @@ cat app.log | logshield scan --dry-run
|
|
|
247
253
|
|
|
248
254
|
```
|
|
249
255
|
logshield (dry-run)
|
|
250
|
-
Detected
|
|
251
|
-
AUTH_BEARER
|
|
256
|
+
Detected 4 redactions:
|
|
257
|
+
AUTH_BEARER x1
|
|
252
258
|
EMAIL x1
|
|
253
259
|
OAUTH_ACCESS_TOKEN x1
|
|
254
260
|
PASSWORD x1
|
|
@@ -345,10 +351,16 @@ Example:
|
|
|
345
351
|
|
|
346
352
|
```
|
|
347
353
|
LogShield Summary
|
|
348
|
-
|
|
349
|
-
|
|
354
|
+
API_KEY_HEADER: 1
|
|
355
|
+
PASSWORD: 2
|
|
350
356
|
```
|
|
351
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
|
+
|
|
352
364
|
---
|
|
353
365
|
|
|
354
366
|
## JSON output
|
|
@@ -362,7 +374,8 @@ logshield scan --json < logs.txt
|
|
|
362
374
|
Notes:
|
|
363
375
|
|
|
364
376
|
- `--json` **cannot** be combined with `--dry-run`
|
|
365
|
-
-
|
|
377
|
+
- Usage errors exit with code `2`
|
|
378
|
+
- Output is always newline-terminated
|
|
366
379
|
|
|
367
380
|
---
|
|
368
381
|
|
package/dist/cli/index.cjs
CHANGED
|
@@ -87,33 +87,25 @@ __export(summary_exports, {
|
|
|
87
87
|
printSummary: () => printSummary
|
|
88
88
|
});
|
|
89
89
|
function printSummary(matches) {
|
|
90
|
-
if (!matches
|
|
91
|
-
process.stderr.write("
|
|
90
|
+
if (!matches.length) {
|
|
91
|
+
process.stderr.write("LogShield Summary\n(no redactions detected)\n");
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
|
-
const
|
|
94
|
+
const counts = {};
|
|
95
95
|
for (const m of matches) {
|
|
96
|
-
|
|
96
|
+
counts[m.rule] = (counts[m.rule] ?? 0) + 1;
|
|
97
97
|
}
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const label = total === 1 ? "redaction" : "redactions";
|
|
105
|
-
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]}
|
|
106
104
|
`);
|
|
107
|
-
for (const { rule, count } of entries) {
|
|
108
|
-
process.stderr.write(
|
|
109
|
-
` ${rule.padEnd(maxLen)} x${count}
|
|
110
|
-
`
|
|
111
|
-
);
|
|
112
105
|
}
|
|
113
106
|
}
|
|
114
107
|
var init_summary = __esm({
|
|
115
108
|
"src/cli/summary.ts"() {
|
|
116
|
-
"use strict";
|
|
117
109
|
}
|
|
118
110
|
});
|
|
119
111
|
|
|
@@ -217,6 +209,15 @@ var init_credentials = __esm({
|
|
|
217
209
|
pattern: /\b(postgres|mysql|mongodb):\/\/([^:\s]+):([^@\s]+)@/gi,
|
|
218
210
|
replace: (_match, _ctx, groups) => `${groups[0]}://${groups[1]}:<REDACTED_PASSWORD>@`
|
|
219
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
|
+
},
|
|
220
221
|
/**
|
|
221
222
|
* API key (common variants):
|
|
222
223
|
* - apiKey=...
|
|
@@ -224,23 +225,19 @@ var init_credentials = __esm({
|
|
|
224
225
|
* - api-key: ...
|
|
225
226
|
* - apikey=...
|
|
226
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.
|
|
227
232
|
*/
|
|
228
233
|
{
|
|
229
234
|
name: "API_KEY",
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
pattern: /\bx-api-key\s*:\s*["']?[A-Za-z0-9_\-]{16,}["']?\b/gi,
|
|
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>"
|
|
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]}`
|
|
244
241
|
}
|
|
245
242
|
];
|
|
246
243
|
}
|
|
@@ -512,7 +509,7 @@ var { printSummary: printSummary2 } = (init_summary(), __toCommonJS(summary_expo
|
|
|
512
509
|
var { sanitizeLog: sanitizeLog2 } = (init_sanitizeLog(), __toCommonJS(sanitizeLog_exports));
|
|
513
510
|
var rawArgs = process.argv.slice(2).map((arg) => arg === "-h" ? "--help" : arg);
|
|
514
511
|
function getVersion() {
|
|
515
|
-
return true ? "0.4.
|
|
512
|
+
return true ? "0.4.3" : "unknown";
|
|
516
513
|
}
|
|
517
514
|
function printHelp() {
|
|
518
515
|
process.stdout.write(`Usage: logshield scan [file]
|
|
@@ -580,6 +577,10 @@ function renderDryRunReport(matches) {
|
|
|
580
577
|
process.stdout.write("No output was modified.\n");
|
|
581
578
|
process.stdout.write("Use without --dry-run to apply.\n");
|
|
582
579
|
}
|
|
580
|
+
function exitUsageError(message) {
|
|
581
|
+
writeErr(message.endsWith("\n") ? message : message + "\n");
|
|
582
|
+
process.exit(2);
|
|
583
|
+
}
|
|
583
584
|
async function main() {
|
|
584
585
|
if (rawArgs.length === 0 || rawArgs.includes("--help")) {
|
|
585
586
|
printHelp();
|
|
@@ -593,8 +594,7 @@ async function main() {
|
|
|
593
594
|
const { flags, positionals } = parseArgs(rawArgs);
|
|
594
595
|
const command = positionals[0];
|
|
595
596
|
if (command !== "scan") {
|
|
596
|
-
|
|
597
|
-
process.exit(1);
|
|
597
|
+
exitUsageError("Unknown command");
|
|
598
598
|
}
|
|
599
599
|
const file = positionals[1];
|
|
600
600
|
const strict = flags.has("--strict");
|
|
@@ -606,16 +606,13 @@ async function main() {
|
|
|
606
606
|
const stdinAuto = isStdinPiped();
|
|
607
607
|
const useStdin = stdinFlag || stdinAuto;
|
|
608
608
|
if (useStdin && file) {
|
|
609
|
-
|
|
610
|
-
process.exit(1);
|
|
609
|
+
exitUsageError("Cannot read from both STDIN and file");
|
|
611
610
|
}
|
|
612
611
|
if (dryRun && json) {
|
|
613
|
-
|
|
614
|
-
process.exit(1);
|
|
612
|
+
exitUsageError("--dry-run cannot be used with --json");
|
|
615
613
|
}
|
|
616
614
|
if (json && summary) {
|
|
617
|
-
|
|
618
|
-
process.exit(1);
|
|
615
|
+
exitUsageError("--summary cannot be used with --json");
|
|
619
616
|
}
|
|
620
617
|
try {
|
|
621
618
|
const input = await readInput2(useStdin ? void 0 : file);
|
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",
|