Assessor_2.0/src/profiles/pingcastle/pingcastle-definition.ts

61 lines
3.5 KiB
TypeScript

import type { CanonicalFindingStatus } from '../../domain/canonical-model';
import type { ProfileValueMapping, ToolProfileDefinition } from '../../domain/tool-profile-definition';
import { commonSeverityMappings, commonStatusMappings } from '../common-normalization';
import { genericFieldRules } from '../generic/generic-field-rules';
// PingCastle berichtet ausschliesslich ausgeloeste RiskRules; der Source-Extractor
// setzt dafuer den Status "Matched". Ohne eine eigene Deklaration greift die
// generische Rueckfallregel (matched -> open/warning), die einen Treffer bewusst
// als blossen Hinweis liest. Fuer einen Exposure-Scanner ist das falsch: die
// Regel hat getroffen, die Exposition ist erkannt. Die Folge war, dass
// exposure_state nie "detected" wurde und damit kein einziger PingCastle-Befund
// als is_ioe galt (RESULT_CONTRACT: exposure_finding + detected -> IOE).
// Nur der exakte Wert "Matched" wird umgedeutet; unspezifische Treffertexte
// bleiben weiterhin ein Hinweis.
const pingCastleStatusMappings: Array<ProfileValueMapping<CanonicalFindingStatus>> = commonStatusMappings
.map(mapping => mapping.normalizedValue === 'failed'
? { ...mapping, patterns: [...mapping.patterns, /^matched$/i] }
: mapping);
export const pingCastleDefinition: ToolProfileDefinition = {
schemaVersion: 1,
id: 'pingcastle',
securityScoring: {
version: '1.0.0',
native: { kind: 'mapped', properties: ['risk.domain', 'risk.global'], scope: 'overall', scale: '0-100', direction: 'lower-is-better' },
targets: ['domain.fqdn'], resultPopulation: 'findings-only'
},
name: 'PingCastle',
version: '1.6.0',
processing: {
mappingVersion: '1.2.0', normalizationVersion: '1.1.0', presentationVersion: '1.2.0',
compatibilityKey: 'pingcastle-canonical-v1.1', changeClass: 'compatible',
reimportRequired: true, affectedDimensions: ['environment-context']
},
sourceSignals: [
{ scope: 'file-name', pattern: /ping[\s_.-]*castle|^ad[_-]hc[_-]/i, contribution: 0.5, message: 'Der Dateiname entspricht einem PingCastle-Healthcheck.' },
{ scope: 'raw-text', pattern: /ping[\s_-]*castle/i, contribution: 0.7, message: 'Der Dateiinhalt enthält einen PingCastle-Marker.' },
{ scope: 'raw-text', pattern: /rule\s+id\s*:/i, contribution: 0.1, message: 'Der Bericht enthält Rule-ID-Details.' },
{ scope: 'table-name', pattern: /^healthcheckdata$/i, contribution: 0.7, message: 'Das XML-Wurzelelement entspricht einem PingCastle-Healthcheck.' },
{ scope: 'table-name', pattern: /^risk\s+scores?$/i, contribution: 0.2, message: 'Eine Tabelle heißt „Risk Scores“.' },
{ scope: 'header', pattern: /^rules?\s+matched$/i, contribution: 0.12, message: 'Die Kopfzeile „Rules matched“ wurde gefunden.' }
],
tableRules: {
preferredNames: [{ pattern: /risk\s+scores?|rule|finding/i, contribution: 0.8, message: 'PingCastle-Risiko- oder Regeltabelle.' }],
discouragedNames: [{ pattern: /domain\s+information|summary/i, contribution: 0.35, message: 'Domänenmetadaten oder Zusammenfassung.' }]
},
fieldRules: genericFieldRules,
normalization: { severity: commonSeverityMappings, status: pingCastleStatusMappings },
presentation: {
titleHeaders: [/^title$/i],
severityHeaders: [/^(?:severity|risk\s*level|score)$/i],
sortHeaders: [/^(?:points?|score)$/i],
sortDirection: 'desc',
defaultStatusFilter: 'all',
statusFilters: [],
showResultSheetTitles: true,
resultSheetTitleRules: [{ pattern: /^PingCastle\s*·\s*/i, replacement: '' }]
},
comparisonKeys: [['rule-id', 'asset'], ['rule-id']]
};