logshield-cli 0.4.4 → 0.6.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/CHANGELOG.md +36 -1
- package/README.md +76 -21
- package/dist/cli/index.cjs +112 -18
- package/package.json +36 -8
- package/dist/android-chrome-192x192.png +0 -0
- package/dist/android-chrome-512x512.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/index-B3qxIuiz.css +0 -1
- package/dist/assets/index-DDJ1Wxio.js +0 -29
- package/dist/engine/applyRules.js +0 -27
- package/dist/engine/engine/applyRules.d.ts +0 -5
- package/dist/engine/engine/applyRules.js +0 -30
- package/dist/engine/engine/guard.d.ts +0 -1
- package/dist/engine/engine/guard.js +0 -12
- package/dist/engine/engine/sanitizeLog.d.ts +0 -11
- package/dist/engine/engine/sanitizeLog.js +0 -18
- package/dist/engine/guard.js +0 -9
- package/dist/engine/rules/cloud.d.ts +0 -2
- package/dist/engine/rules/cloud.js +0 -15
- package/dist/engine/rules/credentials.d.ts +0 -2
- package/dist/engine/rules/credentials.js +0 -15
- package/dist/engine/rules/creditCard.d.ts +0 -2
- package/dist/engine/rules/creditCard.js +0 -15
- package/dist/engine/rules/custom.d.ts +0 -2
- package/dist/engine/rules/custom.js +0 -14
- package/dist/engine/rules/index.d.ts +0 -11
- package/dist/engine/rules/index.js +0 -38
- package/dist/engine/rules/tokens.d.ts +0 -2
- package/dist/engine/rules/tokens.js +0 -20
- package/dist/engine/rules/types.d.ts +0 -9
- package/dist/engine/rules/types.js +0 -2
- package/dist/engine/rules/urls.d.ts +0 -2
- package/dist/engine/rules/urls.js +0 -10
- package/dist/engine/sanitizeLog.js +0 -15
- package/dist/engine/utils/luhn.d.ts +0 -1
- package/dist/engine/utils/luhn.js +0 -19
- package/dist/favicon-16x16.png +0 -0
- package/dist/favicon-32x32.png +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/features.jsx +0 -462
- package/dist/robots.txt +0 -4
- package/dist/rules/cloud.js +0 -12
- package/dist/rules/credentials.js +0 -12
- package/dist/rules/creditCard.js +0 -12
- package/dist/rules/custom.js +0 -11
- package/dist/rules/index.js +0 -35
- package/dist/rules/tokens.js +0 -17
- package/dist/rules/types.js +0 -1
- package/dist/rules/urls.js +0 -7
- package/dist/site.webmanifest +0 -20
- package/dist/utils/luhn.js +0 -16
- package/dist/vite.svg +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.6.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Redact modern platform tokens (GitHub, Slack, npm, PyPI, SendGrid)
|
|
8
|
+
- Redact npmrc auth tokens (`:_authToken=...`)
|
|
9
|
+
- Redact private key blocks (PEM/OpenSSH, including ENCRYPTED PRIVATE KEY)
|
|
10
|
+
|
|
11
|
+
### Docs
|
|
12
|
+
|
|
13
|
+
- Clarified redaction coverage by mode (Default vs Strict) in README and docs
|
|
14
|
+
- Documented the 200KB input safety cap
|
|
15
|
+
|
|
16
|
+
### Notes
|
|
17
|
+
|
|
18
|
+
- No CLI flag changes
|
|
19
|
+
- This release may redact more secrets in default mode (intended)
|
|
20
|
+
|
|
21
|
+
## v0.5.0
|
|
22
|
+
|
|
23
|
+
### Security
|
|
24
|
+
|
|
25
|
+
- Hardened dry-run result shape: dry-run no longer returns the raw input in `output` (safe to serialize and avoids re-leakage in programmatic contexts)
|
|
26
|
+
- `--dry-run` can be combined with `--json` for machine-readable detection without leaking log content (`output` is intentionally empty)
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Added detection-only helper `scanLog(input)` (internal only; safe to serialize)
|
|
31
|
+
|
|
32
|
+
### Compatibility
|
|
33
|
+
|
|
34
|
+
- CLI behavior is unchanged: dry-run still prints a human report and never echoes log content
|
|
35
|
+
- Programmatic consumers: `dryRun` now returns `output: ""` (intentional)
|
|
36
|
+
- npm package ships CLI only; no supported JS API surface is published
|
|
37
|
+
|
|
3
38
|
## v0.4.4
|
|
4
39
|
|
|
5
40
|
### Fixed
|
|
@@ -18,7 +53,7 @@
|
|
|
18
53
|
|
|
19
54
|
- Prevented API key redaction from corrupting header names (`x-api-key`)
|
|
20
55
|
- Preserved key labels when redacting `api_key=...` values
|
|
21
|
-
- Corrected CLI exit code for invalid flag combinations (`--
|
|
56
|
+
- Corrected CLI exit code for invalid flag combinations (e.g., `--summary --json` exits with code 2)
|
|
22
57
|
|
|
23
58
|
### Improved
|
|
24
59
|
|
package/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/logshield-cli)
|
|
4
4
|
[](https://www.npmjs.com/package/logshield-cli)
|
|
5
5
|
[](https://github.com/afria85/LogShield/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/afria85/LogShield/blob/main/LICENSE)
|
|
7
|
+
[](https://github.com/sponsors/afria85)
|
|
6
8
|
|
|
7
9
|
Your logs already contain secrets. You just don't see them.
|
|
8
10
|
|
|
@@ -44,7 +46,7 @@ After LogShield, the same logs are safe to share.
|
|
|
44
46
|
|
|
45
47
|
Use LogShield whenever logs leave your system:
|
|
46
48
|
|
|
47
|
-
- Before
|
|
49
|
+
- Before logs end up in CI output or artifacts
|
|
48
50
|
- Before attaching logs to GitHub issues
|
|
49
51
|
- Before sending logs to third-party support
|
|
50
52
|
- Before sharing logs in Slack or email
|
|
@@ -70,9 +72,9 @@ Use without --dry-run to apply.
|
|
|
70
72
|
|
|
71
73
|
Notes:
|
|
72
74
|
|
|
73
|
-
- The report is printed to stdout
|
|
75
|
+
- The report is printed to **stdout** (human-readable)
|
|
74
76
|
- No log content is echoed
|
|
75
|
-
-
|
|
77
|
+
- In non-`--dry-run` mode, sanitized logs/JSON are written to stdout; summaries/errors go to stderr
|
|
76
78
|
|
|
77
79
|
```bash
|
|
78
80
|
# Enforce redaction (sanitized output)
|
|
@@ -82,10 +84,6 @@ echo "email=test@example.com Authorization: Bearer abcdefghijklmnop" | logshield
|
|
|
82
84
|
- Prefer `--dry-run` first in CI to verify you are not over-redacting.
|
|
83
85
|
- Then switch to enforced mode once you are satisfied with the preview.
|
|
84
86
|
|
|
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.
|
|
88
|
-
|
|
89
87
|
It is designed to be **predictable, conservative, and safe for production pipelines**.
|
|
90
88
|
|
|
91
89
|
---
|
|
@@ -95,7 +93,12 @@ It is designed to be **predictable, conservative, and safe for production pipeli
|
|
|
95
93
|
The website and documentation live in the `/docs` directory.
|
|
96
94
|
They are deployed to **https://logshield.dev** via Vercel.
|
|
97
95
|
|
|
98
|
-
|
|
96
|
+
## Project links
|
|
97
|
+
|
|
98
|
+
- Website: https://logshield.dev
|
|
99
|
+
- Docs: https://logshield.dev/docs.html
|
|
100
|
+
- GitHub: https://github.com/afria85/LogShield
|
|
101
|
+
- Sponsor: https://github.com/sponsors/afria85
|
|
99
102
|
|
|
100
103
|
## Why LogShield exists
|
|
101
104
|
|
|
@@ -129,7 +132,7 @@ The same input always produces the same output.
|
|
|
129
132
|
- No environment-dependent behavior
|
|
130
133
|
- Safe for CI, audits, and reproducibility
|
|
131
134
|
|
|
132
|
-
### 2.
|
|
135
|
+
### 2. Prefer precision over recall
|
|
133
136
|
|
|
134
137
|
LogShield must **not** redact non-secrets.
|
|
135
138
|
|
|
@@ -193,6 +196,23 @@ logshield scan [file]
|
|
|
193
196
|
|
|
194
197
|
If a file is not provided and input is piped, LogShield automatically reads from **STDIN**.
|
|
195
198
|
|
|
199
|
+
Note: the npm package ships the CLI only; there is no supported JS API surface.
|
|
200
|
+
|
|
201
|
+
### Limits
|
|
202
|
+
|
|
203
|
+
- Maximum input size: **200KB** (safety cap). Oversized input exits with code `2`.
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
### Windows note
|
|
207
|
+
|
|
208
|
+
If `logshield` is not found after a global install, ensure your npm global bin directory is on `PATH`.
|
|
209
|
+
|
|
210
|
+
Check it with:
|
|
211
|
+
|
|
212
|
+
- `npm prefix -g` (add this directory to PATH)
|
|
213
|
+
|
|
214
|
+
Restart your terminal after updating PATH.
|
|
215
|
+
|
|
196
216
|
---
|
|
197
217
|
|
|
198
218
|
## CLI Flags
|
|
@@ -213,7 +233,7 @@ If a file is not provided and input is piped, LogShield automatically reads from
|
|
|
213
233
|
Print a compact redaction summary
|
|
214
234
|
|
|
215
235
|
- `--json`
|
|
216
|
-
JSON output (
|
|
236
|
+
JSON output (can be combined with `--dry-run`; output is empty in dry-run)
|
|
217
237
|
|
|
218
238
|
- `--version`
|
|
219
239
|
Print CLI version
|
|
@@ -237,7 +257,7 @@ logshield scan app.log
|
|
|
237
257
|
cat app.log | logshield scan
|
|
238
258
|
```
|
|
239
259
|
|
|
240
|
-
`--stdin` is optional; piped input is auto-detected.
|
|
260
|
+
`--stdin` is optional; piped input is auto-detected. Use `--stdin` to force reading from stdin in TTY/paste workflows.
|
|
241
261
|
|
|
242
262
|
---
|
|
243
263
|
|
|
@@ -306,7 +326,7 @@ jobs:
|
|
|
306
326
|
|
|
307
327
|
- uses: actions/setup-node@v4
|
|
308
328
|
with:
|
|
309
|
-
node-version:
|
|
329
|
+
node-version: 22
|
|
310
330
|
|
|
311
331
|
- run: npm install -g logshield-cli
|
|
312
332
|
|
|
@@ -371,9 +391,16 @@ Structured output for tooling and automation:
|
|
|
371
391
|
logshield scan --json < logs.txt
|
|
372
392
|
```
|
|
373
393
|
|
|
394
|
+
Detection-only JSON (safe to serialize; no log content):
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
logshield scan --json --dry-run < logs.txt
|
|
398
|
+
```
|
|
399
|
+
|
|
374
400
|
Notes:
|
|
375
401
|
|
|
376
|
-
- `--json`
|
|
402
|
+
- `--json` can be combined with `--dry-run` for machine-readable detection
|
|
403
|
+
- In `--dry-run` JSON mode, `output` is intentionally an empty string
|
|
377
404
|
- Usage errors exit with code `2`
|
|
378
405
|
- Output is always newline-terminated
|
|
379
406
|
|
|
@@ -391,16 +418,32 @@ Notes:
|
|
|
391
418
|
|
|
392
419
|
## What gets redacted
|
|
393
420
|
|
|
394
|
-
|
|
421
|
+
LogShield uses a fixed, deterministic rule set. The exact coverage depends on the selected mode.
|
|
395
422
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
-
|
|
423
|
+
### Default mode (recommended)
|
|
424
|
+
|
|
425
|
+
- Passwords (quoted and JSON forms)
|
|
426
|
+
- API key headers (e.g. `x-api-key: ...`)
|
|
427
|
+
- API keys (e.g. `api_key=...`, `api-key: ...`)
|
|
428
|
+
- Authorization Bearer tokens
|
|
399
429
|
- JWTs
|
|
430
|
+
- GitHub tokens (`ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_`, and `github_pat_...`)
|
|
431
|
+
- Slack tokens (`xox*` and `xapp-...`)
|
|
432
|
+
- npm access tokens (`npm_...`)
|
|
433
|
+
- npmrc auth tokens (`:_authToken=...`)
|
|
434
|
+
- PyPI API tokens (`pypi-...`)
|
|
435
|
+
- SendGrid API keys (`SG.<...>.<...>`)
|
|
436
|
+
- Private key blocks (PEM/OpenSSH)
|
|
400
437
|
- Emails
|
|
401
438
|
- URLs with embedded credentials
|
|
402
|
-
- Database credentials (including redis
|
|
403
|
-
- Cloud
|
|
439
|
+
- Database URL credentials (including `redis://...` and `mssql://...`)
|
|
440
|
+
- Cloud credentials in explicit labeled forms (e.g. `AWS_SECRET_ACCESS_KEY=...`)
|
|
441
|
+
|
|
442
|
+
### Strict mode (adds)
|
|
443
|
+
|
|
444
|
+
- Stripe secret keys (e.g. `sk_live_...`, `sk_test_...`)
|
|
445
|
+
- AWS access keys (`AKIA...`)
|
|
446
|
+
- AWS secret keys (strict fallback)
|
|
404
447
|
- Credit card numbers (Luhn-validated)
|
|
405
448
|
|
|
406
449
|
---
|
|
@@ -426,7 +469,7 @@ Depending on rules and mode:
|
|
|
426
469
|
LogShield guarantees:
|
|
427
470
|
|
|
428
471
|
- Deterministic output
|
|
429
|
-
- Stable behavior within **v0.
|
|
472
|
+
- Stable behavior within the current minor line **v0.6.x**
|
|
430
473
|
- No runtime dependencies
|
|
431
474
|
- Snapshot-tested and contract-tested
|
|
432
475
|
- No telemetry
|
|
@@ -456,4 +499,16 @@ It is a **last-line safety net**, not a primary defense.
|
|
|
456
499
|
|
|
457
500
|
## License
|
|
458
501
|
|
|
459
|
-
Apache-2.0
|
|
502
|
+
Apache-2.0 — see `LICENSE`.
|
|
503
|
+
|
|
504
|
+
## Contributing
|
|
505
|
+
|
|
506
|
+
See `CONTRIBUTING.md`.
|
|
507
|
+
|
|
508
|
+
## Security
|
|
509
|
+
|
|
510
|
+
See `SECURITY.md`.
|
|
511
|
+
|
|
512
|
+
## Support
|
|
513
|
+
|
|
514
|
+
See `SUPPORT.md`.
|
package/dist/cli/index.cjs
CHANGED
|
@@ -36,22 +36,24 @@ var readInput_exports = {};
|
|
|
36
36
|
__export(readInput_exports, {
|
|
37
37
|
readInput: () => readInput
|
|
38
38
|
});
|
|
39
|
-
async function readInput(file) {
|
|
39
|
+
async function readInput(file, opts) {
|
|
40
40
|
if (file) {
|
|
41
41
|
if (!import_node_fs.default.existsSync(file)) {
|
|
42
42
|
throw new Error(`File not found: ${file}`);
|
|
43
43
|
}
|
|
44
44
|
return import_node_fs.default.readFileSync(file, "utf8");
|
|
45
45
|
}
|
|
46
|
-
|
|
46
|
+
const stdin = opts?.stdin ?? process.stdin;
|
|
47
|
+
const forceStdin = Boolean(opts?.forceStdin);
|
|
48
|
+
if (!stdin.isTTY || forceStdin) {
|
|
47
49
|
return new Promise((resolve, reject) => {
|
|
48
50
|
let data = "";
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
stdin.setEncoding?.("utf8");
|
|
52
|
+
stdin.on("data", (chunk) => {
|
|
51
53
|
data += chunk;
|
|
52
54
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
stdin.on("end", () => resolve(data));
|
|
56
|
+
stdin.on("error", reject);
|
|
55
57
|
});
|
|
56
58
|
}
|
|
57
59
|
throw new Error("No input provided");
|
|
@@ -155,6 +157,66 @@ var tokenRules;
|
|
|
155
157
|
var init_tokens = __esm({
|
|
156
158
|
"src/rules/tokens.ts"() {
|
|
157
159
|
tokenRules = [
|
|
160
|
+
// Multi-line private key blocks (PEM/OpenSSH). Run early to avoid leaking
|
|
161
|
+
// partial material through other rules.
|
|
162
|
+
{
|
|
163
|
+
name: "PRIVATE_KEY_BLOCK",
|
|
164
|
+
pattern: /-----BEGIN\s+(?:(?:RSA|EC|DSA)\s+)?(?:ENCRYPTED\s+)?PRIVATE\s+KEY-----[\s\S]*?-----END\s+(?:(?:RSA|EC|DSA)\s+)?(?:ENCRYPTED\s+)?PRIVATE\s+KEY-----/gi,
|
|
165
|
+
replace: () => "<REDACTED_PRIVATE_KEY_BLOCK>"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "OPENSSH_PRIVATE_KEY_BLOCK",
|
|
169
|
+
pattern: /-----BEGIN OPENSSH PRIVATE KEY-----[\s\S]*?-----END OPENSSH PRIVATE KEY-----/g,
|
|
170
|
+
replace: () => "<REDACTED_PRIVATE_KEY_BLOCK>"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "PRIVATE_KEY_HEADER",
|
|
174
|
+
pattern: /-----BEGIN\s+(?:(?:RSA|EC|DSA)\s+)?(?:ENCRYPTED\s+)?PRIVATE\s+KEY-----|-----BEGIN OPENSSH PRIVATE KEY-----/gi,
|
|
175
|
+
replace: () => "<REDACTED_PRIVATE_KEY_HEADER>"
|
|
176
|
+
},
|
|
177
|
+
// Modern platform tokens (prefix-anchored, low false-positive).
|
|
178
|
+
{
|
|
179
|
+
name: "GITHUB_TOKEN",
|
|
180
|
+
pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,255}\b/g,
|
|
181
|
+
replace: () => "<REDACTED_GITHUB_TOKEN>"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "GITHUB_FINE_GRAINED_TOKEN",
|
|
185
|
+
pattern: /\bgithub_pat_[A-Za-z0-9]{22}_[A-Za-z0-9]{59}\b/g,
|
|
186
|
+
replace: () => "<REDACTED_GITHUB_TOKEN>"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "SLACK_TOKEN",
|
|
190
|
+
pattern: /\bxox(?:b|p|a|s|r)-[A-Za-z0-9-]{10,250}\b/g,
|
|
191
|
+
replace: () => "<REDACTED_SLACK_TOKEN>"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: "SLACK_APP_TOKEN",
|
|
195
|
+
pattern: /\bxapp-\d-[A-Za-z0-9-]{10,250}\b/g,
|
|
196
|
+
replace: () => "<REDACTED_SLACK_TOKEN>"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "NPM_TOKEN",
|
|
200
|
+
pattern: /\bnpm_[A-Za-z0-9]{36}\b/g,
|
|
201
|
+
replace: () => "<REDACTED_NPM_TOKEN>"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "NPMRC_AUTH_TOKEN",
|
|
205
|
+
// .npmrc-style auth token, commonly seen as:
|
|
206
|
+
// //registry.npmjs.org/:_authToken=...
|
|
207
|
+
pattern: /(:_authToken\s*=\s*)([^\s\r\n]+)/gi,
|
|
208
|
+
replace: (_match, _ctx, groups) => `${groups[0]}<REDACTED_NPM_TOKEN>`
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "PYPI_TOKEN",
|
|
212
|
+
pattern: /\bpypi-[A-Za-z0-9_-]{85,200}\b/g,
|
|
213
|
+
replace: () => "<REDACTED_PYPI_TOKEN>"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "SENDGRID_API_KEY",
|
|
217
|
+
pattern: /\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b/g,
|
|
218
|
+
replace: () => "<REDACTED_SENDGRID_KEY>"
|
|
219
|
+
},
|
|
158
220
|
{
|
|
159
221
|
name: "JWT",
|
|
160
222
|
pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
|
@@ -503,7 +565,7 @@ function sanitizeLog(input, options) {
|
|
|
503
565
|
const matches = [];
|
|
504
566
|
if (ctx.dryRun) {
|
|
505
567
|
applyRules(input, allRules, ctx, matches);
|
|
506
|
-
return { output:
|
|
568
|
+
return { output: "", matches };
|
|
507
569
|
}
|
|
508
570
|
const output = applyRules(input, allRules, ctx, matches);
|
|
509
571
|
return { output, matches };
|
|
@@ -522,8 +584,18 @@ var { writeOutput: writeOutput2 } = (init_writeOutput(), __toCommonJS(writeOutpu
|
|
|
522
584
|
var { printSummary: printSummary2 } = (init_summary(), __toCommonJS(summary_exports));
|
|
523
585
|
var { sanitizeLog: sanitizeLog2 } = (init_sanitizeLog(), __toCommonJS(sanitizeLog_exports));
|
|
524
586
|
var rawArgs = process.argv.slice(2).map((arg) => arg === "-h" ? "--help" : arg);
|
|
587
|
+
var ALLOWED_FLAGS = /* @__PURE__ */ new Set([
|
|
588
|
+
"--strict",
|
|
589
|
+
"--dry-run",
|
|
590
|
+
"--stdin",
|
|
591
|
+
"--fail-on-detect",
|
|
592
|
+
"--json",
|
|
593
|
+
"--summary",
|
|
594
|
+
"--version",
|
|
595
|
+
"--help"
|
|
596
|
+
]);
|
|
525
597
|
function getVersion() {
|
|
526
|
-
return true ? "0.
|
|
598
|
+
return true ? "0.6.0" : "unknown";
|
|
527
599
|
}
|
|
528
600
|
function printHelp() {
|
|
529
601
|
process.stdout.write(`Usage: logshield scan [file]
|
|
@@ -550,14 +622,23 @@ function writeErr(message) {
|
|
|
550
622
|
function parseArgs(args) {
|
|
551
623
|
const flags = /* @__PURE__ */ new Set();
|
|
552
624
|
const positionals = [];
|
|
625
|
+
const unknownFlags = [];
|
|
553
626
|
for (const arg of args) {
|
|
554
|
-
if (arg.startsWith("
|
|
555
|
-
|
|
627
|
+
if (arg.startsWith("-")) {
|
|
628
|
+
if (arg.startsWith("--")) {
|
|
629
|
+
if (!ALLOWED_FLAGS.has(arg)) {
|
|
630
|
+
unknownFlags.push(arg);
|
|
631
|
+
} else {
|
|
632
|
+
flags.add(arg);
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
unknownFlags.push(arg);
|
|
636
|
+
}
|
|
556
637
|
} else {
|
|
557
638
|
positionals.push(arg);
|
|
558
639
|
}
|
|
559
640
|
}
|
|
560
|
-
return { flags, positionals };
|
|
641
|
+
return { flags, positionals, unknownFlags };
|
|
561
642
|
}
|
|
562
643
|
function isStdinPiped() {
|
|
563
644
|
return !process.stdin.isTTY;
|
|
@@ -596,16 +677,23 @@ function exitUsageError(message) {
|
|
|
596
677
|
process.exit(2);
|
|
597
678
|
}
|
|
598
679
|
async function main() {
|
|
599
|
-
if (rawArgs.length === 0
|
|
680
|
+
if (rawArgs.length === 0) {
|
|
600
681
|
printHelp();
|
|
601
682
|
process.exit(0);
|
|
602
683
|
}
|
|
603
|
-
|
|
684
|
+
const { flags, positionals, unknownFlags } = parseArgs(rawArgs);
|
|
685
|
+
if (unknownFlags.length > 0) {
|
|
686
|
+
exitUsageError(`Unknown flag: ${unknownFlags[0]}`);
|
|
687
|
+
}
|
|
688
|
+
if (flags.has("--help")) {
|
|
689
|
+
printHelp();
|
|
690
|
+
process.exit(0);
|
|
691
|
+
}
|
|
692
|
+
if (flags.has("--version")) {
|
|
604
693
|
process.stdout.write(`logshield v${getVersion()}
|
|
605
694
|
`);
|
|
606
695
|
process.exit(0);
|
|
607
696
|
}
|
|
608
|
-
const { flags, positionals } = parseArgs(rawArgs);
|
|
609
697
|
const command = positionals[0];
|
|
610
698
|
if (command !== "scan") {
|
|
611
699
|
exitUsageError("Unknown command");
|
|
@@ -622,16 +710,22 @@ async function main() {
|
|
|
622
710
|
if (useStdin && file) {
|
|
623
711
|
exitUsageError("Cannot read from both STDIN and file");
|
|
624
712
|
}
|
|
625
|
-
if (dryRun && json) {
|
|
626
|
-
exitUsageError("--dry-run cannot be used with --json");
|
|
627
|
-
}
|
|
628
713
|
if (json && summary) {
|
|
629
714
|
exitUsageError("--summary cannot be used with --json");
|
|
630
715
|
}
|
|
631
716
|
try {
|
|
632
|
-
const input = await readInput2(useStdin ? void 0 : file
|
|
717
|
+
const input = await readInput2(useStdin ? void 0 : file, {
|
|
718
|
+
forceStdin: stdinFlag
|
|
719
|
+
});
|
|
633
720
|
const result = sanitizeLog2(input, { strict, dryRun });
|
|
634
721
|
if (dryRun) {
|
|
722
|
+
if (json) {
|
|
723
|
+
writeOutput2(result, { json: true });
|
|
724
|
+
if (failOnDetect && result.matches.length > 0) {
|
|
725
|
+
process.exit(1);
|
|
726
|
+
}
|
|
727
|
+
process.exit(0);
|
|
728
|
+
}
|
|
635
729
|
renderDryRunReport(result.matches);
|
|
636
730
|
if (failOnDetect && result.matches.length > 0) {
|
|
637
731
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,35 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logshield-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"logshield": "dist/cli/index.cjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist",
|
|
10
|
+
"dist/cli",
|
|
11
11
|
"README.md",
|
|
12
12
|
"CHANGELOG.md",
|
|
13
13
|
"LICENSE"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "node scripts/build-cli.cjs",
|
|
17
|
-
"build:web": "vite build --outDir dist-web",
|
|
18
17
|
"build:blog": "node scripts/build-blog.js",
|
|
19
|
-
"dev:web": "vite",
|
|
20
18
|
"typecheck": "tsc -p tsconfig.core.json && tsc -p tsconfig.cli.json --noEmit",
|
|
21
19
|
"pretest": "npm run build",
|
|
22
20
|
"test": "vitest",
|
|
21
|
+
"lint": "npm run typecheck",
|
|
22
|
+
"clean:dist": "node scripts/clean-dist.mjs",
|
|
23
|
+
"prepack": "npm run clean:dist && npm run build",
|
|
24
|
+
"pack:check": "npm pack --dry-run",
|
|
25
|
+
"release:check": "npm run prepublish:check && npm run pack:check",
|
|
23
26
|
"prepublish:check": "npm run typecheck && npm test",
|
|
24
|
-
"prepublishOnly": "npm run prepublish:check"
|
|
27
|
+
"prepublishOnly": "npm run prepublish:check",
|
|
28
|
+
"dev": "node scripts/dev-docs-server.mjs",
|
|
29
|
+
"dev:lan": "node scripts/dev-docs-server.mjs --host 0.0.0.0"
|
|
25
30
|
},
|
|
26
31
|
"devDependencies": {
|
|
27
32
|
"@types/node": "^25.0.3",
|
|
28
|
-
"autoprefixer": "^10.4.23",
|
|
29
33
|
"esbuild": "^0.25.0",
|
|
30
|
-
"postcss": "^8.5.6",
|
|
31
|
-
"tailwindcss": "^3.4.19",
|
|
32
34
|
"typescript": "^5.9.3",
|
|
33
35
|
"vitest": "^4.0.0"
|
|
36
|
+
},
|
|
37
|
+
"description": "Deterministic, rule-based CLI to sanitize secrets from logs. No AI. No cloud. No config.",
|
|
38
|
+
"keywords": [
|
|
39
|
+
"log-sanitization",
|
|
40
|
+
"secret-detection",
|
|
41
|
+
"redaction",
|
|
42
|
+
"security",
|
|
43
|
+
"devops",
|
|
44
|
+
"cli",
|
|
45
|
+
"deterministic",
|
|
46
|
+
"no-ai"
|
|
47
|
+
],
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/afria85/LogShield.git"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://logshield.dev",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/afria85/LogShield/issues"
|
|
55
|
+
},
|
|
56
|
+
"funding": {
|
|
57
|
+
"type": "github",
|
|
58
|
+
"url": "https://github.com/sponsors/afria85"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=18"
|
|
34
62
|
}
|
|
35
63
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--color-bg-primary: 248 250 252;--color-bg-secondary: 241 245 249;--color-bg-card: 255 255 255;--color-text-primary: 15 23 42;--color-text-secondary: 71 85 105;--color-text-muted: 148 163 184;--color-border: 226 232 240;--color-accent: 59 130 246;--color-accent-hover: 37 99 235;--gradient-start: 239 246 255;--gradient-mid: 238 242 255;--gradient-end: 248 250 252}*{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}@media (prefers-color-scheme: dark){*{--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity, 1))}}html{scroll-behavior:smooth}body{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.3s}@media (prefers-color-scheme: dark){body{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity, 1))}}body{margin:0;padding:0;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:JetBrains Mono,Fira Code,source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity, 1))}@media (prefers-color-scheme: dark){::-webkit-scrollbar-track{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity, 1))}}::-webkit-scrollbar-thumb{border-radius:9999px;--tw-bg-opacity: 1;background-color:rgb(203 213 225 / var(--tw-bg-opacity, 1))}@media (prefers-color-scheme: dark){::-webkit-scrollbar-thumb{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity, 1))}}::-webkit-scrollbar-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(148 163 184 / var(--tw-bg-opacity, 1))}@media (prefers-color-scheme: dark){::-webkit-scrollbar-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity, 1))}}::-moz-selection{background-color:#3b82f64d;--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity, 1))}::selection{background-color:#3b82f64d;--tw-text-opacity: 1;color:rgb(30 58 138 / var(--tw-text-opacity, 1))}@media (prefers-color-scheme: dark){::-moz-selection{--tw-text-opacity: 1;color:rgb(219 234 254 / var(--tw-text-opacity, 1))}::selection{--tw-text-opacity: 1;color:rgb(219 234 254 / var(--tw-text-opacity, 1))}}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes pulseGlow{0%,to{box-shadow:0 0 #3b82f666}50%{box-shadow:0 0 20px 5px #3b82f633}}@keyframes gradientShift{0%{background-position:0% 50%}50%{background-position:100% 50%}to{background-position:0% 50%}}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes shine{0%{background-position:-200% center}to{background-position:200% center}}@keyframes bounceUp{0%,to{transform:translateY(0)}50%{transform:translateY(-4px)}}@media print{.no-print{display:none!important}body{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}}:root{--bg: #0f1115;--panel: #151922;--border: #252a36;--text: #e6e8eb;--muted: #9aa1ad;--accent: #ff4d4f}*{box-sizing:border-box}body{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;background:var(--bg);color:var(--text)}.ls-root{min-height:100vh;display:flex;flex-direction:column}.ls-header{padding:16px 24px;border-bottom:1px solid var(--border)}.ls-header h1{margin:0;font-size:20px}.ls-header p{margin:4px 0 0;color:var(--muted);font-size:13px}.ls-controls{display:flex;gap:16px;align-items:center;padding:12px 24px;border-bottom:1px solid var(--border);font-size:13px}.ls-controls label{display:flex;align-items:center;gap:6px;cursor:pointer}.ls-controls button{margin-left:auto;background:transparent;border:1px solid var(--border);color:var(--text);padding:6px 12px;cursor:pointer}.ls-controls button:hover{border-color:var(--accent)}.ls-main{flex:1;display:grid;grid-template-columns:1fr 1fr}.ls-input{width:100%;height:100%;resize:none;border:none;outline:none;padding:16px;background:var(--panel);color:var(--text);border-right:1px solid var(--border);font-size:13px;line-height:1.5}.ls-output{padding:16px;background:#0c0f14;font-size:13px;line-height:1.5;white-space:pre-wrap;overflow:auto}.ls-highlight{background:#ff4d4f40;color:#fff}.ls-footer{padding:8px 24px;border-top:1px solid var(--border);font-size:12px;color:var(--muted)}
|