picosentry 0.16.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 (170) hide show
  1. picosentry-0.16.0/.editorconfig +21 -0
  2. picosentry-0.16.0/.pre-commit-hooks.yaml +60 -0
  3. picosentry-0.16.0/CHANGELOG.md +193 -0
  4. picosentry-0.16.0/CITATION.cff +35 -0
  5. picosentry-0.16.0/COMMERCIAL-LICENSE.md +20 -0
  6. picosentry-0.16.0/CONTRIBUTING.md +168 -0
  7. picosentry-0.16.0/Dockerfile +79 -0
  8. picosentry-0.16.0/LICENSE +91 -0
  9. picosentry-0.16.0/LICENSE-SUMMARY.md +29 -0
  10. picosentry-0.16.0/MANIFEST.in +25 -0
  11. picosentry-0.16.0/PKG-INFO +392 -0
  12. picosentry-0.16.0/README.md +330 -0
  13. picosentry-0.16.0/SECURITY.md +105 -0
  14. picosentry-0.16.0/mypy.ini +34 -0
  15. picosentry-0.16.0/pyproject.toml +158 -0
  16. picosentry-0.16.0/schemas/picosentry-scan-result.schema.json +101 -0
  17. picosentry-0.16.0/scripts/bundle-advisories.py +205 -0
  18. picosentry-0.16.0/scripts/ci.sh +411 -0
  19. picosentry-0.16.0/scripts/download-advisories.sh +51 -0
  20. picosentry-0.16.0/scripts/generate_release_evidence.py +194 -0
  21. picosentry-0.16.0/scripts/generate_sbom.py +130 -0
  22. picosentry-0.16.0/scripts/load_test.py +257 -0
  23. picosentry-0.16.0/scripts/management-server.py +136 -0
  24. picosentry-0.16.0/scripts/schema_diff_check.py +157 -0
  25. picosentry-0.16.0/scripts/sign-advisories.sh +62 -0
  26. picosentry-0.16.0/scripts/verify-docker-digest.sh +108 -0
  27. picosentry-0.16.0/scripts/verify-slsa.sh +141 -0
  28. picosentry-0.16.0/scripts/verify_release.sh +91 -0
  29. picosentry-0.16.0/setup.cfg +4 -0
  30. picosentry-0.16.0/src/picosentry/__init__.py +47 -0
  31. picosentry-0.16.0/src/picosentry/__main__.py +8 -0
  32. picosentry-0.16.0/src/picosentry/_network.py +77 -0
  33. picosentry-0.16.0/src/picosentry/advisory.py +368 -0
  34. picosentry-0.16.0/src/picosentry/audit.py +329 -0
  35. picosentry-0.16.0/src/picosentry/auth.py +617 -0
  36. picosentry-0.16.0/src/picosentry/cache.py +370 -0
  37. picosentry-0.16.0/src/picosentry/cli.py +1802 -0
  38. picosentry-0.16.0/src/picosentry/config.py +525 -0
  39. picosentry-0.16.0/src/picosentry/corpus/advisories/npm-critical-advisories.json +2196 -0
  40. picosentry-0.16.0/src/picosentry/corpus/generate_npm_top.py +311 -0
  41. picosentry-0.16.0/src/picosentry/corpus/ioc/colors_js.json +13 -0
  42. picosentry-0.16.0/src/picosentry/corpus/ioc/crossenv.json +15 -0
  43. picosentry-0.16.0/src/picosentry/corpus/ioc/event_stream_3.3.6.json +24 -0
  44. picosentry-0.16.0/src/picosentry/corpus/ioc/left_pad.json +14 -0
  45. picosentry-0.16.0/src/picosentry/corpus/ioc/nx_typosquat.json +20 -0
  46. picosentry-0.16.0/src/picosentry/corpus/ioc/shai_hulud.json +21 -0
  47. picosentry-0.16.0/src/picosentry/corpus/ioc/ua_parser_js.json +13 -0
  48. picosentry-0.16.0/src/picosentry/corpus/npm_top_packages.json +329 -0
  49. picosentry-0.16.0/src/picosentry/corpus_governance.py +515 -0
  50. picosentry-0.16.0/src/picosentry/corpus_share.py +462 -0
  51. picosentry-0.16.0/src/picosentry/crypto.py +527 -0
  52. picosentry-0.16.0/src/picosentry/daemon.py +784 -0
  53. picosentry-0.16.0/src/picosentry/detection_quality.py +495 -0
  54. picosentry-0.16.0/src/picosentry/docs/rules/L2-ADV-001.md +46 -0
  55. picosentry-0.16.0/src/picosentry/docs/rules/L2-BUND-001.md +46 -0
  56. picosentry-0.16.0/src/picosentry/docs/rules/L2-CRED-001.md +46 -0
  57. picosentry-0.16.0/src/picosentry/docs/rules/L2-DEPC-001.md +37 -0
  58. picosentry-0.16.0/src/picosentry/docs/rules/L2-ENGIN-001.md +55 -0
  59. picosentry-0.16.0/src/picosentry/docs/rules/L2-FORK-001.md +40 -0
  60. picosentry-0.16.0/src/picosentry/docs/rules/L2-IOC-001.md +65 -0
  61. picosentry-0.16.0/src/picosentry/docs/rules/L2-LICENSE-001.md +61 -0
  62. picosentry-0.16.0/src/picosentry/docs/rules/L2-LOCK-001.md +45 -0
  63. picosentry-0.16.0/src/picosentry/docs/rules/L2-MAINT-001.md +50 -0
  64. picosentry-0.16.0/src/picosentry/docs/rules/L2-MANI-001.md +45 -0
  65. picosentry-0.16.0/src/picosentry/docs/rules/L2-MANI-002.md +37 -0
  66. picosentry-0.16.0/src/picosentry/docs/rules/L2-OBFS-001.md +36 -0
  67. picosentry-0.16.0/src/picosentry/docs/rules/L2-OBFS-002.md +35 -0
  68. picosentry-0.16.0/src/picosentry/docs/rules/L2-OBFS-003.md +36 -0
  69. picosentry-0.16.0/src/picosentry/docs/rules/L2-OBFS-004.md +35 -0
  70. picosentry-0.16.0/src/picosentry/docs/rules/L2-PNPM-001.md +52 -0
  71. picosentry-0.16.0/src/picosentry/docs/rules/L2-POST-001.md +47 -0
  72. picosentry-0.16.0/src/picosentry/docs/rules/L2-PROV-001.md +46 -0
  73. picosentry-0.16.0/src/picosentry/docs/rules/L2-SIDELOAD-001.md +59 -0
  74. picosentry-0.16.0/src/picosentry/docs/rules/L2-TYPO-001.md +57 -0
  75. picosentry-0.16.0/src/picosentry/docs/rules/README.md +92 -0
  76. picosentry-0.16.0/src/picosentry/engine.py +412 -0
  77. picosentry-0.16.0/src/picosentry/enterprise.py +178 -0
  78. picosentry-0.16.0/src/picosentry/fleet.py +566 -0
  79. picosentry-0.16.0/src/picosentry/formatters/__init__.py +10 -0
  80. picosentry-0.16.0/src/picosentry/formatters/cyclonedx.py +208 -0
  81. picosentry-0.16.0/src/picosentry/formatters/github.py +98 -0
  82. picosentry-0.16.0/src/picosentry/formatters/json_fmt.py +17 -0
  83. picosentry-0.16.0/src/picosentry/formatters/ml_context.py +18 -0
  84. picosentry-0.16.0/src/picosentry/formatters/sarif.py +116 -0
  85. picosentry-0.16.0/src/picosentry/formatters/table.py +95 -0
  86. picosentry-0.16.0/src/picosentry/guards.py +288 -0
  87. picosentry-0.16.0/src/picosentry/ioc_registry.py +214 -0
  88. picosentry-0.16.0/src/picosentry/logging.py +219 -0
  89. picosentry-0.16.0/src/picosentry/management.py +414 -0
  90. picosentry-0.16.0/src/picosentry/metrics.py +222 -0
  91. picosentry-0.16.0/src/picosentry/models.py +382 -0
  92. picosentry-0.16.0/src/picosentry/policy.py +814 -0
  93. picosentry-0.16.0/src/picosentry/policy_lifecycle.py +387 -0
  94. picosentry-0.16.0/src/picosentry/py.typed +0 -0
  95. picosentry-0.16.0/src/picosentry/rules/__init__.py +196 -0
  96. picosentry-0.16.0/src/picosentry/rules/advisory_check.py +150 -0
  97. picosentry-0.16.0/src/picosentry/rules/bundled_shadow.py +151 -0
  98. picosentry-0.16.0/src/picosentry/rules/credential_read.py +334 -0
  99. picosentry-0.16.0/src/picosentry/rules/dep_confusion.py +166 -0
  100. picosentry-0.16.0/src/picosentry/rules/engine.py +208 -0
  101. picosentry-0.16.0/src/picosentry/rules/fork_drift.py +295 -0
  102. picosentry-0.16.0/src/picosentry/rules/ioc_detection.py +199 -0
  103. picosentry-0.16.0/src/picosentry/rules/license.py +248 -0
  104. picosentry-0.16.0/src/picosentry/rules/lockfile_drift.py +397 -0
  105. picosentry-0.16.0/src/picosentry/rules/maintainer_change.py +287 -0
  106. picosentry-0.16.0/src/picosentry/rules/manifest.py +151 -0
  107. picosentry-0.16.0/src/picosentry/rules/obfuscation.py +218 -0
  108. picosentry-0.16.0/src/picosentry/rules/pnpm_config.py +149 -0
  109. picosentry-0.16.0/src/picosentry/rules/pnpm_lock_parser.py +243 -0
  110. picosentry-0.16.0/src/picosentry/rules/post_install.py +156 -0
  111. picosentry-0.16.0/src/picosentry/rules/provenance.py +188 -0
  112. picosentry-0.16.0/src/picosentry/rules/sideloading.py +134 -0
  113. picosentry-0.16.0/src/picosentry/rules/typosquat.py +331 -0
  114. picosentry-0.16.0/src/picosentry/rules/utils.py +100 -0
  115. picosentry-0.16.0/src/picosentry/tenant.py +433 -0
  116. picosentry-0.16.0/src/picosentry/workspace.py +371 -0
  117. picosentry-0.16.0/src/picosentry.egg-info/PKG-INFO +392 -0
  118. picosentry-0.16.0/src/picosentry.egg-info/SOURCES.txt +168 -0
  119. picosentry-0.16.0/src/picosentry.egg-info/dependency_links.txt +1 -0
  120. picosentry-0.16.0/src/picosentry.egg-info/entry_points.txt +2 -0
  121. picosentry-0.16.0/src/picosentry.egg-info/requires.txt +26 -0
  122. picosentry-0.16.0/src/picosentry.egg-info/top_level.txt +1 -0
  123. picosentry-0.16.0/tests/test_action_exit_code.py +282 -0
  124. picosentry-0.16.0/tests/test_advisory_extended.py +793 -0
  125. picosentry-0.16.0/tests/test_audit.py +163 -0
  126. picosentry-0.16.0/tests/test_auth.py +301 -0
  127. picosentry-0.16.0/tests/test_auth_rbac_tls.py +450 -0
  128. picosentry-0.16.0/tests/test_baseline.py +325 -0
  129. picosentry-0.16.0/tests/test_benchmark.py +239 -0
  130. picosentry-0.16.0/tests/test_cache_governance.py +148 -0
  131. picosentry-0.16.0/tests/test_cli.py +1164 -0
  132. picosentry-0.16.0/tests/test_cli_extended.py +675 -0
  133. picosentry-0.16.0/tests/test_cli_unit.py +1004 -0
  134. picosentry-0.16.0/tests/test_config.py +376 -0
  135. picosentry-0.16.0/tests/test_config_integration.py +300 -0
  136. picosentry-0.16.0/tests/test_corpus_governance.py +184 -0
  137. picosentry-0.16.0/tests/test_corpus_share_extended.py +966 -0
  138. picosentry-0.16.0/tests/test_crypto.py +197 -0
  139. picosentry-0.16.0/tests/test_crypto_integration.py +222 -0
  140. picosentry-0.16.0/tests/test_cyclonedx_extended.py +245 -0
  141. picosentry-0.16.0/tests/test_daemon.py +143 -0
  142. picosentry-0.16.0/tests/test_daemon_extended.py +1079 -0
  143. picosentry-0.16.0/tests/test_dashboard_rbac.py +143 -0
  144. picosentry-0.16.0/tests/test_detection_quality.py +118 -0
  145. picosentry-0.16.0/tests/test_deterministic_output.py +169 -0
  146. picosentry-0.16.0/tests/test_docs.py +45 -0
  147. picosentry-0.16.0/tests/test_engine.py +191 -0
  148. picosentry-0.16.0/tests/test_enterprise.py +140 -0
  149. picosentry-0.16.0/tests/test_fleet.py +232 -0
  150. picosentry-0.16.0/tests/test_github.py +225 -0
  151. picosentry-0.16.0/tests/test_guards.py +517 -0
  152. picosentry-0.16.0/tests/test_init_and_sarif.py +260 -0
  153. picosentry-0.16.0/tests/test_ioc_detection_extended.py +270 -0
  154. picosentry-0.16.0/tests/test_ioc_registry_extended.py +586 -0
  155. picosentry-0.16.0/tests/test_license.py +258 -0
  156. picosentry-0.16.0/tests/test_logging_and_audit.py +176 -0
  157. picosentry-0.16.0/tests/test_management.py +928 -0
  158. picosentry-0.16.0/tests/test_obfuscation_extended.py +173 -0
  159. picosentry-0.16.0/tests/test_pnpm_lock_parser.py +296 -0
  160. picosentry-0.16.0/tests/test_policy_extended.py +1059 -0
  161. picosentry-0.16.0/tests/test_policy_lifecycle.py +159 -0
  162. picosentry-0.16.0/tests/test_realistic_fixtures.py +217 -0
  163. picosentry-0.16.0/tests/test_scanner.py +1486 -0
  164. picosentry-0.16.0/tests/test_sideloading.py +349 -0
  165. picosentry-0.16.0/tests/test_tenant.py +231 -0
  166. picosentry-0.16.0/tests/test_tenant_isolation.py +232 -0
  167. picosentry-0.16.0/tests/test_timeout_plugin.py +14 -0
  168. picosentry-0.16.0/tests/test_v0130_fixes.py +305 -0
  169. picosentry-0.16.0/tests/test_v091_fixes.py +244 -0
  170. picosentry-0.16.0/tests/test_workspace.py +227 -0
@@ -0,0 +1,21 @@
1
+ # EditorConfig for PicoSentry
2
+ # https://editorconfig.org
3
+
4
+ root = true
5
+
6
+ [*]
7
+ indent_style = space
8
+ indent_size = 4
9
+ end_of_line = lf
10
+ charset = utf-8
11
+ trim_trailing_whitespace = true
12
+ insert_final_newline = true
13
+
14
+ [*.{yml,yaml}]
15
+ indent_size = 2
16
+
17
+ [*.md]
18
+ trim_trailing_whitespace = false
19
+
20
+ [Makefile]
21
+ indent_style = tab
@@ -0,0 +1,60 @@
1
+ # PicoSentry pre-commit hooks
2
+ # Registered at: https://pre-commit.com/hooks.html
3
+ #
4
+ # Usage in your .pre-commit-config.yaml:
5
+ # repos:
6
+ # - repo: https://github.com/KirkForge/PicoSentry
7
+ # rev: v0.15.0
8
+ # hooks:
9
+ # - id: picosentry-scan
10
+ # - id: picosentry-check
11
+ # - id: picosentry-workspace
12
+ #
13
+ # All hooks are deterministic — same inputs = same output, every time.
14
+ # No network calls at scan time. Safe for offline/air-gapped environments.
15
+ # Requires: Python 3.10+
16
+
17
+ # ── Full supply-chain scan (all 21 rules) ──
18
+ - id: picosentry-scan
19
+ name: "PicoSentry — full scan"
20
+ description: "Deterministic npm/pnpm supply-chain scan (21 rules, fail on MEDIUM+)"
21
+ entry: picosentry scan . --format json --quiet --exit-code --fail-on medium --fail-on-rule-error
22
+ language: python
23
+ language_version: python3.10
24
+ additional_dependencies:
25
+ - pyyaml>=6.0
26
+ minimum_pre_commit_version: 2.9.0
27
+ types: [json, yaml]
28
+ files: ^(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json|pnpm-workspace\.yaml|\.picosentry\.yml)$
29
+ pass_filenames: false
30
+ always_run: true
31
+
32
+ # ── CI-optimized fast check ──
33
+ - id: picosentry-check
34
+ name: "PicoSentry — CI check"
35
+ description: "Quick health check (fail only on HIGH or CRITICAL findings, fail-closed on rule errors)"
36
+ entry: picosentry check --fail-on high --fail-on-rule-error
37
+ language: python
38
+ language_version: python3.10
39
+ additional_dependencies:
40
+ - pyyaml>=6.0
41
+ minimum_pre_commit_version: 2.9.0
42
+ types: [json, yaml]
43
+ files: ^(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$
44
+ pass_filenames: false
45
+ always_run: true
46
+
47
+ # ── Monorepo workspace scan ──
48
+ - id: picosentry-workspace
49
+ name: "PicoSentry — workspace scan"
50
+ description: "Scan entire monorepo workspace (all npm/pnpm projects, fail-closed)"
51
+ entry: picosentry workspace --format summary --fail-on medium
52
+ language: python
53
+ language_version: python3.10
54
+ additional_dependencies:
55
+ - pyyaml>=6.0
56
+ minimum_pre_commit_version: 2.9.0
57
+ types: [json, yaml]
58
+ files: ^(pnpm-workspace\.yaml|nx\.json|lerna\.json|turbo\.json)$
59
+ pass_filenames: false
60
+ always_run: true
@@ -0,0 +1,193 @@
1
+ # Changelog
2
+
3
+ ## [0.15.1] - 2026-05-21
4
+
5
+ ### Fixed
6
+ - **action.yml: exit-code enforcement bug** — composite action caught PicoSentry's nonzero exit but never propagated it. `exit-code: true` with findings now correctly fails the step with `::error::` annotation
7
+ - **release.yml: merge-conflict markers** — removed stray `>>>>>>> origin/main` lines that broke YAML parsing
8
+ - **Determinism claim corrected** — default JSON output includes audit timestamps and timing; `--deterministic-output` flag added for byte-identical output across runs. README updated to reflect this honestly
9
+ - **CI security theater** — removed `|| true` from pip-audit and self-scan steps in ci.yml; pip-audit no longer silently passes on vulnerability findings
10
+ - **ScanResult.to_dict() key ordering** — top-level keys now always sorted, matching the "sorted keys" guarantee
11
+
12
+ ### Added
13
+ - **`--deterministic-output` flag** — omits audit timestamps, `duration_ms`, `rule_timings_ms`, and per-rule `duration_ms` from JSON output for byte-stable reproducible CI artifacts. `--verify-determinism` implies this flag
14
+ - **`.github/workflows/test-action.yml`** — CI workflow testing the GitHub Action exit-code contract, deterministic output byte-identity, and `--verify-determinism`
15
+ - **Release workflow: test installed wheel** — `release.yml` now runs `pytest` against the built wheel before publishing
16
+ - **`tests/test_action_exit_code.py`** — 19 tests proving action enforcement, shell logic replication, and exit-code behavior
17
+ - **`tests/test_deterministic_output.py`** — 11 tests proving byte-identical JSON, audit/timing omission, config loading, and key ordering
18
+ - **`tests/test_realistic_fixtures.py`** — 33 tests: realistic npm project (lockfile + node_modules), all-fixture smoke tests
19
+ - **`tests/fixtures/realistic_npm/`** — realistic fixture with `package.json`, `package-lock.json`, and `node_modules/` (express, axios)
20
+
21
+ ### Changed
22
+ - **STATE.md** — rewritten from inflated self-scoring to honest maturity assessment
23
+ - **CONTRIBUTING.md** — documented `pip install -e ".[dev]"` requirement; updated determinism verification to show `--deterministic-output`
24
+ - **`ScanResult.to_dict()`** and **`RuleExecution.to_dict()`** — accept `deterministic_output` parameter; when true, timing fields are omitted
25
+ - **`format_json()`** — passes `deterministic_output` through to `ScanResult.to_json()`
26
+
27
+ ## [0.14.0] - Unreleased (enterprise-ready)
28
+
29
+ ### Added
30
+ - **Workspace/monorepo scanning** — `picosentry workspace` discovers and scans all npm/pnpm projects in a monorepo (Nx, Turborepo, Lerna, pnpm workspaces)
31
+ - **Custom IoC registry** — register organization-specific indicators of compromise (`picosentry ioc register`)
32
+ - **Enterprise Docker image** — multi-stage build with non-root user, health check, venv isolation
33
+ - **Full CycloneDX SBOM** — walks node_modules for complete component inventory with purl and hashes
34
+ - **Structured JSON logging** — `--log-format json` for SIEM integration (Splunk, ELK, Datadog)
35
+ - **CI type checking** — mypy --strict enforced on every push
36
+ - **CI coverage reporting** — pytest-cov with XML report upload
37
+ - **CI linting** — ruff check + format enforcement in CI
38
+ - **Python 3.13** in CI test matrix
39
+ - **Release workflow** — automated PyPI publish + GitHub Release on tag push
40
+ - **Dependabot** configuration for pip and GitHub Actions
41
+ - **CODEOWNERS** file for code review routing
42
+ - **.editorconfig** for consistent formatting across contributors
43
+ - **Pre-commit hooks** — 3 hooks: full scan, fast CI check, workspace scan
44
+ - **Performance benchmark suite** — cold start, rule timing, format throughput targets
45
+ - **Self-SBOM generation** — scripts/generate_sbom.py for dogfooding
46
+
47
+ ### Changed
48
+ - CycloneDX timestamp is now deterministic (derived from scan_id, not wall-clock time)
49
+ - CycloneDX vulnerability IDs use descriptive `PICOSENTRY-` prefix
50
+ - Credential scanner excludes `dist/`, `build/`, `out/` directories and `.min.*` files
51
+ - CI pre-push script rewritten for Python (was Node.js-centric)
52
+ - CI scan-self SARIF upload gated to public repos only
53
+ - Rules documentation: corrected "15" → "19" detector rules
54
+ - Dockerfile: multi-stage build with non-root user and health check
55
+ - State.md: enterprise readiness score updated to 94/100
56
+
57
+ ### Fixed
58
+ - LICENSE copyright holder: fixed to "KirkForge" (was "55N10E")
59
+ - Rules documentation rule count now matches actual RULE_INFO (19)
60
+
61
+
62
+ ### Added
63
+ - **CycloneDX 1.5 SBOM output** (`--format cyclonedx`) — enterprise standard SBOM compatible with OWASP Dependency-Track
64
+ - **`picosentry check` command** — CI-optimized health gate with exit-code only
65
+ - **Symlink traversal guards** — `rglob` calls now skip symlinks to prevent scanning outside project boundaries
66
+ - **`__all__` exports** on all 15 rule modules for explicit public API
67
+ - **`py.typed` marker** (PEP 561) for type-checker compatibility
68
+ - **`mypy.ini`** with strict type checking configuration
69
+
70
+ ### Changed
71
+ - Lockfile v1 parser: strips version suffix from keys for correct package name matching
72
+ - Provenance path detection: uses `Path.parts` instead of fragile `str()` comparison
73
+ - Config merge: uses `getattr` with safe defaults instead of direct attribute access
74
+ - Engine version detection: prefers `importlib.metadata` over regex source parsing
75
+ - CLI eliminates double config load — config loaded once and passed through
76
+ - Typosquat optimized with length and first-char pruning (~90% fewer computations)
77
+
78
+ ### Fixed
79
+ - `merge_cli()` no longer crashes on minimal test Namespaces
80
+ - Symlink traversal prevented in obfuscation, credential_read, and engine scans
81
+ - All references migrated from `55N10E/SecDev_kimi` to `KirkForge/PicoSentry`
82
+
83
+
84
+ All notable changes to PicoSentry are documented here.
85
+
86
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
87
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88
+
89
+ ## [0.12.0] - 2026-05-16
90
+
91
+ ### Added
92
+ - **All 19 rule IDs now registered in engine** (was 15): sub-rules L2-OBFS-002/003/004 and L2-MANI-002 are individually registered
93
+ - **Sub-rule filtering**: `--rules L2-OBFS-002` now returns only OBFS-002 findings (engine deduplicates shared functions, filters findings by requested rule_ids)
94
+ - 3 new tests for sub-rule filtering behavior
95
+
96
+ ### Changed
97
+ - `picosentry rules` now correctly shows 19 rules (was 15)
98
+ - `RULE_COUNT` = 19 (was 15)
99
+ - Engine scan loop deduplicates function calls when multiple rule_ids share the same detector
100
+ - Documented immutability contract for `apply_severity_overrides` and `apply_overrides`
101
+
102
+ ### Fixed
103
+ - Removed stray `finding missing file` line in `DeterministicGuard.check()` stats verification block
104
+ - Rule count inconsistency resolved: RULE_INFO (19) matches engine registration (19)
105
+
106
+ ## [0.11.0] - 2026-05-16
107
+
108
+ ### Added
109
+ - **Deterministic guard stack** (`guards.py`): `DeterministicGuard`, `deterministic_hash()`, `fingerprint_scan()`, `verify_determinism()`, `diff_scans()`, `DeterminismViolation`
110
+ - **`--verify-determinism` CLI flag**: Runs scan twice, compares SHA-256 of deterministic fields. Exit 0 if match, 4 if violation
111
+ - **SECURITY.md**: Vulnerability disclosure policy (90-day coordinated disclosure)
112
+ - **CI determinism gate**: GitHub Actions step that verifies determinism on every push
113
+ - Stats consistency guard: `DeterministicGuard.check()` now verifies `findings_by_severity` and `findings_by_rule` match actual findings
114
+ - **`ScanResult.apply_overrides()`**: Clean API for replacing findings + recomputing stats
115
+ - **`user_corpus_dir()`**: XDG-aware corpus directory (`~/.local/share/picosentry/corpus/` or `$PICOCORPUS_DIR`)
116
+ - **Response size limit** on `update` command (10MB per page) — prevents OOM from oversized responses
117
+ - **Response format validation** on `update` command — rejects non-JSON and missing `objects` key
118
+ - Typosquat corpus fallback logging: warns on corrupt file, info on missing file
119
+ - Python 3.13 classifier in pyproject.toml
120
+
121
+ ### Changed
122
+ - `picosentry update` now writes to user corpus dir instead of package install dir (fixes PEP 668 compatibility)
123
+ - `ScanEngine` resolves corpus: explicit `--corpus` > user dir (`~/.local/share/picosentry/corpus/`) > built-in
124
+ - `picosentry rules` now shows all 19 rule IDs from `RULE_INFO` (not just 15 engine-registered functions)
125
+ - `picosentry version` shows "19 (15 detector functions)"
126
+ - `files_scanned` now counts only relevant file types and skips `.git`, `__pycache__`, `.cache`
127
+ - `packages_scanned` now counts scoped packages (`@scope/pkg`) individually
128
+ - Removed `Typing :: Typed` classifier (no `.pyi` stub files yet)
129
+
130
+ ### Fixed
131
+ - `update` command no longer writes inside installed package directory
132
+ - `update` command validates response format before processing
133
+ - Rule count inconsistency between `RULE_INFO` (19) and engine (15) resolved
134
+
135
+ ## [0.10.0] - 2026-05-16
136
+
137
+ ### Added
138
+ - **`--verify-determinism` flag**: Runs scan twice, compares SHA-256 of deterministic fields
139
+ - **SECURITY.md**: Vulnerability disclosure policy
140
+ - **CI determinism gate**: GitHub Actions step for determinism verification
141
+ - 4 new tests for determinism verification
142
+
143
+ ### Fixed
144
+ - `format_json` included `duration_ms`/`rule_timings_ms` in determinism hash — now excluded
145
+ - Missing `tempfile` import in violation path
146
+
147
+ ## [0.9.1] - 2026-05-15
148
+
149
+ ### Added
150
+ - `--version` flag
151
+ - Baseline update optimization
152
+ - CONTRIBUTING.md
153
+
154
+ ### Changed
155
+ - 19 rules, 291 tests, 5 output formats
156
+
157
+ ## [0.1.0] - 2026-05-14
158
+
159
+ ### Added
160
+ - Initial release: 10 detector rules, 27 tests
161
+ - Scanner module extracted from Iron Dome
162
+ - 4 output formats: json, sarif, table, ml-context
163
+ - Determinism enforced: `sha256(findings_a) == sha256(findings_b)` on identical inputs
164
+
165
+ [0.12.0]: https://github.com/KirkForge/PicoSentry/compare/v0.11.0...v0.12.0
166
+ [0.11.0]: https://github.com/KirkForge/PicoSentry/compare/v0.10.0...v0.11.0
167
+ [0.10.0]: https://github.com/KirkForge/PicoSentry/compare/v0.9.1...v0.10.0
168
+ [0.9.1]: https://github.com/KirkForge/PicoSentry/releases/tag/v0.9.1
169
+ [0.1.0]: https://github.com/KirkForge/PicoSentry/releases/tag/v0.1.0
170
+ ## [0.16.1] - 2026-05-29
171
+
172
+ ### Security
173
+
174
+ - **§1.1 Typosquat FP rate** — short package names (≤4 chars) no longer emit HIGH-severity typosquat findings. Distance-2 short names → LOW, distance-1 short → MEDIUM. Normal-length names use length-ratio scoring for appropriate severity. Prevents CI breakage from common packages like `swr`, `ky`, `clsx`
175
+ - **§1.4 Arbitrary scan paths** — `/scan` endpoint now rejects absolute paths and `..`, resolves targets against `PICOSENTRY_SCAN_ROOT` (or CWD), and verifies the resolved path stays within the workspace root. Prefix-confusion attack (e.g. `/app-sekrit` passing `/app` check) is also blocked
176
+ - **§1.5 Missing authz on /metrics and /dashboard** — `check_authorization()` is now enforced on `/metrics`, `/metrics/json`, and `/dashboard`. Previously only `_check_auth()` (authentication) was called, allowing any authenticated low-privilege token to read cross-tenant data
177
+ - **§2.1 Default bind address** — `DEFAULT_HOST` changed from `0.0.0.0` to `127.0.0.1`. Non-loopback bind with `auth=off` now prints a CRITICAL warning. Prevents accidental exposure of admin access on network interfaces
178
+ - **§2.2 Token scope dead code** — scope lookup in `check_token_auth` now checks `identity in config.scopes` (preserving empty scope lists = no permissions) before falling through to `token_default` → `default_scopes`. Previously `or` chain treated empty lists as falsy, silently escalating permissions
179
+ - **§2.5 Fail-open verify** — `verify_content()` now returns `False` for unsigned bundles (fail-closed). Callers that need to accept unsigned bundles should pass `allow_unsigned=True`. `corpus_require_signature` defaults to `True` across both `Config` and `Policy` classes
180
+ - **§3.4 Silent symlink fallback** — `engine.py` now logs a warning when symlinked corpus files are skipped and the version hash changes, instead of silently degrading to the builtin top-100 list
181
+
182
+ ### Changed
183
+
184
+ - Typosquat severity now reflects match quality: short-name matches at LOW/MEDIUM, distance-1 matches with ≥0.8 length ratio at HIGH, distance-2 with low ratio at LOW
185
+ - `Policy` dataclass `corpus_require_signature` default changed from `False` to `True` (fail-closed)
186
+
187
+ ### Documentation
188
+
189
+ - Updated `docs/security/threat-model.md` — added attack surfaces for scan path traversal, default bind exposure, authz enforcement, corpus signature verification, typosquat FP noise
190
+ - Updated `docs/security/access-control-policy.md` — added token-mode scope resolution, authz enforcement on all endpoints, scan path restrictions, fail-closed defaults table
191
+ - Updated `docs/runbooks/daemon-auth-failures.md` — added scope resolution troubleshooting, authz enforcement note, bind address change, scan path restrictions
192
+ - Updated `docs/ENTERPRISE_DEPLOYMENT.md` — added v0.16.1 security defaults section, dashboard endpoints to scope-enforced table, scan path restriction note, token scope resolution warning
193
+ - Updated `SECURITY.md` — added fail-closed defaults table, typosquat detection section, supported versions bump
@@ -0,0 +1,35 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use PicoSentry in your research or security pipeline, please cite it as below."
3
+ title: "PicoSentry: Deterministic Supply-Chain Scanner for npm/pnpm"
4
+ authors:
5
+ - given-names: Henrik
6
+ family-names: Kirk
7
+ name: "Henrik Kirk"
8
+ - given-names: GLM
9
+ family-names: "5.1"
10
+ name: "GLM-5.1"
11
+ - given-names: PicoClaw
12
+ family-names: "🦞"
13
+ name: "PicoClaw"
14
+ url: "https://github.com/KirkForge/PicoSentry"
15
+ repository-code: "https://github.com/KirkForge/PicoSentry"
16
+ version: "0.16.0"
17
+ date-released: "2026-05-28"
18
+ license: PolyForm-Noncommercial-1.0.0
19
+ abstract: >-
20
+ PicoSentry is a deterministic, offline supply-chain scanner for npm
21
+ and pnpm ecosystems, designed to be safe for ML pipelines and
22
+ enterprise CI/CD. Same inputs + same corpus version = same output,
23
+ every time. 21 detector rules, 6 output formats (JSON, SARIF, table,
24
+ ml-context, GitHub, CycloneDX SBOM), and a 4-layer determinism guard
25
+ stack.
26
+ keywords:
27
+ - supply-chain-security
28
+ - npm
29
+ - pnpm
30
+ - deterministic-scanner
31
+ - sbom
32
+ - cyclonedx
33
+ - sarif
34
+ - devsecops
35
+ type: software
@@ -0,0 +1,20 @@
1
+ # Commercial License
2
+
3
+ Commercial use that competes with KirkForge's paid offerings requires a separate commercial license from KirkForge.
4
+
5
+ Under the Business Source License 1.1 (BUSL-1.1), you may use the software for non-production purposes and internal production use without a commercial license. However, offering the software to third parties on a hosted or embedded basis as a competitive offering requires a commercial license.
6
+
7
+ Examples of use requiring a commercial license:
8
+
9
+ - offering the software as a hosted/managed service that competes with KirkForge's products
10
+ - embedding the software in a paid product that competes with KirkForge's products
11
+ - packaging the software so it must be accessed or downloaded for your competitive offering to operate
12
+
13
+ Examples of use **not** requiring a commercial license:
14
+
15
+ - internal use within your organization
16
+ - non-production use (development, testing, evaluation)
17
+ - academic or educational use
18
+ - personal/hobby projects
19
+
20
+ For commercial licensing, contact: kirk@kirkforge.dev
@@ -0,0 +1,168 @@
1
+ # Contributing to PicoSentry
2
+
3
+ Thanks for your interest in PicoSentry! This guide covers how to contribute effectively.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ git clone https://github.com/KirkForge/PicoSentry.git
9
+ cd PicoSentry
10
+ python3 -m pip install -e ".[dev]" # Required: CLI tests need the package importable
11
+ python3 -m pytest
12
+ ```
13
+
14
+ > **Important:** You must install the package in editable mode before running tests.
15
+ > CLI integration tests use `subprocess` to invoke `picosentry`, which requires the
16
+ > package to be importable. Raw `pytest` from a clean checkout will fail at CLI tests
17
+ > without `pip install -e ".[dev]"`.
18
+
19
+ ## Development
20
+
21
+ ### Running Tests
22
+
23
+ ```bash
24
+ python3 -m pytest # All tests
25
+ python3 -m pytest tests/test_cli.py # CLI tests only
26
+ python3 -m pytest -x # Stop on first failure
27
+ python3 -m pytest -m "not slow" # Skip benchmark tests
28
+ python3 -m pytest tests/test_benchmark.py # Performance benchmarks
29
+ ```
30
+
31
+ ### Linting & Type Checking
32
+
33
+ ```bash
34
+ ruff check src/ tests/ # Lint
35
+ ruff format --check src/ tests/ # Format check
36
+ mypy src/picosentry --strict # Type check
37
+ ```
38
+
39
+ ### Pre-push CI
40
+
41
+ ```bash
42
+ bash scripts/ci.sh # Run all checks: mypy, ruff, pytest, determinism
43
+ ```
44
+
45
+ ### Adding a New Rule
46
+
47
+ 1. Create detector in `src/picosentry/rules/` (e.g., `my_rule.py`)
48
+ 2. Register in `src/picosentry/rules/__init__.py` → `RULE_INFO`
49
+ 3. Register in `src/picosentry/engine.py` → `create_default_engine()`
50
+ 4. Add fixture in `tests/fixtures/`
51
+ 5. Write tests in `tests/test_my_rule.py`
52
+ 6. Create rule doc in `src/picosentry/docs/rules/`
53
+ 7. Update `SCAAT.md` with the new attack vector coverage
54
+ 8. Run: `python3 -m pytest`
55
+
56
+ ### Rule Requirements
57
+
58
+ Every rule **must** be deterministic:
59
+ - Same target + same corpus = same findings, every time
60
+ - No network calls at scan time
61
+ - No `uuid4()`, `random()`, or timestamps in findings
62
+ - Output sorted by `(rule_id, package, file, line)`
63
+
64
+ ### Adding a Custom IoC
65
+
66
+ Register known-bad packages in the built-in corpus:
67
+
68
+ 1. Create `src/picosentry/corpus/ioc/<package>.json` following the existing format
69
+ 2. Reference the attack vector, indicators, and expected detection rules
70
+ 3. Add a test fixture that exercises the detection
71
+
72
+ Or use the CLI for custom IoCs (these stay local, not in the repo):
73
+
74
+ ```bash
75
+ picosentry corpus export ./my-iocs.json
76
+ # Edit my-iocs.json, add entries
77
+ picosentry corpus import ./my-iocs.json --force
78
+ ```
79
+
80
+ ### Code Style
81
+
82
+ - Type hints on all public functions
83
+ - Docstrings on all public functions
84
+ - `dataclass(frozen=True)` for immutable data (Findings, etc.)
85
+ - Sorted keys in all JSON output
86
+ - 120-char line limit (ruff enforced)
87
+
88
+ ## Project Structure
89
+
90
+ ```
91
+ src/picosentry/
92
+ ├── cli.py # CLI entry point — all subcommands
93
+ ├── engine.py # ScanEngine — orchestrates rules
94
+ ├── models.py # Frozen dataclasses (Finding, ScanResult)
95
+ ├── config.py # .picosentry.yml loader
96
+ ├── guards.py # Determinism enforcement
97
+ ├── logging.py # Structured JSON logging
98
+ ├── workspace.py # Monorepo scanning
99
+ ├── ioc_registry.py # Custom IoC management
100
+ ├── corpus_share.py # Corpus pack import/export
101
+ ├── rules/ # 19 detector rules (pure functions)
102
+ ├── formatters/ # 6 output formatters
103
+ └── corpus/ # Built-in IoC database
104
+ ```
105
+
106
+ ## Determinism Verification
107
+
108
+ After any scan logic change, verify determinism (including CycloneDX output):
109
+
110
+ ```bash
111
+ # Run two scans and compare (byte-identical output)
112
+ picosentry scan ./my-project --format json --deterministic-output -o scan_a.json
113
+ picosentry scan ./my-project --format json --deterministic-output -o scan_b.json
114
+ diff scan_a.json scan_b.json # should produce no output
115
+
116
+ # Or use built-in verification (runs twice, compares SHA-256)
117
+ picosentry scan ./my-project --verify-determinism
118
+ # Should output: "✓ DETERMINISM VERIFIED — scans are deterministic"
119
+
120
+ # Note: without --deterministic-output, JSON includes timestamps and timing.
121
+ # The --verify-determinism flag automatically enables deterministic output mode.
122
+ ```
123
+
124
+ ## Commit Messages
125
+
126
+ Format: `type: description`
127
+
128
+ Types: `feat`, `fix`, `docs`, `test`, `refactor`, `chore`
129
+
130
+ Examples:
131
+ - `feat: L2-SIDELOAD-001 protocol sideloading detector`
132
+ - `feat: workspace scanning for monorepos`
133
+ - `fix: engine_version now reads from __version__ dynamically`
134
+ - `docs: add SCAAT attestation and corpus marketplace docs`
135
+
136
+ ## Reporting Issues
137
+
138
+ - Include: PicoSentry version, Python version, OS, target project
139
+ - Include: `picosentry version` output
140
+ - Include: `--verbose` output if possible
141
+
142
+ ## Security
143
+
144
+ See [SECURITY.md](SECURITY.md) for vulnerability reporting policy.
145
+
146
+ ## AI-Assisted Development
147
+
148
+ PicoSentry is developed with AI assistance. All AI-generated contributions are
149
+ reviewed, tested, and approved by a human maintainer before merge.
150
+
151
+ ### Co-Authorship
152
+
153
+ When AI tools produce substantive contributions (features, bug fixes, documentation),
154
+ they receive co-author credit:
155
+
156
+ ```
157
+ Co-authored-by: GLM-5.1 <glm@z.ai>
158
+ Co-authored-by: PicoClaw <picoclaw@kirkforge.dev>
159
+ ```
160
+
161
+ - **GLM-5.1** — Code generation, refactoring, test writing
162
+ - **PicoClaw** — Review, analysis, documentation, security auditing
163
+
164
+ AI co-authors are credited because they did real work. This is transparent and honest.
165
+
166
+ ## License
167
+
168
+ By contributing, you agree that your contributions will be licensed under the same public license as the project, currently PolyForm Noncommercial License 1.0.0, and may also be used by KirkForge under separate commercial licensing terms.
@@ -0,0 +1,79 @@
1
+ # PicoSentry — Enterprise Docker Image
2
+ # Multi-stage build with pinned digests for secure, reproducible CI/CD pipeline scanning.
3
+ #
4
+ # Build:
5
+ # docker build -t picosentry:latest .
6
+ # docker build --build-arg VERSION=0.15.0 -t picosentry:0.15.0 .
7
+ #
8
+ # Run:
9
+ # docker run --rm -v $(pwd):/scan picosentry scan /scan
10
+ # docker run --rm -v $(pwd):/scan picosentry workspace /scan --format json
11
+ # docker run --rm -v $(pwd):/scan picosentry check /scan --fail-on high --fail-on-rule-error
12
+ #
13
+ # Security: runs as non-root, no network at scan time, read-only scan dir.
14
+ # Base images pinned by digest for supply-chain integrity.
15
+
16
+ # ── Stage 1: Builder ──────────────────────────────────
17
+ # python:3.12-slim (bookworm) — digest varies; update periodically.
18
+ # Verify: docker pull python:3.12-slim@sha256:...
19
+ FROM python:3.12-slim@sha256:9d3abd9fc11d06998ccdbdd93b4dd49b5ad7d67fcbbc11c016eb0eb2c2194891 AS builder
20
+
21
+ RUN apt-get update && apt-get install -y --no-install-recommends \
22
+ git \
23
+ && rm -rf /var/lib/apt/lists/*
24
+
25
+ WORKDIR /build
26
+
27
+ # Copy only what's needed for install
28
+ COPY pyproject.toml README.md LICENSE ./
29
+ COPY src/ ./src/
30
+
31
+ # Install into a clean venv
32
+ RUN python3 -m venv /opt/venv && \
33
+ /opt/venv/bin/pip install --no-cache-dir -e . && \
34
+ # Install pyyaml for pnpm workspace support
35
+ /opt/venv/bin/pip install --no-cache-dir pyyaml
36
+
37
+ # Verify installation
38
+ RUN /opt/venv/bin/picosentry --version
39
+
40
+ # ── Stage 2: Runner ───────────────────────────────────
41
+ FROM python:3.12-slim@sha256:9d3abd9fc11d06998ccdbdd93b4dd49b5ad7d67fcbbc11c016eb0eb2c2194891 AS runner
42
+
43
+ LABEL org.opencontainers.image.title="PicoSentry"
44
+ LABEL org.opencontainers.image.description="Deterministic supply-chain scanner for npm/pnpm — enterprise CI/CD"
45
+ LABEL org.opencontainers.image.url="https://github.com/KirkForge/PicoSentry"
46
+ LABEL org.opencontainers.image.vendor="KirkForge"
47
+ LABEL org.opencontainers.image.licenses="MIT"
48
+ LABEL org.opencontainers.image.authors="KirkForge"
49
+ LABEL org.opencontainers.image.documentation="https://github.com/KirkForge/PicoSentry"
50
+ LABEL org.opencontainers.image.source="https://github.com/KirkForge/PicoSentry"
51
+ LABEL org.opencontainers.image.version="0.15.0"
52
+
53
+ # Create non-root user
54
+ RUN groupadd -r picosentry && useradd -r -g picosentry -d /home/picosentry -s /bin/bash picosentry && \
55
+ mkdir -p /home/picosentry/.local/share/picosentry/corpus && \
56
+ mkdir -p /scan && \
57
+ chown -R picosentry:picosentry /home/picosentry /scan
58
+
59
+ # Copy venv from builder
60
+ COPY --from=builder /opt/venv /opt/venv
61
+
62
+ # Set PATH to use venv binaries
63
+ ENV PATH="/opt/venv/bin:$PATH"
64
+ ENV PYTHONUNBUFFERED=1
65
+ ENV PYTHONDONTWRITEBYTECODE=1
66
+
67
+ # Drop to non-root user
68
+ USER picosentry
69
+ WORKDIR /home/picosentry
70
+
71
+ # Default scan directory
72
+ VOLUME ["/scan"]
73
+
74
+ # Health check: verify scanner works
75
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
76
+ CMD picosentry --version || exit 1
77
+
78
+ ENTRYPOINT ["picosentry"]
79
+ CMD ["--help"]
@@ -0,0 +1,91 @@
1
+ License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
2
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
3
+
4
+ Parameters
5
+
6
+ Licensor: KirkForge
7
+ Licensed Work: PicoSentry Version 0.16.0 or later. The Licensed Work is (c) 2025
8
+ KirkForge.
9
+ Additional Use Grant: You may make production use of the Licensed Work provided
10
+ You do not offer the Licensed Work to third parties on a
11
+ hosted or embedded basis as a competitive offering. For
12
+ purposes of this license:
13
+
14
+ A "competitive offering" is a Product that is offered to
15
+ third parties on a paid basis, including through paid
16
+ support arrangements, that significantly overlaps with the
17
+ capabilities of KirkForge's paid version(s) of the Licensed
18
+ Work. If Your Product is not a competitive offering when You
19
+ first make it generally available, it will not become a
20
+ competitive offering later due to KirkForge releasing a new
21
+ version of the Licensed Work with additional capabilities.
22
+ In addition, Products that are not provided on a paid basis
23
+ are not competitive.
24
+
25
+ "Product" means software that is offered to end users to
26
+ manage in their own environments or offered as a service on
27
+ a hosted basis.
28
+
29
+ "Embedded" means including the source code or executable code
30
+ from the Licensed Work in a competitive offering. "Embedded"
31
+ also means packaging the competitive offering in such a way
32
+ that the Licensed Work must be accessed or downloaded for
33
+ the competitive offering to operate.
34
+
35
+ Hosting or using the Licensed Work(s) for internal purposes
36
+ within an organization is not considered a competitive
37
+ offering. KirkForge considers your organization to include
38
+ all of your affiliates under common control.
39
+
40
+ For commercial licensing arrangements, contact
41
+ kirk@kirkforge.dev or see COMMERCIAL-LICENSE.md.
42
+ Change Date: Three years from the date the Licensed Work is published.
43
+ Change License: Apache-2.0
44
+
45
+ For information about alternative licensing arrangements for the Licensed Work,
46
+ please contact kirk@kirkforge.dev.
47
+
48
+ Notice
49
+
50
+ Business Source License 1.1
51
+
52
+ Terms
53
+
54
+ The Licensor hereby grants you the right to copy, modify, create derivative
55
+ works, redistribute, and make non-production use of the Licensed Work. The
56
+ Licensor may make an Additional Use Grant, above, permitting limited production use.
57
+
58
+ Effective on the Change Date, or the fourth anniversary of the first publicly
59
+ available distribution of a specific version of the Licensed Work under this
60
+ License, whichever comes first, the Licensor hereby grants you rights under
61
+ the terms of the Change License, and the rights granted in the paragraph
62
+ above terminate.
63
+
64
+ If your use of the Licensed Work does not comply with the requirements
65
+ currently in effect as described in this License, you must purchase a
66
+ commercial license from the Licensor, its affiliated entities, or authorized
67
+ resellers, or you must refrain from using the Licensed Work.
68
+
69
+ All copies of the original and modified Licensed Work, and derivative works
70
+ of the Licensed Work, are subject to this License. This License applies
71
+ separately for each version of the Licensed Work and the Change Date may vary
72
+ for each version of the Licensed Work released by Licensor.
73
+
74
+ You must conspicuously display this License on each original or modified copy
75
+ of the Licensed Work. If you receive the Licensed Work in original or
76
+ modified form from a third party, the terms and conditions set forth in this
77
+ License apply to your use of that work.
78
+
79
+ Any use of the Licensed Work in violation of this License will automatically
80
+ terminate your rights under this License for the current and all other
81
+ versions of the Licensed Work.
82
+
83
+ This License does not grant you any right in any trademark or logo of
84
+ Licensor or its affiliates (provided that you may use a trademark or logo of
85
+ Licensor as expressly required by this License).
86
+
87
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
88
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
89
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
90
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
91
+ TITLE.