61 lines
3.5 KiB
TypeScript
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']]
|
|
};
|