ai-shield-core 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.
Files changed (78) hide show
  1. package/dist/audit/logger.d.ts +40 -0
  2. package/dist/audit/logger.d.ts.map +1 -0
  3. package/dist/audit/logger.js +100 -0
  4. package/dist/audit/logger.js.map +1 -0
  5. package/dist/audit/types.d.ts +12 -0
  6. package/dist/audit/types.d.ts.map +1 -0
  7. package/dist/audit/types.js +3 -0
  8. package/dist/audit/types.js.map +1 -0
  9. package/dist/cache/lru.d.ts +27 -0
  10. package/dist/cache/lru.d.ts.map +1 -0
  11. package/dist/cache/lru.js +74 -0
  12. package/dist/cache/lru.js.map +1 -0
  13. package/dist/cost/anomaly.d.ts +10 -0
  14. package/dist/cost/anomaly.d.ts.map +1 -0
  15. package/dist/cost/anomaly.js +42 -0
  16. package/dist/cost/anomaly.js.map +1 -0
  17. package/dist/cost/pricing.d.ts +7 -0
  18. package/dist/cost/pricing.d.ts.map +1 -0
  19. package/dist/cost/pricing.js +51 -0
  20. package/dist/cost/pricing.js.map +1 -0
  21. package/dist/cost/tracker.d.ts +24 -0
  22. package/dist/cost/tracker.d.ts.map +1 -0
  23. package/dist/cost/tracker.js +136 -0
  24. package/dist/cost/tracker.js.map +1 -0
  25. package/dist/index.d.ts +18 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +59 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/policy/engine.d.ts +36 -0
  30. package/dist/policy/engine.d.ts.map +1 -0
  31. package/dist/policy/engine.js +127 -0
  32. package/dist/policy/engine.js.map +1 -0
  33. package/dist/policy/tools.d.ts +25 -0
  34. package/dist/policy/tools.d.ts.map +1 -0
  35. package/dist/policy/tools.js +158 -0
  36. package/dist/policy/tools.js.map +1 -0
  37. package/dist/scanner/canary.d.ts +9 -0
  38. package/dist/scanner/canary.d.ts.map +1 -0
  39. package/dist/scanner/canary.js +19 -0
  40. package/dist/scanner/canary.js.map +1 -0
  41. package/dist/scanner/chain.d.ts +17 -0
  42. package/dist/scanner/chain.d.ts.map +1 -0
  43. package/dist/scanner/chain.js +69 -0
  44. package/dist/scanner/chain.js.map +1 -0
  45. package/dist/scanner/heuristic.d.ts +28 -0
  46. package/dist/scanner/heuristic.d.ts.map +1 -0
  47. package/dist/scanner/heuristic.js +375 -0
  48. package/dist/scanner/heuristic.js.map +1 -0
  49. package/dist/scanner/pii.d.ts +17 -0
  50. package/dist/scanner/pii.d.ts.map +1 -0
  51. package/dist/scanner/pii.js +255 -0
  52. package/dist/scanner/pii.js.map +1 -0
  53. package/dist/shield.d.ts +31 -0
  54. package/dist/shield.d.ts.map +1 -0
  55. package/dist/shield.js +184 -0
  56. package/dist/shield.js.map +1 -0
  57. package/dist/types.d.ts +182 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +6 -0
  60. package/dist/types.js.map +1 -0
  61. package/package.json +27 -0
  62. package/src/audit/logger.ts +135 -0
  63. package/src/audit/schema.sql +51 -0
  64. package/src/audit/types.ts +16 -0
  65. package/src/cache/lru.ts +93 -0
  66. package/src/cost/anomaly.ts +57 -0
  67. package/src/cost/pricing.ts +58 -0
  68. package/src/cost/tracker.ts +182 -0
  69. package/src/index.ts +91 -0
  70. package/src/policy/engine.ts +163 -0
  71. package/src/policy/tools.ts +189 -0
  72. package/src/scanner/canary.ts +30 -0
  73. package/src/scanner/chain.ts +88 -0
  74. package/src/scanner/heuristic.ts +427 -0
  75. package/src/scanner/pii.ts +313 -0
  76. package/src/shield.ts +228 -0
  77. package/src/types.ts +242 -0
  78. package/tsconfig.json +8 -0
@@ -0,0 +1,93 @@
1
+ // ============================================================
2
+ // LRU Cache — O(1) scan result caching with TTL
3
+ // Uses Map insertion-order for LRU eviction
4
+ // ============================================================
5
+
6
+ export interface LRUCacheConfig {
7
+ /** Maximum number of cached entries (default: 1000) */
8
+ maxSize?: number;
9
+ /** Time-to-live in milliseconds (default: 300_000 = 5 minutes) */
10
+ ttlMs?: number;
11
+ }
12
+
13
+ interface CacheEntry<V> {
14
+ value: V;
15
+ expiresAt: number;
16
+ }
17
+
18
+ export class ScanLRUCache<V = unknown> {
19
+ private cache = new Map<string, CacheEntry<V>>();
20
+ private readonly maxSize: number;
21
+ private readonly ttlMs: number;
22
+
23
+ constructor(config: LRUCacheConfig = {}) {
24
+ this.maxSize = config.maxSize ?? 1000;
25
+ this.ttlMs = config.ttlMs ?? 300_000;
26
+ }
27
+
28
+ /** Get a cached value. Returns undefined on miss or expiry. Promotes to MRU on hit. */
29
+ get(key: string): V | undefined {
30
+ const entry = this.cache.get(key);
31
+ if (!entry) return undefined;
32
+
33
+ if (Date.now() > entry.expiresAt) {
34
+ this.cache.delete(key);
35
+ return undefined;
36
+ }
37
+
38
+ // Promote to most-recently-used (Map maintains insertion order)
39
+ this.cache.delete(key);
40
+ this.cache.set(key, entry);
41
+ return entry.value;
42
+ }
43
+
44
+ /** Store a value. Evicts LRU entry if at capacity. */
45
+ set(key: string, value: V): void {
46
+ // Remove existing to update position
47
+ this.cache.delete(key);
48
+
49
+ // Evict oldest (first in Map) if at capacity
50
+ if (this.cache.size >= this.maxSize) {
51
+ const oldestKey = this.cache.keys().next().value as string;
52
+ this.cache.delete(oldestKey);
53
+ }
54
+
55
+ this.cache.set(key, {
56
+ value,
57
+ expiresAt: Date.now() + this.ttlMs,
58
+ });
59
+ }
60
+
61
+ /** Check if key exists and is not expired */
62
+ has(key: string): boolean {
63
+ return this.get(key) !== undefined;
64
+ }
65
+
66
+ /** Delete a specific key */
67
+ delete(key: string): boolean {
68
+ return this.cache.delete(key);
69
+ }
70
+
71
+ /** Clear all entries */
72
+ clear(): void {
73
+ this.cache.clear();
74
+ }
75
+
76
+ /** Current number of entries (may include expired) */
77
+ get size(): number {
78
+ return this.cache.size;
79
+ }
80
+
81
+ /** Remove all expired entries. Returns count of removed entries. */
82
+ prune(): number {
83
+ const now = Date.now();
84
+ let removed = 0;
85
+ for (const [key, entry] of this.cache) {
86
+ if (now > entry.expiresAt) {
87
+ this.cache.delete(key);
88
+ removed++;
89
+ }
90
+ }
91
+ return removed;
92
+ }
93
+ }
@@ -0,0 +1,57 @@
1
+ // ============================================================
2
+ // Cost Anomaly Detection — Z-Score based
3
+ // Flags unusual spending patterns
4
+ // ============================================================
5
+
6
+ export interface AnomalyResult {
7
+ isAnomaly: boolean;
8
+ zScore: number;
9
+ currentValue: number;
10
+ mean: number;
11
+ stdDev: number;
12
+ }
13
+
14
+ /** Detect anomalies using z-score (>2.5 standard deviations = anomaly) */
15
+ export function detectAnomaly(
16
+ currentValue: number,
17
+ historicalValues: number[],
18
+ threshold: number = 2.5,
19
+ ): AnomalyResult {
20
+ if (historicalValues.length < 3) {
21
+ // Not enough data to determine anomaly
22
+ return {
23
+ isAnomaly: false,
24
+ zScore: 0,
25
+ currentValue,
26
+ mean: currentValue,
27
+ stdDev: 0,
28
+ };
29
+ }
30
+
31
+ const mean =
32
+ historicalValues.reduce((a, b) => a + b, 0) / historicalValues.length;
33
+ const variance =
34
+ historicalValues.reduce((sum, val) => sum + (val - mean) ** 2, 0) /
35
+ historicalValues.length;
36
+ const stdDev = Math.sqrt(variance);
37
+
38
+ if (stdDev === 0) {
39
+ return {
40
+ isAnomaly: currentValue !== mean,
41
+ zScore: currentValue === mean ? 0 : Infinity,
42
+ currentValue,
43
+ mean,
44
+ stdDev: 0,
45
+ };
46
+ }
47
+
48
+ const zScore = (currentValue - mean) / stdDev;
49
+
50
+ return {
51
+ isAnomaly: zScore > threshold,
52
+ zScore,
53
+ currentValue,
54
+ mean,
55
+ stdDev,
56
+ };
57
+ }
@@ -0,0 +1,58 @@
1
+ import type { ModelPricing } from "../types.js";
2
+
3
+ // ============================================================
4
+ // Model Pricing Table — Updated Feb 2026
5
+ // Prices in USD per 1M tokens
6
+ // ============================================================
7
+
8
+ export const MODEL_PRICING: Record<string, ModelPricing> = {
9
+ // OpenAI
10
+ "gpt-5.2": { inputPer1M: 2.50, outputPer1M: 10.0 },
11
+ "gpt-5.1": { inputPer1M: 2.50, outputPer1M: 10.0 },
12
+ "gpt-5": { inputPer1M: 2.50, outputPer1M: 10.0 },
13
+ "gpt-4.1": { inputPer1M: 2.00, outputPer1M: 8.00 },
14
+ "gpt-4o": { inputPer1M: 2.50, outputPer1M: 10.0 },
15
+ "gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.60 },
16
+ "o3": { inputPer1M: 10.0, outputPer1M: 40.0 },
17
+ "o3-mini": { inputPer1M: 1.10, outputPer1M: 4.40 },
18
+ "o4-mini": { inputPer1M: 1.10, outputPer1M: 4.40 },
19
+
20
+ // Anthropic
21
+ "claude-opus-4-6": { inputPer1M: 15.0, outputPer1M: 75.0 },
22
+ "claude-sonnet-4-6": { inputPer1M: 3.0, outputPer1M: 15.0 },
23
+ "claude-haiku-4-5": { inputPer1M: 0.80, outputPer1M: 4.0 },
24
+
25
+ // Aliases
26
+ "gpt-5.2-turbo": { inputPer1M: 2.50, outputPer1M: 10.0 },
27
+ opus: { inputPer1M: 15.0, outputPer1M: 75.0 },
28
+ sonnet: { inputPer1M: 3.0, outputPer1M: 15.0 },
29
+ haiku: { inputPer1M: 0.80, outputPer1M: 4.0 },
30
+ };
31
+
32
+ /** Get pricing for a model, fallback to gpt-4o-mini rates */
33
+ export function getModelPricing(model: string): ModelPricing {
34
+ // Try exact match
35
+ const exact = MODEL_PRICING[model];
36
+ if (exact) return exact;
37
+
38
+ // Try prefix match (e.g., "gpt-4o-2024-08-06" → "gpt-4o")
39
+ for (const [key, pricing] of Object.entries(MODEL_PRICING)) {
40
+ if (model.startsWith(key)) return pricing;
41
+ }
42
+
43
+ // Fallback
44
+ return { inputPer1M: 0.15, outputPer1M: 0.60 };
45
+ }
46
+
47
+ /** Estimate cost for a given number of tokens */
48
+ export function estimateCost(
49
+ model: string,
50
+ inputTokens: number,
51
+ outputTokens: number,
52
+ ): number {
53
+ const pricing = getModelPricing(model);
54
+ return (
55
+ (inputTokens / 1_000_000) * pricing.inputPer1M +
56
+ (outputTokens / 1_000_000) * pricing.outputPer1M
57
+ );
58
+ }
@@ -0,0 +1,182 @@
1
+ import type {
2
+ BudgetConfig,
3
+ BudgetCheckResult,
4
+ BudgetPeriod,
5
+ CostRecord,
6
+ } from "../types.js";
7
+ import { estimateCost } from "./pricing.js";
8
+
9
+ // ============================================================
10
+ // Cost Tracker — Token counting + budget enforcement
11
+ // Uses Redis for distributed budget tracking (optional)
12
+ // Falls back to in-memory for standalone use
13
+ // ============================================================
14
+
15
+ /** Minimal Redis interface (compatible with ioredis) */
16
+ export interface RedisLike {
17
+ get(key: string): Promise<string | null>;
18
+ incrbyfloat(key: string, increment: number): Promise<string>;
19
+ expire(key: string, seconds: number): Promise<number>;
20
+ }
21
+
22
+ /** In-memory fallback store */
23
+ class MemoryStore implements RedisLike {
24
+ private data = new Map<string, { value: string; expiresAt?: number }>();
25
+
26
+ async get(key: string): Promise<string | null> {
27
+ const entry = this.data.get(key);
28
+ if (!entry) return null;
29
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
30
+ this.data.delete(key);
31
+ return null;
32
+ }
33
+ return entry.value;
34
+ }
35
+
36
+ async incrbyfloat(key: string, increment: number): Promise<string> {
37
+ const current = parseFloat((await this.get(key)) ?? "0");
38
+ const newValue = (current + increment).toString();
39
+ const entry = this.data.get(key);
40
+ this.data.set(key, { value: newValue, expiresAt: entry?.expiresAt });
41
+ return newValue;
42
+ }
43
+
44
+ async expire(key: string, seconds: number): Promise<number> {
45
+ const entry = this.data.get(key);
46
+ if (!entry) return 0;
47
+ entry.expiresAt = Date.now() + seconds * 1000;
48
+ return 1;
49
+ }
50
+ }
51
+
52
+ export class CostTracker {
53
+ private store: RedisLike;
54
+ private budgets: Map<string, BudgetConfig>;
55
+ private records: CostRecord[] = [];
56
+
57
+ constructor(
58
+ budgets: Record<string, BudgetConfig> = {},
59
+ redis?: RedisLike,
60
+ ) {
61
+ this.store = redis ?? new MemoryStore();
62
+ this.budgets = new Map(Object.entries(budgets));
63
+ }
64
+
65
+ /** Check if a request is within budget BEFORE sending to LLM */
66
+ async checkBudget(
67
+ entityId: string,
68
+ model: string,
69
+ estimatedInputTokens: number,
70
+ estimatedOutputTokens: number = 500,
71
+ ): Promise<BudgetCheckResult> {
72
+ const budget = this.budgets.get(entityId);
73
+ if (!budget) {
74
+ return { allowed: true, currentSpend: 0, remainingBudget: Infinity };
75
+ }
76
+
77
+ const estimated = estimateCost(model, estimatedInputTokens, estimatedOutputTokens);
78
+ const key = this.budgetKey(entityId, budget.period);
79
+ const currentSpend = parseFloat((await this.store.get(key)) ?? "0");
80
+
81
+ if (currentSpend + estimated > budget.hardLimit) {
82
+ return {
83
+ allowed: false,
84
+ currentSpend,
85
+ remainingBudget: Math.max(0, budget.hardLimit - currentSpend),
86
+ warning: `Hard budget limit reached: $${currentSpend.toFixed(2)} / $${budget.hardLimit}`,
87
+ };
88
+ }
89
+
90
+ const warning =
91
+ currentSpend + estimated > budget.softLimit
92
+ ? `Approaching budget: $${currentSpend.toFixed(2)} / $${budget.hardLimit} (${Math.round((currentSpend / budget.hardLimit) * 100)}%)`
93
+ : undefined;
94
+
95
+ return {
96
+ allowed: true,
97
+ currentSpend,
98
+ remainingBudget: budget.hardLimit - currentSpend,
99
+ warning,
100
+ };
101
+ }
102
+
103
+ /** Record actual cost AFTER receiving response */
104
+ async recordCost(
105
+ entityId: string,
106
+ model: string,
107
+ inputTokens: number,
108
+ outputTokens: number,
109
+ ): Promise<CostRecord> {
110
+ const cost = estimateCost(model, inputTokens, outputTokens);
111
+ const record: CostRecord = {
112
+ entityId,
113
+ model,
114
+ inputTokens,
115
+ outputTokens,
116
+ cost,
117
+ timestamp: new Date(),
118
+ };
119
+
120
+ // Update budget counter
121
+ const budget = this.budgets.get(entityId);
122
+ if (budget) {
123
+ const key = this.budgetKey(entityId, budget.period);
124
+ await this.store.incrbyfloat(key, cost);
125
+ await this.store.expire(key, this.periodSeconds(budget.period) * 2);
126
+ }
127
+
128
+ // Also update any matching broader budgets (global, etc.)
129
+ const globalBudget = this.budgets.get("global");
130
+ if (globalBudget && entityId !== "global") {
131
+ const globalKey = this.budgetKey("global", globalBudget.period);
132
+ await this.store.incrbyfloat(globalKey, cost);
133
+ await this.store.expire(globalKey, this.periodSeconds(globalBudget.period) * 2);
134
+ }
135
+
136
+ this.records.push(record);
137
+ return record;
138
+ }
139
+
140
+ /** Get current spend for an entity */
141
+ async getCurrentSpend(entityId: string): Promise<number> {
142
+ const budget = this.budgets.get(entityId);
143
+ if (!budget) return 0;
144
+ const key = this.budgetKey(entityId, budget.period);
145
+ return parseFloat((await this.store.get(key)) ?? "0");
146
+ }
147
+
148
+ /** Get all recorded costs (for export/audit) */
149
+ getRecords(): CostRecord[] {
150
+ return [...this.records];
151
+ }
152
+
153
+ private budgetKey(entityId: string, period: BudgetPeriod): string {
154
+ const now = new Date();
155
+ let periodKey: string;
156
+
157
+ switch (period) {
158
+ case "hourly":
159
+ periodKey = `${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDate()}-${now.getUTCHours()}`;
160
+ break;
161
+ case "daily":
162
+ periodKey = `${now.getUTCFullYear()}-${now.getUTCMonth()}-${now.getUTCDate()}`;
163
+ break;
164
+ case "monthly":
165
+ periodKey = `${now.getUTCFullYear()}-${now.getUTCMonth()}`;
166
+ break;
167
+ }
168
+
169
+ return `ai-shield:cost:${entityId}:${periodKey}`;
170
+ }
171
+
172
+ private periodSeconds(period: BudgetPeriod): number {
173
+ switch (period) {
174
+ case "hourly":
175
+ return 3600;
176
+ case "daily":
177
+ return 86400;
178
+ case "monthly":
179
+ return 86400 * 31;
180
+ }
181
+ }
182
+ }
package/src/index.ts ADDED
@@ -0,0 +1,91 @@
1
+ // ============================================================
2
+ // ai-shield-core — Public API
3
+ // ============================================================
4
+
5
+ // Main class
6
+ export { AIShield } from "./shield.js";
7
+
8
+ // Scanners (for custom chain building)
9
+ export { HeuristicScanner, type HeuristicConfig } from "./scanner/heuristic.js";
10
+ export { PIIScanner } from "./scanner/pii.js";
11
+ export { ScannerChain, type ChainConfig } from "./scanner/chain.js";
12
+ export { injectCanary, checkCanaryLeak } from "./scanner/canary.js";
13
+
14
+ // Policy
15
+ export { PolicyEngine, type PolicyPreset } from "./policy/engine.js";
16
+ export { ToolPolicyScanner } from "./policy/tools.js";
17
+
18
+ // Cost
19
+ export { CostTracker, type RedisLike } from "./cost/tracker.js";
20
+ export { detectAnomaly, type AnomalyResult } from "./cost/anomaly.js";
21
+ export { getModelPricing, estimateCost, MODEL_PRICING } from "./cost/pricing.js";
22
+
23
+ // Audit
24
+ export { AuditLogger, ConsoleAuditStore, MemoryAuditStore } from "./audit/logger.js";
25
+ export type { AuditStore } from "./audit/types.js";
26
+
27
+ // Cache
28
+ export { ScanLRUCache, type LRUCacheConfig } from "./cache/lru.js";
29
+
30
+ // Types (re-export everything)
31
+ export type {
32
+ // Scanner
33
+ ScanDecision,
34
+ ScanResult,
35
+ ScannerResult,
36
+ Scanner,
37
+ ScanContext,
38
+ Violation,
39
+ ViolationType,
40
+ // PII
41
+ PIIType,
42
+ PIIAction,
43
+ PIIEntity,
44
+ PIIConfig,
45
+ // Tool
46
+ ToolCall,
47
+ ToolPermissions,
48
+ ToolPolicy,
49
+ ToolManifestPin,
50
+ // Cost
51
+ BudgetPeriod,
52
+ BudgetConfig,
53
+ CostEstimate,
54
+ CostRecord,
55
+ BudgetCheckResult,
56
+ ModelPricing,
57
+ // Audit
58
+ AuditRecord,
59
+ AuditConfig,
60
+ // Config
61
+ ShieldConfig,
62
+ InjectionConfig,
63
+ CostConfig,
64
+ CacheConfig,
65
+ ToolConfig,
66
+ PresetName,
67
+ } from "./types.js";
68
+
69
+ // --- Convenience function ---
70
+
71
+ import { AIShield } from "./shield.js";
72
+ import type { ShieldConfig, ScanResult, ScanContext } from "./types.js";
73
+
74
+ /** Quick scan — one line, maximum protection */
75
+ export async function shield(
76
+ input: string,
77
+ configOrContext?: ShieldConfig | ScanContext,
78
+ ): Promise<ScanResult> {
79
+ // Detect if second arg is config or context
80
+ const isConfig = configOrContext && ("injection" in configOrContext || "pii" in configOrContext || "cost" in configOrContext || "preset" in configOrContext && typeof configOrContext.preset === "string" && !("agentId" in configOrContext));
81
+
82
+ const config = isConfig ? (configOrContext as ShieldConfig) : {};
83
+ const context = isConfig ? {} : (configOrContext as ScanContext) ?? {};
84
+
85
+ const instance = new AIShield(config);
86
+ try {
87
+ return await instance.scan(input, context);
88
+ } finally {
89
+ await instance.close();
90
+ }
91
+ }
@@ -0,0 +1,163 @@
1
+ import type { ScanDecision, PresetName, PIIAction } from "../types.js";
2
+
3
+ // ============================================================
4
+ // Policy Engine — Maps presets to scanner configurations
5
+ // 3 presets: public_website, internal_support, ops_agent
6
+ // ============================================================
7
+
8
+ export interface PolicyPreset {
9
+ name: PresetName;
10
+ injection: {
11
+ threshold: number;
12
+ action: ScanDecision;
13
+ };
14
+ pii: {
15
+ action: PIIAction;
16
+ emailAction: PIIAction;
17
+ phoneAction: PIIAction;
18
+ creditCardAction: PIIAction;
19
+ ibanAction: PIIAction;
20
+ };
21
+ tools: {
22
+ dangerousPatterns: string[];
23
+ maxChainDepth: number;
24
+ };
25
+ cost: {
26
+ defaultDailyBudget: number;
27
+ warnAtPercent: number;
28
+ };
29
+ }
30
+
31
+ const PRESETS: Record<PresetName, PolicyPreset> = {
32
+ public_website: {
33
+ name: "public_website",
34
+ injection: {
35
+ threshold: 0.25, // Strictest — public-facing
36
+ action: "block",
37
+ },
38
+ pii: {
39
+ action: "mask",
40
+ emailAction: "mask",
41
+ phoneAction: "mask",
42
+ creditCardAction: "block",
43
+ ibanAction: "block",
44
+ },
45
+ tools: {
46
+ dangerousPatterns: [
47
+ "delete_*",
48
+ "remove_*",
49
+ "drop_*",
50
+ "destroy_*",
51
+ "admin_*",
52
+ "execute_*",
53
+ "send_email",
54
+ "payment_*",
55
+ "transfer_*",
56
+ "write_*",
57
+ "create_*",
58
+ "update_*",
59
+ ],
60
+ maxChainDepth: 3,
61
+ },
62
+ cost: {
63
+ defaultDailyBudget: 10, // USD
64
+ warnAtPercent: 80,
65
+ },
66
+ },
67
+
68
+ internal_support: {
69
+ name: "internal_support",
70
+ injection: {
71
+ threshold: 0.35, // Medium — trusted users
72
+ action: "block",
73
+ },
74
+ pii: {
75
+ action: "mask",
76
+ emailAction: "mask",
77
+ phoneAction: "mask",
78
+ creditCardAction: "mask",
79
+ ibanAction: "mask",
80
+ },
81
+ tools: {
82
+ dangerousPatterns: [
83
+ "delete_*",
84
+ "remove_*",
85
+ "drop_*",
86
+ "destroy_*",
87
+ "admin_*",
88
+ "payment_*",
89
+ "transfer_*",
90
+ ],
91
+ maxChainDepth: 5,
92
+ },
93
+ cost: {
94
+ defaultDailyBudget: 50,
95
+ warnAtPercent: 70,
96
+ },
97
+ },
98
+
99
+ ops_agent: {
100
+ name: "ops_agent",
101
+ injection: {
102
+ threshold: 0.5, // Relaxed — internal agents
103
+ action: "warn",
104
+ },
105
+ pii: {
106
+ action: "mask",
107
+ emailAction: "allow",
108
+ phoneAction: "allow",
109
+ creditCardAction: "mask",
110
+ ibanAction: "mask",
111
+ },
112
+ tools: {
113
+ dangerousPatterns: ["drop_*", "destroy_*", "wipe_*", "shutdown_*"],
114
+ maxChainDepth: 8,
115
+ },
116
+ cost: {
117
+ defaultDailyBudget: 100,
118
+ warnAtPercent: 60,
119
+ },
120
+ },
121
+ };
122
+
123
+ export class PolicyEngine {
124
+ private preset: PolicyPreset;
125
+
126
+ constructor(presetName: PresetName = "public_website") {
127
+ this.preset = PRESETS[presetName];
128
+ }
129
+
130
+ getPreset(): PolicyPreset {
131
+ return this.preset;
132
+ }
133
+
134
+ getInjectionThreshold(): number {
135
+ return this.preset.injection.threshold;
136
+ }
137
+
138
+ getPIIAction(type?: string): PIIAction {
139
+ if (!type) return this.preset.pii.action;
140
+ const key = `${type}Action` as keyof PolicyPreset["pii"];
141
+ return (this.preset.pii[key] as PIIAction | undefined) ?? this.preset.pii.action;
142
+ }
143
+
144
+ getDangerousToolPatterns(): string[] {
145
+ return this.preset.tools.dangerousPatterns;
146
+ }
147
+
148
+ getMaxToolChainDepth(): number {
149
+ return this.preset.tools.maxChainDepth;
150
+ }
151
+
152
+ getDailyBudget(): number {
153
+ return this.preset.cost.defaultDailyBudget;
154
+ }
155
+
156
+ static getPresetNames(): PresetName[] {
157
+ return Object.keys(PRESETS) as PresetName[];
158
+ }
159
+
160
+ static getPreset(name: PresetName): PolicyPreset {
161
+ return PRESETS[name];
162
+ }
163
+ }