# Verification Findings — 2026-05-28

Four parallel read-only audits. Tree audited: `donjon-platform-clean` (canonical-to-be). **Data/automation findings must be re-verified on CT100 `/opt/donjon-platform` — this Windows tree is a frozen mirror.**

---

## A. License / Community-Edition gating — VERDICT: PARTIAL

**Mechanism:** signed license at `data/license.json`; `LicenseManager` (`lib/licensing.py:447-601`) verifies dual sig (Ed25519 + ML-DSA-65, `_validate_license_v2` `:651-716`), fails closed to `community`. Tier drives `TIERS` table (`:172-351`). Enforced at `_check_tier_limit` (`web/api.py:324,348-462`) and `lib/license_guard.py`.

**Enforced (REAL, fail-closed):** scan depth `deep` (`api.py:398`), 16-target cap (`api.py:408`, `licensing.py:1063`), scheduled scans (`api.py:386`), notification channels (`api.py:419`), export formats (`api.py:432`), AI quota 10/day (`api.py:443-460`), SSO/RBAC/tenant/audit/MSSP routes (`api.py:362-380` + `require_feature`).

**Gaps:**
- **P0 placeholder public keys** `lib/licensing.py:79-123` → no real v2 license verifies → silent Community always.
- **P0 trial bypass** `lib/trial_license.py` writes `data/trial-license.json` tier=pro unsigned; honored `licensing.py:503-515`; guard = deletable `data/.trial-used`; fingerprint = `platform.node()/machine()/system()` → infinite free Pro.
- **P0 framework cap (3) unenforced** in scan path; `check_feature_item('scanners',…)` (`licensing.py:1029`) and compliance cap have **zero callers**.
- throttle `get_scan_throttle_ms` (`licensing.py:1055`), `scan_history_days`, `save_projects` defined, never applied.
- fingerprint mismatch warns but returns True (`licensing.py:830-838`) → floating licenses.
- v1 HMAC forge path behind `DONJON_ALLOW_V1_LICENSE=1`, hardcoded key (`licensing.py:132-133,617-645`).
- **dead router** `web/api_license.py:39-87` calls non-existent `guard.get_status/validate/activate/deactivate` → AttributeError. (Working path is `/api/v1/license/*` in `web/api.py`.)
- scanner-set gating absent (`orchestrator.py:184,203-205`) — but CE *sells* all 19, so consistent.

**Runtime-verify:** install w/ no license → confirm Community; sign real Pro license → confirm `get_tier()=="pro"`; delete `.trial-used` → confirm re-grant exploit; hit `/api/license/status` → confirm throw.

---

## B. Airgap — VERDICT: PARTIAL (holds for core scan path)

**Offline flag:** `DONJON_OFFLINE=1`, enforced `intel_feeds.py:108,128,295`, `threat_intel.py:159,218`, `vuln_database.py:583`; tested `tests/test_security_redteam.py:434`. License validation fully offline (`licensing.py:5-15,79`). AI degrades to `template` (`ai_engine.py:200,382`). UI/reports inline CSS/JS, no CDN.

**Bundled offline data:** vuln DB `data/vuln_db/` 673 MB (~Mar 20); intel `data/intel_feeds.db` 218 MB (~Mar 22); bundles `data/bundles/` 112 MB; threat intel `data/threat_intel/` 1.1 MB. Bundle builders real: `tools/build-intel-bundle.py`, `bin/bundle-intel.py`, `bin/bundle-deps.py`, `bin/bundle-tools.py`.

**Gaps:**
- **P1 ASM scanner not offline-guarded** `scanners/asm_scanner.py:284,686,973,1079` (crt.sh / DoH) → 30s timeouts each; graceful only via exception catch, not a guard.
- Bundled data ~2 months stale; needs documented re-bundle cadence.
- `DONJON_OFFLINE` is per-file (6 files), not a global egress kill-switch.
- telemetry `usage_reporter.py:169,185,230` double-gated (`DONJON_TELEMETRY=1` AND tier=managed) — safe.

**Runtime-verify:** `tools/test-airgap.py` exists (note `:772` probes `api.first.org`); run on egress-DENY host w/ tcpdump; ASM scan under capture to confirm crt.sh/DoH attempts.

---

## C. Scanner definitions / update automation — VERDICT: STALE + false-green

Today=2026-05-28. **All data frozen ~2026-03-22 (66–68 days).**

| Dataset | Location | Age |
|---|---|---|
| NVD CVE (324,593) | `data/vuln_db/vuln_intel.db` update_log id36 | 66d |
| OSV (252,748), ExploitDB (46,496), GH adv, CISA alerts, MITRE ATT&CK | `data/intel_feeds.db` | 66d |
| CISA KEV (1,551) | `data/threat_intel/cisa_kev.json` | 68d |
| EPSS (1 CVE only) | `data/threat_intel/epss_cache.json` | 66d |
| Compliance 31 YAML | `config/frameworks/*.yaml` | 68–99d |
| abuse.ch URLhaus/ThreatFox | `intel_feeds.db threat_indicators` | **never (0 rows)** |

**Automation:**
- `bin/update-intel.py --incremental` → `update_nvd_incremental()` (`vuln_database.py:1526`) — **works** (fixed by `469b475`).
- `--kev-only/--epss-only/--feeds-only` (`update-intel.py:69,284,346`) — present, unexercised post-fix.
- `--check` (`update-intel.py:444-501`) — **NVD age blind spot** (only flags `cached_cves==0`, never age).
- `tools/weekly-intel-update.sh` — cron is **comment-only** (`:8`), not in repo `infra/cron.d/`; `exit 0` bypass paths at `:105,119,134,193` skip the FAILED_STEPS summary `:216`.
- **dashboard staleness stub** `web/api_intel.py:27-49` returns hardcoded `{"status":"ok"}` / `[]` → **lies green** regardless of true age. (Same silent-staleness class, moved to UI.)
- **watchdog/heartbeat promised by `469b475` never built**; `.intel-heartbeat-*` absent.
- `bin/update-frameworks.py` — manual only, depends on external `DonjonSec/PolicyUpdate` repo (existence unverified).

**Runtime-verify on CT100:** `crontab -l | grep weekly-intel`; `ls -la tools/weekly-intel-update.sh` (+x?); heartbeat mtime vs now; force `update-intel.py --incremental` and check new `success` row; `update-intel.py --check; echo $?` (expect 1); confirm `NVD_API_KEY` loaded; `update-frameworks.py` resolves repo.

---

## D. Live-site claims — no deploy drift, internal inconsistencies

Live donjonsec.com == local `donjonsec-website/src/pages/` on all headline numbers (19 scanners, 31 frameworks, 16 AI providers, 327K+ CVEs, $110K+, 10K Monte Carlo). **No "168 tests" / "red team validated" on the live site.**

**Inconsistencies to reconcile (present in both live & source):**
1. Scanner *names* differ: /features/ lists MCP Security + Full-Suite Orchestrator; /docs/ lists Adversary Simulation, omits MCP+Orchestrator, table renders **18 rows** — both claim 19.
2. Framework count **31** (headings, Pro card) vs **30** (docs table, pricing comparison table).
3. AI providers **16** claimed but /features/ renders only **7** cards (uniquely names "StepFun"); only /docs/ enumerates 16.
4. Trial endpoint port **9090** (/download/) vs **5000** everywhere else.

**Community Edition (verbatim):** Free forever — 16 targets/session, 3 frameworks, 10 AI queries/day, CSV+JSON, 1 user, all 19 scanners, FAIR risk quant. Pro/Enterprise/MSSP unlock unlimited targets, all frameworks, all exports, scheduled scans, SSO/RBAC/multi-tenant/MSSP. 14-day Pro trial, no card.
