curatedmcp 2.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 +21 -0
- package/README.md +175 -0
- package/dist/audit/catalog.d.ts +5 -0
- package/dist/audit/catalog.d.ts.map +1 -0
- package/dist/audit/catalog.js +69 -0
- package/dist/audit/index.d.ts +10 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +32 -0
- package/dist/audit/report.d.ts +6 -0
- package/dist/audit/report.d.ts.map +1 -0
- package/dist/audit/report.js +79 -0
- package/dist/audit/risk.d.ts +3 -0
- package/dist/audit/risk.d.ts.map +1 -0
- package/dist/audit/risk.js +68 -0
- package/dist/audit/scanner.d.ts +8 -0
- package/dist/audit/scanner.d.ts.map +1 -0
- package/dist/audit/scanner.js +69 -0
- package/dist/audit/types.d.ts +29 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/audit/types.js +2 -0
- package/dist/auth.d.ts +23 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +52 -0
- package/dist/cli/add.d.ts +18 -0
- package/dist/cli/add.d.ts.map +1 -0
- package/dist/cli/add.js +114 -0
- package/dist/cli/audit.d.ts +2 -0
- package/dist/cli/audit.d.ts.map +1 -0
- package/dist/cli/audit.js +58 -0
- package/dist/cli/guard.d.ts +2 -0
- package/dist/cli/guard.d.ts.map +1 -0
- package/dist/cli/guard.js +58 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +44 -0
- package/dist/cli/list.d.ts +5 -0
- package/dist/cli/list.d.ts.map +1 -0
- package/dist/cli/list.js +33 -0
- package/dist/cli/login.d.ts +6 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/login.js +43 -0
- package/dist/cli/remove.d.ts +6 -0
- package/dist/cli/remove.d.ts.map +1 -0
- package/dist/cli/remove.js +15 -0
- package/dist/cli/sync.d.ts +2 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +104 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +132 -0
- package/dist/guard/broker.d.ts +62 -0
- package/dist/guard/broker.d.ts.map +1 -0
- package/dist/guard/broker.js +147 -0
- package/dist/guard/dashboard.d.ts +14 -0
- package/dist/guard/dashboard.d.ts.map +1 -0
- package/dist/guard/dashboard.js +428 -0
- package/dist/guard/default-policy.json +33 -0
- package/dist/guard/index.d.ts +20 -0
- package/dist/guard/index.d.ts.map +1 -0
- package/dist/guard/index.js +61 -0
- package/dist/guard/logger.d.ts +30 -0
- package/dist/guard/logger.d.ts.map +1 -0
- package/dist/guard/logger.js +118 -0
- package/dist/guard/policy.d.ts +19 -0
- package/dist/guard/policy.d.ts.map +1 -0
- package/dist/guard/policy.js +108 -0
- package/dist/guard/proxy.d.ts +29 -0
- package/dist/guard/proxy.d.ts.map +1 -0
- package/dist/guard/proxy.js +109 -0
- package/dist/guard/types.d.ts +70 -0
- package/dist/guard/types.d.ts.map +1 -0
- package/dist/guard/types.js +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +259 -0
- package/dist/proxy.d.ts +122 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +165 -0
- package/dist/stack.d.ts +45 -0
- package/dist/stack.d.ts.map +1 -0
- package/dist/stack.js +93 -0
- package/dist/telemetry.d.ts +15 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +71 -0
- package/dist/tools/get-details.d.ts +14 -0
- package/dist/tools/get-details.d.ts.map +1 -0
- package/dist/tools/get-details.js +27 -0
- package/dist/tools/install.d.ts +2 -0
- package/dist/tools/install.d.ts.map +1 -0
- package/dist/tools/install.js +74 -0
- package/dist/tools/list-categories.d.ts +2 -0
- package/dist/tools/list-categories.d.ts.map +1 -0
- package/dist/tools/list-categories.js +13 -0
- package/dist/tools/search.d.ts +16 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +17 -0
- package/package.json +78 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* ActionLogger - Simple JSON-based persistent logger
|
|
5
|
+
* Production versions can migrate to proper SQLite
|
|
6
|
+
*/
|
|
7
|
+
export class ActionLogger {
|
|
8
|
+
constructor(dbPath) {
|
|
9
|
+
this.logs = [];
|
|
10
|
+
this.approvals = [];
|
|
11
|
+
this.dbPath = path.dirname(dbPath);
|
|
12
|
+
this.logsFile = path.join(this.dbPath, "action_logs.json");
|
|
13
|
+
this.approvalsFile = path.join(this.dbPath, "pending_approvals.json");
|
|
14
|
+
// Ensure directory exists
|
|
15
|
+
if (!fs.existsSync(this.dbPath)) {
|
|
16
|
+
fs.mkdirSync(this.dbPath, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
this.loadFromDisk();
|
|
19
|
+
}
|
|
20
|
+
loadFromDisk() {
|
|
21
|
+
try {
|
|
22
|
+
if (fs.existsSync(this.logsFile)) {
|
|
23
|
+
const data = fs.readFileSync(this.logsFile, "utf-8");
|
|
24
|
+
this.logs = JSON.parse(data);
|
|
25
|
+
}
|
|
26
|
+
if (fs.existsSync(this.approvalsFile)) {
|
|
27
|
+
const data = fs.readFileSync(this.approvalsFile, "utf-8");
|
|
28
|
+
this.approvals = JSON.parse(data);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.warn("Failed to load logs from disk:", error);
|
|
33
|
+
this.logs = [];
|
|
34
|
+
this.approvals = [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
saveToDisk() {
|
|
38
|
+
try {
|
|
39
|
+
fs.writeFileSync(this.logsFile, JSON.stringify(this.logs, null, 2));
|
|
40
|
+
fs.writeFileSync(this.approvalsFile, JSON.stringify(this.approvals, null, 2));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error("Failed to save logs to disk:", error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
initializeSchema() {
|
|
47
|
+
// No-op for JSON-based storage
|
|
48
|
+
}
|
|
49
|
+
logAction(log) {
|
|
50
|
+
// Prevent duplicates
|
|
51
|
+
if (!this.logs.find((l) => l.requestId === log.requestId)) {
|
|
52
|
+
this.logs.push(log);
|
|
53
|
+
this.saveToDisk();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
getPendingApprovals() {
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
return this.approvals
|
|
59
|
+
.filter((a) => a.status === "PENDING" && a.expiresAt > now)
|
|
60
|
+
.sort((a, b) => b.requestedAt - a.requestedAt)
|
|
61
|
+
.slice(0, 100);
|
|
62
|
+
}
|
|
63
|
+
approveAction(requestId, approvedBy) {
|
|
64
|
+
// Update action logs
|
|
65
|
+
const log = this.logs.find((l) => l.requestId === requestId);
|
|
66
|
+
if (log) {
|
|
67
|
+
log.approvalStatus = "APPROVED";
|
|
68
|
+
log.approvedAt = Date.now();
|
|
69
|
+
log.approvedBy = approvedBy;
|
|
70
|
+
}
|
|
71
|
+
// Update pending approvals
|
|
72
|
+
const approval = this.approvals.find((a) => a.requestId === requestId);
|
|
73
|
+
if (approval) {
|
|
74
|
+
approval.status = "APPROVED";
|
|
75
|
+
}
|
|
76
|
+
this.saveToDisk();
|
|
77
|
+
}
|
|
78
|
+
rejectAction(requestId, reason) {
|
|
79
|
+
// Update action logs
|
|
80
|
+
const log = this.logs.find((l) => l.requestId === requestId);
|
|
81
|
+
if (log) {
|
|
82
|
+
log.approvalStatus = "REJECTED";
|
|
83
|
+
log.rejectionReason = reason;
|
|
84
|
+
}
|
|
85
|
+
// Update pending approvals
|
|
86
|
+
const approval = this.approvals.find((a) => a.requestId === requestId);
|
|
87
|
+
if (approval) {
|
|
88
|
+
approval.status = "REJECTED";
|
|
89
|
+
}
|
|
90
|
+
this.saveToDisk();
|
|
91
|
+
}
|
|
92
|
+
getRecentActions(limit = 50) {
|
|
93
|
+
return this.logs
|
|
94
|
+
.sort((a, b) => b.timestamp - a.timestamp)
|
|
95
|
+
.slice(0, limit);
|
|
96
|
+
}
|
|
97
|
+
purgeExpired(retentionMinutes) {
|
|
98
|
+
const expirationTime = Date.now() - retentionMinutes * 60 * 1000;
|
|
99
|
+
const initialLength = this.logs.length;
|
|
100
|
+
this.logs = this.logs.filter((log) => !log.expiresAt || log.expiresAt >= expirationTime);
|
|
101
|
+
const purged = initialLength - this.logs.length;
|
|
102
|
+
if (purged > 0) {
|
|
103
|
+
this.saveToDisk();
|
|
104
|
+
}
|
|
105
|
+
return purged;
|
|
106
|
+
}
|
|
107
|
+
getSummary() {
|
|
108
|
+
const total = this.logs.length;
|
|
109
|
+
const blocked = this.logs.filter((l) => l.action === "BLOCK").length;
|
|
110
|
+
const approved = this.logs.filter((l) => l.approvalStatus === "APPROVED").length;
|
|
111
|
+
const rejected = this.logs.filter((l) => l.approvalStatus === "REJECTED").length;
|
|
112
|
+
return { total, blocked, approved, rejected };
|
|
113
|
+
}
|
|
114
|
+
close() {
|
|
115
|
+
this.saveToDisk();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { PolicyRule, PolicyAction, SeverityLevel, ToolCallRequest } from "./types.js";
|
|
2
|
+
export declare class PolicyEngine {
|
|
3
|
+
private rules;
|
|
4
|
+
private policyFilePath;
|
|
5
|
+
private readonly dangerousPatterns;
|
|
6
|
+
constructor(policyFilePath: string);
|
|
7
|
+
private loadPolicies;
|
|
8
|
+
private addDefaultPolicies;
|
|
9
|
+
evaluateToolCall(request: ToolCallRequest): {
|
|
10
|
+
action: PolicyAction;
|
|
11
|
+
ruleId?: string;
|
|
12
|
+
severity: SeverityLevel;
|
|
13
|
+
};
|
|
14
|
+
addRule(rule: PolicyRule): void;
|
|
15
|
+
removeRule(ruleId: string): void;
|
|
16
|
+
listRules(): PolicyRule[];
|
|
17
|
+
private savePolicies;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/guard/policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAItF,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,cAAc,CAAS;IAG/B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAIhC;gBAEU,cAAc,EAAE,MAAM;IAMlC,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,kBAAkB;IA8B1B,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG;QAC1C,MAAM,EAAE,YAAY,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,aAAa,CAAC;KACzB;IAuDD,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAK/B,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAKhC,SAAS,IAAI,UAAU,EAAE;IAIzB,OAAO,CAAC,YAAY;CAIrB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { minimatch } from "minimatch";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
export class PolicyEngine {
|
|
4
|
+
constructor(policyFilePath) {
|
|
5
|
+
this.rules = new Map();
|
|
6
|
+
// Dangerous patterns (default blocking rules)
|
|
7
|
+
this.dangerousPatterns = {
|
|
8
|
+
shell: ["sh", "bash", "zsh", "cmd", "powershell", "exec", "system"],
|
|
9
|
+
fileDelete: ["unlink", "rmdir", "rm", "del", "DeleteFile"],
|
|
10
|
+
secrets: ["SECRET", "TOKEN", "PASSWORD", "PRIVATE", "API_KEY", "CREDENTIAL"],
|
|
11
|
+
};
|
|
12
|
+
this.policyFilePath = policyFilePath;
|
|
13
|
+
this.loadPolicies();
|
|
14
|
+
this.addDefaultPolicies();
|
|
15
|
+
}
|
|
16
|
+
loadPolicies() {
|
|
17
|
+
try {
|
|
18
|
+
if (fs.existsSync(this.policyFilePath)) {
|
|
19
|
+
const data = fs.readFileSync(this.policyFilePath, "utf-8");
|
|
20
|
+
const policies = JSON.parse(data);
|
|
21
|
+
policies.forEach((rule) => this.rules.set(rule.id, rule));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.warn(`Failed to load policies: ${error}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
addDefaultPolicies() {
|
|
29
|
+
// Only add defaults if no policies exist
|
|
30
|
+
if (this.rules.size === 0) {
|
|
31
|
+
const defaults = [
|
|
32
|
+
{
|
|
33
|
+
id: "default-block-shell",
|
|
34
|
+
name: "Block Shell Execution",
|
|
35
|
+
serverName: "*",
|
|
36
|
+
toolName: "*",
|
|
37
|
+
action: "BLOCK",
|
|
38
|
+
severity: "CRITICAL",
|
|
39
|
+
enabled: true,
|
|
40
|
+
createdAt: Date.now(),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "default-block-file-delete",
|
|
44
|
+
name: "Block File Deletion",
|
|
45
|
+
serverName: "*",
|
|
46
|
+
toolName: "*rm*",
|
|
47
|
+
action: "BLOCK",
|
|
48
|
+
severity: "CRITICAL",
|
|
49
|
+
enabled: true,
|
|
50
|
+
createdAt: Date.now(),
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
defaults.forEach((rule) => this.rules.set(rule.id, rule));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
evaluateToolCall(request) {
|
|
57
|
+
for (const rule of this.rules.values()) {
|
|
58
|
+
if (!rule.enabled)
|
|
59
|
+
continue;
|
|
60
|
+
// Check glob patterns
|
|
61
|
+
if (!minimatch(request.serverId, rule.serverName))
|
|
62
|
+
continue;
|
|
63
|
+
if (!minimatch(request.toolName, rule.toolName))
|
|
64
|
+
continue;
|
|
65
|
+
// Check argument patterns
|
|
66
|
+
if (rule.argumentContains && rule.argumentContains.length > 0) {
|
|
67
|
+
const argsJson = JSON.stringify(request.arguments);
|
|
68
|
+
const hasMatch = rule.argumentContains.some((pattern) => argsJson.includes(pattern));
|
|
69
|
+
if (!hasMatch)
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// Check dangerous patterns in arguments
|
|
73
|
+
const argsJson = JSON.stringify(request.arguments).toUpperCase();
|
|
74
|
+
if (this.dangerousPatterns.shell.some((pattern) => argsJson.includes(pattern.toUpperCase()))) {
|
|
75
|
+
return { action: "BLOCK", ruleId: rule.id, severity: "CRITICAL" };
|
|
76
|
+
}
|
|
77
|
+
if (this.dangerousPatterns.fileDelete.some((pattern) => argsJson.includes(pattern.toUpperCase()))) {
|
|
78
|
+
return { action: "BLOCK", ruleId: rule.id, severity: "CRITICAL" };
|
|
79
|
+
}
|
|
80
|
+
if (this.dangerousPatterns.secrets.some((pattern) => argsJson.includes(pattern))) {
|
|
81
|
+
return { action: "BLOCK", ruleId: rule.id, severity: "CRITICAL" };
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
action: rule.action,
|
|
85
|
+
ruleId: rule.id,
|
|
86
|
+
severity: rule.severity,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Default: allow if no rules match
|
|
90
|
+
return { action: "ALLOW", severity: "INFO" };
|
|
91
|
+
}
|
|
92
|
+
addRule(rule) {
|
|
93
|
+
this.rules.set(rule.id, rule);
|
|
94
|
+
this.savePolicies();
|
|
95
|
+
}
|
|
96
|
+
removeRule(ruleId) {
|
|
97
|
+
this.rules.delete(ruleId);
|
|
98
|
+
this.savePolicies();
|
|
99
|
+
}
|
|
100
|
+
listRules() {
|
|
101
|
+
return Array.from(this.rules.values());
|
|
102
|
+
}
|
|
103
|
+
savePolicies() {
|
|
104
|
+
const policies = Array.from(this.rules.values());
|
|
105
|
+
fs.writeFileSync(this.policyFilePath, JSON.stringify(policies, null, 2));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CuratedBroker } from "./broker.js";
|
|
2
|
+
/**
|
|
3
|
+
* SentinelProxy intercepts MCP tool calls, evaluates them against local policies,
|
|
4
|
+
* and optionally syncs identity + audit to the curatedmcp.com control plane.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SentinelProxy {
|
|
7
|
+
private policyEngine;
|
|
8
|
+
private actionLogger;
|
|
9
|
+
private downstreamCommand;
|
|
10
|
+
private broker;
|
|
11
|
+
constructor(policyFilePath: string, dbPath: string, downstreamCommand: string, broker?: CuratedBroker | null);
|
|
12
|
+
/**
|
|
13
|
+
* Evaluate a tool call request against local policies and the cloud broker.
|
|
14
|
+
* Local policy always wins: a local BLOCK is never overridden by the broker.
|
|
15
|
+
* Broker verify is fail-open: if unreachable, the local decision stands.
|
|
16
|
+
*/
|
|
17
|
+
evaluateRequest(toolName: string, serverId: string, args: Record<string, unknown>): Promise<{
|
|
18
|
+
success: boolean;
|
|
19
|
+
requestId: `${string}-${string}-${string}-${string}-${string}`;
|
|
20
|
+
decision: {
|
|
21
|
+
action: import("./types.js").PolicyAction;
|
|
22
|
+
ruleId?: string;
|
|
23
|
+
severity: import("./types.js").SeverityLevel;
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
start(): Promise<void>;
|
|
27
|
+
shutdown(): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/guard/proxy.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,MAAM,CAAuB;gBAGnC,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,MAAM,EACzB,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI;IAQ/B;;;;OAIG;IACG,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;IAmFzB,KAAK;IAcX,QAAQ;CAGT"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { PolicyEngine } from "./policy.js";
|
|
2
|
+
import { ActionLogger } from "./logger.js";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
/**
|
|
5
|
+
* SentinelProxy intercepts MCP tool calls, evaluates them against local policies,
|
|
6
|
+
* and optionally syncs identity + audit to the curatedmcp.com control plane.
|
|
7
|
+
*/
|
|
8
|
+
export class SentinelProxy {
|
|
9
|
+
constructor(policyFilePath, dbPath, downstreamCommand, broker) {
|
|
10
|
+
this.policyEngine = new PolicyEngine(policyFilePath);
|
|
11
|
+
this.actionLogger = new ActionLogger(dbPath);
|
|
12
|
+
this.downstreamCommand = downstreamCommand;
|
|
13
|
+
this.broker = broker ?? null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Evaluate a tool call request against local policies and the cloud broker.
|
|
17
|
+
* Local policy always wins: a local BLOCK is never overridden by the broker.
|
|
18
|
+
* Broker verify is fail-open: if unreachable, the local decision stands.
|
|
19
|
+
*/
|
|
20
|
+
async evaluateRequest(toolName, serverId, args) {
|
|
21
|
+
const requestId = randomUUID();
|
|
22
|
+
const timestamp = Date.now();
|
|
23
|
+
const toolCall = {
|
|
24
|
+
id: requestId,
|
|
25
|
+
serverId,
|
|
26
|
+
toolName,
|
|
27
|
+
arguments: args,
|
|
28
|
+
timestamp,
|
|
29
|
+
};
|
|
30
|
+
// 1. Local policy evaluation (always runs)
|
|
31
|
+
const decision = this.policyEngine.evaluateToolCall(toolCall);
|
|
32
|
+
const log = {
|
|
33
|
+
id: randomUUID(),
|
|
34
|
+
requestId,
|
|
35
|
+
serverId,
|
|
36
|
+
toolName,
|
|
37
|
+
arguments: JSON.stringify(args),
|
|
38
|
+
action: decision.action,
|
|
39
|
+
severity: decision.severity,
|
|
40
|
+
ruleId: decision.ruleId,
|
|
41
|
+
approvalStatus: decision.action === "REQUIRE_APPROVAL" ? "PENDING" : "CLEARED",
|
|
42
|
+
timestamp,
|
|
43
|
+
expiresAt: timestamp + 24 * 60 * 60 * 1000,
|
|
44
|
+
};
|
|
45
|
+
this.actionLogger.logAction(log);
|
|
46
|
+
if (decision.action === "BLOCK") {
|
|
47
|
+
// Log block to broker (fire-and-forget)
|
|
48
|
+
this.broker?.logInvocation({
|
|
49
|
+
serverSlug: serverId,
|
|
50
|
+
toolName,
|
|
51
|
+
args,
|
|
52
|
+
outcome: "BLOCKED",
|
|
53
|
+
blockReason: decision.ruleId ?? "local_policy",
|
|
54
|
+
latencyMs: Date.now() - timestamp,
|
|
55
|
+
});
|
|
56
|
+
throw new Error(`Tool call blocked by policy: ${decision.ruleId ?? "unknown"}`);
|
|
57
|
+
}
|
|
58
|
+
if (decision.action === "REQUIRE_APPROVAL") {
|
|
59
|
+
throw new Error(`Tool call requires approval: ${requestId}`);
|
|
60
|
+
}
|
|
61
|
+
// 2. Cloud broker verify (only when connected; fail-open)
|
|
62
|
+
let jitTokenId;
|
|
63
|
+
if (this.broker) {
|
|
64
|
+
const jitToken = await this.broker.getJitToken(serverId);
|
|
65
|
+
if (jitToken) {
|
|
66
|
+
const verify = await this.broker.verify(jitToken, serverId, toolName);
|
|
67
|
+
if (!verify.allowed) {
|
|
68
|
+
this.broker.logInvocation({
|
|
69
|
+
serverSlug: serverId,
|
|
70
|
+
toolName,
|
|
71
|
+
args,
|
|
72
|
+
outcome: "BLOCKED",
|
|
73
|
+
blockReason: verify.reason ?? "broker_denied",
|
|
74
|
+
latencyMs: Date.now() - timestamp,
|
|
75
|
+
});
|
|
76
|
+
throw new Error(`Tool call denied by registry: ${verify.reason ?? "policy"}`);
|
|
77
|
+
}
|
|
78
|
+
jitTokenId = verify.jitTokenId;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 3. Log allowed invocation to broker (fire-and-forget)
|
|
82
|
+
this.broker?.logInvocation({
|
|
83
|
+
serverSlug: serverId,
|
|
84
|
+
toolName,
|
|
85
|
+
args,
|
|
86
|
+
outcome: "ALLOWED",
|
|
87
|
+
latencyMs: Date.now() - timestamp,
|
|
88
|
+
jitTokenId,
|
|
89
|
+
});
|
|
90
|
+
return { success: true, requestId, decision };
|
|
91
|
+
}
|
|
92
|
+
async start() {
|
|
93
|
+
// Register with broker on startup (idempotent)
|
|
94
|
+
if (this.broker) {
|
|
95
|
+
const id = await this.broker.register(`Sentinel — ${this.downstreamCommand.split(" ")[0]}`);
|
|
96
|
+
if (id) {
|
|
97
|
+
console.log(`🔐 Connected to CuratedMCP registry (identity: ${id.slice(0, 8)}…)`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.warn("⚠️ CuratedMCP broker configured but registration failed — running in local-only mode");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.log(`🔗 Sentinel Proxy initialized for: ${this.downstreamCommand}`);
|
|
104
|
+
}
|
|
105
|
+
shutdown() {
|
|
106
|
+
this.actionLogger.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type PolicyAction = "ALLOW" | "BLOCK" | "REQUIRE_APPROVAL";
|
|
2
|
+
export type SeverityLevel = "INFO" | "WARNING" | "CRITICAL";
|
|
3
|
+
export type ApprovalStatus = "PENDING" | "APPROVED" | "REJECTED" | "CLEARED";
|
|
4
|
+
export interface PolicyRule {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
serverName: string;
|
|
8
|
+
toolName: string;
|
|
9
|
+
argumentContains?: string[];
|
|
10
|
+
action: PolicyAction;
|
|
11
|
+
severity: SeverityLevel;
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
createdAt: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ToolCallRequest {
|
|
16
|
+
id: string;
|
|
17
|
+
serverId: string;
|
|
18
|
+
toolName: string;
|
|
19
|
+
arguments: Record<string, unknown>;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ActionLog {
|
|
23
|
+
id: string;
|
|
24
|
+
requestId: string;
|
|
25
|
+
serverId: string;
|
|
26
|
+
toolName: string;
|
|
27
|
+
arguments: string;
|
|
28
|
+
action: PolicyAction;
|
|
29
|
+
severity: SeverityLevel;
|
|
30
|
+
ruleId?: string;
|
|
31
|
+
approvalStatus: ApprovalStatus;
|
|
32
|
+
approvedAt?: number;
|
|
33
|
+
approvedBy?: string;
|
|
34
|
+
rejectionReason?: string;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
expiresAt?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface PendingApproval {
|
|
39
|
+
id: string;
|
|
40
|
+
requestId: string;
|
|
41
|
+
serverId: string;
|
|
42
|
+
toolName: string;
|
|
43
|
+
arguments: string;
|
|
44
|
+
ruleId: string;
|
|
45
|
+
requestedAt: number;
|
|
46
|
+
expiresAt: number;
|
|
47
|
+
status: "PENDING" | "APPROVED" | "REJECTED";
|
|
48
|
+
}
|
|
49
|
+
export interface DashboardData {
|
|
50
|
+
summary: {
|
|
51
|
+
totalLogs: number;
|
|
52
|
+
blockedCount: number;
|
|
53
|
+
approvedCount: number;
|
|
54
|
+
rejectedCount: number;
|
|
55
|
+
};
|
|
56
|
+
recentActions: ActionLog[];
|
|
57
|
+
pendingApprovals: PendingApproval[];
|
|
58
|
+
activePolicies: PolicyRule[];
|
|
59
|
+
retentionMinutes: number;
|
|
60
|
+
}
|
|
61
|
+
export interface SentinelConfig {
|
|
62
|
+
policyFile: string;
|
|
63
|
+
dbPath: string;
|
|
64
|
+
dashboardPort: number;
|
|
65
|
+
retentionMinutes: number;
|
|
66
|
+
licenseKey?: string;
|
|
67
|
+
offlineMode: boolean;
|
|
68
|
+
telemetryEnabled: boolean;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/guard/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,kBAAkB,CAAC;AAClE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAE7E,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,aAAa,EAAE,SAAS,EAAE,CAAC;IAC3B,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,cAAc,EAAE,UAAU,EAAE,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|