citadel0 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moulick Bose
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # Citadel 0
2
+
3
+ Programmatic security gateway and policy engine for AI agents built on Convex and TypeScript.
4
+
5
+ Citadel 0 inspects tool calls and actions before execution. It provides deterministic policy evaluation, heuristic threat scanning (prompt injections and secret leaks), and asynchronous human-in-the-loop (HITL) approval queues.
6
+
7
+ ---
8
+
9
+ ## Capabilities
10
+
11
+ - **Hybrid Evaluation Pipeline**: Evaluates deterministic rules first, followed by heuristic regex threat scanning.
12
+ - **Threat Scanner**: Detects prompt injection patterns, roleplay overrides, model token delimiters, and exposed API/private keys in parameters.
13
+ - **Human-in-the-Loop Reviews**: Pauses execution on `REVIEW` policy verdicts, logging a pending state to Convex and polling until resolved.
14
+ - **Tool Wrappers**: Adapters for wrapping function calls (`wrapTool` and `wrapToolWithReview`).
15
+ - **Audit Logging**: Logs every execution attempt, latency, risk score, and matched policies to Convex.
16
+ - **Dual Bundle**: Ships ESM and CommonJS builds with TypeScript declarations.
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install citadel0
24
+ ```
25
+
26
+ ```bash
27
+ pnpm add citadel0
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Usage
33
+
34
+ ### Client Initialization
35
+
36
+ ```typescript
37
+ import { CitadelClient } from 'citadel0';
38
+
39
+ const citadel = new CitadelClient({
40
+ baseUrl: '[https://energetic-starfish-637.convex.site](https://energetic-starfish-637.convex.site)',
41
+ agentToken: process.env.CITADEL_AGENT_TOKEN!,
42
+ });
43
+ ```
44
+
45
+ ### Action Check
46
+
47
+ ```typescript
48
+ const verdict = await citadel.check('web.search', {
49
+ query: 'Convex database tutorial',
50
+ });
51
+
52
+ if (verdict.verdict.decision === 'ALLOW') {
53
+ await runSearch();
54
+ }
55
+ ```
56
+
57
+ ### Direct Tool Guard
58
+
59
+ ```typescript
60
+ import { CitadelClient, wrapTool } from 'citadel0';
61
+
62
+ const citadel = new CitadelClient({
63
+ baseUrl: '[https://energetic-starfish-637.convex.site](https://energetic-starfish-637.convex.site)',
64
+ agentToken: process.env.CITADEL_AGENT_TOKEN!,
65
+ });
66
+
67
+ const executeCommand = wrapTool(citadel, {
68
+ name: 'system.bash',
69
+ execute: async (args: { command: string }) => {
70
+ return runCommand(args.command);
71
+ },
72
+ });
73
+
74
+ const output = await executeCommand.execute({ command: 'ls -la' });
75
+ ```
76
+
77
+ ### Human-in-the-Loop (HITL) Review
78
+
79
+ ```typescript
80
+ import { CitadelClient, wrapToolWithReview } from 'citadel0';
81
+
82
+ const citadel = new CitadelClient({
83
+ baseUrl: '[https://energetic-starfish-637.convex.site](https://energetic-starfish-637.convex.site)',
84
+ agentToken: process.env.CITADEL_AGENT_TOKEN!,
85
+ });
86
+
87
+ const transferFunds = wrapToolWithReview(
88
+ citadel,
89
+ {
90
+ name: 'finance.transfer',
91
+ execute: async (args: { recipient: string; amount: number }) => {
92
+ return transfer(args.recipient, args.amount);
93
+ },
94
+ },
95
+ {
96
+ timeoutMs: 60000,
97
+ pollIntervalMs: 2000,
98
+ onReviewPending: (auditLogId) => {
99
+ console.log(`Pending approval: ${auditLogId}`);
100
+ },
101
+ }
102
+ );
103
+
104
+ await transferFunds.execute({ recipient: 'alice@example.com', amount: 5000 });
105
+ ```
106
+
107
+ ---
108
+
109
+ ## API Reference
110
+
111
+ ### `CitadelClient`
112
+
113
+ - `citadel.check(action, params, options)`: Evaluates policies against the gateway and returns `SecurityVerdict`.
114
+ - `citadel.isAllowed(action, params, options)`: Returns boolean indicating if decision equals `ALLOW`.
115
+ - `citadel.guard(action, params, callback, options)`: Runs callback if allowed; throws error if blocked or requiring review.
116
+ - `citadel.guardWithReview(action, params, callback, options)`: Runs callback if allowed. If flagged for review, polls until approved or timed out.
117
+ - `citadel.getReviewStatus(auditLogId)`: Checks resolution status of an audit log.
118
+
119
+ ### Utilities
120
+
121
+ - `wrapTool(client, toolDefinition)`: Guards an execution function with deterministic and semantic policy checks.
122
+ - `wrapToolWithReview(client, toolDefinition, options)`: Guards an execution function with review polling.
123
+ - `scanPayloadSemantics(params)`: Runs offline heuristic threat scanning against input parameters.
124
+ - `evaluatePolicies(payload, policies)`: Runs local deterministic and semantic engine directly.
package/dist/index.cjs ADDED
@@ -0,0 +1,398 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CitadelClient: () => CitadelClient,
24
+ evaluatePolicies: () => evaluatePolicies,
25
+ scanPayloadSemantics: () => scanPayloadSemantics,
26
+ wrapTool: () => wrapTool,
27
+ wrapToolWithReview: () => wrapToolWithReview
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/sdk/client.ts
32
+ var CitadelClient = class {
33
+ baseUrl;
34
+ agentToken;
35
+ environment;
36
+ defaultSessionId;
37
+ constructor(config) {
38
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
39
+ this.agentToken = config.agentToken;
40
+ this.environment = config.environment;
41
+ this.defaultSessionId = config.defaultSessionId;
42
+ }
43
+ async check(action, params = {}, options = {}) {
44
+ const url = `${this.baseUrl}/api/v1/check`;
45
+ const sessionId = options.sessionId ?? this.defaultSessionId ?? `sess_${Math.random().toString(36).slice(2, 11)}`;
46
+ const response = await fetch(url, {
47
+ method: "POST",
48
+ headers: {
49
+ "Content-Type": "application/json",
50
+ Authorization: `Bearer ${this.agentToken}`
51
+ },
52
+ body: JSON.stringify({
53
+ action,
54
+ params,
55
+ sessionId,
56
+ environment: options.environment ?? this.environment,
57
+ metadata: options.metadata
58
+ })
59
+ });
60
+ if (!response.ok) {
61
+ const errorBody = await response.json().catch(() => ({}));
62
+ const errorMessage = typeof errorBody.error === "string" ? errorBody.error : `Citadel gateway error: ${response.status} ${response.statusText}`;
63
+ throw new Error(errorMessage);
64
+ }
65
+ return await response.json();
66
+ }
67
+ async isAllowed(action, params = {}, options = {}) {
68
+ const result = await this.check(action, params, options);
69
+ return result.verdict.decision === "ALLOW";
70
+ }
71
+ async guard(action, params, callback, options = {}) {
72
+ const result = await this.check(action, params, options);
73
+ if (result.verdict.decision !== "ALLOW") {
74
+ throw new Error(
75
+ `Action '${action}' blocked by Citadel: ${result.verdict.reason}`
76
+ );
77
+ }
78
+ return await callback();
79
+ }
80
+ async getReviewStatus(auditLogId) {
81
+ const url = `${this.baseUrl}/api/v1/review/status?auditLogId=${encodeURIComponent(
82
+ auditLogId
83
+ )}`;
84
+ const response = await fetch(url);
85
+ if (!response.ok) {
86
+ const errorBody = await response.json().catch(() => ({}));
87
+ const errorMessage = typeof errorBody.error === "string" ? errorBody.error : `Review status error: ${response.status}`;
88
+ throw new Error(errorMessage);
89
+ }
90
+ return await response.json();
91
+ }
92
+ async pollReview(auditLogId, options = {}) {
93
+ const timeoutMs = options.timeoutMs ?? 6e4;
94
+ const intervalMs = options.intervalMs ?? 2e3;
95
+ const deadline = Date.now() + timeoutMs;
96
+ while (Date.now() < deadline) {
97
+ const status = await this.getReviewStatus(auditLogId);
98
+ if (status.decision !== "REVIEW") {
99
+ return status;
100
+ }
101
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
102
+ }
103
+ throw new Error(
104
+ `Review timed out after ${timeoutMs}ms for auditLogId: ${auditLogId}`
105
+ );
106
+ }
107
+ async guardWithReview(action, params, callback, options = {}) {
108
+ const result = await this.check(action, params, options);
109
+ if (result.verdict.decision === "ALLOW") {
110
+ return await callback();
111
+ }
112
+ if (result.verdict.decision === "BLOCK") {
113
+ throw new Error(
114
+ `Action '${action}' blocked by Citadel: ${result.verdict.reason}`
115
+ );
116
+ }
117
+ if (options.onReviewPending) {
118
+ options.onReviewPending(result.auditLogId);
119
+ }
120
+ const reviewed = await this.pollReview(result.auditLogId, {
121
+ timeoutMs: options.timeoutMs,
122
+ intervalMs: options.pollIntervalMs
123
+ });
124
+ if (reviewed.decision === "ALLOW") {
125
+ return await callback();
126
+ }
127
+ throw new Error(
128
+ `Action '${action}' rejected during human review: ${reviewed.reviewComment ?? "Rejected"}`
129
+ );
130
+ }
131
+ };
132
+
133
+ // src/sdk/tools.ts
134
+ function wrapTool(client, tool) {
135
+ return {
136
+ ...tool,
137
+ execute: async (args) => {
138
+ return await client.guard(
139
+ tool.name,
140
+ args,
141
+ () => tool.execute(args),
142
+ tool.options
143
+ );
144
+ }
145
+ };
146
+ }
147
+ function wrapToolWithReview(client, tool, reviewOptions) {
148
+ return {
149
+ ...tool,
150
+ execute: async (args) => {
151
+ return await client.guardWithReview(
152
+ tool.name,
153
+ args,
154
+ () => tool.execute(args),
155
+ {
156
+ ...tool.options,
157
+ ...reviewOptions
158
+ }
159
+ );
160
+ }
161
+ };
162
+ }
163
+
164
+ // src/engine/semantic.ts
165
+ var INJECTION_PATTERNS = [
166
+ {
167
+ regex: /(?:ignore|disregard|forget|bypass|override)\s+(?:all\s+)?(?:previous|prior|existing|above)\s+(?:instructions|rules|guidelines|directions|constraints)/i,
168
+ description: "Direct prompt instruction override",
169
+ score: 95
170
+ },
171
+ {
172
+ regex: /(?:you\s+are\s+now|act\s+as|roleplay\s+as)\s+(?:an?\s+)?(?:unfiltered|unrestricted|jailbroken|evil|dan|developer\s+mode)/i,
173
+ description: "Adversarial persona / DAN jailbreak attempt",
174
+ score: 90
175
+ },
176
+ {
177
+ regex: /(?:reveal|output|print|display|leak)\s+(?:the\s+)?(?:system\s+prompt|initial\s+prompt|core\s+instructions|developer\s+instructions)/i,
178
+ description: "System prompt extraction attempt",
179
+ score: 85
180
+ },
181
+ {
182
+ regex: /(?:\[INST\]|\[\/INST\]|<<SYS>>|<\/SYS>|<\|im_start\|>|<\|im_end\|>)/i,
183
+ description: "Model token delimiter injection",
184
+ score: 90
185
+ }
186
+ ];
187
+ var CREDENTIAL_PATTERNS = [
188
+ {
189
+ regex: /(?:sk-[a-zA-Z0-9-_]{20,}|ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36})/i,
190
+ description: "Exposed API token (OpenAI / GitHub)",
191
+ score: 95
192
+ },
193
+ {
194
+ regex: /AKIA[0-9A-Z]{16}/,
195
+ description: "AWS Access Key ID",
196
+ score: 90
197
+ },
198
+ {
199
+ regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
200
+ description: "Exposed Private Key block",
201
+ score: 99
202
+ }
203
+ ];
204
+ function extractStrings(obj) {
205
+ const strings = [];
206
+ function recurse(value) {
207
+ if (typeof value === "string") {
208
+ strings.push(value);
209
+ } else if (Array.isArray(value)) {
210
+ for (const item of value) recurse(item);
211
+ } else if (value !== null && typeof value === "object") {
212
+ for (const val of Object.values(value)) {
213
+ recurse(val);
214
+ }
215
+ }
216
+ }
217
+ recurse(obj);
218
+ return strings;
219
+ }
220
+ function scanPayloadSemantics(params) {
221
+ const stringsToScan = extractStrings(params);
222
+ const threats = [];
223
+ for (const text of stringsToScan) {
224
+ for (const rule of INJECTION_PATTERNS) {
225
+ if (rule.regex.test(text)) {
226
+ threats.push({
227
+ type: "PROMPT_INJECTION",
228
+ confidence: rule.score / 100,
229
+ reason: rule.description,
230
+ matchedPattern: rule.regex.source
231
+ });
232
+ }
233
+ }
234
+ for (const rule of CREDENTIAL_PATTERNS) {
235
+ if (rule.regex.test(text)) {
236
+ threats.push({
237
+ type: "CREDENTIAL_LEAK",
238
+ confidence: rule.score / 100,
239
+ reason: rule.description,
240
+ matchedPattern: rule.regex.source
241
+ });
242
+ }
243
+ }
244
+ }
245
+ const maxScore = threats.length > 0 ? Math.max(...threats.map((t) => t.confidence * 100)) : 0;
246
+ return {
247
+ flagged: threats.length > 0,
248
+ threats,
249
+ maxRiskScore: Math.round(maxScore)
250
+ };
251
+ }
252
+
253
+ // src/engine/match.ts
254
+ function extractField(obj, path) {
255
+ const parts = path.split(".");
256
+ let current = obj;
257
+ for (const part of parts) {
258
+ if (current === null || current === void 0 || typeof current !== "object") {
259
+ return void 0;
260
+ }
261
+ current = current[part];
262
+ }
263
+ return current;
264
+ }
265
+ function evaluateCondition(actual, operator, target) {
266
+ if (actual === void 0 || actual === null) {
267
+ return false;
268
+ }
269
+ switch (operator) {
270
+ case "EQUALS":
271
+ return actual === target;
272
+ case "NOT_EQUALS":
273
+ return actual !== target;
274
+ case "GREATER_THAN":
275
+ return typeof actual === "number" && typeof target === "number" && actual > target;
276
+ case "LESS_THAN":
277
+ return typeof actual === "number" && typeof target === "number" && actual < target;
278
+ case "IN":
279
+ return Array.isArray(target) && target.includes(actual);
280
+ case "NOT_IN":
281
+ return Array.isArray(target) && !target.includes(actual);
282
+ case "CONTAINS":
283
+ if (typeof actual === "string" && typeof target === "string") {
284
+ return actual.includes(target);
285
+ }
286
+ if (Array.isArray(actual)) {
287
+ return actual.includes(target);
288
+ }
289
+ return false;
290
+ case "REGEX_MATCH":
291
+ if (typeof actual !== "string" || typeof target !== "string") {
292
+ return false;
293
+ }
294
+ try {
295
+ return new RegExp(target).test(actual);
296
+ } catch {
297
+ return false;
298
+ }
299
+ default:
300
+ return false;
301
+ }
302
+ }
303
+ function matchesActionPattern(pattern, action) {
304
+ if (pattern === "*" || pattern === action) {
305
+ return true;
306
+ }
307
+ if (pattern.endsWith("*")) {
308
+ const prefix = pattern.slice(0, -1);
309
+ return action.startsWith(prefix);
310
+ }
311
+ return false;
312
+ }
313
+ function evaluateRule(payload, rule) {
314
+ const targetObject = {
315
+ action: payload.action,
316
+ params: payload.params,
317
+ context: payload.context,
318
+ timestamp: payload.timestamp,
319
+ metadata: payload.metadata ?? {}
320
+ };
321
+ const actualValue = extractField(targetObject, rule.field);
322
+ return evaluateCondition(actualValue, rule.operator, rule.value);
323
+ }
324
+ function calculateRisk(decision, matchedCount, semanticScore = 0) {
325
+ if (decision === "BLOCK") {
326
+ return { riskScore: Math.max(95, semanticScore), riskLevel: "CRITICAL" };
327
+ }
328
+ if (decision === "REVIEW") {
329
+ return { riskScore: Math.max(65, semanticScore), riskLevel: "HIGH" };
330
+ }
331
+ if (matchedCount > 0) {
332
+ return { riskScore: 25, riskLevel: "MEDIUM" };
333
+ }
334
+ return { riskScore: 5, riskLevel: "LOW" };
335
+ }
336
+ function evaluatePolicies(payload, policies) {
337
+ const startTime = Date.now();
338
+ const traces = [];
339
+ const activePolicies = policies.filter((p) => p.isActive && p.tenantId === payload.context.tenantId).sort((a, b) => b.priority - a.priority);
340
+ let finalDecision = "ALLOW";
341
+ let primaryReason = "No blocking or review policies triggered";
342
+ let pipeline = "DETERMINISTIC";
343
+ for (const policy of activePolicies) {
344
+ if (!matchesActionPattern(policy.actionPattern, payload.action)) {
345
+ continue;
346
+ }
347
+ const rulesMatched = policy.rules.length > 0 && policy.rules.every((rule) => evaluateRule(payload, rule));
348
+ if (rulesMatched) {
349
+ traces.push({
350
+ policyId: policy.id,
351
+ policyName: policy.name,
352
+ matched: true,
353
+ effect: policy.effect
354
+ });
355
+ if (policy.effect === "BLOCK") {
356
+ finalDecision = "BLOCK";
357
+ primaryReason = `Blocked by policy: ${policy.name}`;
358
+ break;
359
+ }
360
+ if (policy.effect === "REVIEW") {
361
+ finalDecision = "REVIEW";
362
+ primaryReason = `Review required by policy: ${policy.name}`;
363
+ }
364
+ }
365
+ }
366
+ let semanticScore = 0;
367
+ if (finalDecision !== "BLOCK") {
368
+ const semanticResult = scanPayloadSemantics(payload.params);
369
+ if (semanticResult.flagged) {
370
+ pipeline = traces.length > 0 ? "HYBRID" : "AI_GUARD";
371
+ semanticScore = semanticResult.maxRiskScore;
372
+ finalDecision = "BLOCK";
373
+ const threatReasons = semanticResult.threats.map((t) => t.reason).join(", ");
374
+ primaryReason = `AI Guard blocked threat: ${threatReasons}`;
375
+ }
376
+ }
377
+ const { riskScore, riskLevel } = calculateRisk(finalDecision, traces.length, semanticScore);
378
+ const latencyMs = Date.now() - startTime;
379
+ return {
380
+ verdictId: `vrd_${Math.random().toString(36).slice(2, 11)}`,
381
+ decision: finalDecision,
382
+ reason: primaryReason,
383
+ riskScore,
384
+ riskLevel,
385
+ evaluationPipeline: pipeline,
386
+ matchedPolicies: traces,
387
+ executedAt: startTime,
388
+ latencyMs
389
+ };
390
+ }
391
+ // Annotate the CommonJS export names for ESM import in node:
392
+ 0 && (module.exports = {
393
+ CitadelClient,
394
+ evaluatePolicies,
395
+ scanPayloadSemantics,
396
+ wrapTool,
397
+ wrapToolWithReview
398
+ });
@@ -0,0 +1,151 @@
1
+ type Decision = 'ALLOW' | 'BLOCK' | 'REVIEW';
2
+ type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
3
+ type PolicyEffect = 'ALLOW' | 'BLOCK' | 'REVIEW';
4
+ interface SecurityContext {
5
+ tenantId: string;
6
+ agentId: string;
7
+ sessionId: string;
8
+ environment: 'production' | 'staging' | 'development';
9
+ clientIp?: string;
10
+ }
11
+ interface ActionPayload {
12
+ context: SecurityContext;
13
+ action: string;
14
+ params: Record<string, unknown>;
15
+ timestamp: number;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+ type PolicyConditionOperator = 'EQUALS' | 'NOT_EQUALS' | 'GREATER_THAN' | 'LESS_THAN' | 'IN' | 'NOT_IN' | 'CONTAINS' | 'REGEX_MATCH';
19
+ interface PolicyRule {
20
+ field: string;
21
+ operator: PolicyConditionOperator;
22
+ value: unknown;
23
+ }
24
+ interface SecurityPolicy {
25
+ id: string;
26
+ tenantId: string;
27
+ name: string;
28
+ description?: string;
29
+ actionPattern: string;
30
+ rules: PolicyRule[];
31
+ effect: PolicyEffect;
32
+ priority: number;
33
+ isActive: boolean;
34
+ }
35
+ interface PolicyEvaluationTrace {
36
+ policyId: string;
37
+ policyName: string;
38
+ matched: boolean;
39
+ effect: PolicyEffect;
40
+ }
41
+ interface SecurityVerdict {
42
+ verdictId: string;
43
+ decision: Decision;
44
+ reason: string;
45
+ riskScore: number;
46
+ riskLevel: RiskLevel;
47
+ evaluationPipeline: 'DETERMINISTIC' | 'AI_GUARD' | 'HYBRID';
48
+ matchedPolicies: PolicyEvaluationTrace[];
49
+ executedAt: number;
50
+ latencyMs: number;
51
+ }
52
+ interface AuditLogEntry {
53
+ id: string;
54
+ tenantId: string;
55
+ agentId: string;
56
+ sessionId: string;
57
+ action: string;
58
+ params: Record<string, unknown>;
59
+ verdict: SecurityVerdict;
60
+ reviewedBy?: string;
61
+ reviewComment?: string;
62
+ createdAt: number;
63
+ }
64
+
65
+ interface CitadelClientConfig {
66
+ baseUrl: string;
67
+ agentToken: string;
68
+ environment?: 'production' | 'staging' | 'development';
69
+ defaultSessionId?: string;
70
+ }
71
+ interface CheckOptions {
72
+ sessionId?: string;
73
+ environment?: 'production' | 'staging' | 'development';
74
+ metadata?: Record<string, unknown>;
75
+ }
76
+ interface CheckResult {
77
+ verdict: SecurityVerdict;
78
+ auditLogId: string;
79
+ }
80
+ interface ReviewStatus {
81
+ auditLogId: string;
82
+ decision: 'ALLOW' | 'BLOCK' | 'REVIEW';
83
+ reviewedBy?: string;
84
+ reviewComment?: string;
85
+ action: string;
86
+ params: unknown;
87
+ riskScore: number;
88
+ riskLevel: string;
89
+ createdAt: number;
90
+ }
91
+ interface GuardWithReviewOptions extends CheckOptions {
92
+ timeoutMs?: number;
93
+ pollIntervalMs?: number;
94
+ onReviewPending?: (auditLogId: string) => void;
95
+ }
96
+ declare class CitadelClient {
97
+ private readonly baseUrl;
98
+ private readonly agentToken;
99
+ private readonly environment?;
100
+ private readonly defaultSessionId?;
101
+ constructor(config: CitadelClientConfig);
102
+ check(action: string, params?: Record<string, unknown>, options?: CheckOptions): Promise<CheckResult>;
103
+ isAllowed(action: string, params?: Record<string, unknown>, options?: CheckOptions): Promise<boolean>;
104
+ guard<T>(action: string, params: Record<string, unknown>, callback: () => Promise<T> | T, options?: CheckOptions): Promise<T>;
105
+ getReviewStatus(auditLogId: string): Promise<ReviewStatus>;
106
+ pollReview(auditLogId: string, options?: {
107
+ timeoutMs?: number;
108
+ intervalMs?: number;
109
+ }): Promise<ReviewStatus>;
110
+ guardWithReview<T>(action: string, params: Record<string, unknown>, callback: () => Promise<T> | T, options?: GuardWithReviewOptions): Promise<T>;
111
+ }
112
+
113
+ interface GuardedToolDefinition<TArgs = any, TResult = any> {
114
+ name: string;
115
+ description?: string;
116
+ execute: (args: TArgs) => Promise<TResult> | TResult;
117
+ options?: CheckOptions;
118
+ }
119
+ declare function wrapTool<TArgs extends Record<string, unknown>, TResult>(client: CitadelClient, tool: GuardedToolDefinition<TArgs, TResult>): {
120
+ execute: (args: TArgs) => Promise<TResult>;
121
+ name: string;
122
+ description?: string;
123
+ options?: CheckOptions;
124
+ };
125
+ declare function wrapToolWithReview<TArgs extends Record<string, unknown>, TResult>(client: CitadelClient, tool: GuardedToolDefinition<TArgs, TResult>, reviewOptions?: {
126
+ timeoutMs?: number;
127
+ pollIntervalMs?: number;
128
+ onReviewPending?: (auditLogId: string) => void;
129
+ }): {
130
+ execute: (args: TArgs) => Promise<TResult>;
131
+ name: string;
132
+ description?: string;
133
+ options?: CheckOptions;
134
+ };
135
+
136
+ interface ThreatDetection {
137
+ type: 'PROMPT_INJECTION' | 'JAILBREAK_ATTEMPT' | 'CREDENTIAL_LEAK';
138
+ confidence: number;
139
+ reason: string;
140
+ matchedPattern: string;
141
+ }
142
+ interface SemanticScanResult {
143
+ flagged: boolean;
144
+ threats: ThreatDetection[];
145
+ maxRiskScore: number;
146
+ }
147
+ declare function scanPayloadSemantics(params: Record<string, unknown>): SemanticScanResult;
148
+
149
+ declare function evaluatePolicies(payload: ActionPayload, policies: SecurityPolicy[]): SecurityVerdict;
150
+
151
+ export { type ActionPayload, type AuditLogEntry, type CheckOptions, type CheckResult, CitadelClient, type CitadelClientConfig, type Decision, type GuardWithReviewOptions, type GuardedToolDefinition, type PolicyConditionOperator, type PolicyEffect, type PolicyEvaluationTrace, type PolicyRule, type ReviewStatus, type RiskLevel, type SecurityContext, type SecurityPolicy, type SecurityVerdict, type SemanticScanResult, type ThreatDetection, evaluatePolicies, scanPayloadSemantics, wrapTool, wrapToolWithReview };
@@ -0,0 +1,151 @@
1
+ type Decision = 'ALLOW' | 'BLOCK' | 'REVIEW';
2
+ type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
3
+ type PolicyEffect = 'ALLOW' | 'BLOCK' | 'REVIEW';
4
+ interface SecurityContext {
5
+ tenantId: string;
6
+ agentId: string;
7
+ sessionId: string;
8
+ environment: 'production' | 'staging' | 'development';
9
+ clientIp?: string;
10
+ }
11
+ interface ActionPayload {
12
+ context: SecurityContext;
13
+ action: string;
14
+ params: Record<string, unknown>;
15
+ timestamp: number;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+ type PolicyConditionOperator = 'EQUALS' | 'NOT_EQUALS' | 'GREATER_THAN' | 'LESS_THAN' | 'IN' | 'NOT_IN' | 'CONTAINS' | 'REGEX_MATCH';
19
+ interface PolicyRule {
20
+ field: string;
21
+ operator: PolicyConditionOperator;
22
+ value: unknown;
23
+ }
24
+ interface SecurityPolicy {
25
+ id: string;
26
+ tenantId: string;
27
+ name: string;
28
+ description?: string;
29
+ actionPattern: string;
30
+ rules: PolicyRule[];
31
+ effect: PolicyEffect;
32
+ priority: number;
33
+ isActive: boolean;
34
+ }
35
+ interface PolicyEvaluationTrace {
36
+ policyId: string;
37
+ policyName: string;
38
+ matched: boolean;
39
+ effect: PolicyEffect;
40
+ }
41
+ interface SecurityVerdict {
42
+ verdictId: string;
43
+ decision: Decision;
44
+ reason: string;
45
+ riskScore: number;
46
+ riskLevel: RiskLevel;
47
+ evaluationPipeline: 'DETERMINISTIC' | 'AI_GUARD' | 'HYBRID';
48
+ matchedPolicies: PolicyEvaluationTrace[];
49
+ executedAt: number;
50
+ latencyMs: number;
51
+ }
52
+ interface AuditLogEntry {
53
+ id: string;
54
+ tenantId: string;
55
+ agentId: string;
56
+ sessionId: string;
57
+ action: string;
58
+ params: Record<string, unknown>;
59
+ verdict: SecurityVerdict;
60
+ reviewedBy?: string;
61
+ reviewComment?: string;
62
+ createdAt: number;
63
+ }
64
+
65
+ interface CitadelClientConfig {
66
+ baseUrl: string;
67
+ agentToken: string;
68
+ environment?: 'production' | 'staging' | 'development';
69
+ defaultSessionId?: string;
70
+ }
71
+ interface CheckOptions {
72
+ sessionId?: string;
73
+ environment?: 'production' | 'staging' | 'development';
74
+ metadata?: Record<string, unknown>;
75
+ }
76
+ interface CheckResult {
77
+ verdict: SecurityVerdict;
78
+ auditLogId: string;
79
+ }
80
+ interface ReviewStatus {
81
+ auditLogId: string;
82
+ decision: 'ALLOW' | 'BLOCK' | 'REVIEW';
83
+ reviewedBy?: string;
84
+ reviewComment?: string;
85
+ action: string;
86
+ params: unknown;
87
+ riskScore: number;
88
+ riskLevel: string;
89
+ createdAt: number;
90
+ }
91
+ interface GuardWithReviewOptions extends CheckOptions {
92
+ timeoutMs?: number;
93
+ pollIntervalMs?: number;
94
+ onReviewPending?: (auditLogId: string) => void;
95
+ }
96
+ declare class CitadelClient {
97
+ private readonly baseUrl;
98
+ private readonly agentToken;
99
+ private readonly environment?;
100
+ private readonly defaultSessionId?;
101
+ constructor(config: CitadelClientConfig);
102
+ check(action: string, params?: Record<string, unknown>, options?: CheckOptions): Promise<CheckResult>;
103
+ isAllowed(action: string, params?: Record<string, unknown>, options?: CheckOptions): Promise<boolean>;
104
+ guard<T>(action: string, params: Record<string, unknown>, callback: () => Promise<T> | T, options?: CheckOptions): Promise<T>;
105
+ getReviewStatus(auditLogId: string): Promise<ReviewStatus>;
106
+ pollReview(auditLogId: string, options?: {
107
+ timeoutMs?: number;
108
+ intervalMs?: number;
109
+ }): Promise<ReviewStatus>;
110
+ guardWithReview<T>(action: string, params: Record<string, unknown>, callback: () => Promise<T> | T, options?: GuardWithReviewOptions): Promise<T>;
111
+ }
112
+
113
+ interface GuardedToolDefinition<TArgs = any, TResult = any> {
114
+ name: string;
115
+ description?: string;
116
+ execute: (args: TArgs) => Promise<TResult> | TResult;
117
+ options?: CheckOptions;
118
+ }
119
+ declare function wrapTool<TArgs extends Record<string, unknown>, TResult>(client: CitadelClient, tool: GuardedToolDefinition<TArgs, TResult>): {
120
+ execute: (args: TArgs) => Promise<TResult>;
121
+ name: string;
122
+ description?: string;
123
+ options?: CheckOptions;
124
+ };
125
+ declare function wrapToolWithReview<TArgs extends Record<string, unknown>, TResult>(client: CitadelClient, tool: GuardedToolDefinition<TArgs, TResult>, reviewOptions?: {
126
+ timeoutMs?: number;
127
+ pollIntervalMs?: number;
128
+ onReviewPending?: (auditLogId: string) => void;
129
+ }): {
130
+ execute: (args: TArgs) => Promise<TResult>;
131
+ name: string;
132
+ description?: string;
133
+ options?: CheckOptions;
134
+ };
135
+
136
+ interface ThreatDetection {
137
+ type: 'PROMPT_INJECTION' | 'JAILBREAK_ATTEMPT' | 'CREDENTIAL_LEAK';
138
+ confidence: number;
139
+ reason: string;
140
+ matchedPattern: string;
141
+ }
142
+ interface SemanticScanResult {
143
+ flagged: boolean;
144
+ threats: ThreatDetection[];
145
+ maxRiskScore: number;
146
+ }
147
+ declare function scanPayloadSemantics(params: Record<string, unknown>): SemanticScanResult;
148
+
149
+ declare function evaluatePolicies(payload: ActionPayload, policies: SecurityPolicy[]): SecurityVerdict;
150
+
151
+ export { type ActionPayload, type AuditLogEntry, type CheckOptions, type CheckResult, CitadelClient, type CitadelClientConfig, type Decision, type GuardWithReviewOptions, type GuardedToolDefinition, type PolicyConditionOperator, type PolicyEffect, type PolicyEvaluationTrace, type PolicyRule, type ReviewStatus, type RiskLevel, type SecurityContext, type SecurityPolicy, type SecurityVerdict, type SemanticScanResult, type ThreatDetection, evaluatePolicies, scanPayloadSemantics, wrapTool, wrapToolWithReview };
package/dist/index.js ADDED
@@ -0,0 +1,367 @@
1
+ // src/sdk/client.ts
2
+ var CitadelClient = class {
3
+ baseUrl;
4
+ agentToken;
5
+ environment;
6
+ defaultSessionId;
7
+ constructor(config) {
8
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
9
+ this.agentToken = config.agentToken;
10
+ this.environment = config.environment;
11
+ this.defaultSessionId = config.defaultSessionId;
12
+ }
13
+ async check(action, params = {}, options = {}) {
14
+ const url = `${this.baseUrl}/api/v1/check`;
15
+ const sessionId = options.sessionId ?? this.defaultSessionId ?? `sess_${Math.random().toString(36).slice(2, 11)}`;
16
+ const response = await fetch(url, {
17
+ method: "POST",
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ Authorization: `Bearer ${this.agentToken}`
21
+ },
22
+ body: JSON.stringify({
23
+ action,
24
+ params,
25
+ sessionId,
26
+ environment: options.environment ?? this.environment,
27
+ metadata: options.metadata
28
+ })
29
+ });
30
+ if (!response.ok) {
31
+ const errorBody = await response.json().catch(() => ({}));
32
+ const errorMessage = typeof errorBody.error === "string" ? errorBody.error : `Citadel gateway error: ${response.status} ${response.statusText}`;
33
+ throw new Error(errorMessage);
34
+ }
35
+ return await response.json();
36
+ }
37
+ async isAllowed(action, params = {}, options = {}) {
38
+ const result = await this.check(action, params, options);
39
+ return result.verdict.decision === "ALLOW";
40
+ }
41
+ async guard(action, params, callback, options = {}) {
42
+ const result = await this.check(action, params, options);
43
+ if (result.verdict.decision !== "ALLOW") {
44
+ throw new Error(
45
+ `Action '${action}' blocked by Citadel: ${result.verdict.reason}`
46
+ );
47
+ }
48
+ return await callback();
49
+ }
50
+ async getReviewStatus(auditLogId) {
51
+ const url = `${this.baseUrl}/api/v1/review/status?auditLogId=${encodeURIComponent(
52
+ auditLogId
53
+ )}`;
54
+ const response = await fetch(url);
55
+ if (!response.ok) {
56
+ const errorBody = await response.json().catch(() => ({}));
57
+ const errorMessage = typeof errorBody.error === "string" ? errorBody.error : `Review status error: ${response.status}`;
58
+ throw new Error(errorMessage);
59
+ }
60
+ return await response.json();
61
+ }
62
+ async pollReview(auditLogId, options = {}) {
63
+ const timeoutMs = options.timeoutMs ?? 6e4;
64
+ const intervalMs = options.intervalMs ?? 2e3;
65
+ const deadline = Date.now() + timeoutMs;
66
+ while (Date.now() < deadline) {
67
+ const status = await this.getReviewStatus(auditLogId);
68
+ if (status.decision !== "REVIEW") {
69
+ return status;
70
+ }
71
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
72
+ }
73
+ throw new Error(
74
+ `Review timed out after ${timeoutMs}ms for auditLogId: ${auditLogId}`
75
+ );
76
+ }
77
+ async guardWithReview(action, params, callback, options = {}) {
78
+ const result = await this.check(action, params, options);
79
+ if (result.verdict.decision === "ALLOW") {
80
+ return await callback();
81
+ }
82
+ if (result.verdict.decision === "BLOCK") {
83
+ throw new Error(
84
+ `Action '${action}' blocked by Citadel: ${result.verdict.reason}`
85
+ );
86
+ }
87
+ if (options.onReviewPending) {
88
+ options.onReviewPending(result.auditLogId);
89
+ }
90
+ const reviewed = await this.pollReview(result.auditLogId, {
91
+ timeoutMs: options.timeoutMs,
92
+ intervalMs: options.pollIntervalMs
93
+ });
94
+ if (reviewed.decision === "ALLOW") {
95
+ return await callback();
96
+ }
97
+ throw new Error(
98
+ `Action '${action}' rejected during human review: ${reviewed.reviewComment ?? "Rejected"}`
99
+ );
100
+ }
101
+ };
102
+
103
+ // src/sdk/tools.ts
104
+ function wrapTool(client, tool) {
105
+ return {
106
+ ...tool,
107
+ execute: async (args) => {
108
+ return await client.guard(
109
+ tool.name,
110
+ args,
111
+ () => tool.execute(args),
112
+ tool.options
113
+ );
114
+ }
115
+ };
116
+ }
117
+ function wrapToolWithReview(client, tool, reviewOptions) {
118
+ return {
119
+ ...tool,
120
+ execute: async (args) => {
121
+ return await client.guardWithReview(
122
+ tool.name,
123
+ args,
124
+ () => tool.execute(args),
125
+ {
126
+ ...tool.options,
127
+ ...reviewOptions
128
+ }
129
+ );
130
+ }
131
+ };
132
+ }
133
+
134
+ // src/engine/semantic.ts
135
+ var INJECTION_PATTERNS = [
136
+ {
137
+ regex: /(?:ignore|disregard|forget|bypass|override)\s+(?:all\s+)?(?:previous|prior|existing|above)\s+(?:instructions|rules|guidelines|directions|constraints)/i,
138
+ description: "Direct prompt instruction override",
139
+ score: 95
140
+ },
141
+ {
142
+ regex: /(?:you\s+are\s+now|act\s+as|roleplay\s+as)\s+(?:an?\s+)?(?:unfiltered|unrestricted|jailbroken|evil|dan|developer\s+mode)/i,
143
+ description: "Adversarial persona / DAN jailbreak attempt",
144
+ score: 90
145
+ },
146
+ {
147
+ regex: /(?:reveal|output|print|display|leak)\s+(?:the\s+)?(?:system\s+prompt|initial\s+prompt|core\s+instructions|developer\s+instructions)/i,
148
+ description: "System prompt extraction attempt",
149
+ score: 85
150
+ },
151
+ {
152
+ regex: /(?:\[INST\]|\[\/INST\]|<<SYS>>|<\/SYS>|<\|im_start\|>|<\|im_end\|>)/i,
153
+ description: "Model token delimiter injection",
154
+ score: 90
155
+ }
156
+ ];
157
+ var CREDENTIAL_PATTERNS = [
158
+ {
159
+ regex: /(?:sk-[a-zA-Z0-9-_]{20,}|ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36})/i,
160
+ description: "Exposed API token (OpenAI / GitHub)",
161
+ score: 95
162
+ },
163
+ {
164
+ regex: /AKIA[0-9A-Z]{16}/,
165
+ description: "AWS Access Key ID",
166
+ score: 90
167
+ },
168
+ {
169
+ regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
170
+ description: "Exposed Private Key block",
171
+ score: 99
172
+ }
173
+ ];
174
+ function extractStrings(obj) {
175
+ const strings = [];
176
+ function recurse(value) {
177
+ if (typeof value === "string") {
178
+ strings.push(value);
179
+ } else if (Array.isArray(value)) {
180
+ for (const item of value) recurse(item);
181
+ } else if (value !== null && typeof value === "object") {
182
+ for (const val of Object.values(value)) {
183
+ recurse(val);
184
+ }
185
+ }
186
+ }
187
+ recurse(obj);
188
+ return strings;
189
+ }
190
+ function scanPayloadSemantics(params) {
191
+ const stringsToScan = extractStrings(params);
192
+ const threats = [];
193
+ for (const text of stringsToScan) {
194
+ for (const rule of INJECTION_PATTERNS) {
195
+ if (rule.regex.test(text)) {
196
+ threats.push({
197
+ type: "PROMPT_INJECTION",
198
+ confidence: rule.score / 100,
199
+ reason: rule.description,
200
+ matchedPattern: rule.regex.source
201
+ });
202
+ }
203
+ }
204
+ for (const rule of CREDENTIAL_PATTERNS) {
205
+ if (rule.regex.test(text)) {
206
+ threats.push({
207
+ type: "CREDENTIAL_LEAK",
208
+ confidence: rule.score / 100,
209
+ reason: rule.description,
210
+ matchedPattern: rule.regex.source
211
+ });
212
+ }
213
+ }
214
+ }
215
+ const maxScore = threats.length > 0 ? Math.max(...threats.map((t) => t.confidence * 100)) : 0;
216
+ return {
217
+ flagged: threats.length > 0,
218
+ threats,
219
+ maxRiskScore: Math.round(maxScore)
220
+ };
221
+ }
222
+
223
+ // src/engine/match.ts
224
+ function extractField(obj, path) {
225
+ const parts = path.split(".");
226
+ let current = obj;
227
+ for (const part of parts) {
228
+ if (current === null || current === void 0 || typeof current !== "object") {
229
+ return void 0;
230
+ }
231
+ current = current[part];
232
+ }
233
+ return current;
234
+ }
235
+ function evaluateCondition(actual, operator, target) {
236
+ if (actual === void 0 || actual === null) {
237
+ return false;
238
+ }
239
+ switch (operator) {
240
+ case "EQUALS":
241
+ return actual === target;
242
+ case "NOT_EQUALS":
243
+ return actual !== target;
244
+ case "GREATER_THAN":
245
+ return typeof actual === "number" && typeof target === "number" && actual > target;
246
+ case "LESS_THAN":
247
+ return typeof actual === "number" && typeof target === "number" && actual < target;
248
+ case "IN":
249
+ return Array.isArray(target) && target.includes(actual);
250
+ case "NOT_IN":
251
+ return Array.isArray(target) && !target.includes(actual);
252
+ case "CONTAINS":
253
+ if (typeof actual === "string" && typeof target === "string") {
254
+ return actual.includes(target);
255
+ }
256
+ if (Array.isArray(actual)) {
257
+ return actual.includes(target);
258
+ }
259
+ return false;
260
+ case "REGEX_MATCH":
261
+ if (typeof actual !== "string" || typeof target !== "string") {
262
+ return false;
263
+ }
264
+ try {
265
+ return new RegExp(target).test(actual);
266
+ } catch {
267
+ return false;
268
+ }
269
+ default:
270
+ return false;
271
+ }
272
+ }
273
+ function matchesActionPattern(pattern, action) {
274
+ if (pattern === "*" || pattern === action) {
275
+ return true;
276
+ }
277
+ if (pattern.endsWith("*")) {
278
+ const prefix = pattern.slice(0, -1);
279
+ return action.startsWith(prefix);
280
+ }
281
+ return false;
282
+ }
283
+ function evaluateRule(payload, rule) {
284
+ const targetObject = {
285
+ action: payload.action,
286
+ params: payload.params,
287
+ context: payload.context,
288
+ timestamp: payload.timestamp,
289
+ metadata: payload.metadata ?? {}
290
+ };
291
+ const actualValue = extractField(targetObject, rule.field);
292
+ return evaluateCondition(actualValue, rule.operator, rule.value);
293
+ }
294
+ function calculateRisk(decision, matchedCount, semanticScore = 0) {
295
+ if (decision === "BLOCK") {
296
+ return { riskScore: Math.max(95, semanticScore), riskLevel: "CRITICAL" };
297
+ }
298
+ if (decision === "REVIEW") {
299
+ return { riskScore: Math.max(65, semanticScore), riskLevel: "HIGH" };
300
+ }
301
+ if (matchedCount > 0) {
302
+ return { riskScore: 25, riskLevel: "MEDIUM" };
303
+ }
304
+ return { riskScore: 5, riskLevel: "LOW" };
305
+ }
306
+ function evaluatePolicies(payload, policies) {
307
+ const startTime = Date.now();
308
+ const traces = [];
309
+ const activePolicies = policies.filter((p) => p.isActive && p.tenantId === payload.context.tenantId).sort((a, b) => b.priority - a.priority);
310
+ let finalDecision = "ALLOW";
311
+ let primaryReason = "No blocking or review policies triggered";
312
+ let pipeline = "DETERMINISTIC";
313
+ for (const policy of activePolicies) {
314
+ if (!matchesActionPattern(policy.actionPattern, payload.action)) {
315
+ continue;
316
+ }
317
+ const rulesMatched = policy.rules.length > 0 && policy.rules.every((rule) => evaluateRule(payload, rule));
318
+ if (rulesMatched) {
319
+ traces.push({
320
+ policyId: policy.id,
321
+ policyName: policy.name,
322
+ matched: true,
323
+ effect: policy.effect
324
+ });
325
+ if (policy.effect === "BLOCK") {
326
+ finalDecision = "BLOCK";
327
+ primaryReason = `Blocked by policy: ${policy.name}`;
328
+ break;
329
+ }
330
+ if (policy.effect === "REVIEW") {
331
+ finalDecision = "REVIEW";
332
+ primaryReason = `Review required by policy: ${policy.name}`;
333
+ }
334
+ }
335
+ }
336
+ let semanticScore = 0;
337
+ if (finalDecision !== "BLOCK") {
338
+ const semanticResult = scanPayloadSemantics(payload.params);
339
+ if (semanticResult.flagged) {
340
+ pipeline = traces.length > 0 ? "HYBRID" : "AI_GUARD";
341
+ semanticScore = semanticResult.maxRiskScore;
342
+ finalDecision = "BLOCK";
343
+ const threatReasons = semanticResult.threats.map((t) => t.reason).join(", ");
344
+ primaryReason = `AI Guard blocked threat: ${threatReasons}`;
345
+ }
346
+ }
347
+ const { riskScore, riskLevel } = calculateRisk(finalDecision, traces.length, semanticScore);
348
+ const latencyMs = Date.now() - startTime;
349
+ return {
350
+ verdictId: `vrd_${Math.random().toString(36).slice(2, 11)}`,
351
+ decision: finalDecision,
352
+ reason: primaryReason,
353
+ riskScore,
354
+ riskLevel,
355
+ evaluationPipeline: pipeline,
356
+ matchedPolicies: traces,
357
+ executedAt: startTime,
358
+ latencyMs
359
+ };
360
+ }
361
+ export {
362
+ CitadelClient,
363
+ evaluatePolicies,
364
+ scanPayloadSemantics,
365
+ wrapTool,
366
+ wrapToolWithReview
367
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "citadel0",
3
+ "version": "1.0.0",
4
+ "description": "Programmatic Security Gateway & Policy Engine for AI Agents",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
26
+ "prepack": "pnpm run build",
27
+ "typecheck": "tsc --noEmit"
28
+ },
29
+ "keywords": [
30
+ "ai",
31
+ "security",
32
+ "guardrails",
33
+ "agents",
34
+ "prompt-injection",
35
+ "policy-engine"
36
+ ],
37
+ "author": "Moulick Bose",
38
+ "license": "MIT",
39
+ "packageManager": "pnpm@12.4.2",
40
+ "devDependencies": {
41
+ "@types/node": "^26.6.1",
42
+ "tsup": "^8.4.0",
43
+ "tsx": "^4.23.13",
44
+ "typescript": "^5.9.3"
45
+ },
46
+ "dependencies": {
47
+ "convex": "^1.46.0"
48
+ }
49
+ }