ssvc
Version:
TypeScript implementation of SSVC (Stakeholder-Specific Vulnerability Categorization). A prioritization framework to triage CVE vulnerabilities as an alternative or compliment to CVSS
399 lines (373 loc) • 18.6 kB
text/typescript
export enum Exploitation {
NONE = "none",
POC = "poc",
ACTIVE = "active"
}
export enum Automatable {
YES = "yes",
NO = "no"
}
export enum TechnicalImpact {
PARTIAL = "partial",
TOTAL = "total"
}
export enum MissionWellbeingImpact {
LOW = "low",
MEDIUM = "medium",
HIGH = "high"
}
export enum DecisionPriority {
LOW = "low",
MEDIUM = "medium",
HIGH = "high",
IMMEDIATE = "immediate"
}
export enum Utility {
LABORIOUS = "laborious",
EFFICIENT = "efficient",
SUPER_EFFECTIVE = "super effective"
}
export enum SafetyImpact {
NONE = "none",
MINOR = "minor",
MAJOR = "major",
HAZARDOUS = "hazardous",
CATASTROPHIC = "catastrophic"
}
export enum ActionFIRST {
SCHEDULED = "scheduled",
OUT_OF_BAND = "out-of-band",
IMMEDIATE = "immediate"
}
export enum ActionCISA {
TRACK = "Track",
TRACK_STAR = "Track*",
ATTEND = "Attend",
ACT = "Act"
}
export enum Methodology {
FIRST = "FIRST",
CISA = "CISA"
}
const priorityMap: Record<ActionCISA | ActionFIRST, DecisionPriority> = {
[]: DecisionPriority.LOW,
[]: DecisionPriority.MEDIUM,
[]: DecisionPriority.MEDIUM,
[]: DecisionPriority.IMMEDIATE,
[]: DecisionPriority.LOW,
[]: DecisionPriority.MEDIUM,
[]: DecisionPriority.IMMEDIATE,
};
export class OutcomeCISA {
priority: DecisionPriority;
action: ActionCISA;
constructor(action: ActionCISA) {
this.priority = priorityMap[action];
this.action = action;
}
}
export class OutcomeFIRST {
priority: DecisionPriority;
action: ActionFIRST;
constructor(action: ActionFIRST) {
this.priority = priorityMap[action];
this.action = action;
}
}
type DecisionOptions = {
exploitation: Exploitation | string;
technical_impact: TechnicalImpact | string;
methodology: Methodology | string;
automatable?: Automatable | string;
mission_wellbeing?: MissionWellbeingImpact | string;
utility?: Utility | string;
safety_impact?: SafetyImpact | string;
};
export class Decision {
exploitation?: Exploitation;
automatable?: Automatable;
utility?: Utility;
technical_impact?: TechnicalImpact;
safety_impact?: SafetyImpact;
mission_wellbeing?: MissionWellbeingImpact;
outcome?: OutcomeCISA | OutcomeFIRST;
methodology?: Methodology = Methodology.CISA;
constructor(options: DecisionOptions) {
this.methodology = this.toEnum(Methodology, options.methodology);
this.exploitation = this.toEnum(Exploitation, options.exploitation);
this.technical_impact = this.toEnum(TechnicalImpact, options.technical_impact);
if (this.methodology === Methodology.CISA) {
this.automatable = this.toEnum(Automatable, options.automatable);
this.mission_wellbeing = this.toEnum(MissionWellbeingImpact, options.mission_wellbeing);
}
if (this.methodology === Methodology.FIRST) {
this.utility = this.toEnum(Utility, options.utility);
this.safety_impact = this.toEnum(SafetyImpact, options.safety_impact);
}
}
private toEnum<T extends { [key: string]: string }>(
enumObj: T,
value?: T[keyof T] | string
): T[keyof T] | undefined {
if (value === undefined) {
return undefined;
}
if (typeof value === 'string') {
const enumValue = Object.entries(enumObj).find(([_, val]) => val === value);
return enumValue ? enumValue[1] as T[keyof T] : undefined;
}
}
evaluate(): OutcomeCISA | OutcomeFIRST | undefined {
if (this.methodology === Methodology.CISA) {
return this.cisa();
} else if (this.methodology === Methodology.FIRST) {
return this.first();
}
return undefined;
}
cisa(): OutcomeCISA {
this.validateCisa();
const decisionMatrix: Record<Exploitation, Record<Automatable, Partial<Record<TechnicalImpact, Partial<Record<MissionWellbeingImpact, ActionCISA>>>>>> = {
[]: {
[]: {
[]: {
[]: ActionCISA.ATTEND
},
},
[]: {
[]: {
[]: ActionCISA.TRACK_STAR
},
},
},
[]: {
[]: {
[]: {
[]: ActionCISA.TRACK_STAR,
[]: ActionCISA.ATTEND,
},
[]: {
[]: ActionCISA.ATTEND
},
},
[]: {
[]: {
[]: ActionCISA.TRACK_STAR
},
[]: {
[]: ActionCISA.TRACK_STAR,
[]: ActionCISA.ATTEND,
},
},
},
[]: {
[]: {
[]: {
[]: ActionCISA.ATTEND,
[]: ActionCISA.ATTEND,
[]: ActionCISA.ACT,
},
[]: {
[]: ActionCISA.ATTEND,
[]: ActionCISA.ACT,
[]: ActionCISA.ACT,
},
},
[]: {
[]: {
[]: ActionCISA.ATTEND
},
[]: {
[]: ActionCISA.ATTEND,
[]: ActionCISA.ACT,
},
},
},
};
const action = decisionMatrix[this.exploitation!]?.[this.automatable!]?.[this.technical_impact!]?.[this.mission_wellbeing!] ?? ActionCISA.TRACK;
return new OutcomeCISA(action);
}
first(): OutcomeFIRST {
this.validateFirst();
const decisionMatrix: Record<Exploitation, Record<Utility, Record<TechnicalImpact, Record<SafetyImpact, ActionFIRST>>>> = {
[]: {
[]: {
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
},
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
},
},
[]: {
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
},
[]: {
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
},
},
},
[]: {
[]: {
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
},
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
},
},
[]: {
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
},
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
},
},
[]: {
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
},
[]: {
[]: ActionFIRST.SCHEDULED,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
},
},
[]: {
[]: {
[]: {
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
[]: {
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
},
[]: {
[]: {
[]: ActionFIRST.OUT_OF_BAND,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
[]: {
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
},
[]: {
[]: {
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
[]: {
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
[]: ActionFIRST.IMMEDIATE,
},
},
},
};
const action = decisionMatrix[this.exploitation!]?.[this.utility!]?.[this.technical_impact!]?.[this.safety_impact!] ?? ActionFIRST.SCHEDULED;
return new OutcomeFIRST(action)
}
private validateCommon(): void {
if (!Object.values(Exploitation).includes(this.exploitation as Exploitation)) {
throw new Error("Exploitation must be a valid Exploitation enum value");
}
if (!Object.values(TechnicalImpact).includes(this.technical_impact as TechnicalImpact)) {
throw new Error("TechnicalImpact must be a valid TechnicalImpact enum value");
}
}
private validateCisa(): void {
this.validateCommon()
if (!this.automatable || !Object.values(Automatable).includes(this.automatable as Automatable)) {
throw new Error("Automatable must be a valid Automatable enum value");
}
if (!this.mission_wellbeing || !Object.values(MissionWellbeingImpact).includes(this.mission_wellbeing as MissionWellbeingImpact)) {
throw new Error("MissionWellbeingImpact must be a valid MissionWellbeingImpact enum value");
}
}
private validateFirst(): void {
this.validateCommon()
if (!this.utility || !Object.values(Utility).includes(this.utility as Utility)) {
throw new Error("Utility must be a valid Utility enum value");
}
if (!this.safety_impact || !Object.values(SafetyImpact).includes(this.safety_impact as SafetyImpact)) {
throw new Error("SafetyImpact must be a valid SafetyImpact enum value");
}
}
}