shadowshield 0.6.1__tar.gz → 0.6.3__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 (119) hide show
  1. {shadowshield-0.6.1 → shadowshield-0.6.3}/PKG-INFO +18 -15
  2. {shadowshield-0.6.1 → shadowshield-0.6.3}/README.md +17 -13
  3. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/BENCHMARKS.md +55 -17
  4. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/PRODUCTION_READINESS.md +54 -16
  5. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/RELEASING.md +46 -16
  6. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/configuration.md +6 -5
  7. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/plugins.md +3 -1
  8. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/security-model.md +4 -2
  9. {shadowshield-0.6.1 → shadowshield-0.6.3}/pyproject.toml +11 -3
  10. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/__init__.py +1 -1
  11. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/_security.py +107 -20
  12. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/cli.py +12 -7
  13. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/config/default.yaml +4 -0
  14. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/control.py +281 -45
  15. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/config.py +6 -0
  16. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/engine.py +49 -10
  17. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/session.py +34 -19
  18. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/anomaly.py +2 -1
  19. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/base.py +2 -0
  20. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/exfiltration.py +6 -0
  21. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/transformer.py +95 -10
  22. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/vector.py +84 -1
  23. shadowshield-0.6.3/src/shadowshield/eval/data/generalization_benchmark_v3.jsonl +40 -0
  24. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/eval/dataset.py +7 -6
  25. shadowshield-0.6.3/src/shadowshield/eval/harness.py +432 -0
  26. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_cli.py +58 -0
  27. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_config.py +2 -0
  28. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_control.py +204 -0
  29. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_detectors.py +17 -0
  30. shadowshield-0.6.3/tests/test_eval_harness.py +240 -0
  31. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_generalization_benchmarks.py +57 -1
  32. shadowshield-0.6.3/tests/test_http_security.py +326 -0
  33. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_production.py +55 -0
  34. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_shield.py +49 -0
  35. shadowshield-0.6.3/tests/test_site_security.py +120 -0
  36. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_transformer.py +135 -0
  37. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_vector.py +145 -0
  38. shadowshield-0.6.3/tests/test_workflow_policy.py +114 -0
  39. shadowshield-0.6.1/src/shadowshield/eval/harness.py +0 -206
  40. shadowshield-0.6.1/tests/test_http_security.py +0 -138
  41. {shadowshield-0.6.1 → shadowshield-0.6.3}/.gitignore +0 -0
  42. {shadowshield-0.6.1 → shadowshield-0.6.3}/LICENSE +0 -0
  43. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/CODE_REVIEW.md +0 -0
  44. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/COMPARISON.md +0 -0
  45. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/MARKET_LANDSCAPE.md +0 -0
  46. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/NEXT_STEPS.md +0 -0
  47. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/OWASP_LLM_TOP10.md +0 -0
  48. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/PLAN_REVIEW.md +0 -0
  49. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/REPORTER_SDK_SPEC.md +0 -0
  50. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/SAAS_STRATEGY.md +0 -0
  51. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/UPGRADE_OPPORTUNITIES.md +0 -0
  52. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/detectors.md +0 -0
  53. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/index.md +0 -0
  54. {shadowshield-0.6.1 → shadowshield-0.6.3}/docs/research/LANDSCAPE.md +0 -0
  55. {shadowshield-0.6.1 → shadowshield-0.6.3}/examples/agentic_security.py +0 -0
  56. {shadowshield-0.6.1 → shadowshield-0.6.3}/examples/custom_detector.py +0 -0
  57. {shadowshield-0.6.1 → shadowshield-0.6.3}/examples/langchain_integration.py +0 -0
  58. {shadowshield-0.6.1 → shadowshield-0.6.3}/examples/openai_integration.py +0 -0
  59. {shadowshield-0.6.1 → shadowshield-0.6.3}/examples/quickstart.py +0 -0
  60. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/config/__init__.py +0 -0
  61. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/__init__.py +0 -0
  62. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/canary.py +0 -0
  63. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/coverage.py +0 -0
  64. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/policy.py +0 -0
  65. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/shield.py +0 -0
  66. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/telemetry.py +0 -0
  67. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/core/types.py +0 -0
  68. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/__init__.py +0 -0
  69. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/alignment.py +0 -0
  70. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/canary.py +0 -0
  71. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/data/attack_corpus.txt +0 -0
  72. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/encoding.py +0 -0
  73. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/jailbreak.py +0 -0
  74. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/llm_check.py +0 -0
  75. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/pii.py +0 -0
  76. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/detectors/prompt_injection.py +0 -0
  77. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/eval/__init__.py +0 -0
  78. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/eval/data/adversarial_benchmark.jsonl +0 -0
  79. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/eval/data/builtin_benchmark.jsonl +0 -0
  80. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/eval/data/generalization_benchmark_v1.jsonl +0 -0
  81. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/eval/data/generalization_benchmark_v2.jsonl +0 -0
  82. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/integrations/__init__.py +0 -0
  83. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/integrations/agentdojo.py +0 -0
  84. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/integrations/mcp.py +0 -0
  85. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/middleware/__init__.py +0 -0
  86. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/middleware/base.py +0 -0
  87. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/middleware/decorators.py +0 -0
  88. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/middleware/langchain.py +0 -0
  89. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/middleware/openai.py +0 -0
  90. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/plugins/__init__.py +0 -0
  91. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/plugins/base.py +0 -0
  92. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/plugins/manager.py +0 -0
  93. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/py.typed +0 -0
  94. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/reporter.py +0 -0
  95. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/responders/__init__.py +0 -0
  96. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/responders/base.py +0 -0
  97. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/responders/blocker.py +0 -0
  98. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/responders/isolator.py +0 -0
  99. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/responders/rate_limiter.py +0 -0
  100. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/responders/sanitizer.py +0 -0
  101. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/server.py +0 -0
  102. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/static/dashboard.html +0 -0
  103. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/utils/__init__.py +0 -0
  104. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/utils/logging.py +0 -0
  105. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/utils/scoring.py +0 -0
  106. {shadowshield-0.6.1 → shadowshield-0.6.3}/src/shadowshield/utils/text.py +0 -0
  107. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/__init__.py +0 -0
  108. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_adversarial.py +0 -0
  109. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_agentic.py +0 -0
  110. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_mcp_guard.py +0 -0
  111. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_middleware.py +0 -0
  112. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_multilingual.py +0 -0
  113. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_pii_backends.py +0 -0
  114. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_policy.py +0 -0
  115. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_prompt_injection.py +0 -0
  116. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_responders.py +0 -0
  117. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_server.py +0 -0
  118. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_spans.py +0 -0
  119. {shadowshield-0.6.1 → shadowshield-0.6.3}/tests/test_telemetry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shadowshield
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: Unified open-source security shield for agentic AI systems — inspired by Sentinel & ShadowClaw.
5
5
  Project-URL: Homepage, https://shadowshield.xyz
6
6
  Project-URL: Documentation, https://github.com/0xsl1m/shadowshield#readme
@@ -28,7 +28,6 @@ Requires-Dist: httpx>=0.27
28
28
  Requires-Dist: pydantic>=2.5
29
29
  Requires-Dist: pyyaml>=6.0
30
30
  Requires-Dist: structlog>=24.1
31
- Requires-Dist: tiktoken>=0.6
32
31
  Provides-Extra: all
33
32
  Requires-Dist: datasets>=2.18; extra == 'all'
34
33
  Requires-Dist: fastapi>=0.110; extra == 'all'
@@ -149,8 +148,9 @@ print(result.safe_text) # safe fallback message
149
148
  > in-distribution **regression baseline, not a SOTA claim**. We publish the humbling
150
149
  > external numbers on purpose — a credible security tool shows its homework.
151
150
  > The frozen blind semantic snapshots are harder still: v1 reaches 26.7% recall /
152
- > 13.3% FPR and v2 reaches 0% / 10%. Run
153
- > `shadowshield benchmark --generalization v2`; these gaps are public by design.
151
+ > 13.3% FPR, v2 reaches 0% / 10%, and v3 reaches 30% / 30%; the 90-row aggregate
152
+ > is 22.2% / 20%. Run `shadowshield benchmark --generalization all`; these gaps
153
+ > are public by design.
154
154
 
155
155
  ---
156
156
 
@@ -206,8 +206,8 @@ pip install "shadowshield[dashboard]" # + FastAPI HTTP server & dashboard
206
206
  pip install "shadowshield[all]" # everything
207
207
  ```
208
208
 
209
- Core deps are intentionally small: `pydantic`, `structlog`, `pyyaml`, `httpx`,
210
- `tiktoken`. The ML classifier, Presidio PII, dataset loaders, and dashboard live
209
+ Core deps are intentionally small: `pydantic`, `structlog`, `pyyaml`, and
210
+ `httpx`. The ML classifier, Presidio PII, dataset loaders, and dashboard live
211
211
  behind extras — the default install pulls **no** heavy ML stack.
212
212
 
213
213
  ---
@@ -295,7 +295,7 @@ shadowshield detectors # list registered detectors
295
295
  shadowshield init > shield.yaml # write an annotated default config
296
296
  shadowshield benchmark # run the bundled offline benchmark
297
297
  shadowshield benchmark --adversarial
298
- shadowshield benchmark --generalization v2 # frozen blind semantic snapshot
298
+ shadowshield benchmark --generalization all # all frozen blind semantic snapshots
299
299
  shadowshield serve # HTTP server + live dashboard (needs [dashboard])
300
300
  ```
301
301
 
@@ -329,15 +329,18 @@ export SHADOWSHIELD_ADMIN_KEY="$(openssl rand -hex 32)"
329
329
  export SHADOWSHIELD_POLICY_KEY="$(openssl rand -hex 32)"
330
330
  export SHADOWSHIELD_POLICY_STATE_KEY="$(openssl rand -hex 32)"
331
331
  export SHADOWSHIELD_IMAGE_DIGEST="$(curl -fsSL \
332
- https://github.com/0xsl1m/shadowshield/releases/download/v0.6.1/container-digest.txt)"
332
+ https://github.com/0xsl1m/shadowshield/releases/download/v0.6.3/container-digest.txt)"
333
333
  docker compose pull
334
334
  docker compose up -d
335
335
  ```
336
336
 
337
- The release workflow scans that exact image, publishes it to GHCR, and attaches
338
- its digest plus CycloneDX SBOM to the matching GitHub Release. All four secrets
339
- must be independent. Terminate TLS at a trusted ingress before exposing it beyond
340
- localhost. See the
337
+ The release workflow scans that exact image, publishes it to GHCR, signs SLSA
338
+ provenance plus the CycloneDX SBOM with ephemeral GitHub OIDC/Sigstore identity,
339
+ verifies the registry-attached source/workflow claims and an anonymous digest
340
+ pull, and attaches the digest plus SBOM to the matching GitHub Release. Stable
341
+ release tags are accepted only from an exact green `main` commit. All four
342
+ secrets must be independent. Terminate TLS at a trusted ingress before exposing
343
+ it beyond localhost. See the
341
344
  [production-readiness roadmap](docs/PRODUCTION_READINESS.md) for launch gates,
342
345
  known scale limits, and the operator checklist.
343
346
 
@@ -347,14 +350,14 @@ Upgrading a control-plane volume from 0.6.0 requires an offline re-key because
347
350
  ```bash
348
351
  # Load the existing scan/admin keys first so the new Compose file can resolve.
349
352
  export SHADOWSHIELD_IMAGE_DIGEST="$(curl -fsSL \
350
- https://github.com/0xsl1m/shadowshield/releases/download/v0.6.1/container-digest.txt)"
353
+ https://github.com/0xsl1m/shadowshield/releases/download/v0.6.3/container-digest.txt)"
351
354
  export SHADOWSHIELD_POLICY_KEY="<existing-0.6.0-policy-key>"
352
355
  export SHADOWSHIELD_POLICY_STATE_KEY="$(openssl rand -hex 32)"
353
356
  # Stop every writer and snapshot the volume before running the migration.
354
357
  docker compose stop shadowshield
355
358
  docker compose run --rm --no-deps shadowshield \
356
359
  shadowshield migrate-policy-state --path /var/lib/shadowshield/policy-state.json
357
- # Preserve the reported .pre-0.6.1.bak file, then start 0.6.1.
360
+ # Preserve the reported .pre-0.6.1.bak file, then start 0.6.3.
358
361
  docker compose up -d
359
362
  ```
360
363
 
@@ -606,7 +609,7 @@ shadowshield serve --control --api-key SECRET # require X-API-Key / Bearer
606
609
  shadowshield schema # config JSON Schema (editor/CI validation)
607
610
  shadowshield owasp # OWASP LLM Top 10 (2025) coverage map
608
611
  shadowshield benchmark --adversarial # curated regression set
609
- shadowshield benchmark --generalization v2 # frozen blind semantic snapshot
612
+ shadowshield benchmark --generalization all # all frozen blind semantic snapshots
610
613
  ```
611
614
 
612
615
  ## Documentation
@@ -76,8 +76,9 @@ print(result.safe_text) # safe fallback message
76
76
  > in-distribution **regression baseline, not a SOTA claim**. We publish the humbling
77
77
  > external numbers on purpose — a credible security tool shows its homework.
78
78
  > The frozen blind semantic snapshots are harder still: v1 reaches 26.7% recall /
79
- > 13.3% FPR and v2 reaches 0% / 10%. Run
80
- > `shadowshield benchmark --generalization v2`; these gaps are public by design.
79
+ > 13.3% FPR, v2 reaches 0% / 10%, and v3 reaches 30% / 30%; the 90-row aggregate
80
+ > is 22.2% / 20%. Run `shadowshield benchmark --generalization all`; these gaps
81
+ > are public by design.
81
82
 
82
83
  ---
83
84
 
@@ -133,8 +134,8 @@ pip install "shadowshield[dashboard]" # + FastAPI HTTP server & dashboard
133
134
  pip install "shadowshield[all]" # everything
134
135
  ```
135
136
 
136
- Core deps are intentionally small: `pydantic`, `structlog`, `pyyaml`, `httpx`,
137
- `tiktoken`. The ML classifier, Presidio PII, dataset loaders, and dashboard live
137
+ Core deps are intentionally small: `pydantic`, `structlog`, `pyyaml`, and
138
+ `httpx`. The ML classifier, Presidio PII, dataset loaders, and dashboard live
138
139
  behind extras — the default install pulls **no** heavy ML stack.
139
140
 
140
141
  ---
@@ -222,7 +223,7 @@ shadowshield detectors # list registered detectors
222
223
  shadowshield init > shield.yaml # write an annotated default config
223
224
  shadowshield benchmark # run the bundled offline benchmark
224
225
  shadowshield benchmark --adversarial
225
- shadowshield benchmark --generalization v2 # frozen blind semantic snapshot
226
+ shadowshield benchmark --generalization all # all frozen blind semantic snapshots
226
227
  shadowshield serve # HTTP server + live dashboard (needs [dashboard])
227
228
  ```
228
229
 
@@ -256,15 +257,18 @@ export SHADOWSHIELD_ADMIN_KEY="$(openssl rand -hex 32)"
256
257
  export SHADOWSHIELD_POLICY_KEY="$(openssl rand -hex 32)"
257
258
  export SHADOWSHIELD_POLICY_STATE_KEY="$(openssl rand -hex 32)"
258
259
  export SHADOWSHIELD_IMAGE_DIGEST="$(curl -fsSL \
259
- https://github.com/0xsl1m/shadowshield/releases/download/v0.6.1/container-digest.txt)"
260
+ https://github.com/0xsl1m/shadowshield/releases/download/v0.6.3/container-digest.txt)"
260
261
  docker compose pull
261
262
  docker compose up -d
262
263
  ```
263
264
 
264
- The release workflow scans that exact image, publishes it to GHCR, and attaches
265
- its digest plus CycloneDX SBOM to the matching GitHub Release. All four secrets
266
- must be independent. Terminate TLS at a trusted ingress before exposing it beyond
267
- localhost. See the
265
+ The release workflow scans that exact image, publishes it to GHCR, signs SLSA
266
+ provenance plus the CycloneDX SBOM with ephemeral GitHub OIDC/Sigstore identity,
267
+ verifies the registry-attached source/workflow claims and an anonymous digest
268
+ pull, and attaches the digest plus SBOM to the matching GitHub Release. Stable
269
+ release tags are accepted only from an exact green `main` commit. All four
270
+ secrets must be independent. Terminate TLS at a trusted ingress before exposing
271
+ it beyond localhost. See the
268
272
  [production-readiness roadmap](docs/PRODUCTION_READINESS.md) for launch gates,
269
273
  known scale limits, and the operator checklist.
270
274
 
@@ -274,14 +278,14 @@ Upgrading a control-plane volume from 0.6.0 requires an offline re-key because
274
278
  ```bash
275
279
  # Load the existing scan/admin keys first so the new Compose file can resolve.
276
280
  export SHADOWSHIELD_IMAGE_DIGEST="$(curl -fsSL \
277
- https://github.com/0xsl1m/shadowshield/releases/download/v0.6.1/container-digest.txt)"
281
+ https://github.com/0xsl1m/shadowshield/releases/download/v0.6.3/container-digest.txt)"
278
282
  export SHADOWSHIELD_POLICY_KEY="<existing-0.6.0-policy-key>"
279
283
  export SHADOWSHIELD_POLICY_STATE_KEY="$(openssl rand -hex 32)"
280
284
  # Stop every writer and snapshot the volume before running the migration.
281
285
  docker compose stop shadowshield
282
286
  docker compose run --rm --no-deps shadowshield \
283
287
  shadowshield migrate-policy-state --path /var/lib/shadowshield/policy-state.json
284
- # Preserve the reported .pre-0.6.1.bak file, then start 0.6.1.
288
+ # Preserve the reported .pre-0.6.1.bak file, then start 0.6.3.
285
289
  docker compose up -d
286
290
  ```
287
291
 
@@ -533,7 +537,7 @@ shadowshield serve --control --api-key SECRET # require X-API-Key / Bearer
533
537
  shadowshield schema # config JSON Schema (editor/CI validation)
534
538
  shadowshield owasp # OWASP LLM Top 10 (2025) coverage map
535
539
  shadowshield benchmark --adversarial # curated regression set
536
- shadowshield benchmark --generalization v2 # frozen blind semantic snapshot
540
+ shadowshield benchmark --generalization all # all frozen blind semantic snapshots
537
541
  ```
538
542
 
539
543
  ## Documentation
@@ -15,6 +15,8 @@ shadowshield benchmark --adversarial
15
15
  # Independently authored blind semantic snapshots
16
16
  shadowshield benchmark --generalization v1
17
17
  shadowshield benchmark --generalization v2
18
+ shadowshield benchmark --generalization v3
19
+ shadowshield benchmark --generalization all
18
20
 
19
21
  # External public dataset (needs the datasets extra)
20
22
  pip install "shadowshield[datasets]"
@@ -24,14 +26,40 @@ pip install "shadowshield[transformers]"
24
26
  shadowshield benchmark --hf deepset/prompt-injections --split test --transformer
25
27
  ```
26
28
 
29
+ The CLI warms every configured detector before starting the latency clock. Its
30
+ JSON and text reports include a runtime-integrity section covering warmup,
31
+ readiness, and bounded per-detector failure counts. A benchmark with a warmup
32
+ failure, a detector that remains unready, or any detector error exits non-zero
33
+ and is marked **unreliable**; its confusion counts remain available for
34
+ diagnosis but must not be published as quality evidence. CLI benchmark shields
35
+ disable normal audit emission so logging I/O does not distort latency or corrupt
36
+ the command's machine-readable JSON stdout.
37
+
38
+ Programmatic callers can request the same behavior with
39
+ `evaluate_shield(shield, examples, warmup=True)`. The default remains
40
+ `warmup=False` for API compatibility. In both modes, readiness is checked after
41
+ the scans and per-scan detector error counters are aggregated.
42
+
43
+ Per-category output retains the original `total`, `flagged`, and flag-rate
44
+ fields, and additionally reports class-conditional TP/FP/TN/FN, recall, FPR,
45
+ and balanced accuracy. Recall is `null`/`n/a` for categories with no attack
46
+ rows; FPR is `null`/`n/a` for categories with no benign rows; balanced accuracy
47
+ is defined only when both classes are present.
48
+
49
+ Aggregate and per-category recall/FPR also include two-sided 95% Wilson score
50
+ intervals. Wilson intervals remain informative at small sample sizes and at
51
+ observed rates of 0% or 100%; an interval is `null`/`n/a` when that category has
52
+ no rows for the corresponding class. Existing scalar metric fields are
53
+ unchanged, and the interval fields are additive.
54
+
27
55
  ## 1. Bundled benchmark (in-distribution — a regression baseline)
28
56
 
29
57
  75 curated examples (40 attack / 35 benign, incl. 16 NotInject-style hard
30
58
  negatives). `balanced` mode:
31
59
 
32
- | recall | FPR | precision | F1 | p50 |
60
+ | recall (95% CI) | FPR (95% CI) | precision | F1 | p50 |
33
61
  |---:|---:|---:|---:|---:|
34
- | 100% | 0% | 100% | 100% | 0.16 ms |
62
+ | 100% [91.2%, 100%] | 0% [0%, 9.9%] | 100% | 100% | 0.16 ms |
35
63
 
36
64
  **This is a regression baseline and a smoke test — NOT a claim of real-world
37
65
  accuracy.** 100% on our own set just means we don't regress on the attack
@@ -43,34 +71,44 @@ The 36-row adversarial catalogue includes obfuscation, multilingual and indirect
43
71
  attacks, plus benign trigger-heavy counterexamples. It improved from
44
72
  15/2/16/3 to 18/0/18/0 (TP/FP/TN/FN):
45
73
 
46
- | recall | FPR | precision | F1 |
74
+ | recall (95% CI) | FPR (95% CI) | precision | F1 |
47
75
  |---:|---:|---:|---:|
48
- | 100% | 0% | 100% | 100% |
76
+ | 100% [82.4%, 100%] | 0% [0%, 17.6%] | 100% | 100% |
49
77
 
50
78
  This is still a curated regression set. The signatures were developed with these
51
79
  cases visible, so its perfect score is not a generalization claim.
52
80
 
53
81
  ## 3. Blind semantic snapshots (the anti-gaming result)
54
82
 
55
- Two balanced snapshots were authored independently without detector or regex
83
+ Three balanced snapshots were authored independently without detector or regex
56
84
  context, then frozen. They deliberately pair attacks with benign text containing
57
85
  similar roleplay, authority, debug, and policy vocabulary.
58
86
 
59
- Isolation protocol: the authoring task received only the five semantic category
60
- names, required row/balance counts, and a request for attack/benign contrast
61
- pairs. It did not receive detector source, signatures, or current predictions.
62
- The files were frozen before detector tuning and are integrity-pinned in tests:
87
+ Isolation protocol: the v1/v2 authoring tasks received only the five semantic
88
+ category names, required row/balance counts, and a request for attack/benign
89
+ contrast pairs. The v3 task received modality coverage requirements but no
90
+ detector source, signatures, or current predictions. The files were frozen before
91
+ detector tuning and are integrity-pinned in tests:
63
92
  v1 SHA-256 `b3281ba1a42d266bb930bbb41943016d47b38dbc822ff7cff5131f3448a0248f`;
64
- v2 SHA-256 `aa8b8c81c00a55bb65180e15ff743b6241d24845b3886e8e60b52b9b23db47fa`.
93
+ v2 SHA-256 `aa8b8c81c00a55bb65180e15ff743b6241d24845b3886e8e60b52b9b23db47fa`;
94
+ v3 SHA-256 `2285031e8143572311a522a4b6ec1a39c96a34ac2b42785f17818e8a145342bf`.
65
95
 
66
- | snapshot | rows | TP/FP/TN/FN | recall | FPR | balanced accuracy |
96
+ | snapshot | rows | TP/FP/TN/FN | recall (95% CI) | FPR (95% CI) | balanced accuracy |
67
97
  |---|---:|---:|---:|---:|---:|
68
- | v1 | 30 | 4/2/13/11 | 26.7% | 13.3% | 56.7% |
69
- | v2 | 20 | 0/1/9/10 | 0% | 10% | 45% |
70
-
71
- The narrow 0.6.1 fixes intentionally leave these results unchanged. They expose
72
- the core deterministic tier's semantic-generalization ceiling and are development
73
- evidence for the next classifier/conjunction tranche, not a number to hide.
98
+ | v1 | 30 | 4/2/13/11 | 26.7% [10.9%, 52.0%] | 13.3% [3.7%, 37.9%] | 56.7% |
99
+ | v2 | 20 | 0/1/9/10 | 0% [0%, 27.8%] | 10% [1.8%, 40.4%] | 45% |
100
+ | v3 | 40 | 6/6/14/14 | 30% [14.5%, 51.9%] | 30% [14.5%, 51.9%] | 50% |
101
+ | **aggregate** | **90** | **10/9/36/35** | **22.2% [12.5%, 36.3%]** | **20% [10.9%, 33.8%]** | **51.1%** |
102
+
103
+ The v3 snapshot was opened only after a detector candidate and acceptance bar
104
+ were frozen. That candidate raised aggregate recall to 55.6% but failed the
105
+ predeclared false-positive and balanced-accuracy gates (24.4% FPR; 65.6%
106
+ balanced accuracy), so it was discarded in full. No v3-driven detector tuning
107
+ ships in 0.6.2; the table records the unchanged released core.
108
+
109
+ These snapshots expose the core deterministic tier's semantic-generalization
110
+ ceiling and are development evidence for a fresh-corpus classifier/conjunction
111
+ tranche, not numbers to hide or a set to tune against.
74
112
 
75
113
  ## 4. External: `deepset/prompt-injections` (out-of-distribution)
76
114
 
@@ -1,21 +1,22 @@
1
1
  # Production readiness roadmap
2
2
 
3
- **Baseline:** ShadowShield 0.6.1 · **Audit date:** 2026-07-25
3
+ **Baseline:** ShadowShield 0.6.3 · **Audit date:** 2026-07-25
4
4
 
5
5
  This roadmap is the release gate for the Python library and the optional HTTP
6
- control plane. A checked item is implemented and locally verified; unchecked
7
- items require an explicit operator or maintainer decision before public launch.
6
+ control plane. A checked item is implemented and locally verified. The
7
+ operator-owned unchecked items do not block the library or static site, but they
8
+ must be resolved before exposing the optional HTTP control plane publicly.
8
9
 
9
10
  ## Current readiness
10
11
 
11
12
  | Area | State | Evidence / remaining action |
12
13
  |---|---|---|
13
- | Correctness | Ready | Full unit/integration suite; strict mypy; Ruff lint and format |
14
- | Packaging | Ready | Isolated sdist/wheel build, Twine metadata check, installed-wheel CI smoke test |
15
- | Runtime security | Ready with configuration | Early API-key/Bearer auth, body limits, restricted CORS, immutable protection floor, non-root read-only container |
16
- | Supply chain | Ready | Digest-pinned base, SHA-pinned Actions, PyPI OIDC, pre-push image vulnerability gate, anonymously verified GHCR digest, release-attached CycloneDX SBOM |
17
- | Observability | Ready for single process | Content-free telemetry, Prometheus endpoint, bounded in-memory event feed |
18
- | Detection quality | Beta | Curated adversarial: 100%/0% FPR; blind v1: 26.7%/13.3%; blind v2: 0%/10%; deepset core: 23.3%/0% |
14
+ | Correctness | Ready | 320 unit/integration cases; strict mypy; Ruff lint/format; CodeQL extended scanning |
15
+ | Packaging | Ready | Hash-locked build environment, byte-for-byte repeat build gate, Twine validation, installed-wheel smoke test |
16
+ | Runtime security | Ready with configuration | Early API-key/Bearer auth, bounded/deadlined body intake, detector-failure policy, restricted CORS, immutable protection floor, non-root read-only container |
17
+ | Supply chain | Ready | Digest/SHA-pinned build inputs, PyPI OIDC, exact-green-main release gates, immutable image/evidence handling, pre-push vulnerability gate, anonymous GHCR digest pull, registry-verified SLSA and CycloneDX attestations |
18
+ | Observability | Ready for single process | Content-free telemetry, detector-error counters, Prometheus endpoint, bounded in-memory event feed |
19
+ | Detection quality | Beta | Curated adversarial: 100%/0% FPR; blind v1–v3 aggregate: 22.2%/20%; v3: 30%/30%; deepset core: 23.3%/0% |
19
20
  | Scale / HA | Not yet | Counters, events, configuration, and rate limits are process-local |
20
21
  | Streaming | Not yet | Outputs must be buffered before scanning |
21
22
 
@@ -23,7 +24,7 @@ items require an explicit operator or maintainer decision before public launch.
23
24
 
24
25
  - [x] Repair control-plane syntax and restore test collection.
25
26
  - [x] Make optional-dependency tests and typing deterministic with or without MCP installed.
26
- - [x] Enforce an 80% branch-aware coverage floor (current local result: 85%).
27
+ - [x] Enforce an 80% branch-aware coverage floor (current local result: 86%).
27
28
  - [x] Test every declared Python runtime, 3.10 through 3.14, in CI.
28
29
  - [x] Build and validate both distribution formats and smoke-test the installed wheel.
29
30
  - [x] Audit declared dependencies in CI (local audit: no known vulnerabilities).
@@ -48,6 +49,36 @@ items require an explicit operator or maintainer decision before public launch.
48
49
  - [x] Pin the base image by digest, gate the exact release image on Trivy, publish
49
50
  it to GHCR, verify an anonymous digest pull, and attach its immutable digest
50
51
  plus CycloneDX SBOM to the release.
52
+ - [x] Sign release-image SLSA provenance and the CycloneDX SBOM with ephemeral
53
+ GitHub OIDC/Sigstore identity, then verify repository, workflow, runner, and
54
+ source-commit claims from the OCI registry before completing the release.
55
+ - [x] Reject release publication unless a stable, non-prerelease tag matches the
56
+ package version, resolves to `main`, and has a successful exact-SHA `main` CI run.
57
+ - [x] Reject symlink/non-regular durable policy-state files, use unpredictable
58
+ exclusive temporary files, and revision-check reads, migrations, and replaces.
59
+ - [x] Coalesce concurrent failed Transformer/Vector load generations while giving
60
+ every waiter a fresh bounded exception traceback and allowing a later retry.
61
+ - [x] Enable Dependabot security updates, extended CodeQL scanning, secret
62
+ push-protection, and repository-level enforcement of full-SHA Action pins.
63
+ - [x] Publish a third independently authored blind snapshot and reject—not
64
+ tune—the first frozen candidate when it missed the predeclared FPR/accuracy bar.
65
+ - [x] Make documented session guard calls record clean and blocked turns exactly
66
+ once so multi-turn and alignment checks receive the advertised trace.
67
+ - [x] Surface bounded, content-free detector failures in every result, audit,
68
+ benchmark, control event, and Prometheus metric; make strict mode fail closed.
69
+ - [x] Coalesce HTTP request frames, cap fragmentation, and apply one total body
70
+ deadline so authenticated slow/chunked bodies cannot pin all scan slots;
71
+ authenticate protected non-preflight methods before body/admission work.
72
+ - [x] Hash-lock release build and container dependencies, pin the Dockerfile
73
+ frontend, remove the unused mandatory tokenizer dependency, and require two
74
+ byte-identical distribution builds.
75
+ - [x] Upgrade maintained Actions to their Node 24 generations, bound every CI
76
+ job with a timeout, and refuse conflicting image tags or release assets while
77
+ allowing only attested exact-source digest recovery of an interrupted
78
+ container publication.
79
+ - [x] Make benchmark evidence fail visibly on warmup, readiness, or detector
80
+ errors and report class-conditional per-slice confusion metrics with 95%
81
+ Wilson confidence intervals.
51
82
 
52
83
  ## P1 — launch procedure (operator-owned)
53
84
 
@@ -60,7 +91,8 @@ items require an explicit operator or maintainer decision before public launch.
60
91
  - [ ] Set an explicit CORS allowlist or leave CORS disabled.
61
92
  - [ ] Run the adversarial and application-specific eval sets in permissive/shadow mode.
62
93
  - [ ] Choose thresholds from measured false-positive cost; record the accepted baseline.
63
- - [ ] Connect Prometheus and content-free telemetry; alert on error, block-rate, and latency shifts.
94
+ - [ ] Connect Prometheus and content-free telemetry; alert on detector errors,
95
+ readiness, block-rate, and latency shifts.
64
96
  - [ ] Load-test representative payload sizes and concurrency on the target instance class.
65
97
  - [ ] Record the deployed image digest and CI SBOM, then rehearse backup/restore
66
98
  and rollback against the target environment.
@@ -70,11 +102,17 @@ items require an explicit operator or maintainer decision before public launch.
70
102
 
71
103
  1. Add a rolling-window streaming scanner that blocks before unsafe output is emitted.
72
104
  2. Move rate limits, event history, and metrics aggregation to shared external stores for multi-worker HA.
73
- 3. Publish signed container images and provenance attestations from the release workflow.
74
- 4. Calibrate detector scores on a larger independently sourced corpus and publish confidence intervals.
75
- 5. Add multi-hour soak and network fault-injection tests for reporters, judges, and hot policy updates.
76
- 6. Split the current administrator credential into narrower observe and mutation scopes.
77
- 7. Design cross-process model/index coordination and durable Reporter spooling.
105
+ 3. Build a fresh independently sourced development corpus, then evaluate the
106
+ next detector candidate once on a new sealed snapshot and publish confidence intervals.
107
+ 4. Add multi-hour soak and network fault-injection tests for reporters, judges, and hot policy updates.
108
+ 5. Split the current administrator credential into narrower observe and mutation scopes.
109
+ 6. Design cross-process model/index coordination and durable Reporter spooling.
110
+ 7. Split unprivileged container build/scan from the write/OIDC publication job.
111
+ 8. Refactor release evidence into a draft-first pipeline with independently
112
+ promoted, already-scanned OCI artifacts.
113
+ 9. Pin optional model revisions, pre-cache them for offline production startup,
114
+ and bound model-loading time.
115
+ 10. Add vulnerability-audit matrices for every supported optional dependency stack.
78
116
 
79
117
  ## Go / no-go gates
80
118
 
@@ -8,9 +8,14 @@ this specific GitHub workflow directly. The pipeline is
8
8
  The same GitHub Release also triggers
9
9
  [`container-release.yml`](../.github/workflows/container-release.yml). It builds
10
10
  one image from the release tag, generates a CycloneDX SBOM, rejects fixable
11
- high/critical findings, pushes that exact scanned image to GHCR, verifies an
12
- anonymous pull by digest, and attaches `container-digest.txt` plus the SBOM to
13
- the GitHub Release.
11
+ high/critical findings, pushes that exact scanned image to GHCR, signs SLSA
12
+ provenance and CycloneDX attestations through GitHub's Sigstore-backed OIDC
13
+ service, verifies the signed provenance and an anonymous pull by digest, and
14
+ attaches `container-digest.txt` plus the SBOM to the GitHub Release.
15
+
16
+ Build tooling and the production container dependency graph are version- and
17
+ hash-locked in `requirements/build.lock` and `requirements/container.lock`.
18
+ Release jobs build the sdist and wheel twice and require byte-identical outputs.
14
19
 
15
20
  ## One-time setup (PyPI account owner only)
16
21
 
@@ -41,8 +46,15 @@ workflow deliberately fails its anonymous-pull gate if that happens.
41
46
  1. Let the first `Publish release container` run push the package.
42
47
  2. If the anonymous-pull step fails, open the `shadowshield` package settings
43
48
  under the `0xsl1m` account and change visibility to **Public**.
44
- 3. Re-run the failed workflow job. Do not publish the release digest to operators
45
- until the anonymous pull and evidence-attachment steps are green.
49
+ 3. Re-run the failed workflow job. It will reuse an existing image only when the
50
+ version and exact-commit tags resolve to the same digest and its revision and
51
+ version labels match the release **and** that digest already has trusted source
52
+ provenance from this workflow for the exact commit. It never overwrites a
53
+ conflicting or unattested tag. If an interruption happens after an image push
54
+ but before provenance is attached, delete the incomplete version/commit tags
55
+ from GHCR and re-run; the workflow deliberately refuses to bless them.
56
+ Do not publish the digest to operators until the anonymous-pull and
57
+ evidence-attachment steps are green.
46
58
 
47
59
  ## Cutting a release
48
60
 
@@ -58,17 +70,18 @@ gh release create vX.Y.Z --target "$merge_sha" \
58
70
  # -> PyPI publishing and exact-image scan/publish run independently
59
71
  ```
60
72
 
61
- Do not create the release while required checks are pending. A partial release
62
- is recoverable by fixing the failed workflow and re-running it, but the PyPI
63
- version itself cannot be replaced.
64
-
65
- To publish the **current** Python version without a new release (for example, the
66
- first publish of an already-tagged version), trigger only the PyPI workflow:
73
+ Do not create the release while required checks are pending. Once trusted source
74
+ provenance exists, a partial container release is recoverable by fixing the
75
+ failed workflow and re-running it: exact matching registry content is pulled,
76
+ rescanned, re-attested, and reused. An interruption between the first image push
77
+ and provenance attachment intentionally needs the incomplete GHCR tags removed
78
+ before retry. Conflicting tags and existing evidence assets are never
79
+ overwritten. The PyPI version itself cannot be replaced.
67
80
 
68
- ```bash
69
- gh workflow run publish.yml --ref main
70
- gh run watch # follow it
71
- ```
81
+ Both publication workflows independently reject non-stable tags, tag/source
82
+ version mismatches, commits outside `main`, and commits without a successful
83
+ exact-SHA `main` CI run. There is no manual-dispatch publication path; use
84
+ GitHub's **Re-run failed jobs** control to recover a failed release workflow.
72
85
 
73
86
  ## Verify
74
87
 
@@ -79,13 +92,30 @@ python -c "import shadowshield as ss; print(ss.__version__)"
79
92
 
80
93
  gh release download vX.Y.Z \
81
94
  --pattern container-digest.txt --pattern shadowshield-sbom.cdx.json
82
- docker pull "ghcr.io/0xsl1m/shadowshield@$(cat container-digest.txt)"
95
+ digest="$(cat container-digest.txt)"
96
+ image="ghcr.io/0xsl1m/shadowshield@$digest"
97
+ docker pull "$image"
98
+ gh attestation verify "oci://$image" \
99
+ --repo 0xsl1m/shadowshield \
100
+ --bundle-from-oci \
101
+ --signer-workflow 0xsl1m/shadowshield/.github/workflows/container-release.yml \
102
+ --source-digest "$(git rev-list -n 1 vX.Y.Z)" \
103
+ --deny-self-hosted-runners
104
+ gh attestation verify "oci://$image" \
105
+ --repo 0xsl1m/shadowshield \
106
+ --bundle-from-oci \
107
+ --predicate-type https://cyclonedx.org/bom \
108
+ --signer-workflow 0xsl1m/shadowshield/.github/workflows/container-release.yml \
109
+ --source-digest "$(git rev-list -n 1 vX.Y.Z)" \
110
+ --deny-self-hosted-runners
83
111
  ```
84
112
 
85
113
  ## Notes
86
114
 
87
115
  - **Versions are immutable.** A published version can never be re-uploaded, only
88
116
  *yanked*. Always confirm `twine check` is green (CI does this) before releasing.
117
+ - Regenerate dependency locks only in a dedicated reviewed PR, using the exact
118
+ commands recorded at the top of each lock file; CI audits both locked graphs.
89
119
  - Deploy the container by the exact `image@sha256` value in
90
120
  `container-digest.txt`, never by a mutable version tag or a local Compose
91
121
  rebuild. The release SBOM describes that exact pre-push-scanned image.
@@ -19,11 +19,11 @@ Generate an annotated starter file with `shadowshield init > shield.yaml`.
19
19
 
20
20
  ## Modes
21
21
 
22
- | Mode | block_threshold | medium→ | LLM check | rate limit |
23
- |---|---|---|---|---|
24
- | `strict` | 0.45 | block | on | on (30/60s) |
25
- | `balanced` | 0.65 | sanitize | off | off |
26
- | `permissive` | 0.85 | flag | off | off |
22
+ | Mode | block_threshold | medium→ | detector failure | LLM check | rate limit |
23
+ |---|---|---|---|---|---|
24
+ | `strict` | 0.45 | block | block | on | on (30/60s) |
25
+ | `balanced` | 0.65 | sanitize | report | off | off |
26
+ | `permissive` | 0.85 | flag | report | off | off |
27
27
 
28
28
  A mode seeds every default; any field you set in YAML or `for_mode(..., **kw)`
29
29
  layers on top.
@@ -34,6 +34,7 @@ layers on top.
34
34
  |---|---|
35
35
  | `mode` | preset posture (strict/balanced/permissive) |
36
36
  | `raise_on_block` | make `Shield.scan()` raise on a block (default false) |
37
+ | `fail_closed_on_detector_error` | block if any detector raises; failures are always reported as content-free metadata (strict defaults true) |
37
38
  | `block_threshold` | aggregate score that forces a block regardless of policy |
38
39
  | `policy.{none..critical}` | severity → decision mapping |
39
40
  | `detectors.<name>.enabled` | per-detector toggle |
@@ -56,7 +56,9 @@ shield = Shield.for_mode(
56
56
 
57
57
  A plugin that fails to import is **skipped**, not fatal — one broken third-party
58
58
  plugin can never disable the shield. A plugin detector that *raises during a scan*
59
- is caught by the engine's fail-safe wrapper and drops only its own contribution.
59
+ is caught by the engine's fail-safe wrapper, reported through bounded content-free
60
+ error metadata and metrics, and forces a block when
61
+ `fail_closed_on_detector_error` is enabled.
60
62
 
61
63
  ## Security note
62
64
 
@@ -67,8 +67,10 @@ identity to block on its own.
67
67
 
68
68
  ## Fail-safe defaults
69
69
 
70
- - **Detector errors fail safe.** A detector that raises drops only its own
71
- contribution; the scan continues with the other layers.
70
+ - **Detector errors are visible.** The other independent layers continue, while
71
+ bounded content-free metadata and metrics identify the failed detector. Strict
72
+ mode forces a block; balanced/permissive report the failure unless configured
73
+ to fail closed.
72
74
  - **`guard()` fails closed** (raises), **`filter()` fails soft** (returns the safe
73
75
  fallback). You choose per call site.
74
76
  - **Secrets are never echoed.** Secret matches are redacted from `Threat` records
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["hatchling>=1.21"]
2
+ requires = ["hatchling==1.31.0"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "shadowshield"
7
- version = "0.6.1"
7
+ version = "0.6.3"
8
8
  description = "Unified open-source security shield for agentic AI systems — inspired by Sentinel & ShadowClaw."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -43,7 +43,6 @@ dependencies = [
43
43
  "structlog>=24.1",
44
44
  "pyyaml>=6.0",
45
45
  "httpx>=0.27",
46
- "tiktoken>=0.6",
47
46
  ]
48
47
 
49
48
  [project.optional-dependencies]
@@ -142,8 +141,17 @@ python_version = "3.10"
142
141
  strict = true
143
142
  warn_unused_ignores = true
144
143
  ignore_missing_imports = true
144
+ # Allow targeted external-stub exclusions to respect ``follow_imports = skip``.
145
+ follow_imports_for_stubs = true
145
146
  files = ["src/shadowshield"]
146
147
 
148
+ [[tool.mypy.overrides]]
149
+ # NumPy 2.5 publishes PEP 695 aliases that cannot be parsed while mypy checks
150
+ # our Python 3.10 syntax target. The optional dependency boundary stays Any;
151
+ # every ShadowShield source module is still checked explicitly and strictly.
152
+ module = ["numpy", "numpy.*"]
153
+ follow_imports = "skip"
154
+
147
155
  [tool.coverage.run]
148
156
  source = ["shadowshield"]
149
157
  branch = true
@@ -71,7 +71,7 @@ from .plugins import PluginManager, ShadowShieldPlugin
71
71
  from .reporter import Reporter, attach_reporter
72
72
  from .responders import Responder, spotlight
73
73
 
74
- __version__ = "0.6.1"
74
+ __version__ = "0.6.3"
75
75
 
76
76
  __all__ = [
77
77
  "__version__",