risk-graph: stacked layout, 5-col Sankey + client-side what-if - #5
Merged
Merged
Conversation
Stacked layout (hero / threat list / graph), 5-column Sankey with C(СЗИ), client-side what-if simulation mirroring backend QReactionFromVLs, plus new bulk endpoint /api/risk/asset/:id/attack-paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 tasks across 4 phases (backend types/aggregate/service/handler, frontend types/riskFlow lib, 6 components, page rewrite + cleanup) with TDD-style steps and per-task commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Types for the new bulk attack-paths endpoint that returns all threats of a single asset in one request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure function that summarizes a slice of AttackPath into WMax, Level, ThreatCount and UncoveredCount for the asset-level hero block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hardens the ComputeAssetAggregate contract before it is exercised by the new bulk service method on real DB data: a non-empty input where every path has all VLs covered must return UncoveredCount=0, and a path with nil VulnerableLinks must not increment counts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returns all relevant attack paths plus an aggregate (WMax, Level, counts) for a single asset in one call. Skips paths with no sources, vulnerable links and destructive actions to keep the response noise-free. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bulk endpoint returning all attack paths and aggregate metrics for an asset in one request, plus handler unit tests using a stubbed Service. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontend types matching the new backend bulk endpoint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure functions for the new RiskGraphPage:
- buildSankeyGraph: S→ST→VL→{C|DA} graph with normalized flow
- recomputeW: client-side what-if mirroring backend QReactionFromVLs
10 unit tests cover flow conservation, coverage clamping, disabled
controls, severity-weighted splits and edge cases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The severity || 1 fallback only caught zero; negative values produced negative flow values that d3-sankey rejects. Replaced with a Math.max(1, ...) clamp and added a regression test that verifies all link values are non-negative for paths containing a negative-severity VL. Also added the missing delta assertion to the empty-VLs recomputeW test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top hero card showing asset id, name, aggregate W, level badge, threat count and uncovered count, with PDF / Параметры / Back action buttons. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sortable list of all threats for an asset with W bars, uncovered count and selection highlighting. Sortable by W (default), uncovered count or name. Keyboard-navigable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracted formula breakdown panel with optional what-if simulation overrides (effective q_reaction and W from client-side recompute). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d3-sankey layout with custom React-rendered nodes for the 5-column S→ST→VL→C→DA graph. Hover highlights, control nodes are clickable (opens ControlPopover via onControlClick), disabled controls are visually dimmed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modal popover for a single control: shows id, name, coverage value, and a toggle button to disable/re-enable in client-side simulation. Click-outside and Escape close the popover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sticky bottom banner that surfaces an active what-if simulation: chips for each disabled control (click to re-enable), baseline → simulated W with ΔW, plus Сбросить and Сохранить заметку actions. Hidden when no controls are disabled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New layout: AssetRiskHero / ThreatList / (AttackFlowSankey + PtsziBreakdown). Auto-selects max-W threat when ?threat= absent, mirrors selection to the URL via replaceState, and keeps a what-if simulation in client state (disabledControls Set) — recompute mirrors backend QReactionFromVLs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaced by AttackFlowSankey on the rewritten RiskGraphPage; the legacy component was no longer referenced by any route or page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
react-scripts build emits TS2802 because the project's tsconfig has neither downlevelIteration nor an ES2015+ target. Map.forEach is supported on the existing target without flags and yields the same behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move invalid-?threat= toast out of useMemo into a dedicated effect (memos should not produce side effects). - Drop `params` from the URL-sync effect deps to avoid redundant runs. - Wire onPdf in AssetRiskHero to POST /api/risk/report/pdf and trigger a blob download; restores the missing "PDF сценария" hero action. - Guard AttackFlowSankey against zero-VL paths (d3-sankey crashes on graphs with no edges from VL); render a friendly empty state instead. - ControlPopover: auto-focus the toggle on open, restore focus to the invoking element on close (keyboard-only users could not escape it). - Drop unused nodeIndex map in AttackFlowSankey. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reflect what was actually shipped in V1 — the service method calls AssembleAttackPath in a loop, producing N+1 (~40–120 queries for typical assets). True bulk repo method (LoadAssetAttackPaths) and the no-N+1 contract test are deferred to V2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eslint array-callback-return wants every branch of a sort comparator to return a value. The compile-time exhaustive switch already covers all SortKey values; the default is dead but silences the linter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This resolves the react-scripts build error where sub-dependency @types/d3-dispatch (v3.0.7) required TypeScript 5.0+ syntax. Upgraded to 5.1.6 to stay within @typescript-eslint supported limits.
…tly in tests Remove non-existent `type`/`location` columns from assets seed, widen fstec/fsb_protection_class to VARCHAR(64), and reset id sequences after seeding. Drop the schema/seed split in testhelper — all migrations now run in numeric order and fail the test on error instead of being silently logged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire `useAuth().logout` to a new IconBtn in the TopBar so users can sign out from any page. Remove the old App.test.tsx scaffold which no longer matches the current routing and was failing to compile. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pin the model to the source spec in /Расчет идеи/: full S/ST/VL/DA
reference tables, methods of противодействия, the W formula with
Z ∈ {0.5, 1.0}, and an explicit list of asset fields actually used by W.
Drops every mention of the legacy 1-25 / impact×likelihood scale —
this is now the single source of truth for the risk engine and an
anchor for the upcoming legacy purge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete the parallel legacy risk stack: - internal/service/risk/calculator.go (Impact × Likelihood × RegulatoryFactor) - internal/service/risk/recommendations.go (rule-based regulatory advice) - internal/service/asset/cia_calculator.go (auto-derived C/I/A & criticality) - internal/report/pdf.go (legacy PDF report) plus their tests. risk.Service now exposes only PTSZI methods: AssembleAttackPath, AssembleAssetAttackPaths, Overview, ListThreatSources, ListDestructiveActions. OverviewPoint loses its 1-25 back-compat fields (impact/likelihood/score) and returns only W and its decomposition. ZFromAsset is collapsed to the strict thesis form: 0.5 if isolated, else 1.0 (no more prod/stage 0.75 hop). Tests updated accordingly. HTTP layer: drop POST /api/risk/preview, GET /api/risk/asset/:id, and POST /api/risk/report/pdf. Asset request/response no longer carry business_criticality, C/I/A, kii_category, data_category, protection_level, has_personal_data, personal_data_volume, has_internet_access, type, location — the PTSZI formula does not use them. The DB columns still exist; Stage 2 will migrate them away. Asset.Create writes neutral defaults (3) for the remaining NOT NULL CHECK columns until the migration lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add migration 020_drop_legacy_risk_fields:
- drop tables risk_scenarios, recommendation_templates,
risk_scenario_recommendations (the old impact×likelihood engine)
- drop columns from assets: type, location, business_criticality,
confidentiality, integrity, availability, data_category, protection_level,
kii_category, has_personal_data, personal_data_volume, has_internet_access
- drop columns from threats: base_likelihood, attack_vector,
impact_confidentiality, impact_integrity, impact_availability
- drop columns from controls: reduces_likelihood_by, reduces_impact_by
- drop column asset_controls.effectiveness
- drop unused enum types data_category_type, protection_level,
kii_category, risk_status, risk_level, recommendation_status
Older migrations (002, 005, 010) still reference these columns; they run
strictly before 020 so the chronological chain replays cleanly on a fresh DB.
Mirror the schema in domain/models.go and the asset/threat repositories,
services, and HTTP DTOs. Threat input now takes q_threat / q_severity (∈[0,1])
directly — base_likelihood is gone from the API.
Repo tests updated to the new struct shapes; full repo suite passes against
the migrated container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ormula Delete the screens that were shaped by the old impact×likelihood engine: - RiskMapPage (5×5 heatmap) + .backup - RiskPreviewPage (что-если симулятор) + .backup - AssetRiskProfilePage - RiskHeatmapSingle component Drop the corresponding routes (/risk/map, /risk/preview, /assets/:id/risks), nav entries, command-palette actions, and the old Asset/Threat/Risk* type definitions. types.ts and the API client now mirror the W-only backend exactly — no impact, likelihood, score, regulatory_factor, RiskRecommendation, RegulatoryRecommendation, or преview endpoint. AssetFormPage rebuilt to the four PTSZI-relevant inputs: name, description, asset_type_id, owner, environment, is_isolated. КИИ / ПДн / УЗ-1..4 / data_category sections are gone — the form now states explicitly that only `is_isolated` (Z) and the asset's deployed controls (Q^reaction) feed the formula. AssetsPage and DashboardPage rewired to render W max per asset (0..1) and «Изолированный (Z=0.5)» / «Открытый (Z=1.0)» chips instead of the old 1..25 score and КИИ/ПДн tags. The dashboard quick-action now opens the Risk Graph for the first asset rather than the dead simulator route. `npm run build` is clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/report/pdf.go is back, this time PTSZI-only:
- GenerateAttackPathPDF(*AttackPath) renders one (asset, threat) page
with the W decomposition (Q^threat / q^threat / Q^reaction / Z),
the S → ST → VL → DA chain, per-VL coverage status and a
«what raises Q^reaction» recommendation block.
- GenerateAssetReportPDF(*AssetAttackPathsResponse) prints the
asset-level aggregate plus one section per applicable threat.
Both APIs use the embedded NotoSans font (full Cyrillic), no disk
fallback needed.
Two new GET endpoints expose the PDFs:
- GET /api/risk/report/graph/:asset_id/:threat_id
- GET /api/risk/report/asset/:asset_id
Smoke tests exercise both generators and assert the output starts with
the %PDF- magic and is non-trivially sized.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P1 of the FSTEC integration plan. TRUNCATE threats, vulnerabilities, controls with RESTART IDENTITY CASCADE so the upcoming importers (P2 ФСТЭК threats, P3 БДУ snapshot, P4 Минцифры catalog) can repopulate from authoritative sources. Reference catalogues (asset_types, threat_categories, threat_sources, destructive_actions, …) are kept; user-created assets are kept; their orphaned VL/control links die with the cascade. Verified on the running compose stack: post-migration counts are 0 for all wiped tables, 9/8/4/7 for assets/asset_types/threat_sources/DA (unchanged). repo test suite still green against the full migration chain. Plan doc: docs/superpowers/specs/2026-05-05-fstec-bdu-integration.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9-stage plan (P1..P9) that replaces the 26 sample threats / 5 sample vulnerabilities / 30 sample software with the authoritative datasets (227 ФСТЭК threats, 86k БДУ vulnerabilities, 26k Минцифры products). Introduces VL_categories per the diploma (6 entries), redefines asset_vulnerabilities as an inventory of CVE evidence linked to a VL_category, and adds applicability filtering driven by the ФСТЭК "Объект воздействия" field. Each stage = one commit; pauses between P5/P6 and after P7 for review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd/import-fstec-threats — populates `threats` from the official
ФСТЭК thrlist.xlsx (227 entries) hosted in bdu-fstec-mirror.
For each row the importer derives:
• bdu_id = "УБИ.NNN"
• source_type + source_threats links from "Источник угрозы" text
(Внешний → S1+S4, Внутренний → S2+S3, mixed → all four)
• applies_to_asset_types[] from "Объект воздействия" via a regex
dictionary (грид/cloud → Cloud+Server, BIOS → Server+Workstation,
СУБД → Database, сетевой трафик → Network, …)
• impact_c/i/a + status verbatim from the catalogue
• q_threat from intruder potential (low/medium/high → 0.3/0.6/0.9)
• q_severity from CIA flag count (1/2/3 axes → 0.5/0.7/0.9)
• threat_category_id matched against existing categories by keyword
Migration 031 brings back impact_c/i/a (we dropped them in 020 — they
now serve a different purpose: deriving q_severity and the future
threat ↔ DA mapping), plus applies_to_targets/applies_to_asset_types
and a status column.
Verified end-to-end on the running stack:
- 227 threats inserted, 660 source_threats edges
- 156/227 (69%) got asset-type matches automatically
- 122/227 (54%) got a category
- /api/risk/overview returns 450 valid pairs (was 0 after P1 wipe)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd/import-bdu-snapshot — streams the gzipped SQLite snapshot from
bdu-fstec-mirror (~50 MB compressed → 450 MB decompressed) and writes
it atomically to ./data/bdu.sqlite.
internal/bdu — pure-Go (modernc.org/sqlite) wrapper that opens the
snapshot in read-only `immutable=1` mode for fastest reads on a static
file. Exposes:
• Open / Close / Stats
• Get(bduID) — single vulnerability + its CWE codes
• SoftwareLookup(v, n) — every vulnerability matching vendor + name
(basis for the asset-vulnerability auto-detection
coming in P6)
The snapshot lives outside Postgres because it is large and entirely
static between syncs; the server will mount it as a read-only catalogue.
Added /data/ to .gitignore so the 450 MB file never enters version control.
Smoke test exercises the real snapshot when BDU_SNAPSHOT_PATH is set
(skipped in CI, runs locally after an importer run):
- 86 664 vulnerabilities, 715 615 software rows, 92 457 CWE links
- Get("BDU:2014-00001") → Schneider Electric Modicon Quantum, CWE-259
- SoftwareLookup("D-Link", "DSR-500", 5) → 5 hits, CVSS 10.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the hand-crafted ~30-row software_catalog seed with a fresh import from the unofficial каталогпо.рф API, which mirrors the official Минцифры реестр российского ПО (~26 094 products). Schema changes: - 032 truncates the legacy seed (asset_software cascade detaches user rows; user re-attaches via the new asset detail UI in P8). - 033 widens software_catalog.name and .vendor from VARCHAR(256) to TEXT — some каталогпо.рф entries exceed 256 chars (e.g. id=23196). Importer (cmd/import-minreestr): - Per-page UPSERT on registry_number (idempotent re-runs). - Maps subcategories[].name → software_categories.id; default "other". - Sets is_russian = TRUE (every catalogue entry is by definition registered Russian software). FSTEC/FSB flags stay FALSE — that data isn't exposed by this API and will be enriched separately. - Uses stdlib default http.Client; custom Transport with disabled keep-alives or pinned HTTP/1.1 reproducibly hangs against каталогпо.рф's HTTP/2 endpoint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…es (P5) До этого момента таблица vulnerabilities двойственно играла роль и семантического «уязвимого звена» (VL в формуле W), и конкретной CVE-записи. В дипломной модели ПТСЗИ это разные сущности: VL — ровно 6 категорий (нештатное ПО, устаревшее ПО, недекларируемое ПО, обход админом, носители, открытые ОС/ЛВС), а CVE — инвентарь свидетельств присутствия VL на активе (это будет в P6). Изменения: * migration 034: новая vl_categories (6 строк), threat_vulnerable_links и vulnerability_controls (→ vl_category_controls) переключены с FK на vulnerabilities на FK на vl_categories. * migration 035: засеваем 11 канонических контролей из диплома (Антивирус/МСЭ/Honeypot/DZ/IDS/AD/Резерв/L/Шифрование/DS/DD) и матрицу vl_category_controls по таблице соответствия из диплома. * import-fstec-threats: новая эвристика deriveVLCategoryCodes по тексту угрозы; пишет threat_vulnerable_links параллельно с source_threats. На реальных 227 УБИ даёт 305 связей с разумным распределением (VL2 — 198, VL1 — 57, VL6 — 33, остальные малые). * domain.VLNode: VulnerabilityID/Severity заменены на CategoryID/Code/ Description; risk_graph_repository переписан под vl_categories + vl_category_controls; q_reaction по-прежнему «доля VL, у которых есть хотя бы один control с coverage>0 и внедрённый на активе». * Frontend: VLNode.vulnerability_id → category_id + code; ST→VL split стал равномерным (severity у категорий нет); все юнит-тесты riskFlow.test.ts перестроены под новый контракт. Со 100% контролями на активе W = 0.5 для critical-угроз; без контролей W достигает 0.93 (что соответствует «critical»). Применимость по типу актива и presence VL (через CVE) подключим в P6/P7. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раньше asset_vulnerabilities ссылалась на пустую legacy-таблицу vulnerabilities — оператор мог только вручную добавить «уязвимость без данных». Теперь это инвентарь конкретных БДУ-записей, привязанных к VL- категории формулы W. Источник записи: 'auto:asset_software' (когда сервис обнаружил CVE через bdu.SoftwareLookup) или 'manual'. Изменения: * migration 036: новая схема asset_vulnerabilities — bdu_id, cve, cwe, vl_category_id, cvss_score, severity_level, source, software_id, discovered_at. UNIQUE (asset_id, bdu_id, source, COALESCE(software_id,0)) обеспечивает идемпотентные re-runs без дублей. * migration 037: cve расширен до TEXT — БДУ-записи перечисляют десятки связанных CVE через пробел и не влезают в VARCHAR(64). * internal/risk/cwe_to_vl.go: словарь CWE → VL_код (VL1..VL6); fallback → VL2 («устаревшее ПО / уязвимости»). Покрытие: code-injection (VL1), Trojan/embedded malware (VL3), privilege/auth (VL4), info-exposure/network (VL6); всё прочее — VL2. Юнит-тесты. * internal/bdu/snapshot.go: SoftwareLookup теперь использует case-sensitive vendor matching, потому что встроенный SQLite LOWER() ASCII-only — для кириллицы он silent no-op и патрерн с LOWER никогда не совпадал. Имя — латиница, lowercase сохранён. * asset_vulnerability service переписан: AutoDetectFromSoftware (через bdu+CWE→VL), AddManual (с обогащением из БДУ), RemoveByID, RemoveBySoftware (cascade при отвязке ПО). * software service: AttachToAsset/DetachFromAsset, опциональная зависимость VulnerabilityDetector — wired в server.go. * HTTP routes: POST/DELETE /api/assets/:id/software (с возвратом detected_vulnerabilities count), GET /api/assets/:id/software, POST/GET /api/assets/:id/vulnerabilities переписан под bdu_id. * risk_graph_repository.LoadVulnerableLinks: добавил presence_count — число активных asset_vulnerabilities этой VL-категории на активе (для UI-предупреждения в P8). * docker-compose: ./data:/data:ro + BDU_SNAPSHOT_PATH=/data/bdu.sqlite, чтобы сервер видел снимок (470МБ) при старте. Проверено вживую: Astra Linux Special Edition → 200 БДУ-записей (capped) → presence_count=182 на VL2, формула W возвращает 0.77 для critical-угрозы УБИ.003 на активе без контролей. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Из-за того что после P2 в каталоге 227 угроз ФСТЭК, а у каждой угрозы импортёр выводит applies_to_asset_types из «Объекта воздействия», теперь имеет смысл не считать W для каждой пары, а только для применимых. * internal/service/risk/applicability.go: IsApplicable(asset, threat) — пустые targets → universal; конкретные targets без типа актива → conservatively применима; конкретные targets и asset.type не входит → не применима. С тестами. * Overview и AssembleAssetAttackPaths пропускают неприменимые пары до вызова AssembleAttackPath. Лимит ThreatFilter поднят до 500 (по умолчанию был 50, после импорта обрезал каталог до первых 50 угроз). * domain.Threat расширен полями AppliesToTargets, AppliesToAssetTypes, ImpactC/I/A, Status (миграция 031 их уже создала, но domain их не читал). Repository SELECT/UPDATE адаптированы. Эффект: /api/risk/overview уменьшился с потенциальных 9×227=2043 до 1126 пар — ~45% шума отфильтровано. Угрозы вида «BadUSB» больше не показываются для Customer Database, угрозы СУБД — для рабочих станций. Q^reaction-семантика осталась прежней (covered_VLs / total_threat_VLs): этого требует диплом. Presence через CVE-инвентарь индицируется отдельным полем VLNode.presence_count (введено в P6, не входит в W). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Новая страница /assets/:id — это рабочее место оператора по конкретному активу. Три секции, каждая привязана к новому или существующему API: * «Установленное ПО» — список из asset_software с кнопкой «Добавить ПО». Поиск по software_catalog (debounced); при выборе POST /assets/:id/ software и сразу видно, сколько БДУ-записей вытащил автодетект. * «Уязвимости и VL-категории» — asset_vulnerabilities, сгруппированные по vl_category_id. Для каждой VL — её код/имя + развернутый список CVE с CVSS-чипами (color-coded) и пометкой «авто». Можно вручную добавить запись по BDU-id (POST /assets/:id/vulnerabilities) или удалить отдельно. * «Внедрённые контроли» — две полосы chip'ов: «Внедрено» (success-tone, click чтобы отвязать) и «Каталог» (ghost-tone, click чтобы привязать). Catalogue API: GET /api/controls. * Header c agg-карточкой W_max / threat_count / uncovered_count и кнопкой «Пересчитать W» — дёргает /risk/asset/:id/attack-paths и обновляет агрегат без перезагрузки страницы. Backend: * control_repository, control service, control_handlers — CRUD над controls / asset_controls (без модификации catalog'а, только attach/ detach). Routes wired в server.go. * domain.Control получил json-теги (snake_case), чтобы фронт не путался с CamelCase, который Go emits по умолчанию. * api/client.ts расширен типами AssetSoftwareLink, AssetVulnerability, Control, AssetAttackPathsResponse и набором новых вызовов. Живая проверка: Astra Linux → 200 CVE → presence_count в графе риска видит 4 на VL1 и 182 на VL2; внедрение Антивируса (закрывает VL1+VL2) обнуляет uncovered и роняет W с 0.93 до 0.50. Аггрегат: 183 угрозы применимы к Web Application Server, 16 непокрытых VL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В навигации был пункт «Каталог угроз» с бейджем «БДУ», но реальной страницы не было — клик по нему открывал AssetsPage по маршруту-fallback. Теперь это полноценный экран со всеми 227 импортированными УБИ. Frontend: * /threats — таблица с колонками БДУ-id, название, источник, Q^threat, q^severity, CIA-чипы, applies_to_asset_types (имена через /api/asset-types). Поиск по тексту + три фильтра (источник, тип актива, нарушаемое свойство C/I/A). * Drawer-редактор по клику на карандаш: чекбокс-чипы по типам активов, текстовое поле «Объект воздействия», поля Q. Submit → PUT /threats/:id. Backend: * domain/threat: Update/Create+ResponseDTO теперь включают applies_to_targets, applies_to_asset_types, impact_c/i/a, status. Раньше PUT молча терял эти поля при ручной правке угрозы. * Новые справочные endpoint'ы для UI: GET /api/asset-types и GET /api/vl-categories — read-only inline-handlers напрямую через pool, без отдельного service-слоя (одна тривиальная SELECT-выборка каждый). Living check: - /api/threats?limit=3 возвращает applies_to_asset_types и CIA-флаги. - PUT /api/threats/1 с applies_to_asset_types=[6,9] сохраняет массив. - /api/asset-types: 8 типов из 002_seed_data. P9 завершает FSTEC/БДУ/Минцифры-интеграцию из спека 2026-05-05-fstec-bdu-integration.md (P1..P9 готовы). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
87 элементов, 6 цветовых слоёв (sources → ETL → storage → backend services → API → frontend → user) + доменная модель ПТСЗИ S→ST→VL→DA + Controls + формула W + workflow. Открыть: https://excalidraw.com → File → Open → docs/architecture.excalidraw Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drawer на /assets вёл только в /risk/graph/:id и /assets/edit/:id (старая форма). Новая страница AssetDetailPage из P8 (с тремя секциями ПО / уязвимости-VL / контроли) была недоступна — никакая ссылка на /assets/:id из UI не вела. Теперь в drawer 3 кнопки: - «Открыть карточку» (primary) → /assets/:id - «Граф атаки» (outline) → /risk/graph/:id - «Редактировать форму» (ghost) → /assets/edit/:id Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Передавал в Chip несуществующие tone-значения (critical/high/medium/low), которые не входят в ChipTone (neutral|accent|success|warn|danger|ghost). Chip обращается к toneStyles[tone].bg напрямую → undefined → TypeError → React unmounts → белый экран. Чиню маппинг: * levelTone(cvss): danger / warn / neutral * VlChipColor: VL1→warn, VL2/3/6→danger, VL4→warn, VL5→neutral Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
api.attachControl/attachSoftware/addManualVulnerability падали с "Unexpected token 'C', \"Created\" is not valid JSON" — Fiber SendStatus(201) пишет в body литерал "Created", а request<T> безусловно делал JSON.parse(text). Теперь request<T> проверяет Content-Type: если не application/json — возвращает undefined без парсинга. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pgx в binary mode не умеет сканить DATE (OID 1082) в *string и валит весь /api/software?search=… с "can't scan into dest[7]". Поиск ПО из карточки актива (P8 SoftwarePicker) был полностью сломан. Простой fix без миграции типов в domain.Software: добавляю ::text к registry_date / fstec_certificate_date / fstec_valid_until во всех 3 SELECT'ах software_repository (GetByID, List, ListAssetSoftware). Модель Software.RegistryDate остаётся *string, JSON-выдача формата 'YYYY-MM-DD' не меняется. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
До этого SoftwareLookup матчил БДУ-уязвимости только по vendor+name,
поэтому Astra Linux 1.7 получал на актив CVE и для 1.6 «Смоленск» и
для всего что было в каталоге — 200 capped из 12 331 без отбора.
Backend:
* internal/bdu/version.go — парсер русских форм версий из БДУ:
- "-" / "" → catch-all
- exact (case-insensitive)
- "от X до Y включительно" → closed [X, Y]
- "от X до Y" → half-open [X, Y)
- "до Y включительно" / "до Y" → (-∞, Y] или (-∞, Y)
- "от X включительно" / "от X" → [X, +∞) или (X, +∞)
Семвер-сравнение по компонентам через '.', с числовым префиксом
(поддерживает "8.4.16" vs "8.4.10rc1"). Юнит-тесты на все формы.
* SoftwareLookup получил параметр assetVersion. Если задан — SQL
сужается до version-кандидатов (точное, '-', range-like) ДО loading
ORDER BY cvss_score, чтобы редкие версии типа "4.2.6" (3 строки в
БДУ) не вылетали из топ-1000 случайно. После loading — финальный
filter через VersionMatches с дедупом по bdu_id.
Frontend:
* AssetDetailPage SoftwarePicker теперь двухшаговый: поиск → выбор →
поле «Версия (опционально)» → submit. Версия передаётся в POST
/api/assets/:id/software и используется автодетектом.
* Live-проверка для Astra Linux Special Edition:
(empty) → 200 (capped из 12 331)
1.7 → 200 (capped из 6 835)
1.5 «Смоленск» → 200 (capped из 639)
4.2.6 → 3 (всё, что есть в БДУ для этой версии)
999.999 → 0 (никакого ложного матча)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Шлюз каталогпо.рф режет HTTP/2-стримы при Content-Length > ~115KB: limit=100 даёт 300KB, ответ обрезается до 81624 байт, JSON-парсер падает, импортер до сих пор фаталил на первой неудаче (3001/26094). Фикс: - defaultPageSize = 30 (даёт ~98KB, проходит стабильно) - maxPageSize = 35; --page-size > 35 клампится с warn - fetchPageWithRetry: 5 попыток, 1s/2s/4s/8s/16s - Сетевая ошибка после ретраев → пропуск страницы, не Fatalf - exit 2 при наличии пропущенных страниц - Таймаут поднят до 90 минут (с лимитом 30 идёт ~22 мин) Live-проверка: 26094/26094, 0 пропущенных страниц, 0 ретраев. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Закрывает блок «Оценка состояния защищённости» из 7.png диплома:
для каждого актива считаем % соответствия требованиям ИБ-стандартов
на основе внедрённых на нём контролей.
Бэкенд:
- migration 038: справочники compliance_standards (ФСТЭК-17, ISO 27001:2022),
compliance_requirements (25+17 требований), requirement_controls
(77 рёбер ↔ нашими 11 контролями с весами покрытия 0..1)
- repository.ComplianceRepository (read-only)
- service/compliance: AssetOverview / AssetByStandard
алгоритм coverage: max(weight) среди control_id, внедрённых
на активе через asset_controls; overall = avg по требованиям
- 3 эндпоинта:
GET /api/compliance/standards
GET /api/compliance/asset/:assetID
GET /api/compliance/asset/:assetID/standard/:standardCode
Фронт:
- ComplianceSection: плитки стандартов с прогресс-барами + drill-down
со списком требований (фильтры all/✓/◐/✗, группировка по категории)
- На каждое требование: цветная метка покрытия, для развёрнутого —
список covering_controls (внедрено) и missing_controls (что ещё закроет)
- Подключено как 4-я секция на странице карточки актива
Live-проверка на Customer Database (4 контроля внедрено):
FSTEC_17 → 25.6% (✓3/◐6/✗16/25)
ISO_27001 → 22.4% (✓1/◐6/✗10/17)
АВЗ.1, АВЗ.2 = 100% (антивирус), ЗИС.18 = 80% (DDoS), ОЦЛ.1 = 30%
(антивирус как косвенный контроль целостности).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Минцифры использует hierarchical классификатор XX.YY (~100 подкатегорий), наших всего 14 общих кодов (os/dbms/erp/crm/office/antivirus/backup/…). Точное совпадение по name давало 0 / 26 094. Маппинг в три прохода: 1) точный subcategoryid → категория (02.07=dbms, 06.04=mail, 09.07=erp …) 2) префикс XX.* (03.*=antivirus, 04.*=development, 07.*=development) 3) keyword по name как страховка 4) fallback в "other" Распределение после re-import 26094/26094: other 17093 65.5% monitoring 2068 7.9% development 1961 7.5% antivirus 1101 4.2% web 1087 4.2% office 886 3.4% crm 480 1.8% erp 403 1.5% network 251 1.0% dbms 229 0.9% os 198 0.8% backup 197 0.8% virtualization 118 0.5% mail 22 0.1% NULL 1 0.0% 65% "other" — Минцифры включает CAD/BPM/ITSM/мультимедиа, у которых нет аналога в наших 14 кодах; остальные 35% разнесены по нашим категориям. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Расширение ОСЗ из 7.png диплома: добавлен PDF-отчёт с покрытием
по каждому стандарту и пер-категорным разрезом требований.
Бэкенд:
- internal/report/compliance_pdf.go: GenerateCompliancePDF(asset, standards)
Структура страниц:
1. Сводка — плитки по каждому стандарту с overall_score и счётчиками
2. Отдельная страница на стандарт — требования по категориям
с цветной меткой (зелёный/жёлтый/оранжевый/красный) и списками
covering_controls + missing_controls (что бы ещё закрыло).
- service.AssetAllStandards(assetID) — детализация сразу по всем стандартам
- handler GET /api/compliance/asset/:assetID/report.pdf
Фронт:
- ComplianceSection: кнопка «Скачать PDF» в шапке секции
через authFetch + Blob (тот же паттерн что у risk-PDF)
Live-проверка: Customer Database (4 контроля) → 4 страницы, 35KB:
стр.1 ФСТЭК-17 25.6% + ISO 22.4%
стр.2 ФСТЭК-17 детали (АВЗ.1=100%, ОЦЛ.1=30%, ИАФ.1=0% и т.д.)
стр.3 ISO 27001 детали
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Закрывает блок «Формирование организационно-технической документации»
из 7.png диплома + «Рекомендации по усовершенствованию» с группировкой
по 4 мероприятиям из 8.png (АРМ/ЛВС/ЭДО/конф.инф.).
Бэкенд — 3 PDF-генератора:
- document_passport.go — «Технический паспорт АС»
общие сведения, состав ПО, СЗИ, БДУ-уязвимости,
сводная таблица соответствия стандартам
- document_threat_model.go — «Модель угроз ИБ»
формула W, агрегат, перечень угроз, для топ-5
отдельные страницы с разбором S→ST→VL→DA
и параметрами Q^угр / q^серьёз / Q^реакц / Z
- document_protection_plan.go — «Перечень мер защиты»
контроли разнесены по 4 мероприятиям из 8.png,
для непокрытых VL — адресные рекомендации
(что внедрить из vl_category_controls)
document_pack.go — упаковка нескольких PDF в один ZIP.
document_handlers.go — 4 эндпоинта:
GET /api/reports/asset/:id/document/passport.pdf
GET /api/reports/asset/:id/document/threat-model.pdf
GET /api/reports/asset/:id/document/protection-plan.pdf
GET /api/reports/asset/:id/documents.zip (все 3 + compliance отчёт)
Фронт:
- DocumentsMenu — выпадающее меню в шапке карточки актива
«Документы ▾», 5 опций (полный пакет ZIP + 4 отдельных PDF)
- Скачивание через authFetch + blob (как у risk-PDF)
Live-проверка на Customer Database:
passport.pdf 27KB, 1 стр
threat-model.pdf 40KB, 7 стр (общий + 5 разборов)
protection-plan.pdf 28KB, 2 стр
documents.zip 117KB, 4 файла
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Добавил отдельную схему docs/risk-formula.excalidraw — фокус на происхождении 4 параметров формулы W (Q^угроза, q^серьёзность, Q^реакция, Z): источник данных → маппинг → хранение → пример. В обоих excalidraw-файлах заменил рукописный Excalifont (fontFamily=5) на Helvetica (fontFamily=2) + увеличил высоту блоков, чтобы текст не вылезал за границы. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes потребность «не один актив = один отчёт, а вся организация»: агрегированные метрики по 9 активам, таблица активов с W_max/уровнями/ СЗИ/compliance, топ-15 критических (asset × threat) рисков и единый PDF-отчёт. Подключено в Sidebar как пункт «Организация», доступно по /organization. Live: 9 активов, W_max=0.93 (Web Application Server), 8 critical, PDF=31KB / 3 стр. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(1) Цели и задачи ЗИ - Раздел 1.2 в техническом паспорте АС: 4 цели (C/I/A + неотказуемость) и 3 задачи защиты информации. (2) Определение вида обрабатываемой информации - Migration 039: справочник data_categories (6 кодов: public/internal/pdn/ confidential/dsp/kii) + поле assets.data_category_id - Backend: Asset.DataCategoryID, обновлены Create/Update/scan/payload, эндпоинт GET /api/data-categories - UI: SELECT 'Категория обрабатываемой информации' в форме актива - PDF: строка в общих сведениях паспорта АС. (3) 2 категории угроз из 8.png - Migration 040: добавил «Хищение, искажение, блокировка» и «Непреднамеренные воздействия»; переименовал «Утечка информации» → «Утечка по техническим каналам» - Backfill threats.threat_category_id по эвристикам name LIKE: 227 угроз размечены, NULL = 0 основные: НСД=95, Вредоносное ПО=52, Утечка=22, Отказ в обслуживании=16 (4) Каталог СЗИ (БД ПАСЗИ) - Новая страница /controls (ControlCatalogPage) — список 11 контролей по 4 мероприятиям из 8.png (АРМ/ЛВС/Документооборот/Конф.инф.) - Для каждого: описание, какие VL закрывает, на скольких активах внедрён, какие требования ФСТЭК-17 / ISO 27001 покрывает - Подключено в Sidebar как «Каталог СЗИ» в секции 'refs' Live-проверка: - паспорт PDF: 2 стр., раздел 1.2 «Цели и задачи ЗИ» рендерится - /api/data-categories: 6 категорий - /api/controls + UI: 11 контролей по 4 мероприятиям Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Все PDF-отчёты использовали символы ✓ ◐ ✗ → ↳ — NotoSans их не имеет, рендерились как ⬛-квадраты. Плюс длинные заголовки страниц угроз вылезали за правый край листа. Фиксы: - pdf.go: helper statusGlyph() возвращает «OK / Част. / Нет» вместо ✓◐✗ glyphCovered/Partial/Missing константы; стрелки → заменены на ASCII '->' - compliance_pdf.go: метки требований через statusGlyph() - document_passport.go: row() расширен до labelWidth=80mm; таблица соответствия колонки «Закрыто/Частично/Не закрыто/Всего» - document_protection_plan.go: «[внедрено] / [рекомендуется] / [нет]» вместо ✓✗+; «VL <-> control» в footer - document_threat_model.go: длинный заголовок страницы угрозы режется до 55 символов с «…», полное название печатается отдельной строкой - document_organization.go: ↳ заменены на отступы; «актив-источник» Также UI: - AssetDetailPage VulnSection: если ПО привязано но в БДУ нет совпадений, показываем нормальное объяснение (3 возможные причины + дисклеймер «не означает что ПО безопасно — БДУ покрывает только публичные CVE») вместо вводящего в заблуждение «привяжите ПО» при уже привязанном. Live: 5 PDF (passport, threat-model, protection-plan, compliance, organization) теперь рендерятся без ⬛, заголовки помещаются в лист. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 новых файлов тестов (~920 строк) на критичные чистые функции:
cmd/import-fstec-threats/applicability_test.go
TestDeriveQThreat — маппинг 'Возможности нарушителя' → Q^угр
(low/medium/high → 0.3/0.6/0.9 + edge кейсы)
TestDeriveQSeverity — q^серьёзность по числу нарушенных C/I/A
cmd/import-minreestr/categories_test.go
TestResolveCategory — 3-проходный маппер subcategoryid в наши
14 категорий (точный код / префикс / keyword
/ fallback в other), приоритеты, edge кейсы
internal/service/compliance/calc_test.go
TestCalcOverview_* — алгоритм coverage = max(weight) среди
внедрённых controls (5 сценариев: full spread,
max-wins, без контролей, full coverage,
пустые/без рёбер)
internal/service/control/service_test.go (100% coverage модуля)
Mock-репозиторий + проверка валидации id и проброса ошибок репо
internal/report/levels_test.go
TestStatusGlyph — текстовые маркеры OK/Част./Нет
(вместо ✓◐✗ из-за отсутствия глифов в NotoSans)
TestComplianceLevelRGB — цвета по баллу соответствия (4 уровня)
TestLevelRGB / Label — для уровней риска
internal/report/document_protection_plan_test.go
TestJoinControls — склейка контролей в строку (edge: nil/1/N)
TestCountUncoveredVL — дедуп uncovered VL по category_id между
несколькими угрозами
TestGroupControlsByMeasure — разнос 11 контролей по 4 мероприятиям
из 8.png (АРМ/ЛВС/ЭДО/конф.инф.)
internal/report/documents_smoke_test.go
Smoke-тесты на 5 PDF-генераторов: %PDF- magic, размер, nil-data
errors. PackDocuments — ZIP magic + skip empty entries.
Покрытие после:
internal/risk 95.2%
internal/service/control 100.0%
internal/report 83.2%
internal/service/auth 79.5%
internal/service/risk 42.4%
internal/bdu 36.5%
internal/service/compliance 19.0%
Все тесты — чистые юнит-тесты без БД и сети.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
RiskGraphPageпод stacked layout: hero (W-вердикт) → sortable список угроз актива → 5-колонок SankeyS→ST→VL→C→DA+ панель формулы ПТСЗИ.recomputeWзеркалит бэкендныйQReactionFromVLs, sticky-бар показываетΔWи позволяет сохранить заметку вlocalStorage.GET /api/risk/asset/:id/attack-paths— возвращает все AttackPath актива + агрегаты (w_max,level, counts) одним запросом.ComputeAssetAggregate) + 2 handler-теста + 11 frontend Jest-тестов на pureriskFlow.ts(flow conservation, coverage clamping, disabled controls, severity-weighted splits, negative-severity guard).Spec:
docs/superpowers/specs/2026-05-02-risk-graph-visualization-design.mdPlan:
docs/superpowers/plans/2026-05-02-risk-graph-visualization.mdЧто меняется на UI
/risk/graph/:assetId— теперь страница на 1 актив × все угрозы.?threat=<id>опционален; без него авто-выбирается max-W.coverage, VL→DA —(1−cov).frontend/src/components/RiskGraphSankey.tsx(legacy).V2 (документировано в spec, не блокирует merge)
AssembleAssetAttackPathsсейчас вызываетAssembleAttackPathв цикле —N+1(~40–120 запросов на актив с 10–30 угрозами). V2: bulk repo-методLoadAssetAttackPaths+ контракт-тест.RiskGraphPage.test.tsx/ThreatList.test.tsxsnapshot-тесты.localStorage).onParamshero-кнопка (заглушка из spec, V1 без неё).Test plan
go test ./internal/service/... ./internal/transport/... ./internal/domain/...— PASS (backend)cd frontend && CI=true npm test -- --testPathPattern=riskFlow— 11 passedcd frontend && CI=true npm run build— Compiled successfully/risk/graph/<assetId>для актива с ≥3 угрозами и ≥1 контролем — проверить hero/список/Sankey/popover/what-if/PDF./risk/graph/<id>?threat=<id>корректно подсвечивает выбранную угрозу.?threat=<id>→ toast + fallback к max-W.🤖 Generated with Claude Code