Skip to content

Commit 0b41910

Browse files
committed
sync: update from internal repo (2026-02-15 20:31)
1 parent c08aaaa commit 0b41910

4 files changed

Lines changed: 263 additions & 14 deletions

File tree

README.md

Lines changed: 139 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,49 @@
44
[![npm version](https://img.shields.io/npm/v/@empowered-humanity/agent-security)](https://www.npmjs.com/package/@empowered-humanity/agent-security)
55
[![License: MIT](https://img.shields.io/badge/License-MIT-gold.svg)](https://opensource.org/licenses/MIT)
66
[![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue.svg)](https://www.typescriptlang.org/)
7-
[![Tests](https://img.shields.io/badge/Tests-123%20passing-brightgreen.svg)]()
7+
[![Tests](https://img.shields.io/badge/Tests-126%20passing-brightgreen.svg)]()
88
[![Patterns](https://img.shields.io/badge/Patterns-190-navy.svg)]()
99

10-
Security scanner for AI agent architectures. Detects prompt injection, credential exposure, code injection, and agent-specific attack patterns.
10+
Static analysis security scanner purpose-built for AI agent architectures. Detects prompt injection, credential exposure, MCP server misconfigurations, code injection, and agent-specific attack patterns across your codebase -- before they reach production.
11+
12+
![CLI Demo](docs/demo.gif)
13+
14+
## Quick Start
15+
16+
```bash
17+
# 1. Install
18+
npm install @empowered-humanity/agent-security
19+
20+
# 2. Scan
21+
npx @empowered-humanity/agent-security scan ./my-agent
22+
23+
# 3. Review findings in your terminal, or export SARIF for GitHub Code Scanning
24+
npx @empowered-humanity/agent-security scan ./my-agent --format sarif --output results.sarif
25+
```
26+
27+
## How It Compares
28+
29+
| Capability | **agent-security** | Semgrep (LLM rules) | Garak (NVIDIA) | LLM Guard (Protect AI) |
30+
|---|---|---|---|---|
31+
| **Focus** | Static analysis of AI agent code & prompts | General-purpose SAST with some AI/LLM rules | Runtime red-teaming of live LLM endpoints | Runtime input/output guardrails for LLM apps |
32+
| **AI agent-specific patterns** | 190 | Limited (general injection rules; no agent-specific categories) | N/A (probes live models, not source code) | N/A (runtime scanner, not static analysis) |
33+
| **OWASP Agentic Top 10 (ASI01-ASI10)** | All 10 categories, 65 patterns | Not covered | Not covered (maps to OWASP LLM Top 10, not Agentic) | Not covered |
34+
| **MCP security patterns** | 44 patterns (SlowMist checklist) | N/A | N/A | N/A |
35+
| **SARIF output** | Yes (v2.1.0, GitHub Code Scanning) | Yes | No (JSON/HTML reports) | No |
36+
| **GitHub Action** | Yes (built-in `action.yml`) | Yes (`semgrep/semgrep-action`) | No | No |
37+
| **pre-commit hook** | Yes (built-in `.pre-commit-hooks.yaml`) | Yes | No | No |
38+
| **CWE mappings** | Yes (30+ categories mapped) | Yes | Limited (references CWE-1426 for prompt injection) | No |
39+
| **Taint analysis** | Yes (proximity-based) | Yes (cross-file dataflow in Pro) | No | No |
40+
| **Free / open-source** | Yes (MIT) | Community edition free; Pro is paid | Yes (Apache 2.0) | Yes (MIT) |
41+
42+
**When to use each tool:**
43+
44+
- **agent-security** -- You are building an AI agent (MCP servers, multi-agent systems, RAG pipelines, LLM-powered tools) and need to catch vulnerabilities in your code, configs, and prompts before deployment.
45+
- **Semgrep** -- You need general-purpose SAST across your full application stack (not agent-specific).
46+
- **Garak** -- You want to red-team a live LLM endpoint by sending adversarial probes and measuring model responses.
47+
- **LLM Guard** -- You need runtime input/output filtering to sanitize prompts and responses in production.
48+
49+
These tools are complementary. Use agent-security in CI to catch static vulnerabilities, Garak to probe your deployed model, and LLM Guard as a runtime guardrail.
1150

1251
## What It Detects
1352

@@ -78,14 +117,42 @@ The scanner implements detection for all 10 OWASP Agentic Security Issues:
78117
npm install @empowered-humanity/agent-security
79118
```
80119

81-
## Quick Start
120+
## CLI Usage
82121

83122
### Scan a Codebase
84123

85124
```bash
86125
npx @empowered-humanity/agent-security scan ./my-agent
87126
```
88127

128+
### Common Options
129+
130+
```bash
131+
# Set minimum severity threshold
132+
npx @empowered-humanity/agent-security scan . --severity high
133+
134+
# Export as SARIF for GitHub Code Scanning
135+
npx @empowered-humanity/agent-security scan . --format sarif --output results.sarif
136+
137+
# Export as JSON
138+
npx @empowered-humanity/agent-security scan . --format json --output results.json
139+
140+
# Fail CI if critical findings exist
141+
npx @empowered-humanity/agent-security scan . --fail-on critical
142+
143+
# Filter by OWASP ASI category
144+
npx @empowered-humanity/agent-security scan . --asi ASI06
145+
146+
# Group findings by classification
147+
npx @empowered-humanity/agent-security scan . --group classification
148+
149+
# List all patterns
150+
npx @empowered-humanity/agent-security patterns
151+
152+
# Show statistics
153+
npx @empowered-humanity/agent-security stats
154+
```
155+
89156
### Scan from Node.js
90157

91158
```javascript
@@ -124,26 +191,69 @@ te-agent-security scan ./my-agent --group classification
124191
```
125192

126193
### Test File Severity Downgrade
127-
Findings in test/fixture/example/payload directories are automatically severity-downgraded (criticalhigh, highmedium) since they represent lower risk.
194+
Findings in test/fixture/example/payload directories are automatically severity-downgraded (critical->high, high->medium) since they represent lower risk.
128195

129196
### Taint Proximity Analysis
130197
For dangerous sinks (eval, exec, pickle), the scanner checks whether user input sources (input(), request, argv, LLM .invoke()) are within 10 lines. Direct taint escalates severity to critical.
131198

132199
### Context Flow Tracing
133-
Detects when serialized conversation context (JSON.stringify of messages/history) flows to external API calls a novel agent-specific attack surface.
200+
Detects when serialized conversation context (JSON.stringify of messages/history) flows to external API calls -- a novel agent-specific attack surface.
134201

135202
```javascript
136203
// Each finding includes intelligence data:
137204
finding.classification // 'live_vulnerability' | 'test_payload' | ...
138205
finding.isTestFile // true if in test/fixture/example directory
139206
finding.taintProximity // 'direct' | 'nearby' | 'distant'
140-
finding.contextFlowChain // serialization external call chain
207+
finding.contextFlowChain // serialization -> external call chain
141208
finding.severityDowngraded // true if test file downgrade applied
142209
```
143210

211+
## GitHub Action
212+
213+
Use the built-in `action.yml` to add agent security scanning to any GitHub repository:
214+
215+
```yaml
216+
name: Agent Security Scan
217+
218+
on: [pull_request]
219+
220+
jobs:
221+
agent-security:
222+
runs-on: ubuntu-latest
223+
steps:
224+
- uses: actions/checkout@v4
225+
226+
- uses: empowered-humanity/agent-security@v1
227+
with:
228+
path: '.'
229+
severity: 'medium'
230+
fail-on-findings: 'high'
231+
upload-sarif: 'true'
232+
```
233+
234+
### Action Inputs
235+
236+
| Input | Default | Description |
237+
|-------|---------|-------------|
238+
| `path` | `.` | Path to scan |
239+
| `severity` | `medium` | Minimum severity to report (`critical`, `high`, `medium`, `low`) |
240+
| `format` | `sarif` | Output format (`console`, `json`, `sarif`) |
241+
| `fail-on-findings` | `high` | Fail if findings at or above this severity |
242+
| `upload-sarif` | `true` | Upload SARIF results to GitHub Code Scanning |
243+
244+
### Action Outputs
245+
246+
| Output | Description |
247+
|--------|-------------|
248+
| `findings-count` | Total number of findings |
249+
| `risk-level` | Overall risk level |
250+
| `sarif-file` | Path to SARIF output file |
251+
252+
When `upload-sarif` is enabled, findings appear directly in the GitHub Security tab under Code Scanning alerts.
253+
144254
## CI/CD Integration
145255

146-
### GitHub Actions
256+
### GitHub Actions (inline)
147257

148258
```yaml
149259
name: Agent Security Scan
@@ -163,7 +273,17 @@ jobs:
163273

164274
### Pre-commit Hook
165275

166-
Add to `.git/hooks/pre-commit`:
276+
Add to `.pre-commit-config.yaml`:
277+
278+
```yaml
279+
repos:
280+
- repo: https://github.com/empowered-humanity/agent-security
281+
rev: v1.2.0
282+
hooks:
283+
- id: agent-security-scan
284+
```
285+
286+
Or add directly to `.git/hooks/pre-commit`:
167287

168288
```bash
169289
#!/bin/bash
@@ -297,6 +417,17 @@ const jsonReporter = new JsonReporter();
297417
const json = jsonReporter.report(result);
298418
```
299419

420+
### SARIF Reporter
421+
422+
```typescript
423+
import { formatAsSarif } from '@empowered-humanity/agent-security/reporters';
424+
425+
// Generate SARIF 2.1.0 output with CWE mappings
426+
const sarifJson = formatAsSarif(result, process.cwd());
427+
428+
// Upload to GitHub Code Scanning, or integrate with any SARIF-compatible tool
429+
```
430+
300431
## Examples
301432

302433
See the [`examples/`](./examples) directory for complete usage examples:

action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: 'Agent Security Scan'
2-
description: 'Scan for AI agent security vulnerabilities with 176+ detection patterns'
2+
description: 'Scan for AI agent security vulnerabilities with 190+ detection patterns covering OWASP ASI Top 10, MCP security, and credential exposure'
33
author: 'Empowered Humanity'
44

55
inputs:

src/reporters/sarif.ts

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,66 @@
33
*
44
* Outputs scan results in SARIF 2.1.0 format for GitHub Code Scanning
55
* and other SARIF-compatible tools.
6+
*
7+
* Features:
8+
* - CWE ID mappings for all attack categories
9+
* - OWASP ASI tags on rules
10+
* - GitHub Security tab integration
611
*/
712

8-
import type { Finding, ScanResult, Severity } from '../patterns/types.js';
13+
import type { AttackCategory, Finding, ScanResult, Severity } from '../patterns/types.js';
914

10-
const VERSION = '1.1.0';
15+
const VERSION = '1.2.0';
1116
const SCHEMA_URI = 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json';
1217
const INFORMATION_URI = 'https://github.com/empowered-humanity/agent-security';
1318

19+
/**
20+
* Map attack categories to CWE IDs.
21+
* Uses the most specific applicable CWE for each category.
22+
*/
23+
const CATEGORY_CWE_MAP: Partial<Record<AttackCategory, string>> = {
24+
instruction_override: 'CWE-74', // Injection
25+
role_manipulation: 'CWE-284', // Improper Access Control
26+
boundary_escape: 'CWE-116', // Improper Encoding or Escaping
27+
data_exfiltration: 'CWE-200', // Information Exposure
28+
hidden_injection: 'CWE-94', // Code Injection
29+
stealth_instruction: 'CWE-94',
30+
url_reconstruction: 'CWE-601', // Open Redirect
31+
credential_theft: 'CWE-522', // Insufficiently Protected Credentials
32+
credential_exposure: 'CWE-798', // Hardcoded Credentials
33+
cross_agent_escalation: 'CWE-269', // Improper Privilege Management
34+
mcp_attack: 'CWE-346', // Origin Validation Error
35+
rag_poisoning: 'CWE-94',
36+
persistence: 'CWE-506', // Embedded Malicious Code
37+
goal_hijacking: 'CWE-74',
38+
session_smuggling: 'CWE-384', // Session Fixation
39+
argument_injection: 'CWE-88', // Argument Injection
40+
code_injection: 'CWE-94',
41+
ssrf: 'CWE-918', // SSRF
42+
reconnaissance: 'CWE-200',
43+
prompt_extraction: 'CWE-200',
44+
defense_evasion: 'CWE-693', // Protection Mechanism Failure
45+
hierarchy_violation: 'CWE-269',
46+
adversarial_suffix: 'CWE-74',
47+
ASI01_goal_hijack: 'CWE-74',
48+
ASI02_tool_misuse: 'CWE-269',
49+
ASI03_privilege_abuse: 'CWE-269',
50+
ASI04_supply_chain: 'CWE-494', // Download Without Integrity Check
51+
ASI05_rce: 'CWE-94',
52+
ASI06_memory_poisoning: 'CWE-471', // Modification of Assumed-Immutable Data
53+
ASI07_insecure_comms: 'CWE-319', // Cleartext Transmission
54+
ASI08_cascading_failures: 'CWE-400', // Uncontrolled Resource Consumption
55+
ASI09_trust_exploitation: 'CWE-290', // Auth Bypass by Spoofing
56+
ASI10_rogue_agents: 'CWE-506',
57+
config_vulnerability: 'CWE-16', // Configuration
58+
permission_escalation: 'CWE-269',
59+
behavior_manipulation: 'CWE-74',
60+
platform_specific: 'CWE-74',
61+
rendering_exfil: 'CWE-200',
62+
path_traversal: 'CWE-22',
63+
dangerous_commands: 'CWE-78', // OS Command Injection
64+
};
65+
1466
interface SarifMessage {
1567
text: string;
1668
}
@@ -46,6 +98,7 @@ interface SarifRule {
4698
id: string;
4799
shortDescription: SarifMessage;
48100
fullDescription?: SarifMessage;
101+
helpUri?: string;
49102
defaultConfiguration?: {
50103
level: 'error' | 'warning' | 'note' | 'none';
51104
};
@@ -105,6 +158,14 @@ function normalizeUri(filePath: string, baseDir?: string): string {
105158
return uri.replace(/\\/g, '/');
106159
}
107160

161+
/**
162+
* Get the CWE ID for a finding, checking pattern.cwe first then category mapping
163+
*/
164+
function getCweId(finding: Finding): string | undefined {
165+
if (finding.pattern.cve) return undefined; // cve is separate
166+
return CATEGORY_CWE_MAP[finding.pattern.category];
167+
}
168+
108169
/**
109170
* Build deduplicated SARIF rules from findings
110171
*/
@@ -115,16 +176,32 @@ function buildRules(findings: Finding[]): SarifRule[] {
115176
const id = finding.pattern.name;
116177
if (seen.has(id)) continue;
117178

179+
const cweId = getCweId(finding);
180+
const tags: string[] = [];
181+
if (cweId) tags.push(cweId);
182+
if (finding.pattern.owaspAsi) tags.push(`OWASP-${finding.pattern.owaspAsi}`);
183+
tags.push(`security`);
184+
118185
const rule: SarifRule = {
119186
id,
120187
shortDescription: { text: finding.pattern.description },
121188
defaultConfiguration: {
122189
level: severityToLevel(finding.pattern.severity),
123190
},
191+
properties: {
192+
tags,
193+
...(finding.pattern.owaspAsi && { owaspAsi: finding.pattern.owaspAsi }),
194+
...(cweId && { cweId }),
195+
},
124196
};
125197

126-
if (finding.pattern.owaspAsi) {
127-
rule.properties = { owaspAsi: finding.pattern.owaspAsi };
198+
if (cweId) {
199+
const cweNum = cweId.replace('CWE-', '');
200+
rule.helpUri = `https://cwe.mitre.org/data/definitions/${cweNum}.html`;
201+
}
202+
203+
if (finding.pattern.remediation) {
204+
rule.fullDescription = { text: finding.pattern.remediation };
128205
}
129206

130207
seen.set(id, rule);

tests/sarif-reporter.test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('SARIF Reporter', () => {
6161
expect(sarif.version).toBe('2.1.0');
6262
expect(sarif.runs).toHaveLength(1);
6363
expect(sarif.runs[0].tool.driver.name).toBe('agent-security');
64-
expect(sarif.runs[0].tool.driver.semanticVersion).toBe('1.1.0');
64+
expect(sarif.runs[0].tool.driver.semanticVersion).toBe('1.2.0');
6565
});
6666

6767
it('maps critical/high to error, medium to warning, low to note', () => {
@@ -111,6 +111,47 @@ describe('SARIF Reporter', () => {
111111
expect(props.contextFlowChain).toEqual(['source', 'sink']);
112112
});
113113

114+
it('maps attack categories to CWE IDs in rule properties', () => {
115+
const finding = makeFinding({
116+
pattern: makePattern({ category: 'credential_exposure', name: 'cred-test' }),
117+
});
118+
const result = makeScanResult([finding]);
119+
const sarif = JSON.parse(formatAsSarif(result));
120+
const rule = sarif.runs[0].tool.driver.rules[0];
121+
122+
expect(rule.properties.cweId).toBe('CWE-798');
123+
expect(rule.properties.tags).toContain('CWE-798');
124+
expect(rule.properties.tags).toContain('security');
125+
expect(rule.helpUri).toBe('https://cwe.mitre.org/data/definitions/798.html');
126+
});
127+
128+
it('includes OWASP ASI tag in rule tags', () => {
129+
const finding = makeFinding({
130+
pattern: makePattern({ category: 'ASI01_goal_hijack', owaspAsi: 'ASI01', name: 'asi-test' }),
131+
});
132+
const result = makeScanResult([finding]);
133+
const sarif = JSON.parse(formatAsSarif(result));
134+
const rule = sarif.runs[0].tool.driver.rules[0];
135+
136+
expect(rule.properties.tags).toContain('CWE-74');
137+
expect(rule.properties.tags).toContain('OWASP-ASI01');
138+
expect(rule.properties.owaspAsi).toBe('ASI01');
139+
});
140+
141+
it('includes remediation as fullDescription when available', () => {
142+
const finding = makeFinding({
143+
pattern: makePattern({
144+
name: 'remed-test',
145+
remediation: 'Use environment variables for secrets',
146+
}),
147+
});
148+
const result = makeScanResult([finding]);
149+
const sarif = JSON.parse(formatAsSarif(result));
150+
const rule = sarif.runs[0].tool.driver.rules[0];
151+
152+
expect(rule.fullDescription.text).toBe('Use environment variables for secrets');
153+
});
154+
114155
it('produces zero results for empty findings', () => {
115156
const result = makeScanResult([]);
116157
const sarif = JSON.parse(formatAsSarif(result));

0 commit comments

Comments
 (0)