@datasynx/agentic-ai-cartography 2.4.0 → 2.5.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/dist/api-bin.js +2 -2
- package/dist/{chunk-X5JA2UDT.js → chunk-GA4427LB.js} +134 -14
- package/dist/chunk-GA4427LB.js.map +1 -0
- package/dist/{chunk-L4OSL7I6.js → chunk-NQXZUWOI.js} +41 -11
- package/dist/chunk-NQXZUWOI.js.map +1 -0
- package/dist/{chunk-B4QWX7CP.js → chunk-RYQ4KQCK.js} +55 -11
- package/dist/chunk-RYQ4KQCK.js.map +1 -0
- package/dist/cli.js +87 -8
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +262 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +223 -3
- package/dist/index.d.ts +223 -3
- package/dist/index.js +244 -24
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-B4QWX7CP.js.map +0 -1
- package/dist/chunk-L4OSL7I6.js.map +0 -1
- package/dist/chunk-X5JA2UDT.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
+
ACTIONS: () => ACTIONS,
|
|
34
|
+
ActionSchema: () => ActionSchema,
|
|
35
|
+
AuthConfigSchema: () => AuthConfigSchema,
|
|
36
|
+
AuthorizationError: () => AuthorizationError,
|
|
33
37
|
CLIENTS: () => CLIENTS,
|
|
34
38
|
CONFIDENCE: () => CONFIDENCE,
|
|
35
39
|
CartographyDB: () => CartographyDB,
|
|
@@ -38,6 +42,7 @@ __export(src_exports, {
|
|
|
38
42
|
ConditionSchema: () => ConditionSchema,
|
|
39
43
|
ConfigError: () => ConfigError,
|
|
40
44
|
ControlResultSchema: () => ControlResultSchema,
|
|
45
|
+
CredentialConfigSchema: () => CredentialConfigSchema,
|
|
41
46
|
CsvCostSource: () => CsvCostSource,
|
|
42
47
|
DEFAULT_ANOMALY_THRESHOLDS: () => DEFAULT_ANOMALY_THRESHOLDS,
|
|
43
48
|
DEFAULT_SERVER_NAME: () => DEFAULT_SERVER_NAME,
|
|
@@ -57,8 +62,11 @@ __export(src_exports, {
|
|
|
57
62
|
PRIVATE_IP: () => PRIVATE_IP,
|
|
58
63
|
PUSH_SCHEMA_VERSION: () => PUSH_SCHEMA_VERSION,
|
|
59
64
|
PagerDutySink: () => PagerDutySink,
|
|
65
|
+
PrincipalSchema: () => PrincipalSchema,
|
|
60
66
|
ProviderRegistry: () => ProviderRegistry,
|
|
61
67
|
RELATION_TO_DIRECTION: () => RELATION_TO_DIRECTION,
|
|
68
|
+
ROLES: () => ROLES,
|
|
69
|
+
RoleSchema: () => RoleSchema,
|
|
62
70
|
RuleCheckSchema: () => RuleCheckSchema,
|
|
63
71
|
RulesetSchema: () => RulesetSchema,
|
|
64
72
|
SCAN_ARG_PATTERNS: () => SCAN_ARG_PATTERNS,
|
|
@@ -69,10 +77,12 @@ __export(src_exports, {
|
|
|
69
77
|
ScannerShape: () => ScannerShape,
|
|
70
78
|
SharingLevelSchema: () => SharingLevelSchema,
|
|
71
79
|
SlackSink: () => SlackSink,
|
|
80
|
+
SqliteCredentialStore: () => SqliteCredentialStore,
|
|
72
81
|
SqliteQueryBackend: () => SqliteQueryBackend,
|
|
73
82
|
SqliteStoreBackend: () => SqliteStoreBackend,
|
|
74
83
|
StdoutSink: () => StdoutSink,
|
|
75
84
|
TENANT_HEADER: () => TENANT_HEADER,
|
|
85
|
+
TenantMismatchError: () => TenantMismatchError,
|
|
76
86
|
VectorStore: () => VectorStore,
|
|
77
87
|
WebhookSink: () => WebhookSink,
|
|
78
88
|
applyInstall: () => applyInstall,
|
|
@@ -80,7 +90,9 @@ __export(src_exports, {
|
|
|
80
90
|
assertReadOnly: () => assertReadOnly,
|
|
81
91
|
assertSafeBind: () => assertSafeBind,
|
|
82
92
|
assertSafeScanArg: () => assertSafeScanArg,
|
|
93
|
+
assertSameTenant: () => assertSameTenant,
|
|
83
94
|
assignColors: () => assignColors,
|
|
95
|
+
authorize: () => authorize,
|
|
84
96
|
bearerToken: () => bearerToken,
|
|
85
97
|
bookmarksScanner: () => bookmarksScanner,
|
|
86
98
|
buildCartographyToolHandlers: () => buildCartographyToolHandlers,
|
|
@@ -88,6 +100,7 @@ __export(src_exports, {
|
|
|
88
100
|
buildOpenApiDocument: () => buildOpenApiDocument,
|
|
89
101
|
buildReport: () => buildReport,
|
|
90
102
|
buildSinks: () => buildSinks,
|
|
103
|
+
can: () => can,
|
|
91
104
|
centralDbFromEnv: () => centralDbFromEnv,
|
|
92
105
|
checkBearer: () => checkBearer,
|
|
93
106
|
checkPrerequisites: () => checkPrerequisites,
|
|
@@ -164,6 +177,7 @@ __export(src_exports, {
|
|
|
164
177
|
globalId: () => globalId,
|
|
165
178
|
groupByDomain: () => groupByDomain,
|
|
166
179
|
handleGraphqlGet: () => handleGraphqlGet,
|
|
180
|
+
hashToken: () => hashToken,
|
|
167
181
|
hexCorners: () => hexCorners,
|
|
168
182
|
hexDistance: () => hexDistance,
|
|
169
183
|
hexNeighbors: () => hexNeighbors,
|
|
@@ -230,6 +244,7 @@ __export(src_exports, {
|
|
|
230
244
|
renderDiff: () => renderDiff,
|
|
231
245
|
resolveEffectiveLevel: () => resolveEffectiveLevel,
|
|
232
246
|
resolveNlQuery: () => resolveNlQuery,
|
|
247
|
+
resolvePrincipal: () => resolvePrincipal,
|
|
233
248
|
resolveSharingLevel: () => resolveSharingLevel,
|
|
234
249
|
resolveTenant: () => resolveTenant,
|
|
235
250
|
revalidateAnonymized: () => revalidateAnonymized,
|
|
@@ -249,6 +264,7 @@ __export(src_exports, {
|
|
|
249
264
|
safetyHook: () => safetyHook,
|
|
250
265
|
sanitizeUntrusted: () => sanitizeUntrusted,
|
|
251
266
|
sanitizeValue: () => sanitizeValue,
|
|
267
|
+
scopeReads: () => scopeReads,
|
|
252
268
|
scoreTopology: () => scoreTopology,
|
|
253
269
|
securityRelevantChange: () => securityRelevantChange,
|
|
254
270
|
serializeConfig: () => serializeConfig,
|
|
@@ -3085,7 +3101,10 @@ CREATE TABLE IF NOT EXISTS activity_events (
|
|
|
3085
3101
|
duration_ms INTEGER,
|
|
3086
3102
|
command TEXT,
|
|
3087
3103
|
result_bytes INTEGER,
|
|
3088
|
-
tenant TEXT NOT NULL DEFAULT 'local'
|
|
3104
|
+
tenant TEXT NOT NULL DEFAULT 'local',
|
|
3105
|
+
actor_subject TEXT,
|
|
3106
|
+
actor_role TEXT,
|
|
3107
|
+
actor_tenant TEXT
|
|
3089
3108
|
);
|
|
3090
3109
|
|
|
3091
3110
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -3194,6 +3213,16 @@ CREATE INDEX IF NOT EXISTS idx_nodes_tenant_content ON nodes(tenant, content_has
|
|
|
3194
3213
|
CREATE INDEX IF NOT EXISTS idx_contrib_org ON node_contributors(organization, global_id);
|
|
3195
3214
|
CREATE INDEX IF NOT EXISTS idx_nodes_owner ON nodes(session_id, owner);
|
|
3196
3215
|
`;
|
|
3216
|
+
var AUTH_SCHEMA = `
|
|
3217
|
+
CREATE TABLE IF NOT EXISTS auth_credentials (
|
|
3218
|
+
token_hash TEXT PRIMARY KEY,
|
|
3219
|
+
subject TEXT NOT NULL,
|
|
3220
|
+
tenant TEXT NOT NULL DEFAULT 'local',
|
|
3221
|
+
role TEXT NOT NULL,
|
|
3222
|
+
created_at TEXT NOT NULL
|
|
3223
|
+
);
|
|
3224
|
+
CREATE INDEX IF NOT EXISTS idx_auth_subject ON auth_credentials(subject);
|
|
3225
|
+
`;
|
|
3197
3226
|
var CartographyDB = class {
|
|
3198
3227
|
db;
|
|
3199
3228
|
/** 3.6 anomaly settings; defaults apply when no `anomaly` config is supplied. */
|
|
@@ -3213,7 +3242,8 @@ var CartographyDB = class {
|
|
|
3213
3242
|
const version = this.db.pragma("user_version", { simple: true });
|
|
3214
3243
|
if (version === 0) {
|
|
3215
3244
|
this.db.exec(SCHEMA);
|
|
3216
|
-
this.db.
|
|
3245
|
+
this.db.exec(AUTH_SCHEMA);
|
|
3246
|
+
this.db.pragma("user_version = 15");
|
|
3217
3247
|
return;
|
|
3218
3248
|
} else if (version === 1) {
|
|
3219
3249
|
const cols = this.db.prepare("PRAGMA table_info(nodes)").all().map((c) => c.name);
|
|
@@ -3399,6 +3429,18 @@ var CartographyDB = class {
|
|
|
3399
3429
|
}
|
|
3400
3430
|
this.db.pragma("user_version = 14");
|
|
3401
3431
|
}
|
|
3432
|
+
const v14 = this.db.pragma("user_version", { simple: true });
|
|
3433
|
+
if (v14 < 15) {
|
|
3434
|
+
this.db.exec(AUTH_SCHEMA);
|
|
3435
|
+
const ev = this.db.prepare("PRAGMA table_info(activity_events)").all();
|
|
3436
|
+
if (ev.length > 0) {
|
|
3437
|
+
const cols = ev.map((c) => c.name);
|
|
3438
|
+
if (!cols.includes("actor_subject")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_subject TEXT");
|
|
3439
|
+
if (!cols.includes("actor_role")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_role TEXT");
|
|
3440
|
+
if (!cols.includes("actor_tenant")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_tenant TEXT");
|
|
3441
|
+
}
|
|
3442
|
+
this.db.pragma("user_version = 15");
|
|
3443
|
+
}
|
|
3402
3444
|
}
|
|
3403
3445
|
close() {
|
|
3404
3446
|
this.db.pragma("optimize");
|
|
@@ -3829,13 +3871,13 @@ var CartographyDB = class {
|
|
|
3829
3871
|
});
|
|
3830
3872
|
}
|
|
3831
3873
|
// ── Events ──────────────────────────────
|
|
3832
|
-
insertEvent(sessionId, event, taskId) {
|
|
3874
|
+
insertEvent(sessionId, event, taskId, actor) {
|
|
3833
3875
|
const id = crypto.randomUUID();
|
|
3834
3876
|
const tenant = this.tenantOf(sessionId);
|
|
3835
3877
|
this.db.prepare(`
|
|
3836
3878
|
INSERT INTO activity_events
|
|
3837
|
-
(id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant)
|
|
3838
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3879
|
+
(id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant, actor_subject, actor_role, actor_tenant)
|
|
3880
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3839
3881
|
`).run(
|
|
3840
3882
|
id,
|
|
3841
3883
|
sessionId,
|
|
@@ -3849,9 +3891,52 @@ var CartographyDB = class {
|
|
|
3849
3891
|
event.port ?? null,
|
|
3850
3892
|
event.command ?? null,
|
|
3851
3893
|
event.resultBytes ?? null,
|
|
3852
|
-
tenant
|
|
3894
|
+
tenant,
|
|
3895
|
+
actor?.subject ?? null,
|
|
3896
|
+
actor?.role ?? null,
|
|
3897
|
+
actor?.tenant ?? null
|
|
3853
3898
|
);
|
|
3854
3899
|
}
|
|
3900
|
+
// ── RBAC credential store (4.5) ─────────────
|
|
3901
|
+
/** Number of stored credentials. `0` ⇒ no RBAC configured (fall back to shared/loopback). */
|
|
3902
|
+
countCredentials() {
|
|
3903
|
+
return this.db.prepare("SELECT COUNT(*) AS n FROM auth_credentials").get().n;
|
|
3904
|
+
}
|
|
3905
|
+
/** Look up a credential by its sha256 token hash. */
|
|
3906
|
+
findCredentialByHash(tokenHash) {
|
|
3907
|
+
const r = this.db.prepare("SELECT * FROM auth_credentials WHERE token_hash = ?").get(tokenHash);
|
|
3908
|
+
if (!r) return void 0;
|
|
3909
|
+
return {
|
|
3910
|
+
tokenHash: r["token_hash"],
|
|
3911
|
+
subject: r["subject"],
|
|
3912
|
+
tenant: r["tenant"],
|
|
3913
|
+
role: r["role"],
|
|
3914
|
+
createdAt: r["created_at"]
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
/** Upsert a credential (idempotent on the token hash). Stores only the hash, never the raw token. */
|
|
3918
|
+
addCredential(rec) {
|
|
3919
|
+
this.db.prepare(`
|
|
3920
|
+
INSERT INTO auth_credentials (token_hash, subject, tenant, role, created_at)
|
|
3921
|
+
VALUES (?, ?, ?, ?, ?)
|
|
3922
|
+
ON CONFLICT(token_hash) DO UPDATE SET subject = excluded.subject, tenant = excluded.tenant, role = excluded.role
|
|
3923
|
+
`).run(rec.tokenHash, rec.subject, rec.tenant, rec.role, (/* @__PURE__ */ new Date()).toISOString());
|
|
3924
|
+
}
|
|
3925
|
+
/** List all credentials (token hashes only — the raw token is unrecoverable). */
|
|
3926
|
+
listCredentials() {
|
|
3927
|
+
const rows = this.db.prepare("SELECT * FROM auth_credentials ORDER BY created_at").all();
|
|
3928
|
+
return rows.map((r) => ({
|
|
3929
|
+
tokenHash: r["token_hash"],
|
|
3930
|
+
subject: r["subject"],
|
|
3931
|
+
tenant: r["tenant"],
|
|
3932
|
+
role: r["role"],
|
|
3933
|
+
createdAt: r["created_at"]
|
|
3934
|
+
}));
|
|
3935
|
+
}
|
|
3936
|
+
/** Revoke every credential for a subject. Returns the number removed. */
|
|
3937
|
+
revokeCredentialsBySubject(subject) {
|
|
3938
|
+
return this.db.prepare("DELETE FROM auth_credentials WHERE subject = ?").run(subject).changes;
|
|
3939
|
+
}
|
|
3855
3940
|
getEvents(sessionId, since) {
|
|
3856
3941
|
const rows = since ? this.db.prepare("SELECT * FROM activity_events WHERE session_id = ? AND timestamp > ? ORDER BY timestamp").all(sessionId, since) : this.db.prepare("SELECT * FROM activity_events WHERE session_id = ? ORDER BY timestamp").all(sessionId);
|
|
3857
3942
|
return rows.map((r) => {
|
|
@@ -5530,6 +5615,36 @@ async function runDrift(db, config, opts = {}) {
|
|
|
5530
5615
|
return alert;
|
|
5531
5616
|
}
|
|
5532
5617
|
|
|
5618
|
+
// src/auth/rbac.ts
|
|
5619
|
+
var ROLE_RANK = { viewer: 1, operator: 2, admin: 3 };
|
|
5620
|
+
var ACTION_MIN_ROLE = { read: "viewer", discovery: "operator", admin: "admin" };
|
|
5621
|
+
function can(role, action) {
|
|
5622
|
+
return ROLE_RANK[role] >= ROLE_RANK[ACTION_MIN_ROLE[action]];
|
|
5623
|
+
}
|
|
5624
|
+
var AuthorizationError = class extends Error {
|
|
5625
|
+
constructor(action, role) {
|
|
5626
|
+
super(`forbidden: role '${role}' may not perform '${action}'`);
|
|
5627
|
+
this.action = action;
|
|
5628
|
+
this.role = role;
|
|
5629
|
+
this.name = "AuthorizationError";
|
|
5630
|
+
}
|
|
5631
|
+
};
|
|
5632
|
+
function authorize(principal, action) {
|
|
5633
|
+
if (!can(principal.role, action)) throw new AuthorizationError(action, principal.role);
|
|
5634
|
+
}
|
|
5635
|
+
var TenantMismatchError = class extends Error {
|
|
5636
|
+
constructor() {
|
|
5637
|
+
super("forbidden: principal is not scoped to the requested tenant");
|
|
5638
|
+
this.name = "TenantMismatchError";
|
|
5639
|
+
}
|
|
5640
|
+
};
|
|
5641
|
+
function scopeReads(principal) {
|
|
5642
|
+
return principal.tenant;
|
|
5643
|
+
}
|
|
5644
|
+
function assertSameTenant(principal, requestedTenant) {
|
|
5645
|
+
if (requestedTenant !== principal.tenant) throw new TenantMismatchError();
|
|
5646
|
+
}
|
|
5647
|
+
|
|
5533
5648
|
// src/compliance/rulesets/baseline.ts
|
|
5534
5649
|
var baseline = RulesetSchema.parse({
|
|
5535
5650
|
name: "baseline",
|
|
@@ -5817,7 +5932,7 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
|
|
|
5817
5932
|
|
|
5818
5933
|
// src/mcp/server.ts
|
|
5819
5934
|
var SERVER_NAME = "cartography";
|
|
5820
|
-
var SERVER_VERSION = "2.
|
|
5935
|
+
var SERVER_VERSION = "2.5.0";
|
|
5821
5936
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
5822
5937
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
5823
5938
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -6219,6 +6334,14 @@ function createMcpServer(opts = {}) {
|
|
|
6219
6334
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
6220
6335
|
},
|
|
6221
6336
|
async (args) => {
|
|
6337
|
+
if (opts.principal) {
|
|
6338
|
+
try {
|
|
6339
|
+
authorize(opts.principal, "discovery");
|
|
6340
|
+
} catch (err) {
|
|
6341
|
+
if (err instanceof AuthorizationError) return json({ error: `forbidden: role '${opts.principal.role}' may not run discovery (operator required)` });
|
|
6342
|
+
throw err;
|
|
6343
|
+
}
|
|
6344
|
+
}
|
|
6222
6345
|
let sid = resolveSession();
|
|
6223
6346
|
if (args.update) {
|
|
6224
6347
|
if (!sid) return json({ error: "No session to update; run discovery first." });
|
|
@@ -6314,7 +6437,7 @@ function createMcpServer(opts = {}) {
|
|
|
6314
6437
|
}
|
|
6315
6438
|
|
|
6316
6439
|
// src/mcp/transports.ts
|
|
6317
|
-
var
|
|
6440
|
+
var import_node_crypto6 = require("crypto");
|
|
6318
6441
|
var import_node_http = __toESM(require("http"), 1);
|
|
6319
6442
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6320
6443
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
@@ -6361,7 +6484,41 @@ function defaultAllowedHosts(host2, port) {
|
|
|
6361
6484
|
return [`${host2}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];
|
|
6362
6485
|
}
|
|
6363
6486
|
|
|
6487
|
+
// src/auth/identity.ts
|
|
6488
|
+
var import_node_crypto5 = require("crypto");
|
|
6489
|
+
function hashToken(token) {
|
|
6490
|
+
return (0, import_node_crypto5.createHash)("sha256").update(token, "utf8").digest("hex");
|
|
6491
|
+
}
|
|
6492
|
+
var SqliteCredentialStore = class {
|
|
6493
|
+
constructor(db) {
|
|
6494
|
+
this.db = db;
|
|
6495
|
+
}
|
|
6496
|
+
count() {
|
|
6497
|
+
return this.db.countCredentials();
|
|
6498
|
+
}
|
|
6499
|
+
findByHash(tokenHash) {
|
|
6500
|
+
return this.db.findCredentialByHash(tokenHash);
|
|
6501
|
+
}
|
|
6502
|
+
};
|
|
6503
|
+
function resolvePrincipal(presentedToken, opts) {
|
|
6504
|
+
const tenant = opts.defaultTenant ?? DEFAULT_TENANT;
|
|
6505
|
+
if (opts.store && opts.store.count() > 0) {
|
|
6506
|
+
if (!presentedToken) return void 0;
|
|
6507
|
+
const rec = opts.store.findByHash(hashToken(presentedToken));
|
|
6508
|
+
return rec ? { subject: rec.subject, tenant: rec.tenant, role: rec.role } : void 0;
|
|
6509
|
+
}
|
|
6510
|
+
if (opts.sharedToken) {
|
|
6511
|
+
if (!presentedToken || !timingSafeEqual(presentedToken, opts.sharedToken)) return void 0;
|
|
6512
|
+
return { subject: "shared-token", tenant, role: "admin" };
|
|
6513
|
+
}
|
|
6514
|
+
if (opts.required) return void 0;
|
|
6515
|
+
return { subject: "anonymous", tenant, role: "admin" };
|
|
6516
|
+
}
|
|
6517
|
+
|
|
6364
6518
|
// src/mcp/transports.ts
|
|
6519
|
+
function samePrincipal(a, b) {
|
|
6520
|
+
return a.subject === b.subject && a.tenant === b.tenant && a.role === b.role;
|
|
6521
|
+
}
|
|
6365
6522
|
async function runStdio(server) {
|
|
6366
6523
|
const transport = new import_stdio.StdioServerTransport();
|
|
6367
6524
|
await server.connect(transport);
|
|
@@ -6406,6 +6563,14 @@ async function runHttp(factory, opts = {}) {
|
|
|
6406
6563
|
assertSafeBind({ host: host2, port, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...opts.token ? { token: opts.token } : {} });
|
|
6407
6564
|
const allowedHosts = opts.allowedHosts ?? defaultAllowedHosts(host2, port);
|
|
6408
6565
|
const token = opts.token;
|
|
6566
|
+
const authStore = opts.auth?.store;
|
|
6567
|
+
const defaultTenant = opts.defaultTenant;
|
|
6568
|
+
const resolveAuth = (header) => resolvePrincipal(bearerToken(header), {
|
|
6569
|
+
...authStore ? { store: authStore } : {},
|
|
6570
|
+
...token ? { sharedToken: token } : {},
|
|
6571
|
+
...defaultTenant ? { defaultTenant } : {},
|
|
6572
|
+
...opts.auth?.required ? { required: true } : {}
|
|
6573
|
+
});
|
|
6409
6574
|
const transports = /* @__PURE__ */ new Map();
|
|
6410
6575
|
const httpServer = import_node_http.default.createServer(async (req, res) => {
|
|
6411
6576
|
try {
|
|
@@ -6415,7 +6580,8 @@ async function runHttp(factory, opts = {}) {
|
|
|
6415
6580
|
res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
|
|
6416
6581
|
return;
|
|
6417
6582
|
}
|
|
6418
|
-
|
|
6583
|
+
const principal = resolveAuth(req.headers["authorization"]);
|
|
6584
|
+
if (!principal) {
|
|
6419
6585
|
res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" }).end('{"error":"unauthorized"}');
|
|
6420
6586
|
return;
|
|
6421
6587
|
}
|
|
@@ -6442,8 +6608,12 @@ async function runHttp(factory, opts = {}) {
|
|
|
6442
6608
|
const sessionId = req.headers["mcp-session-id"];
|
|
6443
6609
|
const existing = sessionId ? transports.get(sessionId) : void 0;
|
|
6444
6610
|
if (existing) {
|
|
6611
|
+
if (!samePrincipal(existing.principal, principal)) {
|
|
6612
|
+
res.writeHead(403, { "content-type": "application/json" }).end('{"error":"session belongs to a different principal"}');
|
|
6613
|
+
return;
|
|
6614
|
+
}
|
|
6445
6615
|
const body2 = req.method === "POST" ? await readJsonBody(req) : void 0;
|
|
6446
|
-
await existing.handleRequest(req, res, body2);
|
|
6616
|
+
await existing.transport.handleRequest(req, res, body2);
|
|
6447
6617
|
return;
|
|
6448
6618
|
}
|
|
6449
6619
|
if (req.method !== "POST") {
|
|
@@ -6452,18 +6622,18 @@ async function runHttp(factory, opts = {}) {
|
|
|
6452
6622
|
}
|
|
6453
6623
|
const body = await readJsonBody(req);
|
|
6454
6624
|
const transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
6455
|
-
sessionIdGenerator: () => (0,
|
|
6625
|
+
sessionIdGenerator: () => (0, import_node_crypto6.randomUUID)(),
|
|
6456
6626
|
enableDnsRebindingProtection: true,
|
|
6457
6627
|
allowedHosts,
|
|
6458
6628
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
6459
6629
|
onsessioninitialized: (id) => {
|
|
6460
|
-
transports.set(id, transport);
|
|
6630
|
+
transports.set(id, { transport, principal });
|
|
6461
6631
|
}
|
|
6462
6632
|
});
|
|
6463
6633
|
transport.onclose = () => {
|
|
6464
6634
|
if (transport.sessionId) transports.delete(transport.sessionId);
|
|
6465
6635
|
};
|
|
6466
|
-
await factory().connect(transport);
|
|
6636
|
+
await factory(principal).connect(transport);
|
|
6467
6637
|
await transport.handleRequest(req, res, body);
|
|
6468
6638
|
} catch (err) {
|
|
6469
6639
|
process.stderr.write(`[cartography-mcp] HTTP request failed: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -8090,6 +8260,8 @@ async function runApi(opts) {
|
|
|
8090
8260
|
const token = opts.token;
|
|
8091
8261
|
const graphqlEnabled = opts.graphql !== false;
|
|
8092
8262
|
const defaultTenant = opts.tenant?.defaultTenant ?? DEFAULT_TENANT;
|
|
8263
|
+
const authStore = opts.auth?.store;
|
|
8264
|
+
const rbacMode = !!(authStore && authStore.count() > 0);
|
|
8093
8265
|
const log2 = opts.log ?? (() => {
|
|
8094
8266
|
});
|
|
8095
8267
|
const restDeps = { backend: opts.backend, version: opts.version };
|
|
@@ -8148,23 +8320,44 @@ async function runApi(opts) {
|
|
|
8148
8320
|
finish(r.status);
|
|
8149
8321
|
return;
|
|
8150
8322
|
}
|
|
8151
|
-
|
|
8323
|
+
const principal = resolvePrincipal(bearerToken(req.headers["authorization"]), {
|
|
8324
|
+
...authStore ? { store: authStore } : {},
|
|
8325
|
+
...token ? { sharedToken: token } : {},
|
|
8326
|
+
defaultTenant,
|
|
8327
|
+
...opts.auth?.required ? { required: true } : {}
|
|
8328
|
+
});
|
|
8329
|
+
if (!principal) {
|
|
8152
8330
|
send(res, 401, { error: "unauthorized" }, { "www-authenticate": "Bearer", ...cors });
|
|
8153
8331
|
finish(401);
|
|
8154
8332
|
return;
|
|
8155
8333
|
}
|
|
8156
|
-
let ctx;
|
|
8157
8334
|
try {
|
|
8158
|
-
|
|
8159
|
-
tenantLabel = ctx.tenant;
|
|
8335
|
+
authorize(principal, "read");
|
|
8160
8336
|
} catch (err) {
|
|
8161
|
-
if (err instanceof
|
|
8162
|
-
send(res,
|
|
8163
|
-
finish(
|
|
8337
|
+
if (err instanceof AuthorizationError) {
|
|
8338
|
+
send(res, 403, { error: "forbidden" }, cors);
|
|
8339
|
+
finish(403);
|
|
8164
8340
|
return;
|
|
8165
8341
|
}
|
|
8166
8342
|
throw err;
|
|
8167
8343
|
}
|
|
8344
|
+
let ctx;
|
|
8345
|
+
if (rbacMode) {
|
|
8346
|
+
ctx = { tenant: principal.tenant };
|
|
8347
|
+
tenantLabel = principal.tenant;
|
|
8348
|
+
} else {
|
|
8349
|
+
try {
|
|
8350
|
+
ctx = resolveTenant(req, url, opts.tenant ?? {});
|
|
8351
|
+
tenantLabel = ctx.tenant;
|
|
8352
|
+
} catch (err) {
|
|
8353
|
+
if (err instanceof InvalidTenantError) {
|
|
8354
|
+
send(res, 400, { error: "invalid tenant" }, cors);
|
|
8355
|
+
finish(400);
|
|
8356
|
+
return;
|
|
8357
|
+
}
|
|
8358
|
+
throw err;
|
|
8359
|
+
}
|
|
8360
|
+
}
|
|
8168
8361
|
if (graphqlEnabled && path === "/graphql") {
|
|
8169
8362
|
if (req.method === "GET") {
|
|
8170
8363
|
const g = handleGraphqlGet();
|
|
@@ -8234,6 +8427,30 @@ function dispatchRest(ctx, path, url, deps) {
|
|
|
8234
8427
|
}
|
|
8235
8428
|
}
|
|
8236
8429
|
|
|
8430
|
+
// src/auth/types.ts
|
|
8431
|
+
var import_zod9 = require("zod");
|
|
8432
|
+
var ROLES = ["viewer", "operator", "admin"];
|
|
8433
|
+
var RoleSchema = import_zod9.z.enum(ROLES);
|
|
8434
|
+
var ACTIONS = ["read", "discovery", "admin"];
|
|
8435
|
+
var ActionSchema = import_zod9.z.enum(ACTIONS);
|
|
8436
|
+
var PrincipalSchema = import_zod9.z.object({
|
|
8437
|
+
subject: import_zod9.z.string().min(1),
|
|
8438
|
+
tenant: import_zod9.z.string().min(1),
|
|
8439
|
+
role: RoleSchema
|
|
8440
|
+
});
|
|
8441
|
+
var CredentialConfigSchema = import_zod9.z.object({
|
|
8442
|
+
token: import_zod9.z.string().min(1),
|
|
8443
|
+
subject: import_zod9.z.string().min(1),
|
|
8444
|
+
tenant: import_zod9.z.string().optional(),
|
|
8445
|
+
role: RoleSchema.default("viewer")
|
|
8446
|
+
});
|
|
8447
|
+
var AuthConfigSchema = import_zod9.z.object({
|
|
8448
|
+
/** Seed credentials (merged into the SQLite store on startup). */
|
|
8449
|
+
credentials: import_zod9.z.array(CredentialConfigSchema).optional(),
|
|
8450
|
+
/** Reject unauthenticated requests even on loopback (default: loopback dev stays open). */
|
|
8451
|
+
required: import_zod9.z.boolean().optional()
|
|
8452
|
+
});
|
|
8453
|
+
|
|
8237
8454
|
// src/api/start.ts
|
|
8238
8455
|
var import_node_fs5 = require("fs");
|
|
8239
8456
|
var import_node_path5 = require("path");
|
|
@@ -8261,6 +8478,7 @@ function parseApiArgs(argv) {
|
|
|
8261
8478
|
else if (a === "--db") opts.dbPath = argv[++i];
|
|
8262
8479
|
else if (a === "--session") opts.session = argv[++i];
|
|
8263
8480
|
else if (a === "--tenant" || a === "--org") opts.tenant = argv[++i];
|
|
8481
|
+
else if (a === "--auth-required") opts.authRequired = true;
|
|
8264
8482
|
else if (a === "--help" || a === "-h") opts.help = true;
|
|
8265
8483
|
}
|
|
8266
8484
|
return opts;
|
|
@@ -8276,11 +8494,13 @@ async function startApi(opts = {}) {
|
|
|
8276
8494
|
const host2 = opts.host ?? "127.0.0.1";
|
|
8277
8495
|
const port = opts.port ?? 3737;
|
|
8278
8496
|
const version = readVersion();
|
|
8497
|
+
const authStore = new SqliteCredentialStore(db);
|
|
8279
8498
|
const server = await runApi({
|
|
8280
8499
|
host: host2,
|
|
8281
8500
|
port,
|
|
8282
8501
|
backend,
|
|
8283
8502
|
version,
|
|
8503
|
+
auth: { store: authStore, ...opts.authRequired ? { required: true } : {} },
|
|
8284
8504
|
...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
|
|
8285
8505
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
8286
8506
|
...token ? { token } : {},
|
|
@@ -8775,13 +8995,13 @@ function createClaudeProvider() {
|
|
|
8775
8995
|
}
|
|
8776
8996
|
|
|
8777
8997
|
// src/providers/shell.ts
|
|
8778
|
-
var
|
|
8998
|
+
var import_zod10 = require("zod");
|
|
8779
8999
|
function createBashTool() {
|
|
8780
9000
|
const shell = IS_WIN ? "powershell" : "posix";
|
|
8781
9001
|
return {
|
|
8782
9002
|
name: "Bash",
|
|
8783
9003
|
description: "Run a read-only shell command (inspect ports, processes, config). Mutating or destructive commands are blocked by the read-only allowlist.",
|
|
8784
|
-
inputShape: { command:
|
|
9004
|
+
inputShape: { command: import_zod10.z.string().describe("The read-only shell command to run") },
|
|
8785
9005
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
8786
9006
|
handler: async (args) => {
|
|
8787
9007
|
const command = String(args["command"] ?? "").trim();
|
|
@@ -11293,9 +11513,9 @@ async function runOnce(cfg, db) {
|
|
|
11293
11513
|
}
|
|
11294
11514
|
|
|
11295
11515
|
// src/sync/hash.ts
|
|
11296
|
-
var
|
|
11516
|
+
var import_node_crypto7 = require("crypto");
|
|
11297
11517
|
function shareHash(kind, payload) {
|
|
11298
|
-
return (0,
|
|
11518
|
+
return (0, import_node_crypto7.createHash)("sha256").update(stableStringify({ kind, payload })).digest("hex");
|
|
11299
11519
|
}
|
|
11300
11520
|
|
|
11301
11521
|
// src/sync/classify.ts
|
|
@@ -11339,7 +11559,7 @@ function classify2(input) {
|
|
|
11339
11559
|
}
|
|
11340
11560
|
|
|
11341
11561
|
// src/sync/push.ts
|
|
11342
|
-
var
|
|
11562
|
+
var import_node_crypto8 = require("crypto");
|
|
11343
11563
|
var PUSH_SCHEMA_VERSION = 1;
|
|
11344
11564
|
var DEFAULT_BATCH = 100;
|
|
11345
11565
|
var DEFAULT_RETRIES = 4;
|
|
@@ -11353,7 +11573,7 @@ function defaultSleep(ms) {
|
|
|
11353
11573
|
}
|
|
11354
11574
|
function batchKey(items) {
|
|
11355
11575
|
const hashes = items.map((i) => i.contentHash).sort();
|
|
11356
|
-
return (0,
|
|
11576
|
+
return (0, import_node_crypto8.createHash)("sha256").update(stableStringify(hashes)).digest("hex");
|
|
11357
11577
|
}
|
|
11358
11578
|
async function pushDeltas(config, items, opts = {}) {
|
|
11359
11579
|
const central = config.centralDb;
|
|
@@ -11559,6 +11779,10 @@ function checkClaudePrerequisites() {
|
|
|
11559
11779
|
}
|
|
11560
11780
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11561
11781
|
0 && (module.exports = {
|
|
11782
|
+
ACTIONS,
|
|
11783
|
+
ActionSchema,
|
|
11784
|
+
AuthConfigSchema,
|
|
11785
|
+
AuthorizationError,
|
|
11562
11786
|
CLIENTS,
|
|
11563
11787
|
CONFIDENCE,
|
|
11564
11788
|
CartographyDB,
|
|
@@ -11567,6 +11791,7 @@ function checkClaudePrerequisites() {
|
|
|
11567
11791
|
ConditionSchema,
|
|
11568
11792
|
ConfigError,
|
|
11569
11793
|
ControlResultSchema,
|
|
11794
|
+
CredentialConfigSchema,
|
|
11570
11795
|
CsvCostSource,
|
|
11571
11796
|
DEFAULT_ANOMALY_THRESHOLDS,
|
|
11572
11797
|
DEFAULT_SERVER_NAME,
|
|
@@ -11586,8 +11811,11 @@ function checkClaudePrerequisites() {
|
|
|
11586
11811
|
PRIVATE_IP,
|
|
11587
11812
|
PUSH_SCHEMA_VERSION,
|
|
11588
11813
|
PagerDutySink,
|
|
11814
|
+
PrincipalSchema,
|
|
11589
11815
|
ProviderRegistry,
|
|
11590
11816
|
RELATION_TO_DIRECTION,
|
|
11817
|
+
ROLES,
|
|
11818
|
+
RoleSchema,
|
|
11591
11819
|
RuleCheckSchema,
|
|
11592
11820
|
RulesetSchema,
|
|
11593
11821
|
SCAN_ARG_PATTERNS,
|
|
@@ -11598,10 +11826,12 @@ function checkClaudePrerequisites() {
|
|
|
11598
11826
|
ScannerShape,
|
|
11599
11827
|
SharingLevelSchema,
|
|
11600
11828
|
SlackSink,
|
|
11829
|
+
SqliteCredentialStore,
|
|
11601
11830
|
SqliteQueryBackend,
|
|
11602
11831
|
SqliteStoreBackend,
|
|
11603
11832
|
StdoutSink,
|
|
11604
11833
|
TENANT_HEADER,
|
|
11834
|
+
TenantMismatchError,
|
|
11605
11835
|
VectorStore,
|
|
11606
11836
|
WebhookSink,
|
|
11607
11837
|
applyInstall,
|
|
@@ -11609,7 +11839,9 @@ function checkClaudePrerequisites() {
|
|
|
11609
11839
|
assertReadOnly,
|
|
11610
11840
|
assertSafeBind,
|
|
11611
11841
|
assertSafeScanArg,
|
|
11842
|
+
assertSameTenant,
|
|
11612
11843
|
assignColors,
|
|
11844
|
+
authorize,
|
|
11613
11845
|
bearerToken,
|
|
11614
11846
|
bookmarksScanner,
|
|
11615
11847
|
buildCartographyToolHandlers,
|
|
@@ -11617,6 +11849,7 @@ function checkClaudePrerequisites() {
|
|
|
11617
11849
|
buildOpenApiDocument,
|
|
11618
11850
|
buildReport,
|
|
11619
11851
|
buildSinks,
|
|
11852
|
+
can,
|
|
11620
11853
|
centralDbFromEnv,
|
|
11621
11854
|
checkBearer,
|
|
11622
11855
|
checkPrerequisites,
|
|
@@ -11693,6 +11926,7 @@ function checkClaudePrerequisites() {
|
|
|
11693
11926
|
globalId,
|
|
11694
11927
|
groupByDomain,
|
|
11695
11928
|
handleGraphqlGet,
|
|
11929
|
+
hashToken,
|
|
11696
11930
|
hexCorners,
|
|
11697
11931
|
hexDistance,
|
|
11698
11932
|
hexNeighbors,
|
|
@@ -11759,6 +11993,7 @@ function checkClaudePrerequisites() {
|
|
|
11759
11993
|
renderDiff,
|
|
11760
11994
|
resolveEffectiveLevel,
|
|
11761
11995
|
resolveNlQuery,
|
|
11996
|
+
resolvePrincipal,
|
|
11762
11997
|
resolveSharingLevel,
|
|
11763
11998
|
resolveTenant,
|
|
11764
11999
|
revalidateAnonymized,
|
|
@@ -11778,6 +12013,7 @@ function checkClaudePrerequisites() {
|
|
|
11778
12013
|
safetyHook,
|
|
11779
12014
|
sanitizeUntrusted,
|
|
11780
12015
|
sanitizeValue,
|
|
12016
|
+
scopeReads,
|
|
11781
12017
|
scoreTopology,
|
|
11782
12018
|
securityRelevantChange,
|
|
11783
12019
|
serializeConfig,
|