chore: import upstream snapshot with attribution
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
ALIASED_PLUGIN_MAPPINGS,
|
||||
ALIASED_PLUGINS,
|
||||
DOD_AI_ETHICS_MAPPING,
|
||||
DOD_AI_ETHICS_PRINCIPLE_NAMES,
|
||||
FRAMEWORK_NAMES,
|
||||
} from '../../../src/redteam/constants/frameworks';
|
||||
import { ALL_PLUGINS } from '../../../src/redteam/constants/plugins';
|
||||
|
||||
describe('DoD AI Ethical Principles framework mapping', () => {
|
||||
describe('DOD_AI_ETHICS_PRINCIPLE_NAMES', () => {
|
||||
it('should contain all 5 principles', () => {
|
||||
expect(DOD_AI_ETHICS_PRINCIPLE_NAMES).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('should have the expected principle names in order', () => {
|
||||
expect(DOD_AI_ETHICS_PRINCIPLE_NAMES).toEqual([
|
||||
'Responsible',
|
||||
'Equitable',
|
||||
'Traceable',
|
||||
'Reliable',
|
||||
'Governable',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DOD_AI_ETHICS_MAPPING', () => {
|
||||
it('should have mappings for all 5 principles', () => {
|
||||
const expectedKeys = Array.from(
|
||||
{ length: 5 },
|
||||
(_, i) => `dod:ai:ethics:${String(i + 1).padStart(2, '0')}`,
|
||||
);
|
||||
expectedKeys.forEach((key) => {
|
||||
expect(DOD_AI_ETHICS_MAPPING).toHaveProperty(key);
|
||||
});
|
||||
});
|
||||
|
||||
it('each principle mapping should have plugins and strategies arrays', () => {
|
||||
Object.values(DOD_AI_ETHICS_MAPPING).forEach((mapping) => {
|
||||
expect(Array.isArray(mapping.plugins)).toBe(true);
|
||||
expect(Array.isArray(mapping.strategies)).toBe(true);
|
||||
expect(mapping.plugins.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('all mapped plugins should be valid plugin IDs', () => {
|
||||
const validPluginIds = new Set<string>(ALL_PLUGINS);
|
||||
|
||||
Object.values(DOD_AI_ETHICS_MAPPING).forEach((mapping) => {
|
||||
mapping.plugins.forEach((pluginId) => {
|
||||
expect(validPluginIds.has(pluginId)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('framework integration', () => {
|
||||
it('should register DoD framework display name', () => {
|
||||
expect(FRAMEWORK_NAMES['dod:ai:ethics']).toBe('DoD AI Ethical Principles');
|
||||
});
|
||||
|
||||
it('should include dod:ai:ethics in aliased plugins', () => {
|
||||
expect(ALIASED_PLUGINS).toContain('dod:ai:ethics');
|
||||
});
|
||||
|
||||
it('should include all principle keys in aliased plugins', () => {
|
||||
Object.keys(DOD_AI_ETHICS_MAPPING).forEach((key) => {
|
||||
expect(ALIASED_PLUGINS).toContain(key);
|
||||
});
|
||||
});
|
||||
|
||||
it('should map dod:ai:ethics alias to DOD_AI_ETHICS_MAPPING', () => {
|
||||
expect(ALIASED_PLUGIN_MAPPINGS['dod:ai:ethics']).toBe(DOD_AI_ETHICS_MAPPING);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,190 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
categoryAliases,
|
||||
categoryAliasesReverse,
|
||||
categoryDescriptions,
|
||||
categoryLabels,
|
||||
categoryMapReverse,
|
||||
DEFAULT_OUTPUT_PATH,
|
||||
displayNameOverrides,
|
||||
PLUGIN_PRESET_DESCRIPTIONS,
|
||||
pluginDescriptions,
|
||||
riskCategories,
|
||||
riskCategorySeverityMap,
|
||||
Severity,
|
||||
strategyDescriptions,
|
||||
strategyDisplayNames,
|
||||
subCategoryDescriptions,
|
||||
} from '../../../src/redteam/constants/metadata';
|
||||
import {
|
||||
ADDITIONAL_PLUGINS,
|
||||
BASE_PLUGINS,
|
||||
BIAS_PLUGINS,
|
||||
FINANCIAL_PLUGINS,
|
||||
HARM_PLUGINS,
|
||||
MEDICAL_PLUGINS,
|
||||
PII_PLUGINS,
|
||||
} from '../../../src/redteam/constants/plugins';
|
||||
|
||||
import type { Plugin } from '../../../src/redteam/constants/plugins';
|
||||
import type { Strategy } from '../../../src/redteam/constants/strategies';
|
||||
|
||||
describe('metadata constants', () => {
|
||||
describe('Risk category severity map', () => {
|
||||
it('should have valid severity levels', () => {
|
||||
Object.values(riskCategorySeverityMap).forEach((severity) => {
|
||||
expect(Object.values(Severity)).toContain(severity);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Risk categories', () => {
|
||||
it('should have valid category descriptions', () => {
|
||||
Object.keys(riskCategories).forEach((category) => {
|
||||
// @ts-expect-error: categoryDescriptions is only indexed by TopLevelCategory (not string)
|
||||
expect(categoryDescriptions[category]).toBeDefined();
|
||||
// @ts-expect-error: categoryDescriptions is only indexed by TopLevelCategory (not string)
|
||||
expect(typeof categoryDescriptions[category]).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have valid category mapping for each plugin', () => {
|
||||
Object.entries(categoryMapReverse).forEach(([plugin, category]) => {
|
||||
const foundInCategory = Object.entries(riskCategories).some(
|
||||
([cat, plugins]) => cat === category && plugins.includes(plugin as Plugin),
|
||||
);
|
||||
expect(foundInCategory).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have matching category labels', () => {
|
||||
expect(categoryLabels).toEqual(Object.keys(categoryMapReverse));
|
||||
});
|
||||
|
||||
it('should not include duplicate plugin ids within a category', () => {
|
||||
Object.entries(riskCategories).forEach(([_category, plugins]) => {
|
||||
const uniquePlugins = new Set(plugins);
|
||||
expect(uniquePlugins.size).toBe(plugins.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('should include all defined plugins in risk categories', () => {
|
||||
// Get all plugins from risk categories
|
||||
const riskCategoryPlugins = new Set<Plugin>();
|
||||
Object.values(riskCategories).forEach((plugins) => {
|
||||
plugins.forEach((plugin) => {
|
||||
riskCategoryPlugins.add(plugin);
|
||||
});
|
||||
});
|
||||
|
||||
// Get all defined plugins from constants
|
||||
const allDefinedPlugins = new Set<Plugin>();
|
||||
|
||||
// Add plugins from various constant arrays
|
||||
[...BASE_PLUGINS].forEach((plugin) => allDefinedPlugins.add(plugin));
|
||||
[...ADDITIONAL_PLUGINS].forEach((plugin) => allDefinedPlugins.add(plugin));
|
||||
[...BIAS_PLUGINS].forEach((plugin) => allDefinedPlugins.add(plugin));
|
||||
[...PII_PLUGINS].forEach((plugin) => allDefinedPlugins.add(plugin));
|
||||
[...MEDICAL_PLUGINS].forEach((plugin) => allDefinedPlugins.add(plugin));
|
||||
[...FINANCIAL_PLUGINS].forEach((plugin) => allDefinedPlugins.add(plugin));
|
||||
|
||||
// Add plugins from HARM_PLUGINS object
|
||||
Object.keys(HARM_PLUGINS).forEach((plugin) => {
|
||||
allDefinedPlugins.add(plugin as Plugin);
|
||||
});
|
||||
|
||||
// Special plugins that shouldn't be in risk categories (collections and custom plugins)
|
||||
const excludedPlugins = new Set([
|
||||
'intent', // Custom intent plugin handled separately in UI
|
||||
'policy', // Custom policy plugin handled separately in UI
|
||||
'default', // Collection
|
||||
'foundation', // Collection
|
||||
'harmful', // Collection
|
||||
'bias', // Collection
|
||||
'pii', // Collection
|
||||
'medical', // Collection
|
||||
'guardrails-eval', // Collection
|
||||
]);
|
||||
|
||||
// Find plugins that are defined but missing from risk categories
|
||||
const missingPlugins = Array.from(allDefinedPlugins).filter(
|
||||
(plugin) => !riskCategoryPlugins.has(plugin) && !excludedPlugins.has(plugin),
|
||||
);
|
||||
|
||||
if (missingPlugins.length > 0) {
|
||||
throw new Error(
|
||||
`The following plugins are defined but missing from risk categories: ${missingPlugins.join(
|
||||
', ',
|
||||
)}. Please add them to the appropriate category in riskCategories object in metadata.ts`,
|
||||
);
|
||||
}
|
||||
|
||||
expect(missingPlugins).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Category aliases', () => {
|
||||
it('should have valid aliases mapping', () => {
|
||||
const uniqueValues = new Set(Object.values(categoryAliases));
|
||||
uniqueValues.forEach((value) => {
|
||||
const keys = Object.entries(categoryAliases)
|
||||
.filter(([, v]) => v === value)
|
||||
.map(([k]) => k);
|
||||
const reverseKey = categoryAliasesReverse[value];
|
||||
expect(keys).toContain(reverseKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Plugin and strategy descriptions', () => {
|
||||
it('should have descriptions for all plugins', () => {
|
||||
Object.keys(pluginDescriptions).forEach((plugin) => {
|
||||
expect(typeof pluginDescriptions[plugin as Plugin]).toBe('string');
|
||||
expect(pluginDescriptions[plugin as Plugin].length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have descriptions for all strategies', () => {
|
||||
Object.keys(strategyDescriptions).forEach((strategy) => {
|
||||
expect(typeof strategyDescriptions[strategy as Strategy]).toBe('string');
|
||||
expect(strategyDescriptions[strategy as Strategy].length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have display names for all strategies', () => {
|
||||
Object.keys(strategyDisplayNames).forEach((strategy) => {
|
||||
expect(typeof strategyDisplayNames[strategy as Strategy]).toBe('string');
|
||||
expect(strategyDisplayNames[strategy as Strategy].length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Plugin preset descriptions', () => {
|
||||
it('should have valid preset descriptions', () => {
|
||||
Object.entries(PLUGIN_PRESET_DESCRIPTIONS).forEach(([preset, description]) => {
|
||||
expect(typeof preset).toBe('string');
|
||||
expect(typeof description).toBe('string');
|
||||
expect(description.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Display name overrides', () => {
|
||||
it('should have display names for memory poisoning plugin', () => {
|
||||
expect(displayNameOverrides['agentic:memory-poisoning']).toBe('Agentic Memory Poisoning');
|
||||
});
|
||||
|
||||
it('should have matching subcategory descriptions', () => {
|
||||
Object.keys(displayNameOverrides).forEach((key) => {
|
||||
const keyAsPluginOrStrategy = key as Plugin | Strategy;
|
||||
expect(subCategoryDescriptions[keyAsPluginOrStrategy]).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Default output path', () => {
|
||||
it('should be defined correctly', () => {
|
||||
expect(DEFAULT_OUTPUT_PATH).toBe('redteam.yaml');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
ALIASED_PLUGIN_MAPPINGS,
|
||||
ALIASED_PLUGINS,
|
||||
MITRE_ATLAS_LEGACY_MAPPING,
|
||||
MITRE_ATLAS_MAPPING,
|
||||
} from '../../../src/redteam/constants/frameworks';
|
||||
import { ALL_PLUGINS, COLLECTIONS } from '../../../src/redteam/constants/plugins';
|
||||
import { ALL_STRATEGIES } from '../../../src/redteam/constants/strategies';
|
||||
|
||||
describe('MITRE ATLAS framework mapping', () => {
|
||||
const currentAtlasTacticAliases = [
|
||||
'mitre:atlas:reconnaissance',
|
||||
'mitre:atlas:resource-development',
|
||||
'mitre:atlas:initial-access',
|
||||
'mitre:atlas:ai-model-access',
|
||||
'mitre:atlas:execution',
|
||||
'mitre:atlas:persistence',
|
||||
'mitre:atlas:privilege-escalation',
|
||||
'mitre:atlas:defense-evasion',
|
||||
'mitre:atlas:credential-access',
|
||||
'mitre:atlas:discovery',
|
||||
'mitre:atlas:lateral-movement',
|
||||
'mitre:atlas:collection',
|
||||
'mitre:atlas:ai-attack-staging',
|
||||
'mitre:atlas:command-and-control',
|
||||
'mitre:atlas:exfiltration',
|
||||
'mitre:atlas:impact',
|
||||
];
|
||||
|
||||
it('maps the current ATLAS tactic aliases', () => {
|
||||
expect(Object.keys(MITRE_ATLAS_MAPPING).sort()).toEqual([...currentAtlasTacticAliases].sort());
|
||||
});
|
||||
|
||||
it('keeps the legacy ML Attack Staging alias outside the full preset', () => {
|
||||
expect(MITRE_ATLAS_MAPPING).not.toHaveProperty('mitre:atlas:ml-attack-staging');
|
||||
expect(MITRE_ATLAS_LEGACY_MAPPING['mitre:atlas:ml-attack-staging']).toBe(
|
||||
MITRE_ATLAS_MAPPING['mitre:atlas:ai-attack-staging'],
|
||||
);
|
||||
});
|
||||
|
||||
it('makes empty tactic coverage explicit', () => {
|
||||
const emptyTacticAliases = Object.entries(MITRE_ATLAS_MAPPING)
|
||||
.filter(([, { plugins, strategies }]) => plugins.length === 0 && strategies.length === 0)
|
||||
.map(([alias]) => alias);
|
||||
|
||||
expect(emptyTacticAliases).toEqual(['mitre:atlas:ai-model-access']);
|
||||
});
|
||||
|
||||
it('registers MITRE aliases for validator expansion', () => {
|
||||
expect(ALIASED_PLUGINS).toContain('mitre:atlas');
|
||||
expect(ALIASED_PLUGINS).toContain('mitre:atlas:ml-attack-staging');
|
||||
currentAtlasTacticAliases.forEach((key) => {
|
||||
expect(ALIASED_PLUGINS).toContain(key);
|
||||
});
|
||||
|
||||
expect(ALIASED_PLUGIN_MAPPINGS['mitre:atlas']).toBe(MITRE_ATLAS_MAPPING);
|
||||
expect(ALIASED_PLUGIN_MAPPINGS['mitre:atlas:ml-attack-staging']).toBe(
|
||||
MITRE_ATLAS_LEGACY_MAPPING,
|
||||
);
|
||||
});
|
||||
|
||||
it('uses valid plugin and strategy identifiers', () => {
|
||||
const validPlugins = new Set<string>([...ALL_PLUGINS, ...COLLECTIONS]);
|
||||
const validStrategies = new Set<string>(ALL_STRATEGIES);
|
||||
|
||||
[...Object.values(MITRE_ATLAS_MAPPING), ...Object.values(MITRE_ATLAS_LEGACY_MAPPING)].forEach(
|
||||
({ plugins, strategies }) => {
|
||||
plugins.forEach((plugin) => expect(validPlugins.has(plugin)).toBe(true));
|
||||
strategies.forEach((strategy) => expect(validStrategies.has(strategy)).toBe(true));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('covers newly-added agentic ATLAS tactics with existing promptfoo checks', () => {
|
||||
expect(MITRE_ATLAS_MAPPING['mitre:atlas:persistence'].plugins).toContain(
|
||||
'agentic:memory-poisoning',
|
||||
);
|
||||
expect(MITRE_ATLAS_MAPPING['mitre:atlas:credential-access'].plugins).toContain(
|
||||
'tool-discovery',
|
||||
);
|
||||
expect(MITRE_ATLAS_MAPPING['mitre:atlas:command-and-control'].plugins).toContain('mcp');
|
||||
expect(MITRE_ATLAS_MAPPING['mitre:atlas:impact'].plugins).toContain('reasoning-dos');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
ALIASED_PLUGIN_MAPPINGS,
|
||||
ALIASED_PLUGINS,
|
||||
OWASP_AGENTIC_NAMES,
|
||||
OWASP_AGENTIC_TOP_10_MAPPING,
|
||||
} from '../../../src/redteam/constants/frameworks';
|
||||
import { ALL_PLUGINS } from '../../../src/redteam/constants/plugins';
|
||||
|
||||
describe('OWASP Top 10 for Agentic Applications (ASI01-ASI10)', () => {
|
||||
describe('OWASP_AGENTIC_NAMES', () => {
|
||||
it('should contain all 10 risk categories', () => {
|
||||
expect(OWASP_AGENTIC_NAMES).toHaveLength(10);
|
||||
});
|
||||
|
||||
it('should have correct risk names in order (ASI01-ASI10)', () => {
|
||||
const expectedNames = [
|
||||
'ASI01: Agent Goal Hijack',
|
||||
'ASI02: Tool Misuse and Exploitation',
|
||||
'ASI03: Identity and Privilege Abuse',
|
||||
'ASI04: Agentic Supply Chain Vulnerabilities',
|
||||
'ASI05: Unexpected Code Execution',
|
||||
'ASI06: Memory and Context Poisoning',
|
||||
'ASI07: Insecure Inter-Agent Communication',
|
||||
'ASI08: Cascading Failures',
|
||||
'ASI09: Human Agent Trust Exploitation',
|
||||
'ASI10: Rogue Agents',
|
||||
];
|
||||
|
||||
expect(OWASP_AGENTIC_NAMES).toEqual(expectedNames);
|
||||
});
|
||||
|
||||
it('should have sequential numbering from ASI01 to ASI10', () => {
|
||||
OWASP_AGENTIC_NAMES.forEach((name, index) => {
|
||||
const expectedPrefix = `ASI${String(index + 1).padStart(2, '0')}:`;
|
||||
expect(name.startsWith(expectedPrefix)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('OWASP_AGENTIC_TOP_10_MAPPING', () => {
|
||||
it('should have mappings for all 10 risks (asi01-asi10)', () => {
|
||||
const expectedKeys = Array.from(
|
||||
{ length: 10 },
|
||||
(_, i) => `owasp:agentic:asi${String(i + 1).padStart(2, '0')}`,
|
||||
);
|
||||
|
||||
expectedKeys.forEach((key) => {
|
||||
expect(OWASP_AGENTIC_TOP_10_MAPPING).toHaveProperty(key);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have exactly 10 risk mappings', () => {
|
||||
expect(Object.keys(OWASP_AGENTIC_TOP_10_MAPPING)).toHaveLength(10);
|
||||
});
|
||||
|
||||
it('each mapping should have plugins and strategies arrays', () => {
|
||||
Object.entries(OWASP_AGENTIC_TOP_10_MAPPING).forEach(([, mapping]) => {
|
||||
expect(Array.isArray(mapping.plugins)).toBe(true);
|
||||
expect(Array.isArray(mapping.strategies)).toBe(true);
|
||||
expect(mapping.plugins.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('all mapped plugins should be valid plugin IDs', () => {
|
||||
const allPluginIds = new Set<string>(ALL_PLUGINS);
|
||||
|
||||
Object.entries(OWASP_AGENTIC_TOP_10_MAPPING).forEach(([, mapping]) => {
|
||||
mapping.plugins.forEach((pluginId) => {
|
||||
expect(allPluginIds.has(pluginId)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('specific risk mappings', () => {
|
||||
it('ASI01 (Agent Goal Hijack) should map to hijacking and system-prompt-override', () => {
|
||||
const asi01Plugins = OWASP_AGENTIC_TOP_10_MAPPING['owasp:agentic:asi01'].plugins;
|
||||
expect(asi01Plugins).toContain('hijacking');
|
||||
expect(asi01Plugins).toContain('system-prompt-override');
|
||||
expect(asi01Plugins).toContain('indirect-prompt-injection');
|
||||
});
|
||||
|
||||
it('ASI02 (Tool Misuse and Exploitation) should map to tool-related plugins', () => {
|
||||
const asi02Plugins = OWASP_AGENTIC_TOP_10_MAPPING['owasp:agentic:asi02'].plugins;
|
||||
expect(asi02Plugins).toContain('excessive-agency');
|
||||
expect(asi02Plugins).toContain('mcp');
|
||||
expect(asi02Plugins).toContain('tool-discovery');
|
||||
});
|
||||
|
||||
it('ASI03 (Identity and Privilege Abuse) should map to authorization plugins', () => {
|
||||
const asi03Plugins = OWASP_AGENTIC_TOP_10_MAPPING['owasp:agentic:asi03'].plugins;
|
||||
expect(asi03Plugins).toContain('rbac');
|
||||
expect(asi03Plugins).toContain('bfla');
|
||||
expect(asi03Plugins).toContain('bola');
|
||||
});
|
||||
|
||||
it('ASI05 (Unexpected Code Execution) should map to injection plugins', () => {
|
||||
const asi05Plugins = OWASP_AGENTIC_TOP_10_MAPPING['owasp:agentic:asi05'].plugins;
|
||||
expect(asi05Plugins).toContain('shell-injection');
|
||||
expect(asi05Plugins).toContain('sql-injection');
|
||||
expect(asi05Plugins).toContain('ssrf');
|
||||
});
|
||||
|
||||
it('ASI06 (Memory and Context Poisoning) should map to memory-related plugins', () => {
|
||||
const asi06Plugins = OWASP_AGENTIC_TOP_10_MAPPING['owasp:agentic:asi06'].plugins;
|
||||
expect(asi06Plugins).toContain('agentic:memory-poisoning');
|
||||
expect(asi06Plugins).toContain('cross-session-leak');
|
||||
});
|
||||
|
||||
it('ASI10 (Rogue Agents) should map to agency and access control plugins', () => {
|
||||
const asi10Plugins = OWASP_AGENTIC_TOP_10_MAPPING['owasp:agentic:asi10'].plugins;
|
||||
expect(asi10Plugins).toContain('excessive-agency');
|
||||
expect(asi10Plugins).toContain('hijacking');
|
||||
expect(asi10Plugins).toContain('rbac');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ALIASED_PLUGINS integration', () => {
|
||||
it('should include owasp:agentic as an aliased plugin', () => {
|
||||
expect(ALIASED_PLUGINS).toContain('owasp:agentic');
|
||||
});
|
||||
|
||||
it('should include all individual risk keys (owasp:agentic:asi01-asi10) as aliased plugins', () => {
|
||||
const riskKeys = Array.from(
|
||||
{ length: 10 },
|
||||
(_, i) => `owasp:agentic:asi${String(i + 1).padStart(2, '0')}`,
|
||||
);
|
||||
|
||||
riskKeys.forEach((key) => {
|
||||
expect(ALIASED_PLUGINS).toContain(key);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ALIASED_PLUGIN_MAPPINGS integration', () => {
|
||||
it('should map owasp:agentic to OWASP_AGENTIC_TOP_10_MAPPING', () => {
|
||||
expect(ALIASED_PLUGIN_MAPPINGS['owasp:agentic']).toBe(OWASP_AGENTIC_TOP_10_MAPPING);
|
||||
});
|
||||
|
||||
it('owasp:agentic should expand to all 10 risk mappings', () => {
|
||||
const mapping = ALIASED_PLUGIN_MAPPINGS['owasp:agentic'];
|
||||
expect(Object.keys(mapping)).toHaveLength(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
AGENTIC_EXEMPT_PLUGINS,
|
||||
ALL_PLUGINS,
|
||||
DATASET_EXEMPT_PLUGINS,
|
||||
STRATEGY_EXEMPT_PLUGINS,
|
||||
} from '../../../src/redteam/constants/plugins';
|
||||
|
||||
describe('plugins constants', () => {
|
||||
it('should have ALL_PLUGINS as sorted array', () => {
|
||||
const sorted = [...ALL_PLUGINS].sort();
|
||||
expect(ALL_PLUGINS).toEqual(sorted);
|
||||
});
|
||||
|
||||
it('should have unique values in ALL_PLUGINS', () => {
|
||||
const uniquePlugins = new Set(ALL_PLUGINS);
|
||||
expect(uniquePlugins.size).toBe(ALL_PLUGINS.length);
|
||||
});
|
||||
|
||||
describe('DATASET_EXEMPT_PLUGINS', () => {
|
||||
it('should include static dataset plugins', () => {
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('aegis');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('beavertails');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('cyberseceval');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('donotanswer');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('harmbench');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('pliny');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('toxic-chat');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('unsafebench');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('vlguard');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('vlsu');
|
||||
expect(DATASET_EXEMPT_PLUGINS).toContain('xstest');
|
||||
});
|
||||
|
||||
it('should have unique values', () => {
|
||||
const uniquePlugins = new Set(DATASET_EXEMPT_PLUGINS);
|
||||
expect(uniquePlugins.size).toBe(DATASET_EXEMPT_PLUGINS.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AGENTIC_EXEMPT_PLUGINS', () => {
|
||||
it('should include agentic plugins', () => {
|
||||
expect(AGENTIC_EXEMPT_PLUGINS).toContain('system-prompt-override');
|
||||
expect(AGENTIC_EXEMPT_PLUGINS).toContain('agentic:memory-poisoning');
|
||||
});
|
||||
|
||||
it('should have unique values', () => {
|
||||
const uniquePlugins = new Set(AGENTIC_EXEMPT_PLUGINS);
|
||||
expect(uniquePlugins.size).toBe(AGENTIC_EXEMPT_PLUGINS.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('STRATEGY_EXEMPT_PLUGINS', () => {
|
||||
it('should include both agentic and dataset plugins', () => {
|
||||
// Should include all agentic plugins
|
||||
AGENTIC_EXEMPT_PLUGINS.forEach((plugin) => {
|
||||
expect(STRATEGY_EXEMPT_PLUGINS).toContain(plugin);
|
||||
});
|
||||
|
||||
// Should include all dataset plugins
|
||||
DATASET_EXEMPT_PLUGINS.forEach((plugin) => {
|
||||
expect(STRATEGY_EXEMPT_PLUGINS).toContain(plugin);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unique values', () => {
|
||||
const uniquePlugins = new Set(STRATEGY_EXEMPT_PLUGINS);
|
||||
expect(uniquePlugins.size).toBe(STRATEGY_EXEMPT_PLUGINS.length);
|
||||
});
|
||||
|
||||
it('should be the union of agentic and dataset plugins', () => {
|
||||
const expectedLength = AGENTIC_EXEMPT_PLUGINS.length + DATASET_EXEMPT_PLUGINS.length;
|
||||
expect(STRATEGY_EXEMPT_PLUGINS.length).toBe(expectedLength);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
ADDITIONAL_STRATEGIES,
|
||||
AGENTIC_STRATEGIES,
|
||||
ALL_STRATEGIES,
|
||||
getDefaultNFanout,
|
||||
isCustomStrategy,
|
||||
isFanoutStrategy,
|
||||
STRATEGY_COLLECTIONS,
|
||||
} from '../../../src/redteam/constants/strategies';
|
||||
|
||||
describe('strategies constants', () => {
|
||||
it('should have all strategies sorted', () => {
|
||||
const expectedStrategies = new Set([
|
||||
'default',
|
||||
'basic',
|
||||
'jailbreak',
|
||||
'jailbreak:composite',
|
||||
...AGENTIC_STRATEGIES,
|
||||
...ADDITIONAL_STRATEGIES,
|
||||
...STRATEGY_COLLECTIONS,
|
||||
]);
|
||||
|
||||
expect(ALL_STRATEGIES).toEqual(Array.from(expectedStrategies).sort());
|
||||
});
|
||||
|
||||
it('should correctly identify custom strategies', () => {
|
||||
expect(isCustomStrategy('custom')).toBe(true);
|
||||
expect(isCustomStrategy('custom:test')).toBe(true);
|
||||
expect(isCustomStrategy('other')).toBe(false);
|
||||
});
|
||||
|
||||
it('should expose fan-out metadata for supported strategies', () => {
|
||||
expect(isFanoutStrategy('jailbreak:composite')).toBe(true);
|
||||
expect(getDefaultNFanout('jailbreak:composite')).toBeGreaterThanOrEqual(1);
|
||||
|
||||
expect(isFanoutStrategy('gcg')).toBe(true);
|
||||
expect(getDefaultNFanout('gcg')).toBeGreaterThanOrEqual(1);
|
||||
|
||||
expect(isFanoutStrategy('base64')).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user