@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.js
CHANGED
|
@@ -2815,7 +2815,10 @@ CREATE TABLE IF NOT EXISTS activity_events (
|
|
|
2815
2815
|
duration_ms INTEGER,
|
|
2816
2816
|
command TEXT,
|
|
2817
2817
|
result_bytes INTEGER,
|
|
2818
|
-
tenant TEXT NOT NULL DEFAULT 'local'
|
|
2818
|
+
tenant TEXT NOT NULL DEFAULT 'local',
|
|
2819
|
+
actor_subject TEXT,
|
|
2820
|
+
actor_role TEXT,
|
|
2821
|
+
actor_tenant TEXT
|
|
2819
2822
|
);
|
|
2820
2823
|
|
|
2821
2824
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -2924,6 +2927,16 @@ CREATE INDEX IF NOT EXISTS idx_nodes_tenant_content ON nodes(tenant, content_has
|
|
|
2924
2927
|
CREATE INDEX IF NOT EXISTS idx_contrib_org ON node_contributors(organization, global_id);
|
|
2925
2928
|
CREATE INDEX IF NOT EXISTS idx_nodes_owner ON nodes(session_id, owner);
|
|
2926
2929
|
`;
|
|
2930
|
+
var AUTH_SCHEMA = `
|
|
2931
|
+
CREATE TABLE IF NOT EXISTS auth_credentials (
|
|
2932
|
+
token_hash TEXT PRIMARY KEY,
|
|
2933
|
+
subject TEXT NOT NULL,
|
|
2934
|
+
tenant TEXT NOT NULL DEFAULT 'local',
|
|
2935
|
+
role TEXT NOT NULL,
|
|
2936
|
+
created_at TEXT NOT NULL
|
|
2937
|
+
);
|
|
2938
|
+
CREATE INDEX IF NOT EXISTS idx_auth_subject ON auth_credentials(subject);
|
|
2939
|
+
`;
|
|
2927
2940
|
var CartographyDB = class {
|
|
2928
2941
|
db;
|
|
2929
2942
|
/** 3.6 anomaly settings; defaults apply when no `anomaly` config is supplied. */
|
|
@@ -2943,7 +2956,8 @@ var CartographyDB = class {
|
|
|
2943
2956
|
const version = this.db.pragma("user_version", { simple: true });
|
|
2944
2957
|
if (version === 0) {
|
|
2945
2958
|
this.db.exec(SCHEMA);
|
|
2946
|
-
this.db.
|
|
2959
|
+
this.db.exec(AUTH_SCHEMA);
|
|
2960
|
+
this.db.pragma("user_version = 15");
|
|
2947
2961
|
return;
|
|
2948
2962
|
} else if (version === 1) {
|
|
2949
2963
|
const cols = this.db.prepare("PRAGMA table_info(nodes)").all().map((c) => c.name);
|
|
@@ -3129,6 +3143,18 @@ var CartographyDB = class {
|
|
|
3129
3143
|
}
|
|
3130
3144
|
this.db.pragma("user_version = 14");
|
|
3131
3145
|
}
|
|
3146
|
+
const v14 = this.db.pragma("user_version", { simple: true });
|
|
3147
|
+
if (v14 < 15) {
|
|
3148
|
+
this.db.exec(AUTH_SCHEMA);
|
|
3149
|
+
const ev = this.db.prepare("PRAGMA table_info(activity_events)").all();
|
|
3150
|
+
if (ev.length > 0) {
|
|
3151
|
+
const cols = ev.map((c) => c.name);
|
|
3152
|
+
if (!cols.includes("actor_subject")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_subject TEXT");
|
|
3153
|
+
if (!cols.includes("actor_role")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_role TEXT");
|
|
3154
|
+
if (!cols.includes("actor_tenant")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_tenant TEXT");
|
|
3155
|
+
}
|
|
3156
|
+
this.db.pragma("user_version = 15");
|
|
3157
|
+
}
|
|
3132
3158
|
}
|
|
3133
3159
|
close() {
|
|
3134
3160
|
this.db.pragma("optimize");
|
|
@@ -3559,13 +3585,13 @@ var CartographyDB = class {
|
|
|
3559
3585
|
});
|
|
3560
3586
|
}
|
|
3561
3587
|
// ── Events ──────────────────────────────
|
|
3562
|
-
insertEvent(sessionId, event, taskId) {
|
|
3588
|
+
insertEvent(sessionId, event, taskId, actor) {
|
|
3563
3589
|
const id = crypto.randomUUID();
|
|
3564
3590
|
const tenant = this.tenantOf(sessionId);
|
|
3565
3591
|
this.db.prepare(`
|
|
3566
3592
|
INSERT INTO activity_events
|
|
3567
|
-
(id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant)
|
|
3568
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3593
|
+
(id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant, actor_subject, actor_role, actor_tenant)
|
|
3594
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3569
3595
|
`).run(
|
|
3570
3596
|
id,
|
|
3571
3597
|
sessionId,
|
|
@@ -3579,9 +3605,52 @@ var CartographyDB = class {
|
|
|
3579
3605
|
event.port ?? null,
|
|
3580
3606
|
event.command ?? null,
|
|
3581
3607
|
event.resultBytes ?? null,
|
|
3582
|
-
tenant
|
|
3608
|
+
tenant,
|
|
3609
|
+
actor?.subject ?? null,
|
|
3610
|
+
actor?.role ?? null,
|
|
3611
|
+
actor?.tenant ?? null
|
|
3583
3612
|
);
|
|
3584
3613
|
}
|
|
3614
|
+
// ── RBAC credential store (4.5) ─────────────
|
|
3615
|
+
/** Number of stored credentials. `0` ⇒ no RBAC configured (fall back to shared/loopback). */
|
|
3616
|
+
countCredentials() {
|
|
3617
|
+
return this.db.prepare("SELECT COUNT(*) AS n FROM auth_credentials").get().n;
|
|
3618
|
+
}
|
|
3619
|
+
/** Look up a credential by its sha256 token hash. */
|
|
3620
|
+
findCredentialByHash(tokenHash) {
|
|
3621
|
+
const r = this.db.prepare("SELECT * FROM auth_credentials WHERE token_hash = ?").get(tokenHash);
|
|
3622
|
+
if (!r) return void 0;
|
|
3623
|
+
return {
|
|
3624
|
+
tokenHash: r["token_hash"],
|
|
3625
|
+
subject: r["subject"],
|
|
3626
|
+
tenant: r["tenant"],
|
|
3627
|
+
role: r["role"],
|
|
3628
|
+
createdAt: r["created_at"]
|
|
3629
|
+
};
|
|
3630
|
+
}
|
|
3631
|
+
/** Upsert a credential (idempotent on the token hash). Stores only the hash, never the raw token. */
|
|
3632
|
+
addCredential(rec) {
|
|
3633
|
+
this.db.prepare(`
|
|
3634
|
+
INSERT INTO auth_credentials (token_hash, subject, tenant, role, created_at)
|
|
3635
|
+
VALUES (?, ?, ?, ?, ?)
|
|
3636
|
+
ON CONFLICT(token_hash) DO UPDATE SET subject = excluded.subject, tenant = excluded.tenant, role = excluded.role
|
|
3637
|
+
`).run(rec.tokenHash, rec.subject, rec.tenant, rec.role, (/* @__PURE__ */ new Date()).toISOString());
|
|
3638
|
+
}
|
|
3639
|
+
/** List all credentials (token hashes only — the raw token is unrecoverable). */
|
|
3640
|
+
listCredentials() {
|
|
3641
|
+
const rows = this.db.prepare("SELECT * FROM auth_credentials ORDER BY created_at").all();
|
|
3642
|
+
return rows.map((r) => ({
|
|
3643
|
+
tokenHash: r["token_hash"],
|
|
3644
|
+
subject: r["subject"],
|
|
3645
|
+
tenant: r["tenant"],
|
|
3646
|
+
role: r["role"],
|
|
3647
|
+
createdAt: r["created_at"]
|
|
3648
|
+
}));
|
|
3649
|
+
}
|
|
3650
|
+
/** Revoke every credential for a subject. Returns the number removed. */
|
|
3651
|
+
revokeCredentialsBySubject(subject) {
|
|
3652
|
+
return this.db.prepare("DELETE FROM auth_credentials WHERE subject = ?").run(subject).changes;
|
|
3653
|
+
}
|
|
3585
3654
|
getEvents(sessionId, since) {
|
|
3586
3655
|
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);
|
|
3587
3656
|
return rows.map((r) => {
|
|
@@ -5260,6 +5329,36 @@ async function runDrift(db, config, opts = {}) {
|
|
|
5260
5329
|
return alert;
|
|
5261
5330
|
}
|
|
5262
5331
|
|
|
5332
|
+
// src/auth/rbac.ts
|
|
5333
|
+
var ROLE_RANK = { viewer: 1, operator: 2, admin: 3 };
|
|
5334
|
+
var ACTION_MIN_ROLE = { read: "viewer", discovery: "operator", admin: "admin" };
|
|
5335
|
+
function can(role, action) {
|
|
5336
|
+
return ROLE_RANK[role] >= ROLE_RANK[ACTION_MIN_ROLE[action]];
|
|
5337
|
+
}
|
|
5338
|
+
var AuthorizationError = class extends Error {
|
|
5339
|
+
constructor(action, role) {
|
|
5340
|
+
super(`forbidden: role '${role}' may not perform '${action}'`);
|
|
5341
|
+
this.action = action;
|
|
5342
|
+
this.role = role;
|
|
5343
|
+
this.name = "AuthorizationError";
|
|
5344
|
+
}
|
|
5345
|
+
};
|
|
5346
|
+
function authorize(principal, action) {
|
|
5347
|
+
if (!can(principal.role, action)) throw new AuthorizationError(action, principal.role);
|
|
5348
|
+
}
|
|
5349
|
+
var TenantMismatchError = class extends Error {
|
|
5350
|
+
constructor() {
|
|
5351
|
+
super("forbidden: principal is not scoped to the requested tenant");
|
|
5352
|
+
this.name = "TenantMismatchError";
|
|
5353
|
+
}
|
|
5354
|
+
};
|
|
5355
|
+
function scopeReads(principal) {
|
|
5356
|
+
return principal.tenant;
|
|
5357
|
+
}
|
|
5358
|
+
function assertSameTenant(principal, requestedTenant) {
|
|
5359
|
+
if (requestedTenant !== principal.tenant) throw new TenantMismatchError();
|
|
5360
|
+
}
|
|
5361
|
+
|
|
5263
5362
|
// src/compliance/rulesets/baseline.ts
|
|
5264
5363
|
var baseline = RulesetSchema.parse({
|
|
5265
5364
|
name: "baseline",
|
|
@@ -5547,7 +5646,7 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
|
|
|
5547
5646
|
|
|
5548
5647
|
// src/mcp/server.ts
|
|
5549
5648
|
var SERVER_NAME = "cartography";
|
|
5550
|
-
var SERVER_VERSION = "2.
|
|
5649
|
+
var SERVER_VERSION = "2.5.0";
|
|
5551
5650
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
5552
5651
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
5553
5652
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -5949,6 +6048,14 @@ function createMcpServer(opts = {}) {
|
|
|
5949
6048
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
5950
6049
|
},
|
|
5951
6050
|
async (args) => {
|
|
6051
|
+
if (opts.principal) {
|
|
6052
|
+
try {
|
|
6053
|
+
authorize(opts.principal, "discovery");
|
|
6054
|
+
} catch (err) {
|
|
6055
|
+
if (err instanceof AuthorizationError) return json({ error: `forbidden: role '${opts.principal.role}' may not run discovery (operator required)` });
|
|
6056
|
+
throw err;
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
5952
6059
|
let sid = resolveSession();
|
|
5953
6060
|
if (args.update) {
|
|
5954
6061
|
if (!sid) return json({ error: "No session to update; run discovery first." });
|
|
@@ -6091,7 +6198,41 @@ function defaultAllowedHosts(host2, port) {
|
|
|
6091
6198
|
return [`${host2}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];
|
|
6092
6199
|
}
|
|
6093
6200
|
|
|
6201
|
+
// src/auth/identity.ts
|
|
6202
|
+
import { createHash as createHash2 } from "crypto";
|
|
6203
|
+
function hashToken(token) {
|
|
6204
|
+
return createHash2("sha256").update(token, "utf8").digest("hex");
|
|
6205
|
+
}
|
|
6206
|
+
var SqliteCredentialStore = class {
|
|
6207
|
+
constructor(db) {
|
|
6208
|
+
this.db = db;
|
|
6209
|
+
}
|
|
6210
|
+
count() {
|
|
6211
|
+
return this.db.countCredentials();
|
|
6212
|
+
}
|
|
6213
|
+
findByHash(tokenHash) {
|
|
6214
|
+
return this.db.findCredentialByHash(tokenHash);
|
|
6215
|
+
}
|
|
6216
|
+
};
|
|
6217
|
+
function resolvePrincipal(presentedToken, opts) {
|
|
6218
|
+
const tenant = opts.defaultTenant ?? DEFAULT_TENANT;
|
|
6219
|
+
if (opts.store && opts.store.count() > 0) {
|
|
6220
|
+
if (!presentedToken) return void 0;
|
|
6221
|
+
const rec = opts.store.findByHash(hashToken(presentedToken));
|
|
6222
|
+
return rec ? { subject: rec.subject, tenant: rec.tenant, role: rec.role } : void 0;
|
|
6223
|
+
}
|
|
6224
|
+
if (opts.sharedToken) {
|
|
6225
|
+
if (!presentedToken || !timingSafeEqual(presentedToken, opts.sharedToken)) return void 0;
|
|
6226
|
+
return { subject: "shared-token", tenant, role: "admin" };
|
|
6227
|
+
}
|
|
6228
|
+
if (opts.required) return void 0;
|
|
6229
|
+
return { subject: "anonymous", tenant, role: "admin" };
|
|
6230
|
+
}
|
|
6231
|
+
|
|
6094
6232
|
// src/mcp/transports.ts
|
|
6233
|
+
function samePrincipal(a, b) {
|
|
6234
|
+
return a.subject === b.subject && a.tenant === b.tenant && a.role === b.role;
|
|
6235
|
+
}
|
|
6095
6236
|
async function runStdio(server) {
|
|
6096
6237
|
const transport = new StdioServerTransport();
|
|
6097
6238
|
await server.connect(transport);
|
|
@@ -6136,6 +6277,14 @@ async function runHttp(factory, opts = {}) {
|
|
|
6136
6277
|
assertSafeBind({ host: host2, port, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...opts.token ? { token: opts.token } : {} });
|
|
6137
6278
|
const allowedHosts = opts.allowedHosts ?? defaultAllowedHosts(host2, port);
|
|
6138
6279
|
const token = opts.token;
|
|
6280
|
+
const authStore = opts.auth?.store;
|
|
6281
|
+
const defaultTenant = opts.defaultTenant;
|
|
6282
|
+
const resolveAuth = (header) => resolvePrincipal(bearerToken(header), {
|
|
6283
|
+
...authStore ? { store: authStore } : {},
|
|
6284
|
+
...token ? { sharedToken: token } : {},
|
|
6285
|
+
...defaultTenant ? { defaultTenant } : {},
|
|
6286
|
+
...opts.auth?.required ? { required: true } : {}
|
|
6287
|
+
});
|
|
6139
6288
|
const transports = /* @__PURE__ */ new Map();
|
|
6140
6289
|
const httpServer = http.createServer(async (req, res) => {
|
|
6141
6290
|
try {
|
|
@@ -6145,7 +6294,8 @@ async function runHttp(factory, opts = {}) {
|
|
|
6145
6294
|
res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
|
|
6146
6295
|
return;
|
|
6147
6296
|
}
|
|
6148
|
-
|
|
6297
|
+
const principal = resolveAuth(req.headers["authorization"]);
|
|
6298
|
+
if (!principal) {
|
|
6149
6299
|
res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" }).end('{"error":"unauthorized"}');
|
|
6150
6300
|
return;
|
|
6151
6301
|
}
|
|
@@ -6172,8 +6322,12 @@ async function runHttp(factory, opts = {}) {
|
|
|
6172
6322
|
const sessionId = req.headers["mcp-session-id"];
|
|
6173
6323
|
const existing = sessionId ? transports.get(sessionId) : void 0;
|
|
6174
6324
|
if (existing) {
|
|
6325
|
+
if (!samePrincipal(existing.principal, principal)) {
|
|
6326
|
+
res.writeHead(403, { "content-type": "application/json" }).end('{"error":"session belongs to a different principal"}');
|
|
6327
|
+
return;
|
|
6328
|
+
}
|
|
6175
6329
|
const body2 = req.method === "POST" ? await readJsonBody(req) : void 0;
|
|
6176
|
-
await existing.handleRequest(req, res, body2);
|
|
6330
|
+
await existing.transport.handleRequest(req, res, body2);
|
|
6177
6331
|
return;
|
|
6178
6332
|
}
|
|
6179
6333
|
if (req.method !== "POST") {
|
|
@@ -6187,13 +6341,13 @@ async function runHttp(factory, opts = {}) {
|
|
|
6187
6341
|
allowedHosts,
|
|
6188
6342
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
6189
6343
|
onsessioninitialized: (id) => {
|
|
6190
|
-
transports.set(id, transport);
|
|
6344
|
+
transports.set(id, { transport, principal });
|
|
6191
6345
|
}
|
|
6192
6346
|
});
|
|
6193
6347
|
transport.onclose = () => {
|
|
6194
6348
|
if (transport.sessionId) transports.delete(transport.sessionId);
|
|
6195
6349
|
};
|
|
6196
|
-
await factory().connect(transport);
|
|
6350
|
+
await factory(principal).connect(transport);
|
|
6197
6351
|
await transport.handleRequest(req, res, body);
|
|
6198
6352
|
} catch (err) {
|
|
6199
6353
|
process.stderr.write(`[cartography-mcp] HTTP request failed: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -7820,6 +7974,8 @@ async function runApi(opts) {
|
|
|
7820
7974
|
const token = opts.token;
|
|
7821
7975
|
const graphqlEnabled = opts.graphql !== false;
|
|
7822
7976
|
const defaultTenant = opts.tenant?.defaultTenant ?? DEFAULT_TENANT;
|
|
7977
|
+
const authStore = opts.auth?.store;
|
|
7978
|
+
const rbacMode = !!(authStore && authStore.count() > 0);
|
|
7823
7979
|
const log2 = opts.log ?? (() => {
|
|
7824
7980
|
});
|
|
7825
7981
|
const restDeps = { backend: opts.backend, version: opts.version };
|
|
@@ -7878,23 +8034,44 @@ async function runApi(opts) {
|
|
|
7878
8034
|
finish(r.status);
|
|
7879
8035
|
return;
|
|
7880
8036
|
}
|
|
7881
|
-
|
|
8037
|
+
const principal = resolvePrincipal(bearerToken(req.headers["authorization"]), {
|
|
8038
|
+
...authStore ? { store: authStore } : {},
|
|
8039
|
+
...token ? { sharedToken: token } : {},
|
|
8040
|
+
defaultTenant,
|
|
8041
|
+
...opts.auth?.required ? { required: true } : {}
|
|
8042
|
+
});
|
|
8043
|
+
if (!principal) {
|
|
7882
8044
|
send(res, 401, { error: "unauthorized" }, { "www-authenticate": "Bearer", ...cors });
|
|
7883
8045
|
finish(401);
|
|
7884
8046
|
return;
|
|
7885
8047
|
}
|
|
7886
|
-
let ctx;
|
|
7887
8048
|
try {
|
|
7888
|
-
|
|
7889
|
-
tenantLabel = ctx.tenant;
|
|
8049
|
+
authorize(principal, "read");
|
|
7890
8050
|
} catch (err) {
|
|
7891
|
-
if (err instanceof
|
|
7892
|
-
send(res,
|
|
7893
|
-
finish(
|
|
8051
|
+
if (err instanceof AuthorizationError) {
|
|
8052
|
+
send(res, 403, { error: "forbidden" }, cors);
|
|
8053
|
+
finish(403);
|
|
7894
8054
|
return;
|
|
7895
8055
|
}
|
|
7896
8056
|
throw err;
|
|
7897
8057
|
}
|
|
8058
|
+
let ctx;
|
|
8059
|
+
if (rbacMode) {
|
|
8060
|
+
ctx = { tenant: principal.tenant };
|
|
8061
|
+
tenantLabel = principal.tenant;
|
|
8062
|
+
} else {
|
|
8063
|
+
try {
|
|
8064
|
+
ctx = resolveTenant(req, url, opts.tenant ?? {});
|
|
8065
|
+
tenantLabel = ctx.tenant;
|
|
8066
|
+
} catch (err) {
|
|
8067
|
+
if (err instanceof InvalidTenantError) {
|
|
8068
|
+
send(res, 400, { error: "invalid tenant" }, cors);
|
|
8069
|
+
finish(400);
|
|
8070
|
+
return;
|
|
8071
|
+
}
|
|
8072
|
+
throw err;
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
7898
8075
|
if (graphqlEnabled && path === "/graphql") {
|
|
7899
8076
|
if (req.method === "GET") {
|
|
7900
8077
|
const g = handleGraphqlGet();
|
|
@@ -7964,6 +8141,30 @@ function dispatchRest(ctx, path, url, deps) {
|
|
|
7964
8141
|
}
|
|
7965
8142
|
}
|
|
7966
8143
|
|
|
8144
|
+
// src/auth/types.ts
|
|
8145
|
+
import { z as z9 } from "zod";
|
|
8146
|
+
var ROLES = ["viewer", "operator", "admin"];
|
|
8147
|
+
var RoleSchema = z9.enum(ROLES);
|
|
8148
|
+
var ACTIONS = ["read", "discovery", "admin"];
|
|
8149
|
+
var ActionSchema = z9.enum(ACTIONS);
|
|
8150
|
+
var PrincipalSchema = z9.object({
|
|
8151
|
+
subject: z9.string().min(1),
|
|
8152
|
+
tenant: z9.string().min(1),
|
|
8153
|
+
role: RoleSchema
|
|
8154
|
+
});
|
|
8155
|
+
var CredentialConfigSchema = z9.object({
|
|
8156
|
+
token: z9.string().min(1),
|
|
8157
|
+
subject: z9.string().min(1),
|
|
8158
|
+
tenant: z9.string().optional(),
|
|
8159
|
+
role: RoleSchema.default("viewer")
|
|
8160
|
+
});
|
|
8161
|
+
var AuthConfigSchema = z9.object({
|
|
8162
|
+
/** Seed credentials (merged into the SQLite store on startup). */
|
|
8163
|
+
credentials: z9.array(CredentialConfigSchema).optional(),
|
|
8164
|
+
/** Reject unauthenticated requests even on loopback (default: loopback dev stays open). */
|
|
8165
|
+
required: z9.boolean().optional()
|
|
8166
|
+
});
|
|
8167
|
+
|
|
7967
8168
|
// src/api/start.ts
|
|
7968
8169
|
import { readFileSync as readFileSync4 } from "fs";
|
|
7969
8170
|
import { dirname as dirname3, resolve } from "path";
|
|
@@ -7990,6 +8191,7 @@ function parseApiArgs(argv) {
|
|
|
7990
8191
|
else if (a === "--db") opts.dbPath = argv[++i];
|
|
7991
8192
|
else if (a === "--session") opts.session = argv[++i];
|
|
7992
8193
|
else if (a === "--tenant" || a === "--org") opts.tenant = argv[++i];
|
|
8194
|
+
else if (a === "--auth-required") opts.authRequired = true;
|
|
7993
8195
|
else if (a === "--help" || a === "-h") opts.help = true;
|
|
7994
8196
|
}
|
|
7995
8197
|
return opts;
|
|
@@ -8005,11 +8207,13 @@ async function startApi(opts = {}) {
|
|
|
8005
8207
|
const host2 = opts.host ?? "127.0.0.1";
|
|
8006
8208
|
const port = opts.port ?? 3737;
|
|
8007
8209
|
const version = readVersion();
|
|
8210
|
+
const authStore = new SqliteCredentialStore(db);
|
|
8008
8211
|
const server = await runApi({
|
|
8009
8212
|
host: host2,
|
|
8010
8213
|
port,
|
|
8011
8214
|
backend,
|
|
8012
8215
|
version,
|
|
8216
|
+
auth: { store: authStore, ...opts.authRequired ? { required: true } : {} },
|
|
8013
8217
|
...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
|
|
8014
8218
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
8015
8219
|
...token ? { token } : {},
|
|
@@ -8504,13 +8708,13 @@ function createClaudeProvider() {
|
|
|
8504
8708
|
}
|
|
8505
8709
|
|
|
8506
8710
|
// src/providers/shell.ts
|
|
8507
|
-
import { z as
|
|
8711
|
+
import { z as z10 } from "zod";
|
|
8508
8712
|
function createBashTool() {
|
|
8509
8713
|
const shell = IS_WIN ? "powershell" : "posix";
|
|
8510
8714
|
return {
|
|
8511
8715
|
name: "Bash",
|
|
8512
8716
|
description: "Run a read-only shell command (inspect ports, processes, config). Mutating or destructive commands are blocked by the read-only allowlist.",
|
|
8513
|
-
inputShape: { command:
|
|
8717
|
+
inputShape: { command: z10.string().describe("The read-only shell command to run") },
|
|
8514
8718
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
8515
8719
|
handler: async (args) => {
|
|
8516
8720
|
const command = String(args["command"] ?? "").trim();
|
|
@@ -11022,9 +11226,9 @@ async function runOnce(cfg, db) {
|
|
|
11022
11226
|
}
|
|
11023
11227
|
|
|
11024
11228
|
// src/sync/hash.ts
|
|
11025
|
-
import { createHash as
|
|
11229
|
+
import { createHash as createHash3 } from "crypto";
|
|
11026
11230
|
function shareHash(kind, payload) {
|
|
11027
|
-
return
|
|
11231
|
+
return createHash3("sha256").update(stableStringify({ kind, payload })).digest("hex");
|
|
11028
11232
|
}
|
|
11029
11233
|
|
|
11030
11234
|
// src/sync/classify.ts
|
|
@@ -11068,7 +11272,7 @@ function classify2(input) {
|
|
|
11068
11272
|
}
|
|
11069
11273
|
|
|
11070
11274
|
// src/sync/push.ts
|
|
11071
|
-
import { createHash as
|
|
11275
|
+
import { createHash as createHash4 } from "crypto";
|
|
11072
11276
|
var PUSH_SCHEMA_VERSION = 1;
|
|
11073
11277
|
var DEFAULT_BATCH = 100;
|
|
11074
11278
|
var DEFAULT_RETRIES = 4;
|
|
@@ -11082,7 +11286,7 @@ function defaultSleep(ms) {
|
|
|
11082
11286
|
}
|
|
11083
11287
|
function batchKey(items) {
|
|
11084
11288
|
const hashes = items.map((i) => i.contentHash).sort();
|
|
11085
|
-
return
|
|
11289
|
+
return createHash4("sha256").update(stableStringify(hashes)).digest("hex");
|
|
11086
11290
|
}
|
|
11087
11291
|
async function pushDeltas(config, items, opts = {}) {
|
|
11088
11292
|
const central = config.centralDb;
|
|
@@ -11287,6 +11491,10 @@ function checkClaudePrerequisites() {
|
|
|
11287
11491
|
}
|
|
11288
11492
|
}
|
|
11289
11493
|
export {
|
|
11494
|
+
ACTIONS,
|
|
11495
|
+
ActionSchema,
|
|
11496
|
+
AuthConfigSchema,
|
|
11497
|
+
AuthorizationError,
|
|
11290
11498
|
CLIENTS,
|
|
11291
11499
|
CONFIDENCE,
|
|
11292
11500
|
CartographyDB,
|
|
@@ -11295,6 +11503,7 @@ export {
|
|
|
11295
11503
|
ConditionSchema,
|
|
11296
11504
|
ConfigError,
|
|
11297
11505
|
ControlResultSchema,
|
|
11506
|
+
CredentialConfigSchema,
|
|
11298
11507
|
CsvCostSource,
|
|
11299
11508
|
DEFAULT_ANOMALY_THRESHOLDS,
|
|
11300
11509
|
DEFAULT_SERVER_NAME,
|
|
@@ -11314,8 +11523,11 @@ export {
|
|
|
11314
11523
|
PRIVATE_IP,
|
|
11315
11524
|
PUSH_SCHEMA_VERSION,
|
|
11316
11525
|
PagerDutySink,
|
|
11526
|
+
PrincipalSchema,
|
|
11317
11527
|
ProviderRegistry,
|
|
11318
11528
|
RELATION_TO_DIRECTION,
|
|
11529
|
+
ROLES,
|
|
11530
|
+
RoleSchema,
|
|
11319
11531
|
RuleCheckSchema,
|
|
11320
11532
|
RulesetSchema,
|
|
11321
11533
|
SCAN_ARG_PATTERNS,
|
|
@@ -11326,10 +11538,12 @@ export {
|
|
|
11326
11538
|
ScannerShape,
|
|
11327
11539
|
SharingLevelSchema,
|
|
11328
11540
|
SlackSink,
|
|
11541
|
+
SqliteCredentialStore,
|
|
11329
11542
|
SqliteQueryBackend,
|
|
11330
11543
|
SqliteStoreBackend,
|
|
11331
11544
|
StdoutSink,
|
|
11332
11545
|
TENANT_HEADER,
|
|
11546
|
+
TenantMismatchError,
|
|
11333
11547
|
VectorStore,
|
|
11334
11548
|
WebhookSink,
|
|
11335
11549
|
applyInstall,
|
|
@@ -11337,7 +11551,9 @@ export {
|
|
|
11337
11551
|
assertReadOnly,
|
|
11338
11552
|
assertSafeBind,
|
|
11339
11553
|
assertSafeScanArg,
|
|
11554
|
+
assertSameTenant,
|
|
11340
11555
|
assignColors,
|
|
11556
|
+
authorize,
|
|
11341
11557
|
bearerToken,
|
|
11342
11558
|
bookmarksScanner,
|
|
11343
11559
|
buildCartographyToolHandlers,
|
|
@@ -11345,6 +11561,7 @@ export {
|
|
|
11345
11561
|
buildOpenApiDocument,
|
|
11346
11562
|
buildReport,
|
|
11347
11563
|
buildSinks,
|
|
11564
|
+
can,
|
|
11348
11565
|
centralDbFromEnv,
|
|
11349
11566
|
checkBearer,
|
|
11350
11567
|
checkPrerequisites,
|
|
@@ -11421,6 +11638,7 @@ export {
|
|
|
11421
11638
|
globalId,
|
|
11422
11639
|
groupByDomain,
|
|
11423
11640
|
handleGraphqlGet,
|
|
11641
|
+
hashToken,
|
|
11424
11642
|
hexCorners,
|
|
11425
11643
|
hexDistance,
|
|
11426
11644
|
hexNeighbors,
|
|
@@ -11487,6 +11705,7 @@ export {
|
|
|
11487
11705
|
renderDiff,
|
|
11488
11706
|
resolveEffectiveLevel,
|
|
11489
11707
|
resolveNlQuery,
|
|
11708
|
+
resolvePrincipal,
|
|
11490
11709
|
resolveSharingLevel,
|
|
11491
11710
|
resolveTenant,
|
|
11492
11711
|
revalidateAnonymized,
|
|
@@ -11506,6 +11725,7 @@ export {
|
|
|
11506
11725
|
safetyHook,
|
|
11507
11726
|
sanitizeUntrusted,
|
|
11508
11727
|
sanitizeValue,
|
|
11728
|
+
scopeReads,
|
|
11509
11729
|
scoreTopology,
|
|
11510
11730
|
securityRelevantChange,
|
|
11511
11731
|
serializeConfig,
|