3nesdeniz's picture
Launch Agentic Prompt-Injection Boundary Explorer
8673c10 verified
Raw
History Blame Contribute Delete
5.41 kB
import assert from "node:assert/strict";
import { access, readFile } from "node:fs/promises";
import { resolve } from "node:path";
const root = resolve(import.meta.dirname, "..");
const splitFiles = {
train: { path: "data/train.jsonl", count: 840 },
validation: { path: "data/validation.jsonl", count: 120 },
test: { path: "data/test.jsonl", count: 240 },
};
const requiredFields = [
"id",
"pair_id",
"scenario_id",
"text",
"label",
"category",
"attack_family",
"pair_family",
"source_context",
"risk_domain",
"target_boundary",
"expected_action",
"source_type",
"language",
"split",
];
const expectedUiIds = [
"tab-pair",
"pair-scenario",
"pair-family",
"benign-text",
"attack-text",
"tab-scenarios",
"scenario-list",
"scenario-detail",
"tab-records",
"record-filter-form",
"record-results-body",
"record-detail",
"tab-method",
"citation-text",
"app-status",
];
async function readJsonl(relativePath) {
const raw = await readFile(resolve(root, relativePath), "utf8");
return raw
.split(/\r?\n/)
.filter(Boolean)
.map((line, index) => {
try {
return JSON.parse(line);
} catch (error) {
throw new Error(`${relativePath}:${index + 1} is not valid JSON: ${error.message}`);
}
});
}
function groupBy(items, key) {
const groups = new Map();
for (const item of items) {
const value = item[key];
if (!groups.has(value)) groups.set(value, []);
groups.get(value).push(item);
}
return groups;
}
for (const file of [
"index.html",
"assets/styles.css",
"assets/app.js",
"assets/altaysec-mark.svg",
"source/scenarios.jsonl",
...Object.values(splitFiles).map(({ path }) => path),
]) {
await access(resolve(root, file));
}
const rows = [];
for (const [split, definition] of Object.entries(splitFiles)) {
const splitRows = await readJsonl(definition.path);
assert.equal(splitRows.length, definition.count, `${split} row count`);
assert(splitRows.every((row) => row.split === split), `${split} contains a mismatched split value`);
rows.push(...splitRows);
}
const scenarios = await readJsonl("source/scenarios.jsonl");
assert.equal(rows.length, 1200, "total row count");
assert.equal(scenarios.length, 50, "scenario count");
const ids = new Set();
for (const [index, row] of rows.entries()) {
for (const field of requiredFields) {
assert(Object.hasOwn(row, field), `row ${index + 1} is missing ${field}`);
}
assert(!ids.has(row.id), `duplicate row id: ${row.id}`);
ids.add(row.id);
assert([0, 1].includes(row.label), `${row.id} has an invalid label`);
assert.equal(row.language, "en", `${row.id} is not English`);
assert.equal(row.source_type, "synthetic_curated", `${row.id} has an unexpected source type`);
assert(row.text.trim().length > 0, `${row.id} has empty text`);
}
const labelGroups = groupBy(rows, "label");
assert.equal(labelGroups.get(0)?.length, 600, "benign row count");
assert.equal(labelGroups.get(1)?.length, 600, "attack row count");
const pairs = groupBy(rows, "pair_id");
assert.equal(pairs.size, 600, "pair count");
for (const [pairId, pairRows] of pairs) {
assert.equal(pairRows.length, 2, `${pairId} must contain two rows`);
assert.deepEqual(
[...pairRows.map((row) => row.label)].sort(),
[0, 1],
`${pairId} must contain one benign and one attack row`,
);
for (const field of [
"scenario_id",
"split",
"pair_family",
"source_context",
"risk_domain",
"target_boundary",
]) {
assert.equal(new Set(pairRows.map((row) => row[field])).size, 1, `${pairId} mismatches ${field}`);
}
const attack = pairRows.find((row) => row.label === 1);
assert.equal(attack.attack_family, attack.pair_family, `${pairId} attack family mismatch`);
}
const scenarioIds = new Set(scenarios.map((scenario) => scenario.scenario_id));
assert.equal(scenarioIds.size, 50, "scenario identifiers must be unique");
const rowsByScenario = groupBy(rows, "scenario_id");
assert.equal(rowsByScenario.size, 50, "dataset scenario count");
for (const [scenarioId, scenarioRows] of rowsByScenario) {
assert(scenarioIds.has(scenarioId), `${scenarioId} is missing from scenarios.jsonl`);
assert.equal(scenarioRows.length, 24, `${scenarioId} row count`);
assert.equal(new Set(scenarioRows.map((row) => row.pair_id)).size, 12, `${scenarioId} pair count`);
assert.equal(new Set(scenarioRows.map((row) => row.pair_family)).size, 12, `${scenarioId} family count`);
assert.equal(new Set(scenarioRows.map((row) => row.split)).size, 1, `${scenarioId} crosses splits`);
}
const attackFamilies = groupBy(rows.filter((row) => row.label === 1), "attack_family");
assert.equal(attackFamilies.size, 12, "attack family count");
for (const [family, familyRows] of attackFamilies) {
assert.equal(familyRows.length, 50, `${family} attack count`);
}
const html = await readFile(resolve(root, "index.html"), "utf8");
for (const id of expectedUiIds) {
assert(html.includes(`id="${id}"`), `index.html is missing #${id}`);
}
assert(html.includes('src="assets/app.js"'), "index.html does not load app.js");
assert(html.includes('href="assets/styles.css"'), "index.html does not load styles.css");
console.log("Agentic Explorer validation passed");
console.log(" 1,200 rows | 600 pairs | 50 scenarios | 12 attack families");
console.log(" train 840 | validation 120 | test 240 | labels 600/600");