envshield 4.7.1__tar.gz → 4.7.4__tar.gz

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.
Files changed (55) hide show
  1. {envshield-4.7.1 → envshield-4.7.4}/PKG-INFO +129 -7
  2. {envshield-4.7.1 → envshield-4.7.4}/README.md +128 -6
  3. envshield-4.7.4/envshield/__init__.py +1 -0
  4. envshield-4.7.4/envshield/classifier/__init__.py +12 -0
  5. envshield-4.7.4/envshield/classifier/context_classifier.py +313 -0
  6. envshield-4.7.4/envshield/classifier/tokenizer.py +299 -0
  7. {envshield-4.7.1 → envshield-4.7.4}/envshield/cli.py +88 -28
  8. {envshield-4.7.1 → envshield-4.7.4}/envshield/config/manager.py +124 -13
  9. envshield-4.7.4/envshield/core/dependency_diff.py +235 -0
  10. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/discovery.py +32 -13
  11. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/doctor.py +7 -7
  12. envshield-4.7.4/envshield/core/enforcement.py +191 -0
  13. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/exceptions.py +15 -0
  14. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/explain.py +12 -11
  15. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/hooks_manager.py +56 -10
  16. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/importer.py +13 -0
  17. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/scanner.py +642 -10
  18. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/schema_manager.py +48 -25
  19. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/service_discovery.py +19 -2
  20. {envshield-4.7.1 → envshield-4.7.4}/envshield/parsers/_docker_compose.py +88 -11
  21. envshield-4.7.4/envshield/parsers/factory.py +95 -0
  22. {envshield-4.7.1 → envshield-4.7.4}/envshield.egg-info/PKG-INFO +129 -7
  23. {envshield-4.7.1 → envshield-4.7.4}/envshield.egg-info/SOURCES.txt +4 -0
  24. {envshield-4.7.1 → envshield-4.7.4}/pyproject.toml +1 -1
  25. envshield-4.7.1/envshield/__init__.py +0 -1
  26. envshield-4.7.1/envshield/core/dependency_diff.py +0 -112
  27. envshield-4.7.1/envshield/parsers/factory.py +0 -52
  28. {envshield-4.7.1 → envshield-4.7.4}/LICENSE.md +0 -0
  29. {envshield-4.7.1 → envshield-4.7.4}/envshield/__main__.py +0 -0
  30. {envshield-4.7.1 → envshield-4.7.4}/envshield/config/__init__.py +0 -0
  31. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/__init__.py +0 -0
  32. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/contract_diff.py +0 -0
  33. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/dependency_snapshot.py +0 -0
  34. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/file_updater.py +0 -0
  35. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/generator.py +0 -0
  36. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/inspector.py +0 -0
  37. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/schema_snapshot.py +0 -0
  38. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/schema_types.py +0 -0
  39. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/service_manager.py +0 -0
  40. {envshield-4.7.1 → envshield-4.7.4}/envshield/core/setup_manager.py +0 -0
  41. {envshield-4.7.1 → envshield-4.7.4}/envshield/parsers/__init__.py +0 -0
  42. {envshield-4.7.1 → envshield-4.7.4}/envshield/parsers/_base.py +0 -0
  43. {envshield-4.7.1 → envshield-4.7.4}/envshield/parsers/_deployment.py +0 -0
  44. {envshield-4.7.1 → envshield-4.7.4}/envshield/parsers/_dotenv.py +0 -0
  45. {envshield-4.7.1 → envshield-4.7.4}/envshield/parsers/_kubernetes.py +0 -0
  46. {envshield-4.7.1 → envshield-4.7.4}/envshield/parsers/_python.py +0 -0
  47. {envshield-4.7.1 → envshield-4.7.4}/envshield/state.py +0 -0
  48. {envshield-4.7.1 → envshield-4.7.4}/envshield/utils/__init__.py +0 -0
  49. {envshield-4.7.1 → envshield-4.7.4}/envshield/utils/git_utils.py +0 -0
  50. {envshield-4.7.1 → envshield-4.7.4}/envshield/utils/paths.py +0 -0
  51. {envshield-4.7.1 → envshield-4.7.4}/envshield.egg-info/dependency_links.txt +0 -0
  52. {envshield-4.7.1 → envshield-4.7.4}/envshield.egg-info/entry_points.txt +0 -0
  53. {envshield-4.7.1 → envshield-4.7.4}/envshield.egg-info/requires.txt +0 -0
  54. {envshield-4.7.1 → envshield-4.7.4}/envshield.egg-info/top_level.txt +0 -0
  55. {envshield-4.7.1 → envshield-4.7.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: envshield
3
- Version: 4.7.1
3
+ Version: 4.7.4
4
4
  Summary: EnvShield: environment variables as a version-controlled configuration contract.
5
5
  Author-email: Rabbil Yasar Sajal <rabbilyasar@gmail.com>
6
6
  License: MIT License
@@ -270,7 +270,7 @@ Every command above documents its own options with `--help`; the full reference
270
270
 
271
271
  ## Git / CI workflow
272
272
 
273
- This is where the schema stops being documentation and starts being enforcement. Three concrete things EnvShield catches in a PR:
273
+ This is where the schema stops being documentation and starts being enforcement. Three concrete things EnvShield catches before a change reaches production:
274
274
 
275
275
  **A new environment variable used in code but missing from the schema:**
276
276
 
@@ -284,12 +284,14 @@ envshield undeclared origin/main HEAD --service api
284
284
  envshield schema diff origin/main HEAD --service api
285
285
  ```
286
286
 
287
- **A secret accidentally introduced into a commit** — the pre-commit hook runs `scan --staged` automatically:
287
+ **A secret accidentally introduced into a commit:**
288
288
 
289
289
  ```bash
290
290
  envshield hook install
291
291
  ```
292
292
 
293
+ The pre-commit hook runs `scan --staged --enforce`, applying classification-based enforcement with interactive override for high-confidence findings on local commits, and strict blocking in CI. See [Secret safety](#secret-safety) for enforcement behavior details.
294
+
293
295
  In CI, diff against the merge-base, not the base branch's current tip (which can move while the PR is open):
294
296
 
295
297
  ```yaml
@@ -308,7 +310,25 @@ jobs:
308
310
  envshield undeclared "$BASE" HEAD
309
311
  ```
310
312
 
311
- Both commands exit non-zero on a real finding — nothing beyond the exit code is required to gate a PR. Add `--json` if you want to post a custom summary instead.
313
+ Both commands exit non-zero on a real finding, and also on an error that prevented evaluation (a bad revision, a schema that failed to parse) — never a silent "clean" for something it couldn't actually check. Add `--json` if you want to post a custom summary instead.
314
+
315
+ ### SARIF output (`undeclared --sarif`)
316
+
317
+ `envshield undeclared` also supports `--sarif` — a standard [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/) log, for GitHub Code Scanning or any other SARIF-consuming CI tool, so a missing declaration is annotated on its exact file and line rather than only appearing in a CI log:
318
+
319
+ ```yaml
320
+ - run: |
321
+ BASE=$(git merge-base "origin/${{ github.base_ref }}" HEAD)
322
+ envshield undeclared "$BASE" HEAD --sarif > envshield.sarif
323
+ continue-on-error: true # upload the report even when it found something
324
+ - uses: github/codeql-action/upload-sarif@v3
325
+ with:
326
+ sarif_file: envshield.sarif
327
+ ```
328
+
329
+ `--sarif` only exists for `undeclared`, deliberately: each finding there has a real file and line to point at. `schema diff` findings describe the *contract* (a variable added, removed, or changed) rather than a source location, so forcing them into SARIF would mean inventing a line number that isn't real — use its `--json`/exit-code output instead, or annotate it yourself (e.g. a `jq` step over the JSON, emitting `::error::` workflow commands).
330
+
331
+ An error that prevents evaluating a service (a bad revision, a schema that fails to parse) is never turned into a fabricated SARIF result — it's reported as a SARIF `invocation` with `executionSuccessful: false`, so a consumer can't mistake "couldn't check" for "checked and clean."
312
332
 
313
333
  ---
314
334
 
@@ -410,6 +430,34 @@ A few things worth knowing:
410
430
 
411
431
  ---
412
432
 
433
+ ## Docker Compose base + override layers (`files:`)
434
+
435
+ A manifest registration normally points at one file:
436
+
437
+ ```yaml
438
+ manifests:
439
+ - file: docker-compose.yml
440
+ containers:
441
+ api: api
442
+ ```
443
+
444
+ A real Compose project commonly splits its configuration across a base file plus an override that Compose applies automatically (`docker-compose.yml` + `docker-compose.override.yml`, or any other `-f a -f b` combination). Registering only the base misses whatever the override adds; registering the override alone reports the base's own variables as falsely missing, since an override file is deliberately just a delta. `files:` registers the whole ordered layer set as **one** logical manifest instead:
445
+
446
+ ```yaml
447
+ manifests:
448
+ - files:
449
+ - docker-compose.yml
450
+ - docker-compose.override.yml
451
+ containers:
452
+ api: api
453
+ ```
454
+
455
+ Order matters — later files override earlier ones, exactly like `docker compose -f docker-compose.yml -f docker-compose.override.yml` itself. `check`/`doctor`/`explain` all validate the merged result as a single source, under one combined label (`"docker-compose.yml + docker-compose.override.yml"`).
456
+
457
+ This is Compose-specific merge behavior for the fields EnvShield already understands (`environment:`, `env_file:`) — **not** a general YAML merge engine. Volumes, networks, ports, build, and every other Compose field are never inspected or merged. `files:` is unrelated to `completeness: union` above: union combines *presence across different kinds of source* (a local file and a manifest); `files:` merges *values across ordered layers of one Compose source*. A manifest entry can declare `file:` or `files:`, never both.
458
+
459
+ ---
460
+
413
461
  ## Supported languages / discovery
414
462
 
415
463
  Source-code discovery (what powers `undeclared` and `explain`'s "used in source" section) is AST-based for **Python** (`os.environ.get`, `os.getenv`, `os.environ[...]`) and pattern-based for **JavaScript/TypeScript** (`process.env.X`, `process.env["X"]`, `import.meta.env.X`, including single-level destructuring). No other language is discovered yet — the schema and validation commands work with any stack, but `undeclared`/`explain` can only see what's read from these two.
@@ -420,10 +468,84 @@ Source-code discovery (what powers `undeclared` and `explain`'s "used in source"
420
468
 
421
469
  ## Secret safety
422
470
 
423
- A supporting check alongside the contract, not the product itself. `secret = true` on a field tells EnvShield (and `setup`, and code generation) that it's sensitive — the schema records that fact, never the actual value. On top of that:
471
+ A supporting check alongside the contract, not the product itself. `secret = true` on a field tells EnvShield (and `setup`, and code generation) that it's sensitive — the schema records that fact, never the actual value.
472
+
473
+ ### Secret scanning
474
+
475
+ **`envshield scan`** looks for hardcoded secrets by pattern (Stripe, AWS, GitHub tokens, and more), and for env-var reads the schema doesn't declare, in one pass. Values are always redacted in output — a Stripe *publishable* key (`pk_...`) is never flagged, since detection matches the secret-key pattern (`sk_...`) by shape, not the variable's name.
476
+
477
+ ### Context-aware classification
478
+
479
+ Python candidates are analyzed for syntactic context to distinguish code patterns from secret values:
480
+
481
+ ```python
482
+ # Classified as code (suppressed):
483
+ client(key=CONFIG_CONSTANT)
484
+
485
+
486
+ def handler(api_key: str): ...
487
+
488
+
489
+ # Classified as likely secret (detected):
490
+ client(key="sk_live_abc123...")
491
+ ```
492
+
493
+ This reduces false positives on ordinary code patterns without broadly suppressing secret-shaped values. Classification is lightweight and syntactic — it recognizes type annotations, function arguments, and identifier references, but doesn't perform semantic analysis beyond what the tokenizer can see.
494
+
495
+ ### Git hook enforcement
496
+
497
+ **`envshield hook install`** wires secret scanning into your Git workflow with classification-based enforcement:
498
+
499
+ ```bash
500
+ envshield hook install
501
+ ```
502
+
503
+ The pre-commit hook runs `scan --staged --enforce`, which scans only staged content and applies an enforcement policy based on classification confidence.
504
+
505
+ #### Enforcement behavior
506
+
507
+ | Finding type | Interactive (local commit) | Non-interactive (CI) |
508
+ |---|---|---|
509
+ | No findings | Allow | Allow |
510
+ | Clearly code-shaped | Suppressed | Suppressed |
511
+ | Ambiguous | Block | Block |
512
+ | High-confidence secret | Block + explicit override | Block |
513
+
514
+ **Clearly code-shaped** candidates (type annotations, function keyword arguments, etc.) are suppressed by the classifier and don't produce findings.
515
+
516
+ **Ambiguous** findings — insufficient context to confidently classify — block without override in both modes.
517
+
518
+ **High-confidence secrets** (string literals matching secret patterns) block by default but offer an interactive override for local commits:
519
+
520
+ ```
521
+ ⚠ EnvShield found potential secrets
522
+
523
+ HIGH CONFIDENCE
524
+ src/config.py:42 Generic API Key [REDACTED (30 chars)]
525
+
526
+ [1] Abort commit
527
+ [2] Commit anyway
528
+
529
+ Choice: 2
530
+
531
+ Type COMMIT ANYWAY to confirm: COMMIT ANYWAY
532
+
533
+ Proceeding with commit. The finding remains in the repository history.
534
+ ```
535
+
536
+ The override requires exact confirmation text — `yes` or `Y` won't work. EOF (Ctrl+D) or Ctrl+C aborts. Raw secret values are never displayed in enforcement output.
537
+
538
+ In CI or other non-interactive contexts, high-confidence findings block with no prompt — the commit is refused, and the scan exits non-zero.
539
+
540
+ #### Scanning modes
541
+
542
+ ```bash
543
+ envshield scan # Audit entire codebase
544
+ envshield scan --staged # Scan only staged Git content
545
+ envshield scan --staged --enforce # Staged scan + enforcement policy
546
+ ```
424
547
 
425
- - **`envshield scan`** looks for hardcoded secrets by pattern (Stripe, AWS, GitHub tokens, and more), and for env-var reads the schema doesn't declare, in one pass. Values are always redacted in output — a Stripe *publishable* key (`pk_...`) is never flagged, since detection matches the secret-key pattern (`sk_...`) by shape, not the variable's name.
426
- - **`envshield hook install`** wires `scan --staged` into a pre-commit hook, so a real secret is caught before it's committed, not after.
548
+ The pre-commit hook uses the third form. `scan` without flags is for repository-wide audits, not commit-time enforcement.
427
549
 
428
550
  ---
429
551
 
@@ -211,7 +211,7 @@ Every command above documents its own options with `--help`; the full reference
211
211
 
212
212
  ## Git / CI workflow
213
213
 
214
- This is where the schema stops being documentation and starts being enforcement. Three concrete things EnvShield catches in a PR:
214
+ This is where the schema stops being documentation and starts being enforcement. Three concrete things EnvShield catches before a change reaches production:
215
215
 
216
216
  **A new environment variable used in code but missing from the schema:**
217
217
 
@@ -225,12 +225,14 @@ envshield undeclared origin/main HEAD --service api
225
225
  envshield schema diff origin/main HEAD --service api
226
226
  ```
227
227
 
228
- **A secret accidentally introduced into a commit** — the pre-commit hook runs `scan --staged` automatically:
228
+ **A secret accidentally introduced into a commit:**
229
229
 
230
230
  ```bash
231
231
  envshield hook install
232
232
  ```
233
233
 
234
+ The pre-commit hook runs `scan --staged --enforce`, applying classification-based enforcement with interactive override for high-confidence findings on local commits, and strict blocking in CI. See [Secret safety](#secret-safety) for enforcement behavior details.
235
+
234
236
  In CI, diff against the merge-base, not the base branch's current tip (which can move while the PR is open):
235
237
 
236
238
  ```yaml
@@ -249,7 +251,25 @@ jobs:
249
251
  envshield undeclared "$BASE" HEAD
250
252
  ```
251
253
 
252
- Both commands exit non-zero on a real finding — nothing beyond the exit code is required to gate a PR. Add `--json` if you want to post a custom summary instead.
254
+ Both commands exit non-zero on a real finding, and also on an error that prevented evaluation (a bad revision, a schema that failed to parse) — never a silent "clean" for something it couldn't actually check. Add `--json` if you want to post a custom summary instead.
255
+
256
+ ### SARIF output (`undeclared --sarif`)
257
+
258
+ `envshield undeclared` also supports `--sarif` — a standard [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/) log, for GitHub Code Scanning or any other SARIF-consuming CI tool, so a missing declaration is annotated on its exact file and line rather than only appearing in a CI log:
259
+
260
+ ```yaml
261
+ - run: |
262
+ BASE=$(git merge-base "origin/${{ github.base_ref }}" HEAD)
263
+ envshield undeclared "$BASE" HEAD --sarif > envshield.sarif
264
+ continue-on-error: true # upload the report even when it found something
265
+ - uses: github/codeql-action/upload-sarif@v3
266
+ with:
267
+ sarif_file: envshield.sarif
268
+ ```
269
+
270
+ `--sarif` only exists for `undeclared`, deliberately: each finding there has a real file and line to point at. `schema diff` findings describe the *contract* (a variable added, removed, or changed) rather than a source location, so forcing them into SARIF would mean inventing a line number that isn't real — use its `--json`/exit-code output instead, or annotate it yourself (e.g. a `jq` step over the JSON, emitting `::error::` workflow commands).
271
+
272
+ An error that prevents evaluating a service (a bad revision, a schema that fails to parse) is never turned into a fabricated SARIF result — it's reported as a SARIF `invocation` with `executionSuccessful: false`, so a consumer can't mistake "couldn't check" for "checked and clean."
253
273
 
254
274
  ---
255
275
 
@@ -351,6 +371,34 @@ A few things worth knowing:
351
371
 
352
372
  ---
353
373
 
374
+ ## Docker Compose base + override layers (`files:`)
375
+
376
+ A manifest registration normally points at one file:
377
+
378
+ ```yaml
379
+ manifests:
380
+ - file: docker-compose.yml
381
+ containers:
382
+ api: api
383
+ ```
384
+
385
+ A real Compose project commonly splits its configuration across a base file plus an override that Compose applies automatically (`docker-compose.yml` + `docker-compose.override.yml`, or any other `-f a -f b` combination). Registering only the base misses whatever the override adds; registering the override alone reports the base's own variables as falsely missing, since an override file is deliberately just a delta. `files:` registers the whole ordered layer set as **one** logical manifest instead:
386
+
387
+ ```yaml
388
+ manifests:
389
+ - files:
390
+ - docker-compose.yml
391
+ - docker-compose.override.yml
392
+ containers:
393
+ api: api
394
+ ```
395
+
396
+ Order matters — later files override earlier ones, exactly like `docker compose -f docker-compose.yml -f docker-compose.override.yml` itself. `check`/`doctor`/`explain` all validate the merged result as a single source, under one combined label (`"docker-compose.yml + docker-compose.override.yml"`).
397
+
398
+ This is Compose-specific merge behavior for the fields EnvShield already understands (`environment:`, `env_file:`) — **not** a general YAML merge engine. Volumes, networks, ports, build, and every other Compose field are never inspected or merged. `files:` is unrelated to `completeness: union` above: union combines *presence across different kinds of source* (a local file and a manifest); `files:` merges *values across ordered layers of one Compose source*. A manifest entry can declare `file:` or `files:`, never both.
399
+
400
+ ---
401
+
354
402
  ## Supported languages / discovery
355
403
 
356
404
  Source-code discovery (what powers `undeclared` and `explain`'s "used in source" section) is AST-based for **Python** (`os.environ.get`, `os.getenv`, `os.environ[...]`) and pattern-based for **JavaScript/TypeScript** (`process.env.X`, `process.env["X"]`, `import.meta.env.X`, including single-level destructuring). No other language is discovered yet — the schema and validation commands work with any stack, but `undeclared`/`explain` can only see what's read from these two.
@@ -361,10 +409,84 @@ Source-code discovery (what powers `undeclared` and `explain`'s "used in source"
361
409
 
362
410
  ## Secret safety
363
411
 
364
- A supporting check alongside the contract, not the product itself. `secret = true` on a field tells EnvShield (and `setup`, and code generation) that it's sensitive — the schema records that fact, never the actual value. On top of that:
412
+ A supporting check alongside the contract, not the product itself. `secret = true` on a field tells EnvShield (and `setup`, and code generation) that it's sensitive — the schema records that fact, never the actual value.
413
+
414
+ ### Secret scanning
415
+
416
+ **`envshield scan`** looks for hardcoded secrets by pattern (Stripe, AWS, GitHub tokens, and more), and for env-var reads the schema doesn't declare, in one pass. Values are always redacted in output — a Stripe *publishable* key (`pk_...`) is never flagged, since detection matches the secret-key pattern (`sk_...`) by shape, not the variable's name.
417
+
418
+ ### Context-aware classification
419
+
420
+ Python candidates are analyzed for syntactic context to distinguish code patterns from secret values:
421
+
422
+ ```python
423
+ # Classified as code (suppressed):
424
+ client(key=CONFIG_CONSTANT)
425
+
426
+
427
+ def handler(api_key: str): ...
428
+
429
+
430
+ # Classified as likely secret (detected):
431
+ client(key="sk_live_abc123...")
432
+ ```
433
+
434
+ This reduces false positives on ordinary code patterns without broadly suppressing secret-shaped values. Classification is lightweight and syntactic — it recognizes type annotations, function arguments, and identifier references, but doesn't perform semantic analysis beyond what the tokenizer can see.
435
+
436
+ ### Git hook enforcement
437
+
438
+ **`envshield hook install`** wires secret scanning into your Git workflow with classification-based enforcement:
439
+
440
+ ```bash
441
+ envshield hook install
442
+ ```
443
+
444
+ The pre-commit hook runs `scan --staged --enforce`, which scans only staged content and applies an enforcement policy based on classification confidence.
445
+
446
+ #### Enforcement behavior
447
+
448
+ | Finding type | Interactive (local commit) | Non-interactive (CI) |
449
+ |---|---|---|
450
+ | No findings | Allow | Allow |
451
+ | Clearly code-shaped | Suppressed | Suppressed |
452
+ | Ambiguous | Block | Block |
453
+ | High-confidence secret | Block + explicit override | Block |
454
+
455
+ **Clearly code-shaped** candidates (type annotations, function keyword arguments, etc.) are suppressed by the classifier and don't produce findings.
456
+
457
+ **Ambiguous** findings — insufficient context to confidently classify — block without override in both modes.
458
+
459
+ **High-confidence secrets** (string literals matching secret patterns) block by default but offer an interactive override for local commits:
460
+
461
+ ```
462
+ ⚠ EnvShield found potential secrets
463
+
464
+ HIGH CONFIDENCE
465
+ src/config.py:42 Generic API Key [REDACTED (30 chars)]
466
+
467
+ [1] Abort commit
468
+ [2] Commit anyway
469
+
470
+ Choice: 2
471
+
472
+ Type COMMIT ANYWAY to confirm: COMMIT ANYWAY
473
+
474
+ Proceeding with commit. The finding remains in the repository history.
475
+ ```
476
+
477
+ The override requires exact confirmation text — `yes` or `Y` won't work. EOF (Ctrl+D) or Ctrl+C aborts. Raw secret values are never displayed in enforcement output.
478
+
479
+ In CI or other non-interactive contexts, high-confidence findings block with no prompt — the commit is refused, and the scan exits non-zero.
480
+
481
+ #### Scanning modes
482
+
483
+ ```bash
484
+ envshield scan # Audit entire codebase
485
+ envshield scan --staged # Scan only staged Git content
486
+ envshield scan --staged --enforce # Staged scan + enforcement policy
487
+ ```
365
488
 
366
- - **`envshield scan`** looks for hardcoded secrets by pattern (Stripe, AWS, GitHub tokens, and more), and for env-var reads the schema doesn't declare, in one pass. Values are always redacted in output — a Stripe *publishable* key (`pk_...`) is never flagged, since detection matches the secret-key pattern (`sk_...`) by shape, not the variable's name.
367
- - **`envshield hook install`** wires `scan --staged` into a pre-commit hook, so a real secret is caught before it's committed, not after.
489
+ The pre-commit hook uses the third form. `scan` without flags is for repository-wide audits, not commit-time enforcement.
368
490
 
369
491
  ---
370
492
 
@@ -0,0 +1 @@
1
+ __version__ = "4.7.4"
@@ -0,0 +1,12 @@
1
+ # envshield/classifier/__init__.py
2
+ """
3
+ Context classifier for false-positive reduction in secret scanning.
4
+
5
+ This module provides the tokenizer and context classifier used to distinguish
6
+ code patterns (function keyword arguments, type annotations, destructuring)
7
+ from actual secret values, reducing false positives in the Generic API Key
8
+ detector.
9
+
10
+ See Milestone 2 (tokenizer) and Milestone 3 (classifier) documentation for
11
+ design rationale and limitations.
12
+ """