Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
551 changes: 298 additions & 253 deletions package-lock.json

Large diffs are not rendered by default.

42 changes: 29 additions & 13 deletions src/refinements/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ import {
* ```
*/
export function createFirstAliasRefinement() {
return (ctx: z.core.ParsePayload<{ aliases?: Aliases; name: string }>): void => {
if (ctx.value.aliases && ctx.value.aliases.length > 0) {
return (ctx: z.core.ParsePayload<{ aliases?: Aliases; name?: string }>): void => {
if (ctx.value.aliases && ctx.value.aliases.length > 0 && ctx.value.name) {
if (ctx.value.aliases[0] !== ctx.value.name) {
ctx.issues.push({
code: 'custom',
Expand Down Expand Up @@ -70,8 +70,8 @@ export function createFirstAliasRefinement() {
* ```
*/
export function createFirstXMitreAliasRefinement() {
return (ctx: z.core.ParsePayload<{ x_mitre_aliases?: string[]; name: string }>): void => {
if (ctx.value.x_mitre_aliases && ctx.value.x_mitre_aliases.length > 0) {
return (ctx: z.core.ParsePayload<{ x_mitre_aliases?: string[]; name?: string }>): void => {
if (ctx.value.x_mitre_aliases && ctx.value.x_mitre_aliases.length > 0 && ctx.value.name) {
if (ctx.value.x_mitre_aliases[0] !== ctx.value.name) {
ctx.issues.push({
code: 'custom',
Expand Down Expand Up @@ -102,14 +102,18 @@ export function createFirstXMitreAliasRefinement() {
export function createCitationsRefinement() {
return (
ctx: z.core.ParsePayload<{
external_references: ExternalReferences;
external_references?: ExternalReferences;
x_mitre_first_seen_citation?: XMitreFirstSeenCitation;
x_mitre_last_seen_citation?: XMitreLastSeenCitation;
}>,
): void => {
const { external_references, x_mitre_first_seen_citation, x_mitre_last_seen_citation } =
ctx.value;

if (!Array.isArray(external_references)) {
return;
}

// Helper function to extract citation names from a citation string
const extractCitationNames = (citations: string): string[] => {
const matches = citations.match(/\(Citation: ([^)]+)\)/g);
Expand Down Expand Up @@ -340,12 +344,13 @@ export function validateXMitreContentsReferences() {

// Validate each reference in x_mitre_contents
collectionContents.forEach((contentRef: { object_ref: string }, index: number) => {
if (!objectIds.has(contentRef.object_ref)) {
const ref = contentRef.object_ref as AttackObject['id']; // assert type
if (!objectIds.has(ref)) {
ctx.issues.push({
code: 'custom',
message: `STIX ID "${contentRef.object_ref}" referenced in x_mitre_contents is not present in the bundle's objects array`,
message: `STIX ID "${ref}" referenced in x_mitre_contents is not present in the bundle's objects array`,
path: ['objects', 0, 'x_mitre_contents', index, 'object_ref'],
input: contentRef.object_ref,
input: ref,
});
}
});
Expand All @@ -362,14 +367,19 @@ export function createAttackIdInExternalReferencesRefinement() {
ctx: z.core.ParsePayload<
| Technique
| {
external_references: ExternalReferences;
x_mitre_is_subtechnique: XMitreIsSubtechnique;
external_references?: ExternalReferences;
x_mitre_is_subtechnique?: XMitreIsSubtechnique;
}
>,
): void => {
if (ctx.value.external_references === undefined) {
return;
}
if (ctx.value.x_mitre_is_subtechnique === undefined) {
return;
}
// Check if external_references exists and has at least one entry
if (
!ctx.value.external_references ||
!Array.isArray(ctx.value.external_references) ||
ctx.value.external_references.length === 0
) {
Expand Down Expand Up @@ -446,7 +456,7 @@ export function createEnterpriseOnlyPropertiesRefinement() {
ctx: z.core.ParsePayload<
| Technique
| {
x_mitre_domains: XMitreDomains;
x_mitre_domains?: XMitreDomains;
kill_chain_phases?: KillChainPhase[];
x_mitre_permissions_required?: XMitrePermissionsRequired;
x_mitre_effective_permissions?: XMitreEffectivePermissions;
Expand All @@ -458,6 +468,9 @@ export function createEnterpriseOnlyPropertiesRefinement() {
}
>,
): void => {
if (!Array.isArray(ctx.value.x_mitre_domains)) {
return;
}
// Helper variables for domain checks
const inEnterpriseDomain = ctx.value.x_mitre_domains.includes(
attackDomainSchema.enum['enterprise-attack'],
Expand Down Expand Up @@ -556,12 +569,15 @@ export function createMobileOnlyPropertiesRefinement() {
ctx: z.core.ParsePayload<
| Technique
| {
x_mitre_domains: XMitreDomains;
x_mitre_domains?: XMitreDomains;
x_mitre_tactic_type?: XMitreTacticType;
x_mitre_data_sources?: XMitreDataSources;
}
>,
): void => {
if (!Array.isArray(ctx.value.x_mitre_domains)) {
return;
}
// Helper variables for domain checks
const inMobileDomain = ctx.value.x_mitre_domains.includes(
attackDomainSchema.enum['mobile-attack'],
Expand Down
17 changes: 12 additions & 5 deletions src/schemas/sdo/campaign.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export type XMitreLastSeenCitation = z.infer<typeof xMitreLastSeenCitationSchema
//
//==============================================================================

export const campaignSchema = attackBaseDomainObjectSchema
export const extensibleCampaignSchema = attackBaseDomainObjectSchema
.extend({
id: createStixIdValidator('campaign'),

Expand Down Expand Up @@ -121,10 +121,6 @@ export const campaignSchema = attackBaseDomainObjectSchema
revoked: true, // Optional in STIX but required in ATT&CK
})
.strict()
.check((ctx) => {
createFirstAliasRefinement()(ctx);
createCitationsRefinement()(ctx);
})
.meta({
description: `
Campaigns represent sets of adversary activities occurring over a specific time period with shared characteristics and objectives.
Expand All @@ -133,4 +129,15 @@ objects with additional temporal tracking fields.
`.trim(),
});

export const campaignSchema = extensibleCampaignSchema.check((ctx) => {
createFirstAliasRefinement()(ctx);
createCitationsRefinement()(ctx);
});

export const campaignPartialSchema = extensibleCampaignSchema.partial().check((ctx) => {
createFirstAliasRefinement()(ctx);
createCitationsRefinement()(ctx);
});

export type Campaign = z.infer<typeof campaignSchema>;
export type CampaignPartial = z.infer<typeof campaignPartialSchema>;
14 changes: 10 additions & 4 deletions src/schemas/sdo/group.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from '../common/property-schemas/index.js';

// Group Schema
export const groupSchema = attackBaseDomainObjectSchema
export const extensibleGroupSchema = attackBaseDomainObjectSchema
.extend({
id: createStixIdValidator('intrusion-set'),

Expand Down Expand Up @@ -73,9 +73,6 @@ export const groupSchema = attackBaseDomainObjectSchema
}),
})
.strict()
.check((ctx) => {
createFirstAliasRefinement()(ctx);
})
.meta({
description: `
Groups represent clusters of adversary activity with shared characteristics, tools, tactics, or infrastructure.
Expand All @@ -84,4 +81,13 @@ objects and strictly follow the STIX 2.1 specification without additional custom
`.trim(),
});

export const groupSchema = extensibleGroupSchema.check((ctx) => {
createFirstAliasRefinement()(ctx);
});

export const groupPartialSchema = extensibleGroupSchema.partial().check((ctx) => {
createFirstAliasRefinement()(ctx);
});

export type Group = z.infer<typeof groupSchema>;
export type GroupPartial = z.infer<typeof groupPartialSchema>;
16 changes: 13 additions & 3 deletions src/schemas/sdo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ export {

export {
campaignSchema,
campaignPartialSchema,
xMitreFirstSeenCitationSchema,
xMitreLastSeenCitationSchema,
type Campaign,
type CampaignPartial,
type XMitreFirstSeenCitation,
type XMitreLastSeenCitation,
} from './campaign.schema.js';
Expand All @@ -38,7 +40,7 @@ export {

export { detectionStrategySchema, type DetectionStrategy } from './detection-strategy.schema.js';

export { groupSchema, type Group } from './group.schema.js';
export { groupSchema, groupPartialSchema, type Group, type GroupPartial } from './group.schema.js';

export { identitySchema, type Identity } from './identity.schema.js';

Expand All @@ -49,7 +51,14 @@ export {
type XMitreCollectionLayers,
} from './data-source.schema.js';

export { stixArtifactType, stixFileType, malwareSchema, type Malware } from './malware.schema.js';
export {
stixArtifactType,
stixFileType,
malwareSchema,
malwarePartialSchema,
type Malware,
type MalwarePartial,
} from './malware.schema.js';

export {
matrixSchema,
Expand All @@ -71,6 +80,7 @@ export {

export {
techniqueSchema,
techniquePartialSchema,
xMitreDataSourceSchema,
xMitreDataSourcesSchema,
xMitreDefenseBypassesSchema,
Expand Down Expand Up @@ -98,7 +108,7 @@ export {
type XMitreTacticType,
} from './technique.schema.js';

export { toolSchema, type Tool } from './tool.schema.js';
export { toolSchema, toolPartialSchema, type Tool, type ToolPartial } from './tool.schema.js';

export {
attackObjectsSchema,
Expand Down
19 changes: 13 additions & 6 deletions src/schemas/sdo/malware.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const stixArtifactType = createStixIdValidator('artifact').meta({
//
//==============================================================================

export const malwareSchema = attackBaseDomainObjectSchema
export const extensibleMalwareSchema = attackBaseDomainObjectSchema
.extend({
id: createStixIdValidator('malware').meta({
description: 'The unique identifier for this Malware object.',
Expand Down Expand Up @@ -243,10 +243,17 @@ export const malwareSchema = attackBaseDomainObjectSchema
examples: [],
}),
})
.strict()
.check((ctx) => {
createFirstAliasRefinement()(ctx);
createFirstXMitreAliasRefinement()(ctx);
});
.strict();

export const malwareSchema = extensibleMalwareSchema.check((ctx) => {
createFirstAliasRefinement()(ctx);
createFirstXMitreAliasRefinement()(ctx);
});

export const malwarePartialSchema = extensibleMalwareSchema.partial().check((ctx) => {
createFirstAliasRefinement()(ctx);
createFirstXMitreAliasRefinement()(ctx);
});

export type Malware = z.infer<typeof malwareSchema>;
export type MalwarePartial = z.infer<typeof malwarePartialSchema>;
14 changes: 8 additions & 6 deletions src/schemas/sdo/software.schema.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { z } from 'zod/v4';
import { malwareSchema } from './malware.schema.js';
import { toolSchema } from './tool.schema.js';
import { malwarePartialSchema, malwareSchema } from './malware.schema.js';
import { toolPartialSchema, toolSchema } from './tool.schema.js';

//==============================================================================
//
// Software Schema
//
//==============================================================================

export const softwareSchema = z.union([malwareSchema, toolSchema]).meta({
description:
'Software represents tools and malicious code used by adversaries to accomplish their objectives. ATT&CK models software using two STIX object types: [malware](http://docs.oasis-open.org/cti/stix/v2.0/csprd01/part2-stix-objects/stix-v2.0-csprd01-part2-stix-objects.html#_Toc476230945) and [tool](http://docs.oasis-open.org/cti/stix/v2.0/csprd01/part2-stix-objects/stix-v2.0-csprd01-part2-stix-objects.html#_Toc476230961).',
});
export const softwareSchema = z
.union([malwareSchema, toolSchema, malwarePartialSchema, toolPartialSchema])
.meta({
description:
'Software represents tools and malicious code used by adversaries to accomplish their objectives. ATT&CK models software using two STIX object types: [malware](http://docs.oasis-open.org/cti/stix/v2.0/csprd01/part2-stix-objects/stix-v2.0-csprd01-part2-stix-objects.html#_Toc476230945) and [tool](http://docs.oasis-open.org/cti/stix/v2.0/csprd01/part2-stix-objects/stix-v2.0-csprd01-part2-stix-objects.html#_Toc476230961).',
});

export type Software = z.infer<typeof softwareSchema>;
22 changes: 16 additions & 6 deletions src/schemas/sdo/technique.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ export type XMitreDetection = z.infer<typeof xMitreDetectionSchema>;
//
//==============================================================================

export const techniqueSchema = attackBaseDomainObjectSchema
export const extensibleTechniqueSchema = attackBaseDomainObjectSchema
.extend({
id: createStixIdValidator('attack-pattern'),

Expand Down Expand Up @@ -375,11 +375,6 @@ export const techniqueSchema = attackBaseDomainObjectSchema
x_mitre_modified_by_ref: xMitreModifiedByRefSchema.optional(),
})
.strict()
.check((ctx) => {
createAttackIdInExternalReferencesRefinement()(ctx);
createEnterpriseOnlyPropertiesRefinement()(ctx);
createMobileOnlyPropertiesRefinement()(ctx);
})
.meta({
description: `
Techniques describe specific methods adversaries use to achieve tactical objectives and are represented as
Expand All @@ -405,4 +400,19 @@ They are represented as \`attack-pattern\` objects with the same structure as te
- **Platform constraints:** Sub-techniques must use a subset of their parent technique's platforms
`.trim(),
});

export const techniqueSchema = extensibleTechniqueSchema.check((ctx) => {
createAttackIdInExternalReferencesRefinement()(ctx);
createEnterpriseOnlyPropertiesRefinement()(ctx);
createMobileOnlyPropertiesRefinement()(ctx);
});

export const techniquePartialSchema = extensibleTechniqueSchema.partial().check((ctx) => {
// refinements must tolerate missing fields
createAttackIdInExternalReferencesRefinement()(ctx);
createEnterpriseOnlyPropertiesRefinement()(ctx);
createMobileOnlyPropertiesRefinement()(ctx);
});

export type Technique = z.infer<typeof techniqueSchema>;
export type TechniquePartial = z.infer<typeof techniquePartialSchema>;
19 changes: 13 additions & 6 deletions src/schemas/sdo/tool.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
//
//==============================================================================

export const toolSchema = attackBaseDomainObjectSchema
export const extensibleToolSchema = attackBaseDomainObjectSchema
.extend({
id: createStixIdValidator('tool').meta({
description: 'The unique identifier for this Tool object.',
Expand Down Expand Up @@ -87,10 +87,17 @@ export const toolSchema = attackBaseDomainObjectSchema

x_mitre_old_attack_id: createOldMitreAttackIdSchema('tool').optional(),
})
.strict()
.check((ctx) => {
createFirstXMitreAliasRefinement()(ctx);
createFirstAliasRefinement()(ctx);
});
.strict();

export const toolSchema = extensibleToolSchema.check((ctx) => {
createFirstXMitreAliasRefinement()(ctx);
createFirstAliasRefinement()(ctx);
});

export const toolPartialSchema = extensibleToolSchema.partial().check((ctx) => {
createFirstXMitreAliasRefinement()(ctx);
createFirstAliasRefinement()(ctx);
});

export type Tool = z.infer<typeof toolSchema>;
export type ToolPartial = z.infer<typeof toolPartialSchema>;
Loading
Loading