261 lines
10 KiB
TypeScript
261 lines
10 KiB
TypeScript
import type { CanonicalFieldRole, CanonicalFindingStatus, CanonicalSeverity } from './canonical-model';
|
|
|
|
export type ProfileSignalScope = 'file-name' | 'raw-text' | 'table-name' | 'header' | 'cell-value';
|
|
|
|
export interface WeightedPattern {
|
|
pattern: RegExp;
|
|
contribution: number;
|
|
message: string;
|
|
}
|
|
|
|
export interface ProfileSourceSignal extends WeightedPattern {
|
|
scope: ProfileSignalScope;
|
|
}
|
|
|
|
export interface ProfileFieldRule {
|
|
role: CanonicalFieldRole;
|
|
headers: WeightedPattern[];
|
|
/** Conditions apply only within the current source table/candidate shape. */
|
|
whenHeadersPresent?: RegExp[];
|
|
whenHeadersAbsent?: RegExp[];
|
|
}
|
|
|
|
export interface ProfileValueMapping<T extends string> {
|
|
normalizedValue: T;
|
|
patterns: RegExp[];
|
|
}
|
|
|
|
export interface ProfileStatusFilter {
|
|
value: string;
|
|
label: string;
|
|
patterns: RegExp[];
|
|
}
|
|
|
|
export interface ProfileStatusDisplayRule {
|
|
label: string;
|
|
patterns: RegExp[];
|
|
}
|
|
|
|
export type ProcessingChangeClass = 'presentation-only' | 'compatible' | 'semantic-breaking';
|
|
|
|
/**
|
|
* Versionierter Verarbeitungsvertrag eines Werkzeugprofils.
|
|
*
|
|
* Die Profilversion beschreibt das gesamte Paket. Mapping, Normalisierung und
|
|
* Darstellung entwickeln sich dagegen unabhaengig. Der compatibilityKey ist
|
|
* die fail-closed-Grenze fuer Aggregation und Vergleich kanonischer Ergebnisse.
|
|
*/
|
|
export interface ToolProcessingContract {
|
|
mappingVersion: string;
|
|
normalizationVersion: string;
|
|
presentationVersion: string;
|
|
compatibilityKey: string;
|
|
changeClass: ProcessingChangeClass;
|
|
reimportRequired: boolean;
|
|
affectedDimensions: string[];
|
|
}
|
|
|
|
/** Every scanner must explicitly assess native overall-score support before fallback. */
|
|
export interface ToolSecurityScoringDefinition {
|
|
version: string;
|
|
native: {
|
|
kind: 'mapped';
|
|
/** Exact mapped report properties, never fuzzy matches against finding fields. */
|
|
properties: [string, ...string[]];
|
|
scope: 'overall';
|
|
scale: '0-100';
|
|
direction: 'higher-is-better' | 'lower-is-better';
|
|
/** Other native scopes that make this single contribution ambiguous. */
|
|
incompatibleProperties?: string[];
|
|
} | {
|
|
kind: 'unavailable';
|
|
/** Why the supported report formats cannot currently supply a usable native score. */
|
|
reason: string;
|
|
};
|
|
targets: string[];
|
|
benchmark?: string[];
|
|
resultPopulation: 'complete' | 'findings-only' | 'unknown';
|
|
}
|
|
|
|
/**
|
|
* Every scanner must state how its findings may be ordered against each other.
|
|
* Severity is not universal: CIS-CAT delivers none at all and PingCastle carries
|
|
* only Points. A basis is declared with its exact mapped properties, or declared
|
|
* unavailable with a reason. Never infer one from an arbitrary field named
|
|
* severity, level, score or rank, and never order across sources with different
|
|
* bases — see docs/REPORT_STRATEGY.md.
|
|
*/
|
|
export interface ToolPriorityBasisDefinition {
|
|
version: string;
|
|
basis: {
|
|
kind: 'severity';
|
|
/** Exact mapped properties carrying the native criticality. */
|
|
properties: [string, ...string[]];
|
|
} | {
|
|
kind: 'numeric';
|
|
/** Exact mapped properties behind the numeric rank value. */
|
|
properties: [string, ...string[]];
|
|
scale: string;
|
|
direction: 'higher-is-worse' | 'lower-is-worse';
|
|
} | {
|
|
kind: 'unavailable';
|
|
/** Why the supported report formats cannot supply a defensible ordering. */
|
|
reason: string;
|
|
};
|
|
/**
|
|
* Fields that resemble a basis but are not one. Recorded so a later change
|
|
* cannot quietly promote them, which would manufacture a criticality the
|
|
* source never stated.
|
|
*/
|
|
excludedProperties?: Array<{ property: string; reason: string }>;
|
|
}
|
|
|
|
/**
|
|
* Versioned contract for remediation comparisons. This is deliberately
|
|
* separate from score population: a complete score denominator does not prove
|
|
* that an omitted rule or object was re-tested successfully.
|
|
*/
|
|
interface AvailableToolTimeslotComparisonBase {
|
|
version: string;
|
|
kind: 'available';
|
|
scope: {
|
|
kind: string;
|
|
properties: [string, ...string[]];
|
|
};
|
|
observationTimeProperties: [string, ...string[]];
|
|
resultEvidence: 'explicit-results' | 'findings-only' | 'unknown';
|
|
/** `unsupported` keeps missing target occurrences unresolved. */
|
|
negativeEvidence: 'unsupported' | 'complete-explicit-negative';
|
|
diagnosticPolicy: 'fail-closed-by-impact';
|
|
}
|
|
|
|
export type ToolTimeslotComparisonDefinition = (AvailableToolTimeslotComparisonBase & {
|
|
granularity: 'scope' | 'aggregate';
|
|
}) | (AvailableToolTimeslotComparisonBase & {
|
|
granularity: 'asset';
|
|
object: { kind: string; namespace: string; source: 'affected-asset' };
|
|
}) | {
|
|
version: string;
|
|
kind: 'unavailable';
|
|
reason: string;
|
|
};
|
|
|
|
export interface ToolProfileDefinition {
|
|
schemaVersion: 1;
|
|
id: string;
|
|
name: string;
|
|
/** Further scan-run names the import assigns to this profile, for example scope-specific names. */
|
|
runNames?: string[];
|
|
version: string;
|
|
processing: ToolProcessingContract;
|
|
securityScoring: ToolSecurityScoringDefinition;
|
|
priorityBasis: ToolPriorityBasisDefinition;
|
|
timeslotComparison: ToolTimeslotComparisonDefinition;
|
|
fallback?: boolean;
|
|
sourceSignals: ProfileSourceSignal[];
|
|
tableRules: {
|
|
preferredNames: WeightedPattern[];
|
|
discouragedNames: WeightedPattern[];
|
|
};
|
|
fieldRules: ProfileFieldRule[];
|
|
normalization: {
|
|
severity: Array<ProfileValueMapping<CanonicalSeverity>>;
|
|
status: Array<ProfileValueMapping<CanonicalFindingStatus>>;
|
|
};
|
|
presentation: {
|
|
titleHeaders: RegExp[];
|
|
severityHeaders: RegExp[];
|
|
sortHeaders: RegExp[];
|
|
sortDirection: 'asc' | 'desc';
|
|
defaultStatusFilter: string;
|
|
statusFilters: ProfileStatusFilter[];
|
|
/** Geordnete Header-Prioritaet fuer den sichtbaren Ergebnisstatus. */
|
|
statusHeaders?: RegExp[];
|
|
/** Optionale, rein visuelle Beschriftung roher Werkzeugstatus. */
|
|
statusDisplayRules?: ProfileStatusDisplayRule[];
|
|
/**
|
|
* Header, deren Feldwert ein beschreibender Ergebnistext ist statt eines
|
|
* kurzen Status-Badges. Manche Werkzeuge (z. B. Maester) nennen diese
|
|
* Spalte schlicht "Result", waehrend andere (z. B. Purple Knight) unter
|
|
* demselben Namen nur ein kurzes Statuswort fuehren und den Text separat
|
|
* in "Result Message" ablegen. Ohne diese Angabe bleibt der Reader beim
|
|
* Purple-Knight-Verhalten und blendet ein Feld namens "Result" aus.
|
|
*/
|
|
resultMessageHeaders?: RegExp[];
|
|
/**
|
|
* Header, deren Feldwert als eigener benannter Abschnitt in das
|
|
* Results-Fenster gehoert - der Feldname wird zum Titel des Abschnitts.
|
|
*
|
|
* Gedacht fuer Inhalte, die der Bericht selbst nur aufgeklappt zeigt und
|
|
* die deshalb zu den Belegdaten zaehlen, nicht zur Lesespalte: CIS-CAT
|
|
* versteckt hinter "Show Rule Result XML" und "More" das Rule Result XML
|
|
* und die Rahmenwerk-Zuordnungen. Anders als `resultMessageHeaders` (ein
|
|
* einzelner Ergebnistext anstelle einer Ergebnisdatei) duerfen es mehrere
|
|
* Abschnitte sein; sie erscheinen in Feldreihenfolge. Der Reader blendet
|
|
* diese Felder aus, damit dieselbe Aussage nicht doppelt steht.
|
|
*/
|
|
resultSectionHeaders?: RegExp[];
|
|
/**
|
|
* Verknuepfte Belegtabellen benoetigen einen fachlichen Abschnittstitel,
|
|
* wenn ihr Tabellenname Bedeutung traegt und nicht nur die Quelle wiederholt.
|
|
*/
|
|
showResultSheetTitles?: boolean;
|
|
/** Deklarative Umbenennung fachlicher Belegtabellentitel. */
|
|
resultSheetTitleRules?: Array<{ pattern: RegExp; replacement: string }>;
|
|
/**
|
|
* Ergebnisabschnitte, deren Markdown-Listenpunkte als Titel der jeweils
|
|
* folgenden Belegtabelle desselben Markdown-Abschnitts dienen.
|
|
*/
|
|
resultSectionListSheetTitles?: RegExp[];
|
|
/** Zeileninhalte verknuepfter Belegtabellen, die visuell hervorgehoben werden. */
|
|
resultSheetHighlightedRowPatterns?: RegExp[];
|
|
/** Zeileninhalte, welche die erwartete Sollbedingung einer Regel beschreiben. */
|
|
resultSheetCriterionRowPatterns?: RegExp[];
|
|
/** Darstellung einer vom Quellbericht als ausgewertet markierten Belegzeile. */
|
|
resultSheetEvaluatedRowTone?: 'violation';
|
|
/**
|
|
* Explicit per-row status markers written by the source itself (for example a
|
|
* check mark in a status cell). A row is colored only when its cells prove exactly
|
|
* one of both states; nothing is inferred from other values.
|
|
*/
|
|
resultSheetRowStatusCells?: { match: RegExp[]; violation: RegExp[] };
|
|
/**
|
|
* First-read layout of the Finding Reader (user decision 2026-09-25): which retained
|
|
* source fields state the problem, its impact and the next step. Only declared
|
|
* fields are used; a missing field removes its line instead of inventing text.
|
|
*/
|
|
firstRead?: {
|
|
/** Concrete result statement of the scanner for this environment. */
|
|
resultHeaders?: RegExp[];
|
|
/** Explicit count of affected results. */
|
|
affectedHeaders?: RegExp[];
|
|
/** Count linked result-table rows when no explicit count exists. */
|
|
affectedFromResults?: boolean;
|
|
/** Count rows the source itself marks as passed or failed (`resultSheetRowStatusCells`). */
|
|
statusRowCounts?: boolean;
|
|
/** Show the source's criterion and evaluated rows as expected and found. */
|
|
criterionEvidence?: boolean;
|
|
impactHeaders?: RegExp[];
|
|
nextStepHeaders?: RegExp[];
|
|
/** Retained https documentation link offered as the next step when no remediation text exists. */
|
|
nextStepLinkHeaders?: RegExp[];
|
|
/**
|
|
* Native remediation subsection inside another retained field, used when no dedicated
|
|
* remediation field exists. The section runs from the native heading to the end of the field;
|
|
* the text before the heading stays in place as background.
|
|
*/
|
|
nextStepSection?: { headers: RegExp[]; heading: RegExp };
|
|
/**
|
|
* Native reference heading the scanner appends to its impact text. The impact row ends before it;
|
|
* the retained remainder is shown with the references.
|
|
*/
|
|
impactReferenceMarker?: RegExp;
|
|
/** Explanatory text shown below the next step. */
|
|
backgroundHeaders?: RegExp[];
|
|
/** Documentation and references shown after the background. */
|
|
referenceHeaders?: RegExp[];
|
|
};
|
|
};
|
|
comparisonKeys: CanonicalFieldRole[][];
|
|
}
|