/** * BSI Grundschutz++ for the Compliance Engine (Phase 13F, user decision 2026-10-08): the Kernel requirements of * the BSI Stand-der-Technik-Bibliothek and the official BSI mapping ISO/IEC 27001:2022 Annex A -> Grundschutz++, * converted into compact, version-pinned snapshots like the MITRE reference catalogs. Source prose is kept as * published, including its OSCAL parameter placeholders; `bsiRequirementText` resolves them for display. */ export const BSI_KERNEL_CLASS = 'BSI-Stand-der-Technik-Kernel-G0'; export const BSI_NOTICE = 'Source: German Federal Office for Information Security (BSI), Stand-der-Technik-Bibliothek, ' + 'https://github.com/BSI-Bund/Stand-der-Technik-Bibliothek. License: Creative Commons Attribution-ShareAlike 4.0 International ' + '(CC BY-SA 4.0), https://creativecommons.org/licenses/by-sa/4.0/. Compacted; the requirement content is unchanged.'; export type BsiCrosswalkRelationship = 'equivalent-to' | 'subset-of' | 'superset-of' | 'intersects-with'; export interface BsiRequirement { id: string; title: string; /** Top-level practice of the requirement, for example `KONF`. */ practice: string; parentId?: string; /** Statement prose exactly as published, including `{{ insert: param, }}` placeholders. */ statement: string; modalVerb?: string; guidance?: string; securityLevel?: string; effortLevel?: string; /** Parameter id -> its published value, or its label where no value is set. */ params?: Record; } export interface BsiKernelCatalog { schemaVersion: 1; framework: 'bsi-grundschutz-plus-plus'; catalogClass: typeof BSI_KERNEL_CLASS; title: string; version: string; sourceUrl: string; sourceSha256: string; license: string; practices: Array<{ id: string; title: string }>; requirements: BsiRequirement[]; } export interface BsiCrosswalkEntry { uuid: string; isoControlId: string; relationship: BsiCrosswalkRelationship; targets: string[]; } export interface BsiIsoCrosswalk { schemaVersion: 1; framework: 'bsi-iso27001-annex-a-to-grundschutz-plus-plus'; title: string; version: string; sourceUrl: string; sourceSha256: string; license: string; /** Only the mapping whose target is the Kernel catalog; the Methodik mapping is outside the decided scope. */ targetCatalog: string; entries: BsiCrosswalkEntry[]; } type ObjectValue = Record; const object = (value: unknown): ObjectValue => value && typeof value === 'object' && !Array.isArray(value) ? value as ObjectValue : {}; const list = (value: unknown): unknown[] => Array.isArray(value) ? value : []; const text = (value: unknown): string => typeof value === 'string' ? value : ''; const prop = (props: unknown, name: string): string | undefined => list(props).map(object).find(item => item.name === name)?.value as string | undefined; const REQUIREMENT_ID = /^[A-Z]{2,5}(?:\.\d+)+$/; const PARAM_INSERT = /\{\{\s*insert:\s*param,\s*([^\s}]+)\s*\}\}/g; const RELATIONSHIPS = new Set(['equivalent-to', 'subset-of', 'superset-of', 'intersects-with']); export function bsiKernelCatalog(raw: unknown, sourceUrl: string, sourceSha256: string): BsiKernelCatalog { const catalog = object(object(raw).catalog); const metadata = object(catalog.metadata); const version = text(metadata.version); if (!version || !Array.isArray(catalog.groups)) throw new Error('Expected an OSCAL Grundschutz++ catalog with a version.'); const practices: BsiKernelCatalog['practices'] = []; const requirements: BsiRequirement[] = []; const visitControls = (controls: unknown, practice: string, parentId?: string) => { for (const control of list(controls).map(object)) { const id = text(control.id); if (control.class === BSI_KERNEL_CLASS) { if (!REQUIREMENT_ID.test(id)) throw new Error(`Invalid Kernel requirement id: ${id || '(missing)'}`); const parts = list(control.parts).map(object); const statementPart = parts.find(part => part.name === 'statement'); const statement = text(statementPart?.prose); if (!statement) throw new Error(`Kernel requirement ${id} has no statement.`); const params = Object.fromEntries(list(control.params).map(object).map(param => { const values = list(param.values).filter((value): value is string => typeof value === 'string'); return [text(param.id), values.length === 1 ? values[0] : text(param.label)]; })); for (const part of parts) { for (const match of text(part.prose).matchAll(PARAM_INSERT)) { if (!params[match[1]]) throw new Error(`Kernel requirement ${id} inserts unknown parameter ${match[1]}.`); } } const guidance = text(parts.find(part => part.name === 'guidance')?.prose); requirements.push({ id, title: text(control.title), practice, ...(parentId ? { parentId } : {}), statement, ...optional('modalVerb', prop(statementPart?.props, 'modal_verb')), ...optional('guidance', guidance || undefined), ...optional('securityLevel', prop(control.props, 'sec_level')), ...optional('effortLevel', prop(control.props, 'effort_level')), ...(Object.keys(params).length ? { params } : {}) }); } visitControls(control.controls, practice, id); } }; const visitGroups = (groups: unknown, practice?: string) => { for (const group of list(groups).map(object)) { const own = practice ?? text(group.id); if (!practice) practices.push({ id: own, title: text(group.title) }); visitControls(group.controls, own); visitGroups(group.groups, own); } }; visitGroups(catalog.groups); if (!requirements.length) throw new Error('The catalog contains no Kernel requirements.'); const ids = requirements.map(item => item.id); const repeated = ids.filter((id, index) => ids.indexOf(id) !== index); if (repeated.length) throw new Error(`Repeated Kernel requirement ids: ${[...new Set(repeated)].join(', ')}`); const used = new Set(requirements.map(item => item.practice)); return { schemaVersion: 1, framework: 'bsi-grundschutz-plus-plus', catalogClass: BSI_KERNEL_CLASS, title: text(metadata.title), version, sourceUrl, sourceSha256, license: BSI_NOTICE, practices: practices.filter(item => used.has(item.id)), requirements }; } export function bsiIsoCrosswalk(raw: unknown, sourceUrl: string, sourceSha256: string): BsiIsoCrosswalk { const collection = object(object(raw)['mapping-collection']); const metadata = object(collection.metadata); const version = text(metadata.version); if (!version || !Array.isArray(collection.mappings)) throw new Error('Expected an OSCAL mapping collection with a version.'); const kernel = collection.mappings.map(object).filter(mapping => /Kernel/i.test(text(object(mapping['target-resource']).href))); if (kernel.length !== 1) throw new Error(`Expected exactly one mapping to the Kernel catalog, found ${kernel.length}.`); const entries = list(kernel[0].maps).map(object).map((map): BsiCrosswalkEntry => { const relationship = text(map.relationship); const sources = list(map.sources).map(object).map(source => text(source['id-ref'])); const targets = list(map.targets).map(object).map(target => text(target['id-ref'])); if (!RELATIONSHIPS.has(relationship)) throw new Error(`Unknown mapping relationship: ${relationship || '(missing)'}`); if (sources.length !== 1 || !/^[5-8]\.\d+$/.test(sources[0])) throw new Error(`Expected one ISO Annex A source control, found ${sources.join(', ') || 'none'}.`); if (!targets.length || targets.some(target => !REQUIREMENT_ID.test(target))) throw new Error(`Invalid mapping targets for ISO ${sources[0]}.`); return { uuid: text(map.uuid), isoControlId: sources[0], relationship: relationship as BsiCrosswalkRelationship, targets }; }); return { schemaVersion: 1, framework: 'bsi-iso27001-annex-a-to-grundschutz-plus-plus', title: text(metadata.title), version, sourceUrl, sourceSha256, license: BSI_NOTICE, targetCatalog: text(object(kernel[0]['target-resource']).href), entries }; } /** Statement or guidance prose with each parameter placeholder replaced by its published value or label. */ export function bsiRequirementText(prose: string, params: Record | undefined): string { return prose.replace(PARAM_INSERT, (placeholder, id: string) => params?.[id] ?? placeholder); } export interface BsiDerivedRequirement { requirementId: string; title: string; practice: string; /** Every accepted ISO control and relationship through which the requirement was reached. */ via: Array<{ isoControlId: string; relationship: BsiCrosswalkRelationship }>; } export interface BsiDerivation { crosswalkVersion: string; catalogVersion: string; requirements: BsiDerivedRequirement[]; /** Mapping targets that the pinned Kernel catalog does not contain. They are reported, never reinterpreted. */ missingTargets: Array<{ isoControlId: string; requirementId: string }>; } /** * Stage 1 (user decision 2026-10-08): the Kernel requirements the official BSI mapping assigns to accepted ISO * controls. Derived only, never a decision of its own. Output is sorted, so the input order does not matter. */ export function deriveBsiRequirements(acceptedIsoControlIds: readonly string[], crosswalk: BsiIsoCrosswalk, catalog: BsiKernelCatalog): BsiDerivation { const accepted = new Set(acceptedIsoControlIds); const byId = new Map(catalog.requirements.map(item => [item.id, item])); const derived = new Map(); const missing = new Map(); for (const entry of crosswalk.entries) { if (!accepted.has(entry.isoControlId)) continue; for (const target of entry.targets) { const requirement = byId.get(target); if (!requirement) { missing.set(`${entry.isoControlId}\u0000${target}`, { isoControlId: entry.isoControlId, requirementId: target }); continue; } const item = derived.get(target) ?? { requirementId: target, title: requirement.title, practice: requirement.practice, via: [] }; if (!item.via.some(via => via.isoControlId === entry.isoControlId && via.relationship === entry.relationship)) { item.via.push({ isoControlId: entry.isoControlId, relationship: entry.relationship }); } derived.set(target, item); } } const order = (a: string, b: string) => a.localeCompare(b, 'en', { numeric: true }); return { crosswalkVersion: crosswalk.version, catalogVersion: catalog.version, requirements: [...derived.values()] .map(item => ({ ...item, via: item.via.sort((a, b) => order(a.isoControlId, b.isoControlId) || a.relationship.localeCompare(b.relationship)) })) .sort((a, b) => order(a.requirementId, b.requirementId)), missingTargets: [...missing.values()].sort((a, b) => order(a.isoControlId, b.isoControlId) || order(a.requirementId, b.requirementId)) }; } export interface BsiOverviewRequirement extends BsiDerivedRequirement { /** Statement with its parameters filled in, for display. */ statement: string; modalVerb?: string; } /** The derived BSI requirements a response carries, with the catalog provenance and attribution the UI shows. */ export interface BsiOverview { catalogVersion: string; crosswalkVersion: string; license: string; /** Every practice of the catalog in catalog order, so the UI can order and title them without the catalog. */ practices: Array<{ id: string; title: string }>; requirements: BsiOverviewRequirement[]; missingTargets: BsiDerivation['missingTargets']; } /** Stage 1 overview for accepted ISO controls; undefined when nothing is derived, so ISO-only responses stay unchanged. */ export function bsiOverview(acceptedIsoControlIds: readonly string[], crosswalk: BsiIsoCrosswalk, catalog: BsiKernelCatalog): BsiOverview | undefined { const derivation = deriveBsiRequirements(acceptedIsoControlIds, crosswalk, catalog); if (!derivation.requirements.length && !derivation.missingTargets.length) return undefined; const byId = new Map(catalog.requirements.map(item => [item.id, item])); return { catalogVersion: catalog.version, crosswalkVersion: crosswalk.version, license: catalog.license, practices: catalog.practices, requirements: derivation.requirements.map(item => { const requirement = byId.get(item.requirementId)!; return { ...item, statement: bsiRequirementText(requirement.statement, requirement.params), ...(requirement.modalVerb ? { modalVerb: requirement.modalVerb } : {}) }; }), missingTargets: derivation.missingTargets }; } /** Every Kernel requirement by practice in catalog order with its title (CC BY-SA 4.0), and whether the BSI mapping can reach it from any ISO control. */ export interface BsiFootprint { catalogVersion: string; crosswalkVersion: string; practices: Array<{ id: string; title: string; requirements: Array<{ id: string; title: string; reachable: boolean }> }>; } /** The Client Info footprint grid; independent of decisions, so it exists before anything is derived. */ export function bsiFootprint(crosswalk: BsiIsoCrosswalk, catalog: BsiKernelCatalog): BsiFootprint { const reachable = new Set(crosswalk.entries.flatMap(entry => entry.targets)); return { catalogVersion: catalog.version, crosswalkVersion: crosswalk.version, practices: catalog.practices.map(practice => ({ ...practice, requirements: catalog.requirements .filter(requirement => requirement.practice === practice.id) .map(requirement => ({ id: requirement.id, title: requirement.title, reachable: reachable.has(requirement.id) })) })) }; } /** * The findings of each derived requirement: every finding of an accepted ISO control the requirement was * reached through, once per Result DB row, like the ISO theme counts. */ export function bsiRequirementFindings( controls: ReadonlyArray<{ controlId: string; findings: readonly F[] }>, overview: BsiOverview | undefined ): Map { const byControl = new Map(controls.map(control => [control.controlId, control.findings])); return new Map((overview?.requirements ?? []).map(requirement => { const findings = new Map(); for (const via of requirement.via) { for (const finding of byControl.get(via.isoControlId) ?? []) if (!findings.has(finding.resultProjectionId)) findings.set(finding.resultProjectionId, finding); } return [requirement.requirementId, [...findings.values()]]; })); } function optional(key: K, value: string | undefined): Partial> { return value ? { [key]: value } as Record : {}; }