envshield 4.7.3__tar.gz → 4.8.0__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 (58) hide show
  1. {envshield-4.7.3 → envshield-4.8.0}/PKG-INFO +90 -10
  2. {envshield-4.7.3 → envshield-4.8.0}/README.md +89 -9
  3. envshield-4.8.0/envshield/__init__.py +1 -0
  4. {envshield-4.7.3 → envshield-4.8.0}/envshield/cli.py +280 -113
  5. {envshield-4.7.3 → envshield-4.8.0}/envshield/config/manager.py +468 -65
  6. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/contract_diff.py +133 -51
  7. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/dependency_diff.py +24 -3
  8. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/dependency_snapshot.py +26 -6
  9. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/discovery.py +312 -21
  10. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/doctor.py +66 -48
  11. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/enforcement.py +6 -0
  12. envshield-4.8.0/envshield/core/evaluator.py +598 -0
  13. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/exceptions.py +116 -0
  14. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/explain.py +62 -92
  15. envshield-4.8.0/envshield/core/file_updater.py +296 -0
  16. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/generator.py +31 -15
  17. envshield-4.8.0/envshield/core/hooks_manager.py +669 -0
  18. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/importer.py +15 -2
  19. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/scanner.py +197 -504
  20. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/schema_manager.py +255 -108
  21. envshield-4.8.0/envshield/core/schema_scope.py +296 -0
  22. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/schema_snapshot.py +66 -6
  23. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/schema_types.py +139 -24
  24. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/service_discovery.py +39 -9
  25. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/setup_manager.py +54 -24
  26. envshield-4.8.0/envshield/core/source_files.py +124 -0
  27. {envshield-4.7.3 → envshield-4.8.0}/envshield/parsers/_base.py +6 -0
  28. {envshield-4.7.3 → envshield-4.8.0}/envshield/parsers/_docker_compose.py +32 -13
  29. {envshield-4.7.3 → envshield-4.8.0}/envshield.egg-info/PKG-INFO +90 -10
  30. {envshield-4.7.3 → envshield-4.8.0}/envshield.egg-info/SOURCES.txt +3 -0
  31. {envshield-4.7.3 → envshield-4.8.0}/pyproject.toml +1 -1
  32. envshield-4.7.3/envshield/__init__.py +0 -1
  33. envshield-4.7.3/envshield/core/file_updater.py +0 -135
  34. envshield-4.7.3/envshield/core/hooks_manager.py +0 -170
  35. {envshield-4.7.3 → envshield-4.8.0}/LICENSE.md +0 -0
  36. {envshield-4.7.3 → envshield-4.8.0}/envshield/__main__.py +0 -0
  37. {envshield-4.7.3 → envshield-4.8.0}/envshield/classifier/__init__.py +0 -0
  38. {envshield-4.7.3 → envshield-4.8.0}/envshield/classifier/context_classifier.py +0 -0
  39. {envshield-4.7.3 → envshield-4.8.0}/envshield/classifier/tokenizer.py +0 -0
  40. {envshield-4.7.3 → envshield-4.8.0}/envshield/config/__init__.py +0 -0
  41. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/__init__.py +0 -0
  42. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/inspector.py +0 -0
  43. {envshield-4.7.3 → envshield-4.8.0}/envshield/core/service_manager.py +0 -0
  44. {envshield-4.7.3 → envshield-4.8.0}/envshield/parsers/__init__.py +0 -0
  45. {envshield-4.7.3 → envshield-4.8.0}/envshield/parsers/_deployment.py +0 -0
  46. {envshield-4.7.3 → envshield-4.8.0}/envshield/parsers/_dotenv.py +0 -0
  47. {envshield-4.7.3 → envshield-4.8.0}/envshield/parsers/_kubernetes.py +0 -0
  48. {envshield-4.7.3 → envshield-4.8.0}/envshield/parsers/_python.py +0 -0
  49. {envshield-4.7.3 → envshield-4.8.0}/envshield/parsers/factory.py +0 -0
  50. {envshield-4.7.3 → envshield-4.8.0}/envshield/state.py +0 -0
  51. {envshield-4.7.3 → envshield-4.8.0}/envshield/utils/__init__.py +0 -0
  52. {envshield-4.7.3 → envshield-4.8.0}/envshield/utils/git_utils.py +0 -0
  53. {envshield-4.7.3 → envshield-4.8.0}/envshield/utils/paths.py +0 -0
  54. {envshield-4.7.3 → envshield-4.8.0}/envshield.egg-info/dependency_links.txt +0 -0
  55. {envshield-4.7.3 → envshield-4.8.0}/envshield.egg-info/entry_points.txt +0 -0
  56. {envshield-4.7.3 → envshield-4.8.0}/envshield.egg-info/requires.txt +0 -0
  57. {envshield-4.7.3 → envshield-4.8.0}/envshield.egg-info/top_level.txt +0 -0
  58. {envshield-4.7.3 → envshield-4.8.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: envshield
3
- Version: 4.7.3
3
+ Version: 4.8.0
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
@@ -165,10 +165,11 @@ What each field actually means:
165
165
  | `enum` | Value must be one of this list. Implies an enum type regardless of `type`. |
166
166
  | `pattern` | A regex the value must also match, e.g. `pattern = "^v\\d+\\.\\d+\\.\\d+$"`. |
167
167
  | `secret` | Marks the variable sensitive — masked input in `setup`, masked in generated code. `import` auto-suggests it from the variable's name/value; it never overwrites a `secret` value you've already committed. |
168
- | `defaultValue` | What `setup` writes automatically. A variable still has to be explicitly present in your local file even with a default — `check`/`doctor` name the default inline so a missing one is obvious. |
168
+ | `defaultValue` | What `setup` writes automatically (and `.env.example` and generated code show). A default never makes a variable optional: it still has to be explicitly present in your local file — `check`/`doctor` name the default inline so a missing one is obvious. |
169
+ | `required` | Optional. `required = false` makes a variable optional: it may be absent or blank, but a value that is set must still be valid. `required = true` is the default. Can't be combined with `requiredIf`. |
169
170
  | `description` | Shown in `setup`, copied into generated code. |
170
171
 
171
- **There is no separate `required = true/false` field.** Requiredness is derived: unconditional by default, waived by a `defaultValue`, or made conditional by `requiredIf` — see below.
172
+ Requiredness: every variable is required unless `required = false`, or `requiredIf` makes it conditional (see below). Any other key, or an unknown `type`, is an error — a typo like `default = "8000"` is reported (`did you mean 'defaultValue'?`) instead of silently ignored.
172
173
 
173
174
  ### Conditional requirements (`requiredIf`)
174
175
 
@@ -210,7 +211,7 @@ Four stages, in order: define the contract, validate against it, discover what c
210
211
 
211
212
  ### Validate
212
213
 
213
- **`envshield check`** — validate local configuration, and commonly-used Docker Compose and Kubernetes deployment-manifest patterns, against the same schema (see [Known limitations](#known-limitations)).
214
+ **`envshield check`** — validate local configuration, and commonly-used Docker Compose and Kubernetes deployment-manifest patterns, against the same schema (see [Known limitations](#known-limitations)). It also scans the service's own source code and fails on a variable the code reads but the schema doesn't declare (the same files the pre-commit `scan` checks). `--process-env` also counts variables set in your shell (for a name the schema declares, the shell's value wins over the file's). `--json` adds a versioned, value-free report per service.
214
215
 
215
216
  ```bash
216
217
  $ envshield check
@@ -270,7 +271,7 @@ Every command above documents its own options with `--help`; the full reference
270
271
 
271
272
  ## Git / CI workflow
272
273
 
273
- This is where the schema stops being documentation and starts being enforcement. Three concrete things EnvShield catches in a PR:
274
+ This is where the schema stops being documentation and starts being enforcement. Three concrete things EnvShield catches before a change reaches production:
274
275
 
275
276
  **A new environment variable used in code but missing from the schema:**
276
277
 
@@ -284,12 +285,14 @@ envshield undeclared origin/main HEAD --service api
284
285
  envshield schema diff origin/main HEAD --service api
285
286
  ```
286
287
 
287
- **A secret accidentally introduced into a commit** — the pre-commit hook runs `scan --staged` automatically:
288
+ **A secret accidentally introduced into a commit:**
288
289
 
289
290
  ```bash
290
291
  envshield hook install
291
292
  ```
292
293
 
294
+ 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.
295
+
293
296
  In CI, diff against the merge-base, not the base branch's current tip (which can move while the PR is open):
294
297
 
295
298
  ```yaml
@@ -466,10 +469,84 @@ Source-code discovery (what powers `undeclared` and `explain`'s "used in source"
466
469
 
467
470
  ## Secret safety
468
471
 
469
- 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:
472
+ 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.
473
+
474
+ ### Secret scanning
475
+
476
+ **`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.
477
+
478
+ ### Context-aware classification
479
+
480
+ Python candidates are analyzed for syntactic context to distinguish code patterns from secret values:
481
+
482
+ ```python
483
+ # Classified as code (suppressed):
484
+ client(key=CONFIG_CONSTANT)
485
+
486
+
487
+ def handler(api_key: str): ...
488
+
489
+
490
+ # Classified as likely secret (detected):
491
+ client(key="sk_live_abc123...")
492
+ ```
493
+
494
+ 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.
495
+
496
+ ### Git hook enforcement
497
+
498
+ **`envshield hook install`** wires secret scanning into your Git workflow with classification-based enforcement:
499
+
500
+ ```bash
501
+ envshield hook install
502
+ ```
503
+
504
+ The pre-commit hook runs `scan --staged --enforce`, which scans only staged content and applies an enforcement policy based on classification confidence.
505
+
506
+ #### Enforcement behavior
507
+
508
+ | Finding type | Interactive (local commit) | Non-interactive (CI) |
509
+ |---|---|---|
510
+ | No findings | Allow | Allow |
511
+ | Clearly code-shaped | Suppressed | Suppressed |
512
+ | Ambiguous | Block | Block |
513
+ | High-confidence secret | Block + explicit override | Block |
514
+
515
+ **Clearly code-shaped** candidates (type annotations, function keyword arguments, etc.) are suppressed by the classifier and don't produce findings.
516
+
517
+ **Ambiguous** findings — insufficient context to confidently classify — block without override in both modes.
518
+
519
+ **High-confidence secrets** (string literals matching secret patterns) block by default but offer an interactive override for local commits:
520
+
521
+ ```
522
+ ⚠ EnvShield found potential secrets
523
+
524
+ HIGH CONFIDENCE
525
+ src/config.py:42 Generic API Key [REDACTED (30 chars)]
526
+
527
+ [1] Abort commit
528
+ [2] Commit anyway
529
+
530
+ Choice: 2
531
+
532
+ Type COMMIT ANYWAY to confirm: COMMIT ANYWAY
533
+
534
+ Proceeding with commit. The finding remains in the repository history.
535
+ ```
536
+
537
+ 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.
538
+
539
+ In CI or other non-interactive contexts, high-confidence findings block with no prompt — the commit is refused, and the scan exits non-zero.
540
+
541
+ #### Scanning modes
542
+
543
+ ```bash
544
+ envshield scan # Audit entire codebase
545
+ envshield scan --staged # Scan only staged Git content
546
+ envshield scan --staged --enforce # Staged scan + enforcement policy
547
+ ```
470
548
 
471
- - **`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. Python candidates are context-aware: code-shaped patterns like `key=CONSTANT` or `api_key: str` may be suppressed when they're clearly not secret values, while secret-shaped string literals remain detectable regardless of context.
472
- - **`envshield hook install`** wires `scan --staged` into a pre-commit hook, so a real secret is caught before it's committed, not after.
549
+ The pre-commit hook uses the third form. `scan` without flags is for repository-wide audits, not commit-time enforcement.
473
550
 
474
551
  ---
475
552
 
@@ -493,12 +570,15 @@ envshield uninstall --yes # skip the confirmation prompt
493
570
 
494
571
  ## Known limitations
495
572
 
496
- EnvShield is built around the patterns real projects actually use. A few narrower cases aren't handled yet — none of them let a secret leak or let a genuinely missing required variable pass as clean:
573
+ EnvShield is built around the patterns real projects actually use. A few narrower cases aren't handled yet. None of them let a secret leak; the first one can let a value EnvShield couldn't see pass `check`:
497
574
 
575
+ - **Unverified manifest values pass the v4 gate.** When a deployment manifest only *points* at a value — a Kubernetes `secretKeyRef`/`configMapKeyRef` to an object outside the file, a Secret key in the same file, a Compose `${VAR}` or bare `KEY`, or a `${VAR-x}`/`${VAR:-x}` fallback — EnvShield can't see the deployed value. `check` reports these variables as unverified (`valid: null`, status `not_visible`, listed in `summary.unverified`, and named in the human output), but they still pass: exit code and `success` are unchanged. A fallback that itself violates the contract still fails. Making the gate act on unverified values is planned for v5.
498
576
  - **Docker Compose:** `${VAR}`, `${VAR:-default}`, and the no-colon `${VAR-default}` form are all supported. The `${VAR:?}`/`${VAR:+}` forms and embedded/multiple interpolation references in one value aren't evaluated yet.
499
577
  - **Kubernetes:** `env[].valueFrom.secretKeyRef`/`configMapKeyRef` are matched by the container's environment-variable name (`env[].name`) — name your schema variable after the env var, not the key — and, when the referenced Secret/ConfigMap is defined in the same manifest, the referenced key is validated to actually exist there. `envFrom.prefix` is applied. `initContainers` are selectable via `--container` but never auto-selected by default.
500
578
  - **Source discovery:** Python is AST-based; JavaScript/TypeScript is pattern-based. No other language is discovered yet.
501
579
  - **.env:** multiline quoted values aren't supported.
580
+ - **Windows:** the write-safety fix for a failed permission change (BL-162) hasn't been run on Windows; where `os.fchmod` is missing (Windows before Python 3.13) it's skipped, which is tested on Linux by simulation only.
581
+ - **CI:** runs on Ubuntu only (Python 3.10–3.12).
502
582
 
503
583
  These are bounded, tracked engineering work — see the [roadmap](ROADMAP.md) for what's next and what EnvShield deliberately isn't building.
504
584
 
@@ -106,10 +106,11 @@ What each field actually means:
106
106
  | `enum` | Value must be one of this list. Implies an enum type regardless of `type`. |
107
107
  | `pattern` | A regex the value must also match, e.g. `pattern = "^v\\d+\\.\\d+\\.\\d+$"`. |
108
108
  | `secret` | Marks the variable sensitive — masked input in `setup`, masked in generated code. `import` auto-suggests it from the variable's name/value; it never overwrites a `secret` value you've already committed. |
109
- | `defaultValue` | What `setup` writes automatically. A variable still has to be explicitly present in your local file even with a default — `check`/`doctor` name the default inline so a missing one is obvious. |
109
+ | `defaultValue` | What `setup` writes automatically (and `.env.example` and generated code show). A default never makes a variable optional: it still has to be explicitly present in your local file — `check`/`doctor` name the default inline so a missing one is obvious. |
110
+ | `required` | Optional. `required = false` makes a variable optional: it may be absent or blank, but a value that is set must still be valid. `required = true` is the default. Can't be combined with `requiredIf`. |
110
111
  | `description` | Shown in `setup`, copied into generated code. |
111
112
 
112
- **There is no separate `required = true/false` field.** Requiredness is derived: unconditional by default, waived by a `defaultValue`, or made conditional by `requiredIf` — see below.
113
+ Requiredness: every variable is required unless `required = false`, or `requiredIf` makes it conditional (see below). Any other key, or an unknown `type`, is an error — a typo like `default = "8000"` is reported (`did you mean 'defaultValue'?`) instead of silently ignored.
113
114
 
114
115
  ### Conditional requirements (`requiredIf`)
115
116
 
@@ -151,7 +152,7 @@ Four stages, in order: define the contract, validate against it, discover what c
151
152
 
152
153
  ### Validate
153
154
 
154
- **`envshield check`** — validate local configuration, and commonly-used Docker Compose and Kubernetes deployment-manifest patterns, against the same schema (see [Known limitations](#known-limitations)).
155
+ **`envshield check`** — validate local configuration, and commonly-used Docker Compose and Kubernetes deployment-manifest patterns, against the same schema (see [Known limitations](#known-limitations)). It also scans the service's own source code and fails on a variable the code reads but the schema doesn't declare (the same files the pre-commit `scan` checks). `--process-env` also counts variables set in your shell (for a name the schema declares, the shell's value wins over the file's). `--json` adds a versioned, value-free report per service.
155
156
 
156
157
  ```bash
157
158
  $ envshield check
@@ -211,7 +212,7 @@ Every command above documents its own options with `--help`; the full reference
211
212
 
212
213
  ## Git / CI workflow
213
214
 
214
- This is where the schema stops being documentation and starts being enforcement. Three concrete things EnvShield catches in a PR:
215
+ This is where the schema stops being documentation and starts being enforcement. Three concrete things EnvShield catches before a change reaches production:
215
216
 
216
217
  **A new environment variable used in code but missing from the schema:**
217
218
 
@@ -225,12 +226,14 @@ envshield undeclared origin/main HEAD --service api
225
226
  envshield schema diff origin/main HEAD --service api
226
227
  ```
227
228
 
228
- **A secret accidentally introduced into a commit** — the pre-commit hook runs `scan --staged` automatically:
229
+ **A secret accidentally introduced into a commit:**
229
230
 
230
231
  ```bash
231
232
  envshield hook install
232
233
  ```
233
234
 
235
+ 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.
236
+
234
237
  In CI, diff against the merge-base, not the base branch's current tip (which can move while the PR is open):
235
238
 
236
239
  ```yaml
@@ -407,10 +410,84 @@ Source-code discovery (what powers `undeclared` and `explain`'s "used in source"
407
410
 
408
411
  ## Secret safety
409
412
 
410
- 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:
413
+ 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.
414
+
415
+ ### Secret scanning
416
+
417
+ **`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.
418
+
419
+ ### Context-aware classification
420
+
421
+ Python candidates are analyzed for syntactic context to distinguish code patterns from secret values:
422
+
423
+ ```python
424
+ # Classified as code (suppressed):
425
+ client(key=CONFIG_CONSTANT)
426
+
427
+
428
+ def handler(api_key: str): ...
429
+
430
+
431
+ # Classified as likely secret (detected):
432
+ client(key="sk_live_abc123...")
433
+ ```
434
+
435
+ 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.
436
+
437
+ ### Git hook enforcement
438
+
439
+ **`envshield hook install`** wires secret scanning into your Git workflow with classification-based enforcement:
440
+
441
+ ```bash
442
+ envshield hook install
443
+ ```
444
+
445
+ The pre-commit hook runs `scan --staged --enforce`, which scans only staged content and applies an enforcement policy based on classification confidence.
446
+
447
+ #### Enforcement behavior
448
+
449
+ | Finding type | Interactive (local commit) | Non-interactive (CI) |
450
+ |---|---|---|
451
+ | No findings | Allow | Allow |
452
+ | Clearly code-shaped | Suppressed | Suppressed |
453
+ | Ambiguous | Block | Block |
454
+ | High-confidence secret | Block + explicit override | Block |
455
+
456
+ **Clearly code-shaped** candidates (type annotations, function keyword arguments, etc.) are suppressed by the classifier and don't produce findings.
457
+
458
+ **Ambiguous** findings — insufficient context to confidently classify — block without override in both modes.
459
+
460
+ **High-confidence secrets** (string literals matching secret patterns) block by default but offer an interactive override for local commits:
461
+
462
+ ```
463
+ ⚠ EnvShield found potential secrets
464
+
465
+ HIGH CONFIDENCE
466
+ src/config.py:42 Generic API Key [REDACTED (30 chars)]
467
+
468
+ [1] Abort commit
469
+ [2] Commit anyway
470
+
471
+ Choice: 2
472
+
473
+ Type COMMIT ANYWAY to confirm: COMMIT ANYWAY
474
+
475
+ Proceeding with commit. The finding remains in the repository history.
476
+ ```
477
+
478
+ 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.
479
+
480
+ In CI or other non-interactive contexts, high-confidence findings block with no prompt — the commit is refused, and the scan exits non-zero.
481
+
482
+ #### Scanning modes
483
+
484
+ ```bash
485
+ envshield scan # Audit entire codebase
486
+ envshield scan --staged # Scan only staged Git content
487
+ envshield scan --staged --enforce # Staged scan + enforcement policy
488
+ ```
411
489
 
412
- - **`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. Python candidates are context-aware: code-shaped patterns like `key=CONSTANT` or `api_key: str` may be suppressed when they're clearly not secret values, while secret-shaped string literals remain detectable regardless of context.
413
- - **`envshield hook install`** wires `scan --staged` into a pre-commit hook, so a real secret is caught before it's committed, not after.
490
+ The pre-commit hook uses the third form. `scan` without flags is for repository-wide audits, not commit-time enforcement.
414
491
 
415
492
  ---
416
493
 
@@ -434,12 +511,15 @@ envshield uninstall --yes # skip the confirmation prompt
434
511
 
435
512
  ## Known limitations
436
513
 
437
- EnvShield is built around the patterns real projects actually use. A few narrower cases aren't handled yet — none of them let a secret leak or let a genuinely missing required variable pass as clean:
514
+ EnvShield is built around the patterns real projects actually use. A few narrower cases aren't handled yet. None of them let a secret leak; the first one can let a value EnvShield couldn't see pass `check`:
438
515
 
516
+ - **Unverified manifest values pass the v4 gate.** When a deployment manifest only *points* at a value — a Kubernetes `secretKeyRef`/`configMapKeyRef` to an object outside the file, a Secret key in the same file, a Compose `${VAR}` or bare `KEY`, or a `${VAR-x}`/`${VAR:-x}` fallback — EnvShield can't see the deployed value. `check` reports these variables as unverified (`valid: null`, status `not_visible`, listed in `summary.unverified`, and named in the human output), but they still pass: exit code and `success` are unchanged. A fallback that itself violates the contract still fails. Making the gate act on unverified values is planned for v5.
439
517
  - **Docker Compose:** `${VAR}`, `${VAR:-default}`, and the no-colon `${VAR-default}` form are all supported. The `${VAR:?}`/`${VAR:+}` forms and embedded/multiple interpolation references in one value aren't evaluated yet.
440
518
  - **Kubernetes:** `env[].valueFrom.secretKeyRef`/`configMapKeyRef` are matched by the container's environment-variable name (`env[].name`) — name your schema variable after the env var, not the key — and, when the referenced Secret/ConfigMap is defined in the same manifest, the referenced key is validated to actually exist there. `envFrom.prefix` is applied. `initContainers` are selectable via `--container` but never auto-selected by default.
441
519
  - **Source discovery:** Python is AST-based; JavaScript/TypeScript is pattern-based. No other language is discovered yet.
442
520
  - **.env:** multiline quoted values aren't supported.
521
+ - **Windows:** the write-safety fix for a failed permission change (BL-162) hasn't been run on Windows; where `os.fchmod` is missing (Windows before Python 3.13) it's skipped, which is tested on Linux by simulation only.
522
+ - **CI:** runs on Ubuntu only (Python 3.10–3.12).
443
523
 
444
524
  These are bounded, tracked engineering work — see the [roadmap](ROADMAP.md) for what's next and what EnvShield deliberately isn't building.
445
525
 
@@ -0,0 +1 @@
1
+ __version__ = "4.8.0"