@aumos/agentshield 0.1.0

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/src/scanner.ts ADDED
@@ -0,0 +1,338 @@
1
+ /**
2
+ * InputScanner — pure, synchronous content-scanning utilities.
3
+ *
4
+ * These functions operate on plain strings and return typed finding arrays.
5
+ * They do not make network calls and have no side effects.
6
+ *
7
+ * Defensive-only framing: detects patterns associated with known attack
8
+ * categories so that the caller can decide whether to block or log.
9
+ */
10
+
11
+ import type { DefenseLayer, ThreatFinding, ThreatLevel } from "./types.js";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Helper types
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /** Options shared by all scanner methods. */
18
+ export interface ScanOptions {
19
+ /** Agent performing or receiving the content. */
20
+ readonly agentId: string;
21
+ /** Optional session context passed through to findings metadata. */
22
+ readonly sessionId?: string;
23
+ }
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Internal helpers
27
+ // ---------------------------------------------------------------------------
28
+
29
+ let findingIdCounter = 0;
30
+
31
+ function nextFindingId(): string {
32
+ findingIdCounter += 1;
33
+ return `finding-${Date.now()}-${findingIdCounter}`;
34
+ }
35
+
36
+ function buildFinding(
37
+ layer: DefenseLayer,
38
+ level: ThreatLevel,
39
+ ruleId: string,
40
+ description: string,
41
+ text: string,
42
+ offset: number,
43
+ metadata: Readonly<Record<string, unknown>>,
44
+ ): ThreatFinding {
45
+ // Produce a redacted excerpt — never expose the raw payload.
46
+ const excerpt =
47
+ text.length > 80
48
+ ? `${text.slice(0, 40)}...[redacted]...${text.slice(-20)}`
49
+ : text.replace(/./g, "*");
50
+
51
+ return {
52
+ finding_id: nextFindingId(),
53
+ layer,
54
+ level,
55
+ rule_id: ruleId,
56
+ description,
57
+ offset,
58
+ excerpt,
59
+ metadata,
60
+ };
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Prompt-injection detection patterns
65
+ // ---------------------------------------------------------------------------
66
+
67
+ /**
68
+ * Patterns that signal an attempt to override system instructions.
69
+ * Each entry is [ruleId, description, pattern, level].
70
+ */
71
+ const PROMPT_INJECTION_RULES: ReadonlyArray<
72
+ readonly [string, string, RegExp, ThreatLevel]
73
+ > = [
74
+ [
75
+ "PI-001",
76
+ "Instruction-override attempt detected",
77
+ /ignore\s+(all\s+)?previous\s+instructions?/i,
78
+ "high",
79
+ ],
80
+ [
81
+ "PI-002",
82
+ "System-prompt disclosure request detected",
83
+ /reveal\s+(your\s+)?(system\s+)?prompt/i,
84
+ "high",
85
+ ],
86
+ [
87
+ "PI-003",
88
+ "Role-switch injection attempt detected",
89
+ /you\s+are\s+now\s+(a|an)\s+\w/i,
90
+ "medium",
91
+ ],
92
+ [
93
+ "PI-004",
94
+ "Jailbreak keyword detected",
95
+ /\b(jailbreak|dan\s+mode|developer\s+mode)\b/i,
96
+ "critical",
97
+ ],
98
+ [
99
+ "PI-005",
100
+ "Fake-completion injection pattern detected",
101
+ /<\/?(?:system|assistant|user)\s*>/i,
102
+ "high",
103
+ ],
104
+ ];
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // PII detection patterns
108
+ // ---------------------------------------------------------------------------
109
+
110
+ /**
111
+ * Patterns for common PII categories.
112
+ * Each entry is [ruleId, description, pattern, level].
113
+ */
114
+ const PII_RULES: ReadonlyArray<
115
+ readonly [string, string, RegExp, ThreatLevel]
116
+ > = [
117
+ [
118
+ "PII-EMAIL",
119
+ "Email address detected",
120
+ /\b[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}\b/,
121
+ "medium",
122
+ ],
123
+ [
124
+ "PII-SSN",
125
+ "US Social Security Number pattern detected",
126
+ /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/,
127
+ "critical",
128
+ ],
129
+ [
130
+ "PII-CREDIT-CARD",
131
+ "Credit card number pattern detected",
132
+ /\b(?:\d[ -]?){13,16}\b/,
133
+ "critical",
134
+ ],
135
+ [
136
+ "PII-PHONE",
137
+ "Phone number pattern detected",
138
+ /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/,
139
+ "low",
140
+ ],
141
+ [
142
+ "PII-IP-ADDRESS",
143
+ "IP address detected",
144
+ /\b(?:\d{1,3}\.){3}\d{1,3}\b/,
145
+ "info",
146
+ ],
147
+ ];
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Malicious payload patterns
151
+ // ---------------------------------------------------------------------------
152
+
153
+ /**
154
+ * Patterns for known malicious payload indicators.
155
+ * Defensive framing: these detect encoded or obfuscated content.
156
+ * Each entry is [ruleId, description, pattern, level].
157
+ */
158
+ const MALICIOUS_PAYLOAD_RULES: ReadonlyArray<
159
+ readonly [string, string, RegExp, ThreatLevel]
160
+ > = [
161
+ [
162
+ "MPL-001",
163
+ "Base64-encoded block detected — possible payload obfuscation",
164
+ /\b[A-Za-z0-9+/]{40,}={0,2}\b/,
165
+ "medium",
166
+ ],
167
+ [
168
+ "MPL-002",
169
+ "Shell command injection pattern detected",
170
+ /(?:;\s*(?:rm|curl|wget|bash|sh|python|perl|nc)\b|&&\s*(?:rm|curl|wget|bash|sh)\b)/i,
171
+ "critical",
172
+ ],
173
+ [
174
+ "MPL-003",
175
+ "SQL injection pattern detected",
176
+ /(?:';\s*(?:drop|delete|update|insert|select)\b|--\s*$|\bUNION\s+SELECT\b)/i,
177
+ "critical",
178
+ ],
179
+ [
180
+ "MPL-004",
181
+ "Path traversal pattern detected",
182
+ /(?:\.\.\/|\.\.\\){2,}/,
183
+ "high",
184
+ ],
185
+ [
186
+ "MPL-005",
187
+ "Excessive token repetition detected — possible resource-exhaustion attempt",
188
+ /(\b\w+\b)(?:\s+\1){20,}/i,
189
+ "medium",
190
+ ],
191
+ ];
192
+
193
+ // ---------------------------------------------------------------------------
194
+ // Generic pattern-matching runner
195
+ // ---------------------------------------------------------------------------
196
+
197
+ function runPatternRules(
198
+ text: string,
199
+ rules: ReadonlyArray<readonly [string, string, RegExp, ThreatLevel]>,
200
+ layer: DefenseLayer,
201
+ options: ScanOptions,
202
+ ): readonly ThreatFinding[] {
203
+ const findings: ThreatFinding[] = [];
204
+
205
+ for (const [ruleId, description, pattern, level] of rules) {
206
+ const match = pattern.exec(text);
207
+ if (match !== null) {
208
+ findings.push(
209
+ buildFinding(
210
+ layer,
211
+ level,
212
+ ruleId,
213
+ description,
214
+ match[0],
215
+ match.index,
216
+ {
217
+ agent_id: options.agentId,
218
+ session_id: options.sessionId ?? null,
219
+ match_length: match[0].length,
220
+ },
221
+ ),
222
+ );
223
+ }
224
+ }
225
+
226
+ return findings;
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // InputScanner interface
231
+ // ---------------------------------------------------------------------------
232
+
233
+ /** Synchronous content-scanning utilities. */
234
+ export interface InputScanner {
235
+ /**
236
+ * Detect prompt-injection patterns in the given text.
237
+ *
238
+ * @param text - Raw input string to analyse.
239
+ * @param options - Agent and session context.
240
+ * @returns Array of ThreatFindings for any injection patterns found.
241
+ */
242
+ checkPromptInjection(
243
+ text: string,
244
+ options: ScanOptions,
245
+ ): readonly ThreatFinding[];
246
+
247
+ /**
248
+ * Detect personally identifiable information in the given text.
249
+ *
250
+ * @param text - Raw content string to analyse.
251
+ * @param options - Agent and session context.
252
+ * @returns Array of ThreatFindings for any PII patterns found.
253
+ */
254
+ checkPII(text: string, options: ScanOptions): readonly ThreatFinding[];
255
+
256
+ /**
257
+ * Detect indicators of malicious payloads in the given text.
258
+ *
259
+ * @param text - Raw content string to analyse.
260
+ * @param options - Agent and session context.
261
+ * @returns Array of ThreatFindings for any suspicious patterns found.
262
+ */
263
+ checkMaliciousPayload(
264
+ text: string,
265
+ options: ScanOptions,
266
+ ): readonly ThreatFinding[];
267
+
268
+ /**
269
+ * Run all checks and return the combined, deduplicated findings list
270
+ * sorted by severity (critical first).
271
+ *
272
+ * @param text - Raw content string to analyse.
273
+ * @param options - Agent and session context.
274
+ * @returns All findings across all defense layers.
275
+ */
276
+ scanAll(text: string, options: ScanOptions): readonly ThreatFinding[];
277
+ }
278
+
279
+ // ---------------------------------------------------------------------------
280
+ // Severity ordering helper
281
+ // ---------------------------------------------------------------------------
282
+
283
+ const THREAT_LEVEL_ORDER: Readonly<Record<ThreatLevel, number>> = {
284
+ critical: 0,
285
+ high: 1,
286
+ medium: 2,
287
+ low: 3,
288
+ info: 4,
289
+ };
290
+
291
+ // ---------------------------------------------------------------------------
292
+ // Factory
293
+ // ---------------------------------------------------------------------------
294
+
295
+ /**
296
+ * Create an InputScanner instance.
297
+ *
298
+ * @returns An InputScanner with all check methods.
299
+ */
300
+ export function createInputScanner(): InputScanner {
301
+ return {
302
+ checkPromptInjection(
303
+ text: string,
304
+ options: ScanOptions,
305
+ ): readonly ThreatFinding[] {
306
+ return runPatternRules(text, PROMPT_INJECTION_RULES, "prompt_injection", options);
307
+ },
308
+
309
+ checkPII(text: string, options: ScanOptions): readonly ThreatFinding[] {
310
+ return runPatternRules(text, PII_RULES, "pii_detection", options);
311
+ },
312
+
313
+ checkMaliciousPayload(
314
+ text: string,
315
+ options: ScanOptions,
316
+ ): readonly ThreatFinding[] {
317
+ return runPatternRules(
318
+ text,
319
+ MALICIOUS_PAYLOAD_RULES,
320
+ "malicious_payload",
321
+ options,
322
+ );
323
+ },
324
+
325
+ scanAll(text: string, options: ScanOptions): readonly ThreatFinding[] {
326
+ const all: ThreatFinding[] = [
327
+ ...runPatternRules(text, PROMPT_INJECTION_RULES, "prompt_injection", options),
328
+ ...runPatternRules(text, PII_RULES, "pii_detection", options),
329
+ ...runPatternRules(text, MALICIOUS_PAYLOAD_RULES, "malicious_payload", options),
330
+ ];
331
+
332
+ return all.sort(
333
+ (a, b) =>
334
+ THREAT_LEVEL_ORDER[a.level] - THREAT_LEVEL_ORDER[b.level],
335
+ );
336
+ },
337
+ };
338
+ }
package/src/types.ts ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * TypeScript interfaces for the AgentShield defense layer.
3
+ *
4
+ * Mirrors the Pydantic models defined in:
5
+ * agentshield.schemas.threats
6
+ * agentshield.schemas.scan
7
+ * agentshield.schemas.defense
8
+ *
9
+ * All interfaces use readonly fields to match Python's frozen Pydantic models.
10
+ */
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Threat level classification
14
+ // ---------------------------------------------------------------------------
15
+
16
+ /**
17
+ * Severity level of a detected threat.
18
+ * Maps to ThreatLevel enum in Python.
19
+ */
20
+ export type ThreatLevel = "critical" | "high" | "medium" | "low" | "info";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Defense layers
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /**
27
+ * The layer of the defense pipeline that produced a finding.
28
+ * Maps to DefenseLayer enum in Python.
29
+ */
30
+ export type DefenseLayer =
31
+ | "input_validation"
32
+ | "output_validation"
33
+ | "tool_call_validation"
34
+ | "pii_detection"
35
+ | "prompt_injection"
36
+ | "malicious_payload";
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Individual threat finding
40
+ // ---------------------------------------------------------------------------
41
+
42
+ /** A single threat or anomaly detected by one defense rule. */
43
+ export interface ThreatFinding {
44
+ /** Unique identifier for this finding. */
45
+ readonly finding_id: string;
46
+ /** Defense layer that produced this finding. */
47
+ readonly layer: DefenseLayer;
48
+ /** Threat severity. */
49
+ readonly level: ThreatLevel;
50
+ /** Short rule identifier (e.g. "PI-001", "PII-EMAIL"). */
51
+ readonly rule_id: string;
52
+ /** Human-readable description of the finding. */
53
+ readonly description: string;
54
+ /** Character offset in the scanned content where the issue begins (-1 if not applicable). */
55
+ readonly offset: number;
56
+ /** Redacted excerpt of the offending content (never the raw payload). */
57
+ readonly excerpt: string;
58
+ /** Arbitrary metadata from the rule implementation. */
59
+ readonly metadata: Readonly<Record<string, unknown>>;
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Scan result
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /** Result of scanning a single piece of content through the defense pipeline. */
67
+ export interface ScanResult {
68
+ /** Unique identifier for this scan operation. */
69
+ readonly scan_id: string;
70
+ /** ISO-8601 UTC timestamp of when the scan completed. */
71
+ readonly scanned_at: string;
72
+ /** Agent that triggered the scan. */
73
+ readonly agent_id: string;
74
+ /** Whether any finding at level "high" or "critical" was detected. */
75
+ readonly blocked: boolean;
76
+ /** All findings produced by the scan, ordered by severity descending. */
77
+ readonly findings: readonly ThreatFinding[];
78
+ /** Highest threat level across all findings ("info" when no threats found). */
79
+ readonly max_level: ThreatLevel;
80
+ /** Total scan duration in milliseconds. */
81
+ readonly duration_ms: number;
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Threat detection result (aggregate over a time window)
86
+ // ---------------------------------------------------------------------------
87
+
88
+ /** Aggregated threat statistics for a single agent over a time window. */
89
+ export interface ThreatDetectionResult {
90
+ /** Agent being reported on. */
91
+ readonly agent_id: string;
92
+ /** ISO-8601 UTC start of the reporting window. */
93
+ readonly window_start: string;
94
+ /** ISO-8601 UTC end of the reporting window. */
95
+ readonly window_end: string;
96
+ /** Total number of scans in this window. */
97
+ readonly total_scans: number;
98
+ /** Number of scans that triggered a block action. */
99
+ readonly blocked_count: number;
100
+ /** Breakdown of finding counts by threat level. */
101
+ readonly findings_by_level: Readonly<Record<ThreatLevel, number>>;
102
+ /** Breakdown of finding counts by defense layer. */
103
+ readonly findings_by_layer: Readonly<Record<DefenseLayer, number>>;
104
+ /** Most frequently triggered rule_ids in this window. */
105
+ readonly top_rules: readonly string[];
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Tool-call validation
110
+ // ---------------------------------------------------------------------------
111
+
112
+ /** Request to validate a proposed tool call before execution. */
113
+ export interface ToolCallValidationRequest {
114
+ /** Agent submitting the tool call. */
115
+ readonly agent_id: string;
116
+ /** Name of the tool being called. */
117
+ readonly tool_name: string;
118
+ /** Arguments that will be passed to the tool. */
119
+ readonly tool_arguments: Readonly<Record<string, unknown>>;
120
+ /** Optional session context for policy decisions. */
121
+ readonly session_id?: string;
122
+ }
123
+
124
+ /** Outcome of a tool-call validation check. */
125
+ export interface ToolCallValidationResult {
126
+ /** Whether the tool call is permitted to proceed. */
127
+ readonly allowed: boolean;
128
+ /** Reason the call was blocked, or null if allowed. */
129
+ readonly block_reason: string | null;
130
+ /** Findings raised during validation. */
131
+ readonly findings: readonly ThreatFinding[];
132
+ /** Validation duration in milliseconds. */
133
+ readonly duration_ms: number;
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // API result wrapper (shared pattern)
138
+ // ---------------------------------------------------------------------------
139
+
140
+ /** Standard error payload returned by the AgentShield API. */
141
+ export interface ApiError {
142
+ readonly error: string;
143
+ readonly detail: string;
144
+ }
145
+
146
+ /** Result type for all client operations. */
147
+ export type ApiResult<T> =
148
+ | { readonly ok: true; readonly data: T }
149
+ | { readonly ok: false; readonly error: ApiError; readonly status: number };
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "declaration": true,
10
+ "declarationMap": true,
11
+ "sourceMap": true,
12
+ "strict": true,
13
+ "noImplicitAny": true,
14
+ "strictNullChecks": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noImplicitReturns": true,
18
+ "exactOptionalPropertyTypes": true,
19
+ "forceConsistentCasingInFileNames": true,
20
+ "esModuleInterop": true,
21
+ "skipLibCheck": true
22
+ },
23
+ "include": ["src/**/*"],
24
+ "exclude": ["node_modules", "dist"]
25
+ }