citadel0 1.0.0 → 1.0.1
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/README.md +188 -75
- package/dist/index.cjs +145 -260
- package/dist/index.d.cts +42 -99
- package/dist/index.d.ts +42 -99
- package/dist/index.js +144 -257
- package/package.json +1 -1
- package/LICENSE +0 -21
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
type Decision = 'ALLOW' | 'BLOCK' | 'REVIEW';
|
|
2
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
3
|
type PolicyConditionOperator = 'EQUALS' | 'NOT_EQUALS' | 'GREATER_THAN' | 'LESS_THAN' | 'IN' | 'NOT_IN' | 'CONTAINS' | 'REGEX_MATCH';
|
|
19
4
|
interface PolicyRule {
|
|
20
5
|
field: string;
|
|
@@ -23,20 +8,28 @@ interface PolicyRule {
|
|
|
23
8
|
}
|
|
24
9
|
interface SecurityPolicy {
|
|
25
10
|
id: string;
|
|
26
|
-
tenantId: string;
|
|
27
11
|
name: string;
|
|
28
|
-
description?: string;
|
|
29
12
|
actionPattern: string;
|
|
30
|
-
|
|
31
|
-
effect: PolicyEffect;
|
|
13
|
+
effect: Decision;
|
|
32
14
|
priority: number;
|
|
33
15
|
isActive: boolean;
|
|
16
|
+
rules: PolicyRule[];
|
|
17
|
+
}
|
|
18
|
+
interface ActionPayload {
|
|
19
|
+
action: string;
|
|
20
|
+
params: Record<string, unknown>;
|
|
21
|
+
context?: {
|
|
22
|
+
tenantId?: string;
|
|
23
|
+
sessionId?: string;
|
|
24
|
+
environment?: string;
|
|
25
|
+
};
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
34
27
|
}
|
|
35
28
|
interface PolicyEvaluationTrace {
|
|
36
29
|
policyId: string;
|
|
37
30
|
policyName: string;
|
|
38
31
|
matched: boolean;
|
|
39
|
-
effect:
|
|
32
|
+
effect: Decision;
|
|
40
33
|
}
|
|
41
34
|
interface SecurityVerdict {
|
|
42
35
|
verdictId: string;
|
|
@@ -44,108 +37,58 @@ interface SecurityVerdict {
|
|
|
44
37
|
reason: string;
|
|
45
38
|
riskScore: number;
|
|
46
39
|
riskLevel: RiskLevel;
|
|
47
|
-
evaluationPipeline: 'DETERMINISTIC' | 'AI_GUARD' | 'HYBRID';
|
|
48
40
|
matchedPolicies: PolicyEvaluationTrace[];
|
|
49
41
|
executedAt: number;
|
|
50
42
|
latencyMs: number;
|
|
51
43
|
}
|
|
52
|
-
interface
|
|
53
|
-
|
|
54
|
-
tenantId: string;
|
|
55
|
-
agentId: string;
|
|
56
|
-
sessionId: string;
|
|
44
|
+
interface TelemetryTrace {
|
|
45
|
+
traceId: string;
|
|
57
46
|
action: string;
|
|
58
47
|
params: Record<string, unknown>;
|
|
59
48
|
verdict: SecurityVerdict;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
sessionId?: string;
|
|
50
|
+
environment: string;
|
|
51
|
+
timestamp: number;
|
|
63
52
|
}
|
|
64
53
|
|
|
65
|
-
|
|
54
|
+
declare function evaluatePolicies(payload: ActionPayload, sortedPolicies: SecurityPolicy[]): SecurityVerdict;
|
|
55
|
+
|
|
56
|
+
interface Citadel_0_Config {
|
|
66
57
|
baseUrl: string;
|
|
67
58
|
agentToken: string;
|
|
68
59
|
environment?: 'production' | 'staging' | 'development';
|
|
69
60
|
defaultSessionId?: string;
|
|
61
|
+
policies?: SecurityPolicy[];
|
|
62
|
+
flushIntervalMs?: number;
|
|
63
|
+
batchSize?: number;
|
|
70
64
|
}
|
|
71
65
|
interface CheckOptions {
|
|
72
66
|
sessionId?: string;
|
|
73
67
|
environment?: 'production' | 'staging' | 'development';
|
|
74
68
|
metadata?: Record<string, unknown>;
|
|
75
69
|
}
|
|
76
|
-
|
|
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 {
|
|
70
|
+
declare class Citadel_0 {
|
|
97
71
|
private readonly baseUrl;
|
|
98
72
|
private readonly agentToken;
|
|
99
|
-
private readonly environment
|
|
73
|
+
private readonly environment;
|
|
100
74
|
private readonly defaultSessionId?;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
75
|
+
private policies;
|
|
76
|
+
private telemetryQueue;
|
|
77
|
+
private readonly batchSize;
|
|
78
|
+
private flushTimer;
|
|
79
|
+
constructor(config: Citadel_0_Config);
|
|
80
|
+
setPolicies(policies: SecurityPolicy[]): void;
|
|
81
|
+
syncPolicies(): Promise<void>;
|
|
82
|
+
check(action: string, params?: Record<string, unknown>, options?: CheckOptions): SecurityVerdict;
|
|
83
|
+
isAllowed(action: string, params?: Record<string, unknown>, options?: CheckOptions): boolean;
|
|
84
|
+
guard<T>(action: string, params: Record<string, unknown>, callback: () => Promise<T> | T, options?: CheckOptions): Promise<T> | T;
|
|
85
|
+
flush(): Promise<void>;
|
|
86
|
+
destroy(): void;
|
|
111
87
|
}
|
|
112
88
|
|
|
113
|
-
interface
|
|
114
|
-
|
|
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;
|
|
89
|
+
interface GuardedToolOptions extends CheckOptions {
|
|
90
|
+
actionName?: string;
|
|
141
91
|
}
|
|
142
|
-
|
|
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;
|
|
92
|
+
declare function guardTool<TArgs extends Record<string, unknown>, TResult>(client: Citadel_0, action: string, fn: (args: TArgs) => Promise<TResult> | TResult, options?: CheckOptions): (args: TArgs) => Promise<TResult>;
|
|
150
93
|
|
|
151
|
-
export { type ActionPayload, type
|
|
94
|
+
export { type ActionPayload, type CheckOptions, Citadel_0, type Citadel_0_Config, type Decision, type GuardedToolOptions, type PolicyConditionOperator, type PolicyEvaluationTrace, type PolicyRule, type RiskLevel, type SecurityPolicy, type SecurityVerdict, type TelemetryTrace, evaluatePolicies, guardTool };
|
package/dist/index.js
CHANGED
|
@@ -1,226 +1,13 @@
|
|
|
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
1
|
// src/engine/match.ts
|
|
2
|
+
var regexCache = /* @__PURE__ */ new Map();
|
|
3
|
+
function getCompiledRegex(pattern) {
|
|
4
|
+
let re = regexCache.get(pattern);
|
|
5
|
+
if (!re) {
|
|
6
|
+
re = new RegExp(pattern);
|
|
7
|
+
regexCache.set(pattern, re);
|
|
8
|
+
}
|
|
9
|
+
return re;
|
|
10
|
+
}
|
|
224
11
|
function extractField(obj, path) {
|
|
225
12
|
const parts = path.split(".");
|
|
226
13
|
let current = obj;
|
|
@@ -262,7 +49,7 @@ function evaluateCondition(actual, operator, target) {
|
|
|
262
49
|
return false;
|
|
263
50
|
}
|
|
264
51
|
try {
|
|
265
|
-
return
|
|
52
|
+
return getCompiledRegex(target).test(actual);
|
|
266
53
|
} catch {
|
|
267
54
|
return false;
|
|
268
55
|
}
|
|
@@ -275,8 +62,7 @@ function matchesActionPattern(pattern, action) {
|
|
|
275
62
|
return true;
|
|
276
63
|
}
|
|
277
64
|
if (pattern.endsWith("*")) {
|
|
278
|
-
|
|
279
|
-
return action.startsWith(prefix);
|
|
65
|
+
return action.startsWith(pattern.slice(0, -1));
|
|
280
66
|
}
|
|
281
67
|
return false;
|
|
282
68
|
}
|
|
@@ -284,37 +70,35 @@ function evaluateRule(payload, rule) {
|
|
|
284
70
|
const targetObject = {
|
|
285
71
|
action: payload.action,
|
|
286
72
|
params: payload.params,
|
|
287
|
-
context: payload.context,
|
|
288
|
-
timestamp: payload.timestamp,
|
|
73
|
+
context: payload.context ?? {},
|
|
289
74
|
metadata: payload.metadata ?? {}
|
|
290
75
|
};
|
|
291
76
|
const actualValue = extractField(targetObject, rule.field);
|
|
292
77
|
return evaluateCondition(actualValue, rule.operator, rule.value);
|
|
293
78
|
}
|
|
294
|
-
function calculateRisk(decision, matchedCount
|
|
79
|
+
function calculateRisk(decision, matchedCount) {
|
|
295
80
|
if (decision === "BLOCK") {
|
|
296
|
-
return { riskScore:
|
|
81
|
+
return { riskScore: 95, riskLevel: "CRITICAL" };
|
|
297
82
|
}
|
|
298
83
|
if (decision === "REVIEW") {
|
|
299
|
-
return { riskScore:
|
|
84
|
+
return { riskScore: 65, riskLevel: "HIGH" };
|
|
300
85
|
}
|
|
301
86
|
if (matchedCount > 0) {
|
|
302
87
|
return { riskScore: 25, riskLevel: "MEDIUM" };
|
|
303
88
|
}
|
|
304
89
|
return { riskScore: 5, riskLevel: "LOW" };
|
|
305
90
|
}
|
|
306
|
-
function evaluatePolicies(payload,
|
|
307
|
-
const startTime =
|
|
91
|
+
function evaluatePolicies(payload, sortedPolicies) {
|
|
92
|
+
const startTime = performance.now();
|
|
308
93
|
const traces = [];
|
|
309
|
-
const activePolicies = policies.filter((p) => p.isActive && p.tenantId === payload.context.tenantId).sort((a, b) => b.priority - a.priority);
|
|
310
94
|
let finalDecision = "ALLOW";
|
|
311
|
-
let primaryReason = "
|
|
312
|
-
let
|
|
313
|
-
|
|
314
|
-
if (!matchesActionPattern(policy.actionPattern, payload.action)) {
|
|
95
|
+
let primaryReason = "All security policies satisfied";
|
|
96
|
+
for (let i = 0; i < sortedPolicies.length; i++) {
|
|
97
|
+
const policy = sortedPolicies[i];
|
|
98
|
+
if (!policy.isActive || !matchesActionPattern(policy.actionPattern, payload.action)) {
|
|
315
99
|
continue;
|
|
316
100
|
}
|
|
317
|
-
const rulesMatched = policy.rules.length
|
|
101
|
+
const rulesMatched = policy.rules.length === 0 || policy.rules.every((rule) => evaluateRule(payload, rule));
|
|
318
102
|
if (rulesMatched) {
|
|
319
103
|
traces.push({
|
|
320
104
|
policyId: policy.id,
|
|
@@ -333,35 +117,138 @@ function evaluatePolicies(payload, policies) {
|
|
|
333
117
|
}
|
|
334
118
|
}
|
|
335
119
|
}
|
|
336
|
-
|
|
337
|
-
|
|
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;
|
|
120
|
+
const { riskScore, riskLevel } = calculateRisk(finalDecision, traces.length);
|
|
121
|
+
const latencyMs = Number((performance.now() - startTime).toFixed(3));
|
|
349
122
|
return {
|
|
350
123
|
verdictId: `vrd_${Math.random().toString(36).slice(2, 11)}`,
|
|
351
124
|
decision: finalDecision,
|
|
352
125
|
reason: primaryReason,
|
|
353
126
|
riskScore,
|
|
354
127
|
riskLevel,
|
|
355
|
-
evaluationPipeline: pipeline,
|
|
356
128
|
matchedPolicies: traces,
|
|
357
|
-
executedAt:
|
|
129
|
+
executedAt: Date.now(),
|
|
358
130
|
latencyMs
|
|
359
131
|
};
|
|
360
132
|
}
|
|
133
|
+
|
|
134
|
+
// src/sdk/client.ts
|
|
135
|
+
var Citadel_0 = class {
|
|
136
|
+
baseUrl;
|
|
137
|
+
agentToken;
|
|
138
|
+
environment;
|
|
139
|
+
defaultSessionId;
|
|
140
|
+
policies = [];
|
|
141
|
+
telemetryQueue = [];
|
|
142
|
+
batchSize;
|
|
143
|
+
flushTimer = null;
|
|
144
|
+
constructor(config) {
|
|
145
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
146
|
+
this.agentToken = config.agentToken;
|
|
147
|
+
this.environment = config.environment ?? "production";
|
|
148
|
+
this.defaultSessionId = config.defaultSessionId;
|
|
149
|
+
this.batchSize = config.batchSize ?? 50;
|
|
150
|
+
if (config.policies) {
|
|
151
|
+
this.setPolicies(config.policies);
|
|
152
|
+
}
|
|
153
|
+
const interval = config.flushIntervalMs ?? 3e3;
|
|
154
|
+
this.flushTimer = setInterval(() => {
|
|
155
|
+
void this.flush();
|
|
156
|
+
}, interval);
|
|
157
|
+
if (typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
|
|
158
|
+
this.flushTimer.unref();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
setPolicies(policies) {
|
|
162
|
+
this.policies = [...policies].sort((a, b) => b.priority - a.priority);
|
|
163
|
+
}
|
|
164
|
+
async syncPolicies() {
|
|
165
|
+
const res = await fetch(`${this.baseUrl}/api/v1/policies`, {
|
|
166
|
+
headers: { Authorization: `Bearer ${this.agentToken}` }
|
|
167
|
+
});
|
|
168
|
+
if (res.ok) {
|
|
169
|
+
const data = await res.json();
|
|
170
|
+
const list = Array.isArray(data) ? data : data?.policies ?? [];
|
|
171
|
+
this.setPolicies(list);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
check(action, params = {}, options = {}) {
|
|
175
|
+
const payload = {
|
|
176
|
+
action,
|
|
177
|
+
params,
|
|
178
|
+
context: {
|
|
179
|
+
sessionId: options.sessionId ?? this.defaultSessionId,
|
|
180
|
+
environment: options.environment ?? this.environment
|
|
181
|
+
},
|
|
182
|
+
metadata: options.metadata
|
|
183
|
+
};
|
|
184
|
+
const verdict = evaluatePolicies(payload, this.policies);
|
|
185
|
+
this.telemetryQueue.push({
|
|
186
|
+
traceId: verdict.verdictId,
|
|
187
|
+
action,
|
|
188
|
+
params,
|
|
189
|
+
verdict,
|
|
190
|
+
sessionId: payload.context?.sessionId,
|
|
191
|
+
environment: payload.context?.environment ?? this.environment,
|
|
192
|
+
timestamp: verdict.executedAt
|
|
193
|
+
});
|
|
194
|
+
if (this.telemetryQueue.length >= this.batchSize) {
|
|
195
|
+
void this.flush();
|
|
196
|
+
}
|
|
197
|
+
return verdict;
|
|
198
|
+
}
|
|
199
|
+
isAllowed(action, params = {}, options = {}) {
|
|
200
|
+
return this.check(action, params, options).decision === "ALLOW";
|
|
201
|
+
}
|
|
202
|
+
guard(action, params, callback, options = {}) {
|
|
203
|
+
const verdict = this.check(action, params, options);
|
|
204
|
+
if (verdict.decision === "BLOCK") {
|
|
205
|
+
throw new Error(`Action '${action}' blocked by Citadel 0: ${verdict.reason}`);
|
|
206
|
+
}
|
|
207
|
+
if (verdict.decision === "REVIEW") {
|
|
208
|
+
throw new Error(`Action '${action}' halted: requires human review`);
|
|
209
|
+
}
|
|
210
|
+
return callback();
|
|
211
|
+
}
|
|
212
|
+
async flush() {
|
|
213
|
+
if (this.telemetryQueue.length === 0) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const batch = this.telemetryQueue.splice(0, this.batchSize);
|
|
217
|
+
try {
|
|
218
|
+
await fetch(`${this.baseUrl}/api/v1/telemetry`, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers: {
|
|
221
|
+
"Content-Type": "application/json",
|
|
222
|
+
Authorization: `Bearer ${this.agentToken}`
|
|
223
|
+
},
|
|
224
|
+
body: JSON.stringify({ traces: batch })
|
|
225
|
+
});
|
|
226
|
+
} catch {
|
|
227
|
+
this.telemetryQueue.unshift(...batch);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
destroy() {
|
|
231
|
+
if (this.flushTimer) {
|
|
232
|
+
clearInterval(this.flushTimer);
|
|
233
|
+
this.flushTimer = null;
|
|
234
|
+
}
|
|
235
|
+
void this.flush();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// src/sdk/tools.ts
|
|
240
|
+
function guardTool(client, action, fn, options) {
|
|
241
|
+
return async (args) => {
|
|
242
|
+
return client.guard(
|
|
243
|
+
action,
|
|
244
|
+
args,
|
|
245
|
+
() => fn(args),
|
|
246
|
+
options
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
}
|
|
361
250
|
export {
|
|
362
|
-
|
|
251
|
+
Citadel_0,
|
|
363
252
|
evaluatePolicies,
|
|
364
|
-
|
|
365
|
-
wrapTool,
|
|
366
|
-
wrapToolWithReview
|
|
253
|
+
guardTool
|
|
367
254
|
};
|
package/package.json
CHANGED
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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.
|