diff --git a/README.md b/README.md index 73bfa9e59f3..2ea725786e2 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ - 👾 Hacker Jargon - “Try to reboot the SQL bus, maybe it will bypass the virtual application!” - 🧍 Names - Generate virtual humans with a complete online and offline identity. - 🔢 Numbers - Of course, we can also generate random numbers and strings. +- 🛡 Security – Generate vulnerability identifiers > **Note**: Faker tries to generate realistic data and not obvious fake data. > The generated names, addresses, emails, phone numbers, and/or other data might be coincidentally valid information. diff --git a/src/faker.ts b/src/faker.ts index 0283bd6dd6e..b2a76acc3c4 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -23,6 +23,7 @@ import { NameModule } from './modules/name'; import { PhoneModule } from './modules/phone'; import { RandomModule } from './modules/random'; import { ScienceModule } from './modules/science'; +import { SecurityModule } from './modules/security'; import { SystemModule } from './modules/system'; import { UniqueModule } from './modules/unique'; import { VehicleModule } from './modules/vehicle'; @@ -103,6 +104,7 @@ export class Faker { readonly name: NameModule = new NameModule(this); readonly phone: PhoneModule = new PhoneModule(this); readonly science: ScienceModule = new ScienceModule(this); + readonly security: SecurityModule = new SecurityModule(this); readonly system: SystemModule = new SystemModule(this); readonly vehicle: VehicleModule = new VehicleModule(this); readonly word: WordModule = new WordModule(this); diff --git a/src/index.ts b/src/index.ts index 984a8c758d5..c5661f952ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,6 +60,7 @@ export type { GenderType, NameModule, SexType } from './modules/name'; export type { PhoneModule } from './modules/phone'; export type { RandomModule } from './modules/random'; export type { ChemicalElement, ScienceModule, Unit } from './modules/science'; +export type { Cvss, SecurityModule, SeverityRating } from './modules/security'; export type { SystemModule } from './modules/system'; export type { UniqueModule } from './modules/unique'; export type { VehicleModule } from './modules/vehicle'; diff --git a/src/internal/toDate.ts b/src/internal/toDate.ts new file mode 100644 index 00000000000..86ea3e8fd06 --- /dev/null +++ b/src/internal/toDate.ts @@ -0,0 +1,16 @@ +/** + * Converts date passed as a string, number, or Date to a Date object. + * If nothing or a non parseable value is passed, takes current date. + * + * @param date The input to convert to a date. + * + * @since 8.0.0 + */ +export function toDate(date?: string | Date | number): Date { + date = new Date(date); + if (isNaN(date.valueOf())) { + date = new Date(); + } + + return date; +} diff --git a/src/modules/date/index.ts b/src/modules/date/index.ts index 111fc4bae6f..f2582444f81 100644 --- a/src/modules/date/index.ts +++ b/src/modules/date/index.ts @@ -1,21 +1,7 @@ import type { Faker } from '../..'; import type { DateEntryDefinition } from '../../definitions'; import { FakerError } from '../../errors/faker-error'; - -/** - * Converts date passed as a string, number or Date to a Date object. - * If nothing or a non parseable value is passed, takes current date. - * - * @param date Date - */ -function toDate(date?: string | Date | number): Date { - date = new Date(date); - if (isNaN(date.valueOf())) { - date = new Date(); - } - - return date; -} +import { toDate } from '../../internal/toDate'; /** * Module to generate dates. diff --git a/src/modules/security/index.ts b/src/modules/security/index.ts new file mode 100644 index 00000000000..2247c4f9f72 --- /dev/null +++ b/src/modules/security/index.ts @@ -0,0 +1,174 @@ +import type { Faker } from '../..'; +import { faker } from '../..'; +import { toDate } from '../../internal/toDate'; + +/** + * The possible definitions related to + * Common Vulnerability Scoring System (CVSS). + */ +export interface Cvss { + /** + * A value ranging from 0 to 10. + */ + score: number; + + /** + * A compressed textual representation of the values used to derive a score + */ + vector: string; + + /** + * A textual representation of the numeric score. + * + * Where: + * - None – 0 + * - Low – 0.1 - 3.9 + * - Medium – 4.0 - 6.9 + * - High – 7.0 - 8.9 + * - Critical – 9.0 - 10.0 + */ + rating: SeverityRating; +} + +/** + * Possible textual rating definitions for a CVSS identifier + */ +export type SeverityRating = 'none' | 'low' | 'medium' | 'high' | 'critical'; + +/** + * Module to generate security related entries. + */ +export class SecurityModule { + constructor(private readonly faker: Faker) { + // Bind `this` so namespaced is working correctly + for (const name of Object.getOwnPropertyNames(SecurityModule.prototype)) { + if (name === 'constructor' || typeof this[name] !== 'function') { + continue; + } + this[name] = this[name].bind(this); + } + } + + /** + * Generates a random CVE between the given boundaries. + * Based on: + * https://www.cve.org/ + * + * @param options The options to use. Defaults to `{}`. + * @param options.from The lower date boundary. Defaults to `1999-01-01T00:00:00.000Z`. + * @param options.to The upper date boundary. Defaults to `now`. + * + * @example + * faker.security.cve() // 'CVE-2011-0762' + * faker.security.cve({ from:'2020-01-01T00:00:00.000Z', to: '2030-01-01T00:00:00.000Z' }) // 'CVE-2028-0762' + * faker.security.cve({ from:'2020-01-01T00:00:00.000Z' }) // 'CVE-2028-0762' + * faker.security.cve({ to: '2019-12-31T00:00:00.000Z' }) // 'CVE-2018-0762' + * + * @since 8.0.0 + */ + cve( + options: { + from?: string | Date | number; + to?: string | Date | number; + } = {} + ): string { + const fromDateTime = toDate(options?.from || '1999-01-01T00:00:00.000Z'); + const toDateTime = toDate(options?.to); + + return [ + 'CVE', + // Year + this.faker.date.between(fromDateTime, toDateTime).getFullYear(), + // Sequence in the year + this.faker.random.numeric(5, { allowLeadingZeros: true }), + ].join('-'); + } + + /** + * Generates a random CWE (Common Weakness Enumeration) identifier. + * Based on: + * https://cwe.mitre.org/data/index.html + * + * @example + * faker.security.cwe() // 'CWE-123' + * + * @since 8.0.0 + */ + cwe(): string { + return ['CWE', this.faker.datatype.number({ min: 0, max: 1388 })].join('-'); + } + + /** + * Generates random CVSS (Common Vulnerability Scoring System) data. + * Based on: + * https://www.first.org/cvss/calculator/3.1 + * + * @example + * faker.security.cvss() // { score: 3.8, vector: 'CVSS:3.1/AV:P/AC:H/PR:H/UI:R/S:U/C:H/I:N/A:N/E:P/RL:W/RC:C', rating: 'low' } + * + * @since 8.0.0 + */ + cvss(): Cvss { + const score = this.faker.datatype.float({ + min: 0, + max: 10, + precision: 0.1, + }); + return { + score, + vector: [ + 'CVSS:3.1', + `AV:${randomCharFromString('NALP')}`, + `AC:${randomCharFromString('LH')}`, + `PR:${randomCharFromString('NLH')}`, + `UI:${randomCharFromString('NR')}`, + `S:${randomCharFromString('UC')}`, + `C:${randomCharFromString('NLH')}`, + `I:${randomCharFromString('NLH')}`, + `A:${randomCharFromString('NLH')}`, + ].join('/'), + rating: getRating(score), + }; + } +} + +/** + * Returns a random character from a string. + * + * @param string + * + * @example + * randomCharFromString('abc'); // 'b' + */ +function randomCharFromString(string: string): string { + return String(faker.helpers.arrayElement(string.split(''))); +} + +/** + * Returns the textual representation of a score. + * + * @param score A number between 0 and 10. Defaults to `0`. + * + * @example + * getRating(1); // 'low' + */ +function getRating(score: number = 0): SeverityRating { + if (score === 0) { + return 'none'; + } + + if (score >= 0.1 && score <= 3.9) { + return 'low'; + } + if (score >= 4.0 && score <= 6.9) { + return 'medium'; + } + if (score >= 7.0 && score <= 8.9) { + return 'high'; + } + if (score >= 9.0 && score <= 10.0) { + return 'critical'; + } + + return 'none'; +} diff --git a/test/__snapshots__/security.spec.ts.snap b/test/__snapshots__/security.spec.ts.snap new file mode 100644 index 00000000000..4846961d822 --- /dev/null +++ b/test/__snapshots__/security.spec.ts.snap @@ -0,0 +1,49 @@ +// Vitest Snapshot v1 + +exports[`security > 42 > cve > with from and to date 1`] = `"CVE-2010-79177"`; + +exports[`security > 42 > cve > with from date 1`] = `"CVE-2022-79177"`; + +exports[`security > 42 > cve > with to date 1`] = `"CVE-2007-79177"`; + +exports[`security > 42 > cvss 1`] = ` +{ + "rating": "low", + "score": 3.7, + "vector": "CVSS:3.1/AV:P/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N", +} +`; + +exports[`security > 42 > cwe 1`] = `"CWE-520"`; + +exports[`security > 1211 > cve > with from and to date 1`] = `"CVE-2021-48721"`; + +exports[`security > 1211 > cve > with from date 1`] = `"CVE-2022-48721"`; + +exports[`security > 1211 > cve > with to date 1`] = `"CVE-2020-48721"`; + +exports[`security > 1211 > cvss 1`] = ` +{ + "rating": "critical", + "score": 9.3, + "vector": "CVSS:3.1/AV:A/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:H", +} +`; + +exports[`security > 1211 > cwe 1`] = `"CWE-1289"`; + +exports[`security > 1337 > cve > with from and to date 1`] = `"CVE-2007-51225"`; + +exports[`security > 1337 > cve > with from date 1`] = `"CVE-2022-51225"`; + +exports[`security > 1337 > cve > with to date 1`] = `"CVE-2005-51225"`; + +exports[`security > 1337 > cvss 1`] = ` +{ + "rating": "low", + "score": 2.6, + "vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N", +} +`; + +exports[`security > 1337 > cwe 1`] = `"CWE-363"`; diff --git a/test/security.spec.ts b/test/security.spec.ts new file mode 100644 index 00000000000..30ef6856321 --- /dev/null +++ b/test/security.spec.ts @@ -0,0 +1,56 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { faker } from '../src'; +import { seededTests } from './support/seededRuns'; + +const NON_SEEDED_BASED_RUN = 5; + +describe('security', () => { + afterEach(() => { + faker.locale = 'en'; + }); + + seededTests(faker, 'security', (t) => { + t.describe('cve', (t) => + t + .it('with from date', { from: '2022-08-16' }) + .it('with to date', { to: '2022-08-16' }) + .it('with from and to date', { from: '2002-08-16', to: '2022-08-16' }) + ); + t.itEach('cwe', 'cvss'); + }); + + describe(`random seeded tests for seed ${JSON.stringify( + faker.seed() + )}`, () => { + for (let i = 1; i <= NON_SEEDED_BASED_RUN; i++) { + describe('cve()', () => { + it('should return a well formed string', () => { + expect(faker.security.cve()).toMatch(/^CVE-[0-9]{4}-[0-9]{4}/); + }); + }); + + describe('cwe()', () => { + it('should return a well formed string', () => { + expect(faker.security.cwe()).toMatch(/^CWE-([0-9]+)$/); + }); + }); + + describe('cvss()', () => { + it('should return an object', () => { + const cvss = faker.security.cvss(); + expect(cvss).toBeTypeOf('object'); + }); + + it('should return a numeric value', () => { + expect(faker.security.cvss().score).toEqual(expect.any(Number)); + }); + + it('should return a well formed string', () => { + expect(faker.security.cvss().vector).toMatch( + /^CVSS:3.1\/AV:[NALP]\/AC:[LH]\/PR:[NLH]\/UI:[NR]\/S:[UC]\/C:[NLH]\/I:[NLH]\/A:[NLH]/ + ); + }); + }); + } + }); +});