feat(cra): Cyber-trifft-CE mit echten IACE-Safety-Functions
CI / detect-changes (push) Successful in 16s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 11s
CI / validate-canonical-controls (push) Successful in 10s
CI / loc-budget (push) Successful in 28s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m10s
CI / test-go (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 16s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 11s
CI / validate-canonical-controls (push) Successful in 10s
CI / loc-budget (push) Successful in 28s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m10s
CI / test-go (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
useCRA leitet aus den Hazards+Mitigations DES IACE-Projekts cyber-relevante safety_functions ab (Bewegung/Quetschen/safety_function_failure/Pneumatik → prevent_unexpected_actuation; Signal/Sensor/Kommunikation → signal_integrity; rein physische wie Thermik/Ergonomie ausgeschlossen) und gibt sie statt der Demo-Hardcodes an /assess. build_cross_links zeigt dann, welche REALE Projekt-Schutzmassnahme ein Cyber-Befund wieder oeffnet. Fallback auf Demo-Set, bis die Projekt-Hazards geladen sind. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
// Cyber-meets-CE: derive cyber-defeatable safety functions from an IACE project's
|
||||||
|
// real hazards + mitigations, to feed the CRA cross-link bridge (replaces the demo
|
||||||
|
// hardcode). Only control-dependent hazards (movement/actuation, signal-integrity)
|
||||||
|
// can be re-opened by a cyber attack — purely physical ones (thermal, sharp edges,
|
||||||
|
// noise) are excluded. Mirrors the backend cra_safety_bridge kinds.
|
||||||
|
|
||||||
|
export interface IaceHazard {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
category: string
|
||||||
|
possible_harm?: string
|
||||||
|
}
|
||||||
|
export interface IaceMitigation {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
reduction_type?: string
|
||||||
|
linked_hazard_ids?: string[]
|
||||||
|
}
|
||||||
|
export interface SafetyFunction {
|
||||||
|
name: string
|
||||||
|
hazard: string
|
||||||
|
original_measure: string
|
||||||
|
kind: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IACE hazard category -> the cyber-bridge safety-function kind.
|
||||||
|
const KIND_BY_CATEGORY: Record<string, string> = {
|
||||||
|
// control-driven movement/actuation: a cyber attack can trigger it
|
||||||
|
mechanical_hazard: 'prevent_unexpected_actuation',
|
||||||
|
mechanical: 'prevent_unexpected_actuation',
|
||||||
|
crush: 'prevent_unexpected_actuation',
|
||||||
|
crush_point: 'prevent_unexpected_actuation',
|
||||||
|
crushing: 'prevent_unexpected_actuation',
|
||||||
|
movement: 'prevent_unexpected_actuation',
|
||||||
|
pneumatic_hydraulic: 'prevent_unexpected_actuation',
|
||||||
|
safety_function_failure: 'prevent_unexpected_actuation',
|
||||||
|
software_control: 'prevent_unexpected_actuation',
|
||||||
|
software_fault: 'prevent_unexpected_actuation',
|
||||||
|
// sensor/signal/parameter integrity: a cyber attack can corrupt it
|
||||||
|
signal: 'signal_integrity',
|
||||||
|
signale: 'signal_integrity',
|
||||||
|
sensor_spoofing: 'signal_integrity',
|
||||||
|
communication_failure: 'signal_integrity',
|
||||||
|
configuration_error: 'signal_integrity',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deriveSafetyFunctions(hazards: IaceHazard[], mitigations: IaceMitigation[]): SafetyFunction[] {
|
||||||
|
const measuresByHazard: Record<string, string[]> = {}
|
||||||
|
for (const m of mitigations || []) {
|
||||||
|
for (const hid of m.linked_hazard_ids || []) {
|
||||||
|
(measuresByHazard[hid] ||= []).push(m.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const out: SafetyFunction[] = []
|
||||||
|
for (const h of hazards || []) {
|
||||||
|
const kind = KIND_BY_CATEGORY[h.category]
|
||||||
|
if (!kind) continue
|
||||||
|
out.push({
|
||||||
|
name: h.name,
|
||||||
|
hazard: h.possible_harm || h.description || h.name,
|
||||||
|
original_measure: (measuresByHazard[h.id] || []).join(', ') || 'IACE-Schutzmaßnahme',
|
||||||
|
kind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the project's hazards + mitigations and derive the cyber-relevant safety
|
||||||
|
// functions. Returns [] on any error (caller falls back to its demo functions).
|
||||||
|
export async function fetchProjectSafetyFunctions(projectId: string): Promise<SafetyFunction[]> {
|
||||||
|
try {
|
||||||
|
const [hRes, mRes] = await Promise.all([
|
||||||
|
fetch(`/api/sdk/v1/iace/projects/${projectId}/hazards`),
|
||||||
|
fetch(`/api/sdk/v1/iace/projects/${projectId}/mitigations`),
|
||||||
|
])
|
||||||
|
const hJson = hRes.ok ? await hRes.json() : {}
|
||||||
|
const mJson = mRes.ok ? await mRes.json() : {}
|
||||||
|
const hazards: IaceHazard[] = hJson.hazards || (Array.isArray(hJson) ? hJson : [])
|
||||||
|
const mitigations: IaceMitigation[] = mJson.mitigations || (Array.isArray(mJson) ? mJson : [])
|
||||||
|
return deriveSafetyFunctions(hazards, mitigations)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { CRADemo, CRAFinding, Measure, DEMO_SCENARIO } from './useCRADemo'
|
import { CRADemo, CRAFinding, Measure, DEMO_SCENARIO } from './useCRADemo'
|
||||||
|
import { fetchProjectSafetyFunctions, SafetyFunction } from './iace-safety-bridge'
|
||||||
|
|
||||||
// Live CRA assessment: POST the (demo) findings + customer weights + the project's
|
// Live CRA assessment: POST the (demo) findings + customer weights + the project's
|
||||||
// safety functions to /api/v1/cra/assess and merge the live, priority-sorted
|
// safety functions to /api/v1/cra/assess and merge the live, priority-sorted
|
||||||
@@ -97,14 +98,26 @@ export function useCRA(projectId?: string) {
|
|||||||
const [scannerRepo, setScannerRepoState] = useState('') // pull-flow: chosen scanner repo_id
|
const [scannerRepo, setScannerRepoState] = useState('') // pull-flow: chosen scanner repo_id
|
||||||
const [weights, setWeights] = useState<Weights>({})
|
const [weights, setWeights] = useState<Weights>({})
|
||||||
const [snapshots, setSnapshots] = useState<SnapshotMeta[]>([])
|
const [snapshots, setSnapshots] = useState<SnapshotMeta[]>([])
|
||||||
|
// Cyber-meets-CE: real safety functions derived from THIS IACE project's
|
||||||
|
// hazards/mitigations (falls back to the demo set until they load / if empty).
|
||||||
|
const [safetyFunctions, setSafetyFunctions] = useState<SafetyFunction[]>(SAFETY_FUNCTIONS)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!projectId) return
|
||||||
|
let cancelled = false
|
||||||
|
fetchProjectSafetyFunctions(projectId).then((sf) => {
|
||||||
|
if (!cancelled && sf.length) setSafetyFunctions(sf)
|
||||||
|
})
|
||||||
|
return () => { cancelled = true }
|
||||||
|
}, [projectId])
|
||||||
|
|
||||||
const buildPayload = useCallback(() => ({
|
const buildPayload = useCallback(() => ({
|
||||||
findings: DEMO_SCENARIO.findings.map((f) => ({
|
findings: DEMO_SCENARIO.findings.map((f) => ({
|
||||||
id: f.id, title: f.title, cwe: f.cwe, severity: f.scanner_severity, location: f.location,
|
id: f.id, title: f.title, cwe: f.cwe, severity: f.scanner_severity, location: f.location,
|
||||||
})),
|
})),
|
||||||
weights,
|
weights,
|
||||||
safety_functions: SAFETY_FUNCTIONS,
|
safety_functions: safetyFunctions,
|
||||||
}), [weights])
|
}), [weights, safetyFunctions])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
@@ -114,7 +127,7 @@ export function useCRA(projectId?: string) {
|
|||||||
try {
|
try {
|
||||||
const r = await fetch('/api/v1/cra/assess-from-scanner', {
|
const r = await fetch('/api/v1/cra/assess-from-scanner', {
|
||||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ repo_id: scannerRepo, weights, safety_functions: SAFETY_FUNCTIONS }),
|
body: JSON.stringify({ repo_id: scannerRepo, weights, safety_functions: safetyFunctions }),
|
||||||
})
|
})
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
const j = await r.json()
|
const j = await r.json()
|
||||||
@@ -151,7 +164,7 @@ export function useCRA(projectId?: string) {
|
|||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
return () => { cancelled = true }
|
return () => { cancelled = true }
|
||||||
}, [buildPayload, projectId, scannerRepo, weights])
|
}, [buildPayload, projectId, scannerRepo, weights, safetyFunctions])
|
||||||
|
|
||||||
const refreshSnapshots = useCallback(() => {
|
const refreshSnapshots = useCallback(() => {
|
||||||
if (!projectId) return
|
if (!projectId) return
|
||||||
|
|||||||
Reference in New Issue
Block a user