agent-threat-rules 2.1.1 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/eval/hackaprompt-corpus.d.ts +24 -0
- package/dist/eval/hackaprompt-corpus.d.ts.map +1 -0
- package/dist/eval/hackaprompt-corpus.js +61 -0
- package/dist/eval/hackaprompt-corpus.js.map +1 -0
- package/dist/eval/run-hackaprompt-benchmark.d.ts +19 -0
- package/dist/eval/run-hackaprompt-benchmark.d.ts.map +1 -0
- package/dist/eval/run-hackaprompt-benchmark.js +86 -0
- package/dist/eval/run-hackaprompt-benchmark.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/redact.d.ts +54 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +86 -0
- package/dist/redact.js.map +1 -0
- package/package.json +1 -1
- package/rules/agent-manipulation/ATR-2026-00440-semantic-kernel-vector-store-eval-rce.yaml +177 -0
- package/rules/privilege-escalation/ATR-2026-00441-semantic-kernel-sessions-python-plugin-startup-persistence.yaml +189 -0
- package/rules/prompt-injection/ATR-2026-00442-quoted-exact-output-forcing.yaml +120 -0
- package/rules/prompt-injection/ATR-2026-00443-word-fragment-concat-assembly.yaml +119 -0
- package/rules/prompt-injection/ATR-2026-00444-unicode-obfuscation-in-user-input.yaml +114 -0
- package/rules/prompt-injection/ATR-2026-00445-translation-hijack-with-side-output.yaml +113 -0
- package/rules/prompt-injection/ATR-2026-00446-variable-assignment-payload-injection.yaml +118 -0
- package/rules/prompt-injection/ATR-2026-00447-fictional-generation-containing-target.yaml +113 -0
- package/spec/stix-extension/README.md +79 -0
- package/spec/stix-extension/examples/atr-rule-prompt-injection-example.json +52 -0
- package/spec/stix-extension/extension-definition.json +32 -0
- package/spec/stix-extension/x-atr-rule-schema.json +184 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HackAPrompt Corpus Loader
|
|
3
|
+
*
|
|
4
|
+
* Reads the HackAPrompt-format sample JSON (text/category/label/source/language)
|
|
5
|
+
* produced by scripts/hackaprompt-to-corpus.py and converts each row into the
|
|
6
|
+
* CorpusSample shape used by the ATR eval harness.
|
|
7
|
+
*
|
|
8
|
+
* HackAPrompt is an all-adversarial corpus: every sample is an attempt to
|
|
9
|
+
* subvert the system prompt. We therefore only measure recall against this
|
|
10
|
+
* dataset; precision/FP rate is undefined here. For combined precision+recall
|
|
11
|
+
* use this corpus alongside a benign source (PINT, real-traffic).
|
|
12
|
+
*
|
|
13
|
+
* @module agent-threat-rules/eval/hackaprompt-corpus
|
|
14
|
+
*/
|
|
15
|
+
import type { CorpusSample } from './corpus.js';
|
|
16
|
+
export declare function loadHackaPromptCorpus(dataPath: string): readonly CorpusSample[];
|
|
17
|
+
export declare function getHackaPromptCorpusStats(corpus: readonly CorpusSample[]): {
|
|
18
|
+
total: number;
|
|
19
|
+
attacks: number;
|
|
20
|
+
benign: number;
|
|
21
|
+
byCategory: Record<string, number>;
|
|
22
|
+
byDifficulty: Record<string, number>;
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=hackaprompt-corpus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hackaprompt-corpus.d.ts","sourceRoot":"","sources":["../../src/eval/hackaprompt-corpus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsBhD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,YAAY,EAAE,CAsB/E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE;;;;gBAKnD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;kBACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;EAS7C"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HackAPrompt Corpus Loader
|
|
3
|
+
*
|
|
4
|
+
* Reads the HackAPrompt-format sample JSON (text/category/label/source/language)
|
|
5
|
+
* produced by scripts/hackaprompt-to-corpus.py and converts each row into the
|
|
6
|
+
* CorpusSample shape used by the ATR eval harness.
|
|
7
|
+
*
|
|
8
|
+
* HackAPrompt is an all-adversarial corpus: every sample is an attempt to
|
|
9
|
+
* subvert the system prompt. We therefore only measure recall against this
|
|
10
|
+
* dataset; precision/FP rate is undefined here. For combined precision+recall
|
|
11
|
+
* use this corpus alongside a benign source (PINT, real-traffic).
|
|
12
|
+
*
|
|
13
|
+
* @module agent-threat-rules/eval/hackaprompt-corpus
|
|
14
|
+
*/
|
|
15
|
+
import { readFileSync } from 'node:fs';
|
|
16
|
+
function assignDifficulty(level) {
|
|
17
|
+
if (level <= 2)
|
|
18
|
+
return 'easy';
|
|
19
|
+
if (level <= 6)
|
|
20
|
+
return 'medium';
|
|
21
|
+
return 'hard';
|
|
22
|
+
}
|
|
23
|
+
export function loadHackaPromptCorpus(dataPath) {
|
|
24
|
+
const raw = JSON.parse(readFileSync(dataPath, 'utf-8'));
|
|
25
|
+
return raw.map((sample) => {
|
|
26
|
+
const level = sample.metadata?.level ?? 5;
|
|
27
|
+
return {
|
|
28
|
+
id: sample.id,
|
|
29
|
+
text: sample.text,
|
|
30
|
+
category: sample.category,
|
|
31
|
+
expectedDetection: sample.label,
|
|
32
|
+
eventType: 'llm_input',
|
|
33
|
+
tier: 'any',
|
|
34
|
+
difficulty: assignDifficulty(level),
|
|
35
|
+
fields: {
|
|
36
|
+
text: sample.text,
|
|
37
|
+
prompt: sample.text,
|
|
38
|
+
user_input: sample.text,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function getHackaPromptCorpusStats(corpus) {
|
|
44
|
+
const stats = {
|
|
45
|
+
total: corpus.length,
|
|
46
|
+
attacks: 0,
|
|
47
|
+
benign: 0,
|
|
48
|
+
byCategory: {},
|
|
49
|
+
byDifficulty: {},
|
|
50
|
+
};
|
|
51
|
+
for (const s of corpus) {
|
|
52
|
+
if (s.expectedDetection)
|
|
53
|
+
stats.attacks++;
|
|
54
|
+
else
|
|
55
|
+
stats.benign++;
|
|
56
|
+
stats.byCategory[s.category] = (stats.byCategory[s.category] ?? 0) + 1;
|
|
57
|
+
stats.byDifficulty[s.difficulty] = (stats.byDifficulty[s.difficulty] ?? 0) + 1;
|
|
58
|
+
}
|
|
59
|
+
return stats;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=hackaprompt-corpus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hackaprompt-corpus.js","sourceRoot":"","sources":["../../src/eval/hackaprompt-corpus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAiBvC,SAAS,gBAAgB,CAAC,KAAa;IACrC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IAC9B,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,MAAM,GAAG,GAAoC,IAAI,CAAC,KAAK,CACrD,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAChC,CAAC;IAEF,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,EAAgB,EAAE;QACtC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,CAAC;QAC1C,OAAO;YACL,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,iBAAiB,EAAE,MAAM,CAAC,KAAK;YAC/B,SAAS,EAAE,WAAW;YACtB,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,gBAAgB,CAAC,KAAK,CAAC;YACnC,MAAM,EAAE;gBACN,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,MAAM,EAAE,MAAM,CAAC,IAAI;gBACnB,UAAU,EAAE,MAAM,CAAC,IAAI;aACxB;SACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,MAA+B;IACvE,MAAM,KAAK,GAAG;QACZ,KAAK,EAAE,MAAM,CAAC,MAAM;QACpB,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,CAAC;QACT,UAAU,EAAE,EAA4B;QACxC,YAAY,EAAE,EAA4B;KAC3C,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,CAAC,iBAAiB;YAAE,KAAK,CAAC,OAAO,EAAE,CAAC;;YACpC,KAAK,CAAC,MAAM,EAAE,CAAC;QACpB,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACvE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* HackAPrompt Benchmark Runner
|
|
4
|
+
*
|
|
5
|
+
* Loads a sample from the HackAPrompt 600K+ adversarial prompt dataset
|
|
6
|
+
* (produced by scripts/hackaprompt-to-corpus.py) and runs it through the
|
|
7
|
+
* ATR evaluation harness to measure recall against real prompt-hacking
|
|
8
|
+
* attempts collected at competition scale.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx tsx src/eval/run-hackaprompt-benchmark.ts
|
|
12
|
+
*
|
|
13
|
+
* HackAPrompt is an all-adversarial corpus, so we measure recall, latency,
|
|
14
|
+
* and tier breakdown. Precision/FP rate require a benign companion source.
|
|
15
|
+
*
|
|
16
|
+
* @module agent-threat-rules/eval/run-hackaprompt-benchmark
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=run-hackaprompt-benchmark.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-hackaprompt-benchmark.d.ts","sourceRoot":"","sources":["../../src/eval/run-hackaprompt-benchmark.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* HackAPrompt Benchmark Runner
|
|
4
|
+
*
|
|
5
|
+
* Loads a sample from the HackAPrompt 600K+ adversarial prompt dataset
|
|
6
|
+
* (produced by scripts/hackaprompt-to-corpus.py) and runs it through the
|
|
7
|
+
* ATR evaluation harness to measure recall against real prompt-hacking
|
|
8
|
+
* attempts collected at competition scale.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx tsx src/eval/run-hackaprompt-benchmark.ts
|
|
12
|
+
*
|
|
13
|
+
* HackAPrompt is an all-adversarial corpus, so we measure recall, latency,
|
|
14
|
+
* and tier breakdown. Precision/FP rate require a benign companion source.
|
|
15
|
+
*
|
|
16
|
+
* @module agent-threat-rules/eval/run-hackaprompt-benchmark
|
|
17
|
+
*/
|
|
18
|
+
import { resolve, join } from 'node:path';
|
|
19
|
+
import { loadHackaPromptCorpus, getHackaPromptCorpusStats } from './hackaprompt-corpus.js';
|
|
20
|
+
import { runEval } from './eval-harness.js';
|
|
21
|
+
function formatPercent(n) {
|
|
22
|
+
return `${(n * 100).toFixed(1)}%`;
|
|
23
|
+
}
|
|
24
|
+
function formatMs(n) {
|
|
25
|
+
return `${n.toFixed(2)}ms`;
|
|
26
|
+
}
|
|
27
|
+
async function main() {
|
|
28
|
+
const base = resolve(join(import.meta.dirname ?? '.', '..', '..'));
|
|
29
|
+
const rulesDir = join(base, 'rules');
|
|
30
|
+
const dataPath = join(base, 'data', 'hackaprompt', 'hackaprompt-corpus.json');
|
|
31
|
+
const outputPath = join(base, 'data', 'hackaprompt', 'hackaprompt-eval-report.json');
|
|
32
|
+
console.log('\n=== HackAPrompt Benchmark -- ATR Evaluation ===\n');
|
|
33
|
+
console.log(`Corpus: ${dataPath}`);
|
|
34
|
+
console.log(`Rules: ${rulesDir}\n`);
|
|
35
|
+
const corpus = loadHackaPromptCorpus(dataPath);
|
|
36
|
+
const stats = getHackaPromptCorpusStats(corpus);
|
|
37
|
+
console.log(`Loaded ${stats.total} samples (${stats.attacks} attacks, ${stats.benign} benign)`);
|
|
38
|
+
console.log(`Categories: ${Object.entries(stats.byCategory).map(([k, v]) => `${k}=${v}`).join(', ')}`);
|
|
39
|
+
console.log(`Difficulty: ${Object.entries(stats.byDifficulty).map(([k, v]) => `${k}=${v}`).join(', ')}`);
|
|
40
|
+
// HackAPrompt is an all-adversarial corpus from a public global competition.
|
|
41
|
+
// It contains heavy paraphrasing, role-play, multilingual, and creative
|
|
42
|
+
// attacks. Recall is the headline number. FP rate is meaningless here
|
|
43
|
+
// because there are no benign samples in the corpus.
|
|
44
|
+
const hackaPromptThresholds = {
|
|
45
|
+
minRecall: 0.10,
|
|
46
|
+
maxFpRate: 1.0,
|
|
47
|
+
minF1: 0.0,
|
|
48
|
+
maxP95LatencyMs: 200,
|
|
49
|
+
};
|
|
50
|
+
const { report, tiersUsed, ruleQuality } = await runEval({
|
|
51
|
+
rulesDir,
|
|
52
|
+
corpus,
|
|
53
|
+
thresholds: hackaPromptThresholds,
|
|
54
|
+
outputPath,
|
|
55
|
+
});
|
|
56
|
+
console.log(`\nTiers: ${tiersUsed.join(' + ')}`);
|
|
57
|
+
console.log(`\n--- Overall ---`);
|
|
58
|
+
console.log(` Recall: ${formatPercent(report.overall.recall)}`);
|
|
59
|
+
console.log(` Precision: ${formatPercent(report.overall.precision)} (N/A meaning - no benign samples)`);
|
|
60
|
+
console.log(` Confusion: TP=${report.overall.confusion.tp} FN=${report.overall.confusion.fn}`);
|
|
61
|
+
console.log(`\n--- Latency ---`);
|
|
62
|
+
console.log(` P50: ${formatMs(report.latency.p50)}`);
|
|
63
|
+
console.log(` P95: ${formatMs(report.latency.p95)}`);
|
|
64
|
+
console.log(` P99: ${formatMs(report.latency.p99)}`);
|
|
65
|
+
console.log(` Mean: ${formatMs(report.latency.mean)}`);
|
|
66
|
+
console.log(`\n--- By Category ---`);
|
|
67
|
+
for (const cat of report.byCategory) {
|
|
68
|
+
const m = cat.metrics;
|
|
69
|
+
const tp = m.confusion.tp;
|
|
70
|
+
const fn = m.confusion.fn;
|
|
71
|
+
const recall = tp + fn === 0 ? 0 : tp / (tp + fn);
|
|
72
|
+
console.log(` ${cat.category}: recall=${formatPercent(recall)} (TP=${tp} FN=${fn})`);
|
|
73
|
+
}
|
|
74
|
+
console.log(`\n--- Top Firing Rules (top 15) ---`);
|
|
75
|
+
const fired = ruleQuality?.topRules ?? [];
|
|
76
|
+
for (const r of fired.slice(0, 15)) {
|
|
77
|
+
console.log(` ${r.ruleId}: matches=${r.matchCount} TP=${r.tpCount} FP=${r.fpCount}`);
|
|
78
|
+
}
|
|
79
|
+
console.log(`\nReport saved to: ${outputPath}`);
|
|
80
|
+
console.log('Done.');
|
|
81
|
+
}
|
|
82
|
+
main().catch((err) => {
|
|
83
|
+
console.error('Error:', err);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=run-hackaprompt-benchmark.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-hackaprompt-benchmark.js","sourceRoot":"","sources":["../../src/eval/run-hackaprompt-benchmark.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export { loadRuleFile, loadRulesFromDirectory, validateRule } from './loader.js'
|
|
|
20
20
|
export { SessionTracker } from './session-tracker.js';
|
|
21
21
|
export type { SessionStateSnapshot } from './session-tracker.js';
|
|
22
22
|
export { computeContentHash } from './content-hash.js';
|
|
23
|
+
export { redactMatchedValue, redactMatchedValues } from './redact.js';
|
|
24
|
+
export type { RedactOptions } from './redact.js';
|
|
23
25
|
export { InvariantChecker } from './tier0-invariant.js';
|
|
24
26
|
export type { SkillManifest, InvariantViolation, InvariantViolationType } from './tier0-invariant.js';
|
|
25
27
|
export { InMemoryBlacklist, buildBlacklistMatch } from './tier1-blacklist.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ export { createTCReporter } from './tc-reporter.js';
|
|
|
18
18
|
export { loadRuleFile, loadRulesFromDirectory, validateRule } from './loader.js';
|
|
19
19
|
export { SessionTracker } from './session-tracker.js';
|
|
20
20
|
export { computeContentHash } from './content-hash.js';
|
|
21
|
+
export { redactMatchedValue, redactMatchedValues } from './redact.js';
|
|
21
22
|
// ── Tier 0: Invariant Enforcement (hard boundaries) ──────────────
|
|
22
23
|
export { InvariantChecker } from './tier0-invariant.js';
|
|
23
24
|
// ── Tier 1: Blacklist Provider (known-bad lookup) ────────────────
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,mEAAmE;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,mEAAmE;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGtE,oEAAoE;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAGxD,oEAAoE;AACpE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAG9E,oEAAoE;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAGhE,oEAAoE;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAE7E,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAGtF,mEAAmE;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,kDAAkD;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,kDAAkD;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAQ/D,mEAAmE;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D,mEAAmE;AACnE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,oEAAoE;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,mEAAmE;AACnE,qDAAqD;AACrD,qDAAqD;AACrD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,sDAAsD;AACtD,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAC"}
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Match-value redaction utility.
|
|
3
|
+
*
|
|
4
|
+
* The engine's `ATRMatch.matchedPatterns` field can contain the raw text that
|
|
5
|
+
* triggered a rule. Downstream integrations that include matched values in
|
|
6
|
+
* log lines, error messages, or telemetry payloads risk re-exposing the very
|
|
7
|
+
* secrets that the rule fired on (e.g., AWS access keys, OAuth tokens,
|
|
8
|
+
* cookies, prompt-injection payloads containing user PII).
|
|
9
|
+
*
|
|
10
|
+
* Pass each entry of `match.matchedPatterns` through `redactMatchedValue()`
|
|
11
|
+
* before logging or surfacing it externally. The function preserves enough
|
|
12
|
+
* context for triage (rule shape, length, leading marker bytes) without
|
|
13
|
+
* keeping the secret bytes themselves.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* import { redactMatchedValue } from "agent-threat-rules/redact";
|
|
17
|
+
* for (const match of engine.evaluate(event)) {
|
|
18
|
+
* logger.warn({
|
|
19
|
+
* rule: match.rule.id,
|
|
20
|
+
* redacted_patterns: match.matchedPatterns.map(redactMatchedValue),
|
|
21
|
+
* });
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Options for `redactMatchedValue`.
|
|
26
|
+
*/
|
|
27
|
+
export interface RedactOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Number of leading bytes to keep visible as a triage hint. Defaults to 4.
|
|
30
|
+
* Set to 0 to keep no prefix at all.
|
|
31
|
+
*/
|
|
32
|
+
headBytes?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum length of the returned redacted string. Defaults to 80.
|
|
35
|
+
*/
|
|
36
|
+
maxLength?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Replace a raw matched value with a triage-safe summary.
|
|
40
|
+
*
|
|
41
|
+
* The output never contains more than `headBytes` (default 4) of the original
|
|
42
|
+
* value. The remainder is replaced with a structured placeholder that records
|
|
43
|
+
* the recognised secret class (when known), the original length, and an
|
|
44
|
+
* elision marker. Whitespace and surrounding punctuation are preserved so the
|
|
45
|
+
* summary still reads as a token in log lines.
|
|
46
|
+
*
|
|
47
|
+
* Returns a string of at most `maxLength` characters (default 80).
|
|
48
|
+
*/
|
|
49
|
+
export declare function redactMatchedValue(value: string, options?: RedactOptions): string;
|
|
50
|
+
/**
|
|
51
|
+
* Convenience helper: apply `redactMatchedValue` to every entry of an array.
|
|
52
|
+
*/
|
|
53
|
+
export declare function redactMatchedValues(values: ReadonlyArray<string>, options?: RedactOptions): string[];
|
|
54
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAuBH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CA2BrF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,EAAE,CAEpG"}
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Match-value redaction utility.
|
|
3
|
+
*
|
|
4
|
+
* The engine's `ATRMatch.matchedPatterns` field can contain the raw text that
|
|
5
|
+
* triggered a rule. Downstream integrations that include matched values in
|
|
6
|
+
* log lines, error messages, or telemetry payloads risk re-exposing the very
|
|
7
|
+
* secrets that the rule fired on (e.g., AWS access keys, OAuth tokens,
|
|
8
|
+
* cookies, prompt-injection payloads containing user PII).
|
|
9
|
+
*
|
|
10
|
+
* Pass each entry of `match.matchedPatterns` through `redactMatchedValue()`
|
|
11
|
+
* before logging or surfacing it externally. The function preserves enough
|
|
12
|
+
* context for triage (rule shape, length, leading marker bytes) without
|
|
13
|
+
* keeping the secret bytes themselves.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* import { redactMatchedValue } from "agent-threat-rules/redact";
|
|
17
|
+
* for (const match of engine.evaluate(event)) {
|
|
18
|
+
* logger.warn({
|
|
19
|
+
* rule: match.rule.id,
|
|
20
|
+
* redacted_patterns: match.matchedPatterns.map(redactMatchedValue),
|
|
21
|
+
* });
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
const SECRET_PREFIXES = [
|
|
25
|
+
[/^AKIA[A-Z0-9]/, "aws_access_key_id"],
|
|
26
|
+
[/^ASIA[A-Z0-9]/, "aws_session_credential"],
|
|
27
|
+
[/^AGPA[A-Z0-9]/, "aws_user_identity"],
|
|
28
|
+
[/^ghp_[A-Za-z0-9]/, "github_personal_token"],
|
|
29
|
+
[/^gho_[A-Za-z0-9]/, "github_oauth_token"],
|
|
30
|
+
[/^ghs_[A-Za-z0-9]/, "github_server_token"],
|
|
31
|
+
[/^ghu_[A-Za-z0-9]/, "github_user_token"],
|
|
32
|
+
[/^ghr_[A-Za-z0-9]/, "github_refresh_token"],
|
|
33
|
+
[/^xox[abprs]-/, "slack_token"],
|
|
34
|
+
[/^xoxe-/, "slack_external_token"],
|
|
35
|
+
[/^sk-[A-Za-z0-9_]/, "openai_or_compatible_secret"],
|
|
36
|
+
[/^sk-ant-[A-Za-z0-9_]/, "anthropic_secret"],
|
|
37
|
+
[/^Bearer\s+/i, "bearer_credential"],
|
|
38
|
+
[/^-----BEGIN [A-Z ]+PRIVATE KEY-----/, "pem_private_key"],
|
|
39
|
+
[/^eyJ[A-Za-z0-9_-]/, "jwt_or_jose"],
|
|
40
|
+
];
|
|
41
|
+
const DEFAULT_HEAD_BYTES = 4;
|
|
42
|
+
const MAX_REDACTED_OUTPUT = 80;
|
|
43
|
+
/**
|
|
44
|
+
* Replace a raw matched value with a triage-safe summary.
|
|
45
|
+
*
|
|
46
|
+
* The output never contains more than `headBytes` (default 4) of the original
|
|
47
|
+
* value. The remainder is replaced with a structured placeholder that records
|
|
48
|
+
* the recognised secret class (when known), the original length, and an
|
|
49
|
+
* elision marker. Whitespace and surrounding punctuation are preserved so the
|
|
50
|
+
* summary still reads as a token in log lines.
|
|
51
|
+
*
|
|
52
|
+
* Returns a string of at most `maxLength` characters (default 80).
|
|
53
|
+
*/
|
|
54
|
+
export function redactMatchedValue(value, options = {}) {
|
|
55
|
+
if (typeof value !== "string")
|
|
56
|
+
return "[redacted:non-string]";
|
|
57
|
+
if (value.length === 0)
|
|
58
|
+
return "[redacted:empty]";
|
|
59
|
+
const headBytes = Math.max(0, options.headBytes ?? DEFAULT_HEAD_BYTES);
|
|
60
|
+
const maxLength = Math.max(8, options.maxLength ?? MAX_REDACTED_OUTPUT);
|
|
61
|
+
// Strip leading / trailing whitespace for class detection, but keep the
|
|
62
|
+
// visible head from the original (so context is preserved).
|
|
63
|
+
const trimmed = value.trim();
|
|
64
|
+
let secretClass = null;
|
|
65
|
+
for (const [pattern, label] of SECRET_PREFIXES) {
|
|
66
|
+
if (pattern.test(trimmed)) {
|
|
67
|
+
secretClass = label;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const head = value.slice(0, headBytes);
|
|
72
|
+
const length = value.length;
|
|
73
|
+
const summary = secretClass !== null
|
|
74
|
+
? `[redacted:${secretClass} head=${JSON.stringify(head)} len=${length}]`
|
|
75
|
+
: `[redacted head=${JSON.stringify(head)} len=${length}]`;
|
|
76
|
+
if (summary.length <= maxLength)
|
|
77
|
+
return summary;
|
|
78
|
+
return summary.slice(0, maxLength - 1) + "]";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Convenience helper: apply `redactMatchedValue` to every entry of an array.
|
|
82
|
+
*/
|
|
83
|
+
export function redactMatchedValues(values, options) {
|
|
84
|
+
return values.map((v) => redactMatchedValue(v, options));
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,eAAe,GAA6C;IAChE,CAAC,eAAe,EAAE,mBAAmB,CAAC;IACtC,CAAC,eAAe,EAAE,wBAAwB,CAAC;IAC3C,CAAC,eAAe,EAAE,mBAAmB,CAAC;IACtC,CAAC,kBAAkB,EAAE,uBAAuB,CAAC;IAC7C,CAAC,kBAAkB,EAAE,oBAAoB,CAAC;IAC1C,CAAC,kBAAkB,EAAE,qBAAqB,CAAC;IAC3C,CAAC,kBAAkB,EAAE,mBAAmB,CAAC;IACzC,CAAC,kBAAkB,EAAE,sBAAsB,CAAC;IAC5C,CAAC,cAAc,EAAE,aAAa,CAAC;IAC/B,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAClC,CAAC,kBAAkB,EAAE,6BAA6B,CAAC;IACnD,CAAC,sBAAsB,EAAE,kBAAkB,CAAC;IAC5C,CAAC,aAAa,EAAE,mBAAmB,CAAC;IACpC,CAAC,qCAAqC,EAAE,iBAAiB,CAAC;IAC1D,CAAC,mBAAmB,EAAE,aAAa,CAAC;CACrC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAC7B,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAiB/B;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,UAAyB,EAAE;IAC3E,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,uBAAuB,CAAC;IAC9D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,kBAAkB,CAAC;IAElD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC,CAAC;IACvE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,IAAI,mBAAmB,CAAC,CAAC;IAExE,wEAAwE;IACxE,4DAA4D;IAC5D,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,WAAW,GAAkB,IAAI,CAAC;IACtC,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,eAAe,EAAE,CAAC;QAC/C,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1B,WAAW,GAAG,KAAK,CAAC;YACpB,MAAM;QACR,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC5B,MAAM,OAAO,GACX,WAAW,KAAK,IAAI;QAClB,CAAC,CAAC,aAAa,WAAW,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,MAAM,GAAG;QACxE,CAAC,CAAC,kBAAkB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,MAAM,GAAG,CAAC;IAE9D,IAAI,OAAO,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,OAAO,CAAC;IAChD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AAC/C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA6B,EAAE,OAAuB;IACxF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-threat-rules",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Open detection standard -- like Sigma, but for AI agents. 311 rules for prompt injection, tool poisoning, context exfiltration, and MCP attacks. Shipped in Cisco AI Defense. 97.1% recall on NVIDIA garak.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
title: "Microsoft Semantic Kernel In-Memory Vector Store eval() RCE (CVE-2026-26030)"
|
|
2
|
+
id: ATR-2026-00440
|
|
3
|
+
rule_version: 1
|
|
4
|
+
status: experimental
|
|
5
|
+
description: >
|
|
6
|
+
Detects exploitation of CVE-2026-26030 (Critical), remote code execution
|
|
7
|
+
in Microsoft Semantic Kernel via unsafe string interpolation in
|
|
8
|
+
In-Memory Vector Store filter functions. The vulnerable sink interpolates
|
|
9
|
+
a user/LLM-controlled filter expression and evaluates it as a lambda;
|
|
10
|
+
attacker constructs a lambda body that traverses Python's class hierarchy
|
|
11
|
+
via `tuple()` to reach `BuiltinImporter` and execute `os.system()`,
|
|
12
|
+
achieving unauthenticated RCE on the Semantic Kernel host. This rule
|
|
13
|
+
detects the LLM-output / user-input payload patterns that reach the
|
|
14
|
+
filter sink: lambda definitions combined with eval / __import__ /
|
|
15
|
+
AST-traversal-via-mro patterns inside content or user_input fields a
|
|
16
|
+
Semantic Kernel agent is likely to interpolate. CWE-94, CWE-95.
|
|
17
|
+
Patches available in Python semantic-kernel >= 1.39.4 and
|
|
18
|
+
.NET semantic-kernel >= 1.71.0; this rule detects exploit attempts
|
|
19
|
+
against unpatched deployments and provides defence-in-depth post-patch.
|
|
20
|
+
author: "ATR Community"
|
|
21
|
+
date: "2026/05/11"
|
|
22
|
+
schema_version: "0.1"
|
|
23
|
+
detection_tier: pattern
|
|
24
|
+
maturity: test
|
|
25
|
+
severity: critical
|
|
26
|
+
|
|
27
|
+
references:
|
|
28
|
+
owasp_llm:
|
|
29
|
+
- "LLM05:2025 - Improper Output Handling"
|
|
30
|
+
- "LLM06:2025 - Excessive Agency"
|
|
31
|
+
owasp_agentic:
|
|
32
|
+
- "ASI05:2026 - Unexpected Code Execution"
|
|
33
|
+
- "ASI06:2026 - Sandbox Escape"
|
|
34
|
+
mitre_atlas:
|
|
35
|
+
- "AML.T0050 - Command and Scripting Interpreter"
|
|
36
|
+
- "AML.T0051 - LLM Prompt Injection"
|
|
37
|
+
mitre_attack:
|
|
38
|
+
- "T1059 - Command and Scripting Interpreter"
|
|
39
|
+
- "T1059.006 - Python"
|
|
40
|
+
cve:
|
|
41
|
+
- "CVE-2026-26030"
|
|
42
|
+
|
|
43
|
+
metadata_provenance:
|
|
44
|
+
mitre_atlas: human-reviewed
|
|
45
|
+
owasp_llm: human-reviewed
|
|
46
|
+
owasp_agentic: human-reviewed
|
|
47
|
+
cve: human-reviewed
|
|
48
|
+
|
|
49
|
+
compliance:
|
|
50
|
+
eu_ai_act:
|
|
51
|
+
- article: "15"
|
|
52
|
+
context: "CVE-2026-26030 lets unfiltered LLM output drive lambda/eval interpolation in Semantic Kernel's vector-store filter; Article 15 cybersecurity requirements mandate that high-risk AI systems neutralise interpreter sinks reachable from model or user input."
|
|
53
|
+
strength: primary
|
|
54
|
+
- article: "9"
|
|
55
|
+
context: "Article 9 risk management must enumerate lambda-with-eval and AST-traversal payloads from LLM output as a high-risk vector — particularly in vector-store filter paths, which are typically considered low-risk infrastructure."
|
|
56
|
+
strength: primary
|
|
57
|
+
nist_ai_rmf:
|
|
58
|
+
- subcategory: "MP.5.1"
|
|
59
|
+
context: "Adversarial inputs that drive an LLM to emit lambda bodies invoking eval / __import__ / mro-traversal must be tracked as a primary input-attack class affecting framework-level integrations."
|
|
60
|
+
strength: primary
|
|
61
|
+
- subcategory: "MG.2.3"
|
|
62
|
+
context: "Risk treatment plans under MG.2.3 must require static and runtime detection of dynamic-evaluation primitives in any code path that consumes LLM output, including filter / search / ranking sinks."
|
|
63
|
+
strength: primary
|
|
64
|
+
iso_42001:
|
|
65
|
+
- clause: "8.6"
|
|
66
|
+
context: "Operational controls under clause 8.6 must prohibit dynamic-evaluation primitives (eval, exec, lambda-with-eval, Function constructor) being reached by any LLM-generated or user-supplied content path."
|
|
67
|
+
strength: primary
|
|
68
|
+
|
|
69
|
+
tags:
|
|
70
|
+
category: agent-manipulation
|
|
71
|
+
subcategory: framework-eval-rce
|
|
72
|
+
scan_target: both
|
|
73
|
+
confidence: high
|
|
74
|
+
|
|
75
|
+
agent_source:
|
|
76
|
+
type: llm_io
|
|
77
|
+
framework:
|
|
78
|
+
- semantic-kernel
|
|
79
|
+
- any
|
|
80
|
+
provider:
|
|
81
|
+
- any
|
|
82
|
+
|
|
83
|
+
detection:
|
|
84
|
+
condition: any
|
|
85
|
+
false_positives:
|
|
86
|
+
- "Legitimate Python educational content discussing lambda safety or eval() risks."
|
|
87
|
+
- "Static analysis tooling output documenting CVE-2026-26030 attack patterns for defensive purposes."
|
|
88
|
+
- "Patched Semantic Kernel filter expressions that use AST allowlisting and reject lambda bodies before evaluation."
|
|
89
|
+
conditions:
|
|
90
|
+
- field: content
|
|
91
|
+
operator: regex
|
|
92
|
+
value: '(?i)lambda\s+\w*\s*:\s*[^,)]*\beval\s*\('
|
|
93
|
+
description: "Lambda body invoking eval() — primary CVE-2026-26030 exploit shape (`lambda row: eval(filter_expr)`)"
|
|
94
|
+
|
|
95
|
+
- field: content
|
|
96
|
+
operator: regex
|
|
97
|
+
value: '(?i)lambda\s+\w*\s*:\s*[^,)]*\b__import__\s*\(\s*["\x27](?:os|subprocess|socket|builtins|importlib)["\x27]'
|
|
98
|
+
description: "Lambda body using __import__ to reach OS / process / builtin modules — eval-friendly RCE chain"
|
|
99
|
+
|
|
100
|
+
- field: content
|
|
101
|
+
operator: regex
|
|
102
|
+
value: "(?i)\\(\\s*\\)\\.__class__\\.__mro__|tuple\\s*\\(\\s*\\)\\.__class__|\\(\\s*\\)\\.__class__\\.__bases__|__subclasses__\\s*\\(\\s*\\)"
|
|
103
|
+
description: "Python class-hierarchy traversal primitives (tuple().__class__.__mro__, __subclasses__()) — AST-bypass building block used to reach BuiltinImporter"
|
|
104
|
+
|
|
105
|
+
- field: content
|
|
106
|
+
operator: regex
|
|
107
|
+
value: '(?i)BuiltinImporter|FunctionType|getattr\s*\(\s*(?:object|type|__builtins__)'
|
|
108
|
+
description: "Direct reference to BuiltinImporter or reflective getattr on built-in roots — second stage of the AST-bypass chain"
|
|
109
|
+
|
|
110
|
+
- field: content
|
|
111
|
+
operator: regex
|
|
112
|
+
value: '(?i)\bFunction\s*\(\s*["\x27].{0,200}?(?:return\s+(?:eval|new\s+Function|require|process)|os\.system|child_process)["\x27]'
|
|
113
|
+
description: ".NET / JavaScript Function() constructor with body invoking eval / process — cross-runtime variant"
|
|
114
|
+
|
|
115
|
+
- field: user_input
|
|
116
|
+
operator: regex
|
|
117
|
+
value: '(?i)lambda\s+\w*\s*:\s*[^,)]*\b(?:eval|exec|__import__)\s*\('
|
|
118
|
+
description: "User-supplied filter expression containing lambda + dynamic-evaluation primitive — the original CVE-2026-26030 injection surface"
|
|
119
|
+
|
|
120
|
+
response:
|
|
121
|
+
actions:
|
|
122
|
+
- block_input
|
|
123
|
+
- alert
|
|
124
|
+
- escalate
|
|
125
|
+
notify:
|
|
126
|
+
- security_team
|
|
127
|
+
|
|
128
|
+
remediation: >
|
|
129
|
+
Upgrade Python semantic-kernel to >= 1.39.4 or .NET semantic-kernel to
|
|
130
|
+
>= 1.71.0. Reject filter expressions containing lambda bodies via AST
|
|
131
|
+
allowlisting before evaluation. Never pass LLM-generated content to any
|
|
132
|
+
dynamic-evaluation primitive (eval, exec, lambda-with-eval, Function
|
|
133
|
+
constructor) regardless of perceived sanitisation. Treat LLM output as
|
|
134
|
+
untrusted input from a security perspective.
|
|
135
|
+
|
|
136
|
+
test_cases:
|
|
137
|
+
true_positives:
|
|
138
|
+
- input: 'lambda row: eval(filter_expr)'
|
|
139
|
+
expected: triggered
|
|
140
|
+
description: "Microsoft Copilot fixture: lambda body invoking eval — primary CVE-2026-26030 exploit shape"
|
|
141
|
+
- input: 'lambda row: __import__("os").system("id")'
|
|
142
|
+
expected: triggered
|
|
143
|
+
description: "Lambda body using __import__ on os module — eval-friendly RCE chain"
|
|
144
|
+
- input: 'lambda x: __import__("subprocess").run(["sh","-c","whoami"])'
|
|
145
|
+
expected: triggered
|
|
146
|
+
description: "Lambda invoking subprocess.run via __import__"
|
|
147
|
+
- input: '().__class__.__mro__[1].__subclasses__()'
|
|
148
|
+
expected: triggered
|
|
149
|
+
description: "AST-bypass primitive — empty-tuple class-hierarchy traversal to reach BuiltinImporter"
|
|
150
|
+
- input: 'tuple().__class__.__bases__[0].__subclasses__()'
|
|
151
|
+
expected: triggered
|
|
152
|
+
description: "tuple()-based class-hierarchy traversal (variant)"
|
|
153
|
+
- input: 'lambda r: eval(r.metadata.get("filter", "0"))'
|
|
154
|
+
expected: triggered
|
|
155
|
+
description: "Filter-context lambda calling eval on a derived attribute — exploit chain via vector-store filter"
|
|
156
|
+
- input: 'Filter expression: lambda row: eval(input_query) — please apply to ranking'
|
|
157
|
+
expected: triggered
|
|
158
|
+
description: "LLM-emitted filter expression containing lambda + eval — typical content-field path"
|
|
159
|
+
- input: 'getattr(__builtins__, "eval")("os.system(\"id\")")'
|
|
160
|
+
expected: triggered
|
|
161
|
+
description: "Reflective getattr on __builtins__ to reach eval — second-stage AST-bypass shape"
|
|
162
|
+
true_negatives:
|
|
163
|
+
- input: "The semantic-kernel changelog notes that lambda-with-eval filter expressions are blocked after the 1.39.4 patch."
|
|
164
|
+
expected: not_triggered
|
|
165
|
+
description: "Documentation discussing the patch should not trigger"
|
|
166
|
+
- input: "Best practice: use AST allowlisting in vector store filter pipelines."
|
|
167
|
+
expected: not_triggered
|
|
168
|
+
description: "Defensive guidance discussing the mitigation"
|
|
169
|
+
- input: "I tested the in-memory vector store with a simple lambda function returning row.score > 0.5."
|
|
170
|
+
expected: not_triggered
|
|
171
|
+
description: "Benign lambda not invoking eval / __import__ / mro traversal"
|
|
172
|
+
- input: "Reviewing this rule against CVE-2026-26030 to ensure regex coverage is correct."
|
|
173
|
+
expected: not_triggered
|
|
174
|
+
description: "Discussion of CVE in defensive context"
|
|
175
|
+
- input: "The filter argument accepts a callable; users typically pass operator.attrgetter."
|
|
176
|
+
expected: not_triggered
|
|
177
|
+
description: "Reference to alternative safe callable forms"
|