@datasynx/agentic-ai-cartography 2.4.0 → 2.6.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-PQ7Q6MI5.js} +130 -12
- package/dist/chunk-PQ7Q6MI5.js.map +1 -0
- package/dist/{chunk-B4QWX7CP.js → chunk-X3UWUX3G.js} +55 -11
- package/dist/chunk-X3UWUX3G.js.map +1 -0
- package/dist/cli.js +91 -33
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +363 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -3
- package/dist/index.d.ts +262 -3
- package/dist/index.js +343 -58
- 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,
|
|
@@ -136,6 +149,7 @@ __export(src_exports, {
|
|
|
136
149
|
diffTopology: () => diffTopology,
|
|
137
150
|
edgesToConnections: () => edgesToConnections,
|
|
138
151
|
enrichCosts: () => enrichCosts,
|
|
152
|
+
entitiesToYaml: () => entitiesToYaml,
|
|
139
153
|
evaluateCheck: () => evaluateCheck,
|
|
140
154
|
evaluateRule: () => evaluateRule,
|
|
141
155
|
evidenceLine: () => evidenceLine,
|
|
@@ -164,6 +178,7 @@ __export(src_exports, {
|
|
|
164
178
|
globalId: () => globalId,
|
|
165
179
|
groupByDomain: () => groupByDomain,
|
|
166
180
|
handleGraphqlGet: () => handleGraphqlGet,
|
|
181
|
+
hashToken: () => hashToken,
|
|
167
182
|
hexCorners: () => hexCorners,
|
|
168
183
|
hexDistance: () => hexDistance,
|
|
169
184
|
hexNeighbors: () => hexNeighbors,
|
|
@@ -230,6 +245,7 @@ __export(src_exports, {
|
|
|
230
245
|
renderDiff: () => renderDiff,
|
|
231
246
|
resolveEffectiveLevel: () => resolveEffectiveLevel,
|
|
232
247
|
resolveNlQuery: () => resolveNlQuery,
|
|
248
|
+
resolvePrincipal: () => resolvePrincipal,
|
|
233
249
|
resolveSharingLevel: () => resolveSharingLevel,
|
|
234
250
|
resolveTenant: () => resolveTenant,
|
|
235
251
|
revalidateAnonymized: () => revalidateAnonymized,
|
|
@@ -249,6 +265,7 @@ __export(src_exports, {
|
|
|
249
265
|
safetyHook: () => safetyHook,
|
|
250
266
|
sanitizeUntrusted: () => sanitizeUntrusted,
|
|
251
267
|
sanitizeValue: () => sanitizeValue,
|
|
268
|
+
scopeReads: () => scopeReads,
|
|
252
269
|
scoreTopology: () => scoreTopology,
|
|
253
270
|
securityRelevantChange: () => securityRelevantChange,
|
|
254
271
|
serializeConfig: () => serializeConfig,
|
|
@@ -262,6 +279,7 @@ __export(src_exports, {
|
|
|
262
279
|
startApi: () => startApi,
|
|
263
280
|
stripSensitive: () => stripSensitive,
|
|
264
281
|
timingSafeEqual: () => timingSafeEqual,
|
|
282
|
+
toBackstageEntities: () => toBackstageEntities,
|
|
265
283
|
validateScanner: () => validateScanner,
|
|
266
284
|
vscodeDeeplink: () => vscodeDeeplink,
|
|
267
285
|
zodToJsonSchema: () => zodToJsonSchema
|
|
@@ -3085,7 +3103,10 @@ CREATE TABLE IF NOT EXISTS activity_events (
|
|
|
3085
3103
|
duration_ms INTEGER,
|
|
3086
3104
|
command TEXT,
|
|
3087
3105
|
result_bytes INTEGER,
|
|
3088
|
-
tenant TEXT NOT NULL DEFAULT 'local'
|
|
3106
|
+
tenant TEXT NOT NULL DEFAULT 'local',
|
|
3107
|
+
actor_subject TEXT,
|
|
3108
|
+
actor_role TEXT,
|
|
3109
|
+
actor_tenant TEXT
|
|
3089
3110
|
);
|
|
3090
3111
|
|
|
3091
3112
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -3194,6 +3215,16 @@ CREATE INDEX IF NOT EXISTS idx_nodes_tenant_content ON nodes(tenant, content_has
|
|
|
3194
3215
|
CREATE INDEX IF NOT EXISTS idx_contrib_org ON node_contributors(organization, global_id);
|
|
3195
3216
|
CREATE INDEX IF NOT EXISTS idx_nodes_owner ON nodes(session_id, owner);
|
|
3196
3217
|
`;
|
|
3218
|
+
var AUTH_SCHEMA = `
|
|
3219
|
+
CREATE TABLE IF NOT EXISTS auth_credentials (
|
|
3220
|
+
token_hash TEXT PRIMARY KEY,
|
|
3221
|
+
subject TEXT NOT NULL,
|
|
3222
|
+
tenant TEXT NOT NULL DEFAULT 'local',
|
|
3223
|
+
role TEXT NOT NULL,
|
|
3224
|
+
created_at TEXT NOT NULL
|
|
3225
|
+
);
|
|
3226
|
+
CREATE INDEX IF NOT EXISTS idx_auth_subject ON auth_credentials(subject);
|
|
3227
|
+
`;
|
|
3197
3228
|
var CartographyDB = class {
|
|
3198
3229
|
db;
|
|
3199
3230
|
/** 3.6 anomaly settings; defaults apply when no `anomaly` config is supplied. */
|
|
@@ -3213,7 +3244,8 @@ var CartographyDB = class {
|
|
|
3213
3244
|
const version = this.db.pragma("user_version", { simple: true });
|
|
3214
3245
|
if (version === 0) {
|
|
3215
3246
|
this.db.exec(SCHEMA);
|
|
3216
|
-
this.db.
|
|
3247
|
+
this.db.exec(AUTH_SCHEMA);
|
|
3248
|
+
this.db.pragma("user_version = 15");
|
|
3217
3249
|
return;
|
|
3218
3250
|
} else if (version === 1) {
|
|
3219
3251
|
const cols = this.db.prepare("PRAGMA table_info(nodes)").all().map((c) => c.name);
|
|
@@ -3399,6 +3431,18 @@ var CartographyDB = class {
|
|
|
3399
3431
|
}
|
|
3400
3432
|
this.db.pragma("user_version = 14");
|
|
3401
3433
|
}
|
|
3434
|
+
const v14 = this.db.pragma("user_version", { simple: true });
|
|
3435
|
+
if (v14 < 15) {
|
|
3436
|
+
this.db.exec(AUTH_SCHEMA);
|
|
3437
|
+
const ev = this.db.prepare("PRAGMA table_info(activity_events)").all();
|
|
3438
|
+
if (ev.length > 0) {
|
|
3439
|
+
const cols = ev.map((c) => c.name);
|
|
3440
|
+
if (!cols.includes("actor_subject")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_subject TEXT");
|
|
3441
|
+
if (!cols.includes("actor_role")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_role TEXT");
|
|
3442
|
+
if (!cols.includes("actor_tenant")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_tenant TEXT");
|
|
3443
|
+
}
|
|
3444
|
+
this.db.pragma("user_version = 15");
|
|
3445
|
+
}
|
|
3402
3446
|
}
|
|
3403
3447
|
close() {
|
|
3404
3448
|
this.db.pragma("optimize");
|
|
@@ -3829,13 +3873,13 @@ var CartographyDB = class {
|
|
|
3829
3873
|
});
|
|
3830
3874
|
}
|
|
3831
3875
|
// ── Events ──────────────────────────────
|
|
3832
|
-
insertEvent(sessionId, event, taskId) {
|
|
3876
|
+
insertEvent(sessionId, event, taskId, actor) {
|
|
3833
3877
|
const id = crypto.randomUUID();
|
|
3834
3878
|
const tenant = this.tenantOf(sessionId);
|
|
3835
3879
|
this.db.prepare(`
|
|
3836
3880
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3881
|
+
(id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant, actor_subject, actor_role, actor_tenant)
|
|
3882
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3839
3883
|
`).run(
|
|
3840
3884
|
id,
|
|
3841
3885
|
sessionId,
|
|
@@ -3849,9 +3893,52 @@ var CartographyDB = class {
|
|
|
3849
3893
|
event.port ?? null,
|
|
3850
3894
|
event.command ?? null,
|
|
3851
3895
|
event.resultBytes ?? null,
|
|
3852
|
-
tenant
|
|
3896
|
+
tenant,
|
|
3897
|
+
actor?.subject ?? null,
|
|
3898
|
+
actor?.role ?? null,
|
|
3899
|
+
actor?.tenant ?? null
|
|
3853
3900
|
);
|
|
3854
3901
|
}
|
|
3902
|
+
// ── RBAC credential store (4.5) ─────────────
|
|
3903
|
+
/** Number of stored credentials. `0` ⇒ no RBAC configured (fall back to shared/loopback). */
|
|
3904
|
+
countCredentials() {
|
|
3905
|
+
return this.db.prepare("SELECT COUNT(*) AS n FROM auth_credentials").get().n;
|
|
3906
|
+
}
|
|
3907
|
+
/** Look up a credential by its sha256 token hash. */
|
|
3908
|
+
findCredentialByHash(tokenHash) {
|
|
3909
|
+
const r = this.db.prepare("SELECT * FROM auth_credentials WHERE token_hash = ?").get(tokenHash);
|
|
3910
|
+
if (!r) return void 0;
|
|
3911
|
+
return {
|
|
3912
|
+
tokenHash: r["token_hash"],
|
|
3913
|
+
subject: r["subject"],
|
|
3914
|
+
tenant: r["tenant"],
|
|
3915
|
+
role: r["role"],
|
|
3916
|
+
createdAt: r["created_at"]
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3919
|
+
/** Upsert a credential (idempotent on the token hash). Stores only the hash, never the raw token. */
|
|
3920
|
+
addCredential(rec) {
|
|
3921
|
+
this.db.prepare(`
|
|
3922
|
+
INSERT INTO auth_credentials (token_hash, subject, tenant, role, created_at)
|
|
3923
|
+
VALUES (?, ?, ?, ?, ?)
|
|
3924
|
+
ON CONFLICT(token_hash) DO UPDATE SET subject = excluded.subject, tenant = excluded.tenant, role = excluded.role
|
|
3925
|
+
`).run(rec.tokenHash, rec.subject, rec.tenant, rec.role, (/* @__PURE__ */ new Date()).toISOString());
|
|
3926
|
+
}
|
|
3927
|
+
/** List all credentials (token hashes only — the raw token is unrecoverable). */
|
|
3928
|
+
listCredentials() {
|
|
3929
|
+
const rows = this.db.prepare("SELECT * FROM auth_credentials ORDER BY created_at").all();
|
|
3930
|
+
return rows.map((r) => ({
|
|
3931
|
+
tokenHash: r["token_hash"],
|
|
3932
|
+
subject: r["subject"],
|
|
3933
|
+
tenant: r["tenant"],
|
|
3934
|
+
role: r["role"],
|
|
3935
|
+
createdAt: r["created_at"]
|
|
3936
|
+
}));
|
|
3937
|
+
}
|
|
3938
|
+
/** Revoke every credential for a subject. Returns the number removed. */
|
|
3939
|
+
revokeCredentialsBySubject(subject) {
|
|
3940
|
+
return this.db.prepare("DELETE FROM auth_credentials WHERE subject = ?").run(subject).changes;
|
|
3941
|
+
}
|
|
3855
3942
|
getEvents(sessionId, since) {
|
|
3856
3943
|
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
3944
|
return rows.map((r) => {
|
|
@@ -4605,6 +4692,9 @@ var SqliteQueryBackend = class {
|
|
|
4605
4692
|
node(ctx, id, sessionId) {
|
|
4606
4693
|
return this.db.getNode(this.resolveSession(ctx, sessionId), id);
|
|
4607
4694
|
}
|
|
4695
|
+
edges(ctx, sessionId) {
|
|
4696
|
+
return this.db.getEdges(this.resolveSession(ctx, sessionId));
|
|
4697
|
+
}
|
|
4608
4698
|
dependencies(ctx, id, q, sessionId) {
|
|
4609
4699
|
const sid = this.resolveSession(ctx, sessionId);
|
|
4610
4700
|
return this.db.getDependencies(sid, id, {
|
|
@@ -5530,6 +5620,36 @@ async function runDrift(db, config, opts = {}) {
|
|
|
5530
5620
|
return alert;
|
|
5531
5621
|
}
|
|
5532
5622
|
|
|
5623
|
+
// src/auth/rbac.ts
|
|
5624
|
+
var ROLE_RANK = { viewer: 1, operator: 2, admin: 3 };
|
|
5625
|
+
var ACTION_MIN_ROLE = { read: "viewer", discovery: "operator", admin: "admin" };
|
|
5626
|
+
function can(role, action) {
|
|
5627
|
+
return ROLE_RANK[role] >= ROLE_RANK[ACTION_MIN_ROLE[action]];
|
|
5628
|
+
}
|
|
5629
|
+
var AuthorizationError = class extends Error {
|
|
5630
|
+
constructor(action, role) {
|
|
5631
|
+
super(`forbidden: role '${role}' may not perform '${action}'`);
|
|
5632
|
+
this.action = action;
|
|
5633
|
+
this.role = role;
|
|
5634
|
+
this.name = "AuthorizationError";
|
|
5635
|
+
}
|
|
5636
|
+
};
|
|
5637
|
+
function authorize(principal, action) {
|
|
5638
|
+
if (!can(principal.role, action)) throw new AuthorizationError(action, principal.role);
|
|
5639
|
+
}
|
|
5640
|
+
var TenantMismatchError = class extends Error {
|
|
5641
|
+
constructor() {
|
|
5642
|
+
super("forbidden: principal is not scoped to the requested tenant");
|
|
5643
|
+
this.name = "TenantMismatchError";
|
|
5644
|
+
}
|
|
5645
|
+
};
|
|
5646
|
+
function scopeReads(principal) {
|
|
5647
|
+
return principal.tenant;
|
|
5648
|
+
}
|
|
5649
|
+
function assertSameTenant(principal, requestedTenant) {
|
|
5650
|
+
if (requestedTenant !== principal.tenant) throw new TenantMismatchError();
|
|
5651
|
+
}
|
|
5652
|
+
|
|
5533
5653
|
// src/compliance/rulesets/baseline.ts
|
|
5534
5654
|
var baseline = RulesetSchema.parse({
|
|
5535
5655
|
name: "baseline",
|
|
@@ -5817,7 +5937,7 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
|
|
|
5817
5937
|
|
|
5818
5938
|
// src/mcp/server.ts
|
|
5819
5939
|
var SERVER_NAME = "cartography";
|
|
5820
|
-
var SERVER_VERSION = "2.
|
|
5940
|
+
var SERVER_VERSION = "2.6.0";
|
|
5821
5941
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
5822
5942
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
5823
5943
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -6219,6 +6339,14 @@ function createMcpServer(opts = {}) {
|
|
|
6219
6339
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
6220
6340
|
},
|
|
6221
6341
|
async (args) => {
|
|
6342
|
+
if (opts.principal) {
|
|
6343
|
+
try {
|
|
6344
|
+
authorize(opts.principal, "discovery");
|
|
6345
|
+
} catch (err) {
|
|
6346
|
+
if (err instanceof AuthorizationError) return json({ error: `forbidden: role '${opts.principal.role}' may not run discovery (operator required)` });
|
|
6347
|
+
throw err;
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6222
6350
|
let sid = resolveSession();
|
|
6223
6351
|
if (args.update) {
|
|
6224
6352
|
if (!sid) return json({ error: "No session to update; run discovery first." });
|
|
@@ -6314,7 +6442,7 @@ function createMcpServer(opts = {}) {
|
|
|
6314
6442
|
}
|
|
6315
6443
|
|
|
6316
6444
|
// src/mcp/transports.ts
|
|
6317
|
-
var
|
|
6445
|
+
var import_node_crypto6 = require("crypto");
|
|
6318
6446
|
var import_node_http = __toESM(require("http"), 1);
|
|
6319
6447
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6320
6448
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
@@ -6361,7 +6489,41 @@ function defaultAllowedHosts(host2, port) {
|
|
|
6361
6489
|
return [`${host2}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];
|
|
6362
6490
|
}
|
|
6363
6491
|
|
|
6492
|
+
// src/auth/identity.ts
|
|
6493
|
+
var import_node_crypto5 = require("crypto");
|
|
6494
|
+
function hashToken(token) {
|
|
6495
|
+
return (0, import_node_crypto5.createHash)("sha256").update(token, "utf8").digest("hex");
|
|
6496
|
+
}
|
|
6497
|
+
var SqliteCredentialStore = class {
|
|
6498
|
+
constructor(db) {
|
|
6499
|
+
this.db = db;
|
|
6500
|
+
}
|
|
6501
|
+
count() {
|
|
6502
|
+
return this.db.countCredentials();
|
|
6503
|
+
}
|
|
6504
|
+
findByHash(tokenHash) {
|
|
6505
|
+
return this.db.findCredentialByHash(tokenHash);
|
|
6506
|
+
}
|
|
6507
|
+
};
|
|
6508
|
+
function resolvePrincipal(presentedToken, opts) {
|
|
6509
|
+
const tenant = opts.defaultTenant ?? DEFAULT_TENANT;
|
|
6510
|
+
if (opts.store && opts.store.count() > 0) {
|
|
6511
|
+
if (!presentedToken) return void 0;
|
|
6512
|
+
const rec = opts.store.findByHash(hashToken(presentedToken));
|
|
6513
|
+
return rec ? { subject: rec.subject, tenant: rec.tenant, role: rec.role } : void 0;
|
|
6514
|
+
}
|
|
6515
|
+
if (opts.sharedToken) {
|
|
6516
|
+
if (!presentedToken || !timingSafeEqual(presentedToken, opts.sharedToken)) return void 0;
|
|
6517
|
+
return { subject: "shared-token", tenant, role: "admin" };
|
|
6518
|
+
}
|
|
6519
|
+
if (opts.required) return void 0;
|
|
6520
|
+
return { subject: "anonymous", tenant, role: "admin" };
|
|
6521
|
+
}
|
|
6522
|
+
|
|
6364
6523
|
// src/mcp/transports.ts
|
|
6524
|
+
function samePrincipal(a, b) {
|
|
6525
|
+
return a.subject === b.subject && a.tenant === b.tenant && a.role === b.role;
|
|
6526
|
+
}
|
|
6365
6527
|
async function runStdio(server) {
|
|
6366
6528
|
const transport = new import_stdio.StdioServerTransport();
|
|
6367
6529
|
await server.connect(transport);
|
|
@@ -6406,6 +6568,14 @@ async function runHttp(factory, opts = {}) {
|
|
|
6406
6568
|
assertSafeBind({ host: host2, port, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...opts.token ? { token: opts.token } : {} });
|
|
6407
6569
|
const allowedHosts = opts.allowedHosts ?? defaultAllowedHosts(host2, port);
|
|
6408
6570
|
const token = opts.token;
|
|
6571
|
+
const authStore = opts.auth?.store;
|
|
6572
|
+
const defaultTenant = opts.defaultTenant;
|
|
6573
|
+
const resolveAuth = (header) => resolvePrincipal(bearerToken(header), {
|
|
6574
|
+
...authStore ? { store: authStore } : {},
|
|
6575
|
+
...token ? { sharedToken: token } : {},
|
|
6576
|
+
...defaultTenant ? { defaultTenant } : {},
|
|
6577
|
+
...opts.auth?.required ? { required: true } : {}
|
|
6578
|
+
});
|
|
6409
6579
|
const transports = /* @__PURE__ */ new Map();
|
|
6410
6580
|
const httpServer = import_node_http.default.createServer(async (req, res) => {
|
|
6411
6581
|
try {
|
|
@@ -6415,7 +6585,8 @@ async function runHttp(factory, opts = {}) {
|
|
|
6415
6585
|
res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
|
|
6416
6586
|
return;
|
|
6417
6587
|
}
|
|
6418
|
-
|
|
6588
|
+
const principal = resolveAuth(req.headers["authorization"]);
|
|
6589
|
+
if (!principal) {
|
|
6419
6590
|
res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" }).end('{"error":"unauthorized"}');
|
|
6420
6591
|
return;
|
|
6421
6592
|
}
|
|
@@ -6442,8 +6613,12 @@ async function runHttp(factory, opts = {}) {
|
|
|
6442
6613
|
const sessionId = req.headers["mcp-session-id"];
|
|
6443
6614
|
const existing = sessionId ? transports.get(sessionId) : void 0;
|
|
6444
6615
|
if (existing) {
|
|
6616
|
+
if (!samePrincipal(existing.principal, principal)) {
|
|
6617
|
+
res.writeHead(403, { "content-type": "application/json" }).end('{"error":"session belongs to a different principal"}');
|
|
6618
|
+
return;
|
|
6619
|
+
}
|
|
6445
6620
|
const body2 = req.method === "POST" ? await readJsonBody(req) : void 0;
|
|
6446
|
-
await existing.handleRequest(req, res, body2);
|
|
6621
|
+
await existing.transport.handleRequest(req, res, body2);
|
|
6447
6622
|
return;
|
|
6448
6623
|
}
|
|
6449
6624
|
if (req.method !== "POST") {
|
|
@@ -6452,18 +6627,18 @@ async function runHttp(factory, opts = {}) {
|
|
|
6452
6627
|
}
|
|
6453
6628
|
const body = await readJsonBody(req);
|
|
6454
6629
|
const transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
6455
|
-
sessionIdGenerator: () => (0,
|
|
6630
|
+
sessionIdGenerator: () => (0, import_node_crypto6.randomUUID)(),
|
|
6456
6631
|
enableDnsRebindingProtection: true,
|
|
6457
6632
|
allowedHosts,
|
|
6458
6633
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
6459
6634
|
onsessioninitialized: (id) => {
|
|
6460
|
-
transports.set(id, transport);
|
|
6635
|
+
transports.set(id, { transport, principal });
|
|
6461
6636
|
}
|
|
6462
6637
|
});
|
|
6463
6638
|
transport.onclose = () => {
|
|
6464
6639
|
if (transport.sessionId) transports.delete(transport.sessionId);
|
|
6465
6640
|
};
|
|
6466
|
-
await factory().connect(transport);
|
|
6641
|
+
await factory(principal).connect(transport);
|
|
6467
6642
|
await transport.handleRequest(req, res, body);
|
|
6468
6643
|
} catch (err) {
|
|
6469
6644
|
process.stderr.write(`[cartography-mcp] HTTP request failed: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -7295,6 +7470,54 @@ function headerValue(req, name) {
|
|
|
7295
7470
|
return v;
|
|
7296
7471
|
}
|
|
7297
7472
|
|
|
7473
|
+
// src/backstage.ts
|
|
7474
|
+
var COMPONENT_TYPES = ["web_service", "container", "pod"];
|
|
7475
|
+
function sanitize(id) {
|
|
7476
|
+
return id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
7477
|
+
}
|
|
7478
|
+
function toBackstageEntities(nodes, edges, opts = {}) {
|
|
7479
|
+
const owner = opts.org ?? "unknown";
|
|
7480
|
+
return nodes.map((node) => {
|
|
7481
|
+
const kind = COMPONENT_TYPES.includes(node.type) ? "Component" : node.type === "api_endpoint" ? "API" : "Resource";
|
|
7482
|
+
const dependsOn = edges.filter((e) => e.sourceId === node.id).map((e) => `resource:default/${sanitize(e.targetId)}`);
|
|
7483
|
+
return {
|
|
7484
|
+
apiVersion: "backstage.io/v1alpha1",
|
|
7485
|
+
kind,
|
|
7486
|
+
metadata: {
|
|
7487
|
+
name: sanitize(node.id),
|
|
7488
|
+
annotations: {
|
|
7489
|
+
"cartography/discovered-at": node.discoveredAt,
|
|
7490
|
+
"cartography/confidence": String(node.confidence)
|
|
7491
|
+
}
|
|
7492
|
+
},
|
|
7493
|
+
spec: {
|
|
7494
|
+
type: node.type,
|
|
7495
|
+
lifecycle: "production",
|
|
7496
|
+
owner: node.owner ?? owner,
|
|
7497
|
+
...dependsOn.length > 0 ? { dependsOn } : {}
|
|
7498
|
+
}
|
|
7499
|
+
};
|
|
7500
|
+
});
|
|
7501
|
+
}
|
|
7502
|
+
function entitiesToYaml(entities) {
|
|
7503
|
+
return entities.map((e) => {
|
|
7504
|
+
const lines = [
|
|
7505
|
+
`apiVersion: ${e.apiVersion}`,
|
|
7506
|
+
`kind: ${e.kind}`,
|
|
7507
|
+
`metadata:`,
|
|
7508
|
+
` name: ${e.metadata.name}`,
|
|
7509
|
+
` annotations:`,
|
|
7510
|
+
...Object.entries(e.metadata.annotations).map(([k, v]) => ` ${k}: "${v}"`),
|
|
7511
|
+
`spec:`,
|
|
7512
|
+
` type: ${e.spec.type}`,
|
|
7513
|
+
` lifecycle: ${e.spec.lifecycle}`,
|
|
7514
|
+
` owner: ${e.spec.owner}`,
|
|
7515
|
+
...e.spec.dependsOn && e.spec.dependsOn.length > 0 ? [" dependsOn:", ...e.spec.dependsOn.map((d) => ` - ${d}`)] : []
|
|
7516
|
+
];
|
|
7517
|
+
return lines.join("\n");
|
|
7518
|
+
}).join("\n---\n");
|
|
7519
|
+
}
|
|
7520
|
+
|
|
7298
7521
|
// src/api/schemas.ts
|
|
7299
7522
|
var import_zod8 = require("zod");
|
|
7300
7523
|
var DIRECTIONS = ["downstream", "upstream", "both"];
|
|
@@ -7430,6 +7653,21 @@ var ErrorResponse = import_zod8.z.object({
|
|
|
7430
7653
|
error: import_zod8.z.string(),
|
|
7431
7654
|
code: import_zod8.z.string().optional()
|
|
7432
7655
|
});
|
|
7656
|
+
var BackstageEntitySchema = import_zod8.z.object({
|
|
7657
|
+
apiVersion: import_zod8.z.literal("backstage.io/v1alpha1"),
|
|
7658
|
+
kind: import_zod8.z.enum(["Component", "API", "Resource"]),
|
|
7659
|
+
metadata: import_zod8.z.object({
|
|
7660
|
+
name: import_zod8.z.string(),
|
|
7661
|
+
annotations: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.string())
|
|
7662
|
+
}),
|
|
7663
|
+
spec: import_zod8.z.object({
|
|
7664
|
+
type: import_zod8.z.string(),
|
|
7665
|
+
lifecycle: import_zod8.z.string(),
|
|
7666
|
+
owner: import_zod8.z.string(),
|
|
7667
|
+
dependsOn: import_zod8.z.array(import_zod8.z.string()).optional()
|
|
7668
|
+
})
|
|
7669
|
+
});
|
|
7670
|
+
var BackstageCatalogResponse = import_zod8.z.object({ entities: import_zod8.z.array(BackstageEntitySchema) });
|
|
7433
7671
|
var API_SCHEMAS = {
|
|
7434
7672
|
Node: NodeSchema2,
|
|
7435
7673
|
Edge: EdgeSchema2,
|
|
@@ -7441,10 +7679,13 @@ var API_SCHEMAS = {
|
|
|
7441
7679
|
Session: SessionSchema,
|
|
7442
7680
|
Sessions: SessionsResponse,
|
|
7443
7681
|
Health: HealthResponse,
|
|
7444
|
-
Error: ErrorResponse
|
|
7682
|
+
Error: ErrorResponse,
|
|
7683
|
+
BackstageEntity: BackstageEntitySchema,
|
|
7684
|
+
BackstageCatalog: BackstageCatalogResponse
|
|
7445
7685
|
};
|
|
7446
7686
|
|
|
7447
7687
|
// src/api/rest.ts
|
|
7688
|
+
var BACKSTAGE_NODE_CAP = 1e3;
|
|
7448
7689
|
function toApiNode(n) {
|
|
7449
7690
|
const out = { id: n.id, type: n.type, name: n.name, confidence: n.confidence, tags: n.tags };
|
|
7450
7691
|
if (n.domain !== void 0) out["domain"] = n.domain;
|
|
@@ -7579,6 +7820,14 @@ function handleHealth(ctx, d) {
|
|
|
7579
7820
|
const h = d.backend.health(ctx);
|
|
7580
7821
|
return ok(validateOut(HealthResponse, { status: "ok", version: d.version, store: h.store, sessions: h.sessions }));
|
|
7581
7822
|
}
|
|
7823
|
+
function handleBackstageCatalog(ctx, d) {
|
|
7824
|
+
return guard(() => {
|
|
7825
|
+
const page = d.backend.nodes(ctx, { limit: BACKSTAGE_NODE_CAP });
|
|
7826
|
+
const edges = d.backend.edges(ctx);
|
|
7827
|
+
const entities = toBackstageEntities(page.nodes, edges, { org: ctx.tenant });
|
|
7828
|
+
return ok(validateOut(BackstageCatalogResponse, { entities }));
|
|
7829
|
+
});
|
|
7830
|
+
}
|
|
7582
7831
|
|
|
7583
7832
|
// src/api/openapi.ts
|
|
7584
7833
|
function defOf(schema) {
|
|
@@ -7735,6 +7984,13 @@ function buildOpenApiDocument(opts) {
|
|
|
7735
7984
|
parameters: [TENANT_PARAM],
|
|
7736
7985
|
responses: { "200": ok2("Sessions", "Sessions"), ...errorResponses() }
|
|
7737
7986
|
}
|
|
7987
|
+
},
|
|
7988
|
+
"/v1/backstage/catalog": {
|
|
7989
|
+
get: {
|
|
7990
|
+
summary: "The tenant topology as Backstage catalog entities (live data source, 4.6)",
|
|
7991
|
+
parameters: [SESSION_PARAM, TENANT_PARAM],
|
|
7992
|
+
responses: { "200": ok2("BackstageCatalog", "Backstage catalog entities"), ...errorResponses() }
|
|
7993
|
+
}
|
|
7738
7994
|
}
|
|
7739
7995
|
}
|
|
7740
7996
|
};
|
|
@@ -8090,6 +8346,8 @@ async function runApi(opts) {
|
|
|
8090
8346
|
const token = opts.token;
|
|
8091
8347
|
const graphqlEnabled = opts.graphql !== false;
|
|
8092
8348
|
const defaultTenant = opts.tenant?.defaultTenant ?? DEFAULT_TENANT;
|
|
8349
|
+
const authStore = opts.auth?.store;
|
|
8350
|
+
const rbacMode = !!(authStore && authStore.count() > 0);
|
|
8093
8351
|
const log2 = opts.log ?? (() => {
|
|
8094
8352
|
});
|
|
8095
8353
|
const restDeps = { backend: opts.backend, version: opts.version };
|
|
@@ -8148,23 +8406,44 @@ async function runApi(opts) {
|
|
|
8148
8406
|
finish(r.status);
|
|
8149
8407
|
return;
|
|
8150
8408
|
}
|
|
8151
|
-
|
|
8409
|
+
const principal = resolvePrincipal(bearerToken(req.headers["authorization"]), {
|
|
8410
|
+
...authStore ? { store: authStore } : {},
|
|
8411
|
+
...token ? { sharedToken: token } : {},
|
|
8412
|
+
defaultTenant,
|
|
8413
|
+
...opts.auth?.required ? { required: true } : {}
|
|
8414
|
+
});
|
|
8415
|
+
if (!principal) {
|
|
8152
8416
|
send(res, 401, { error: "unauthorized" }, { "www-authenticate": "Bearer", ...cors });
|
|
8153
8417
|
finish(401);
|
|
8154
8418
|
return;
|
|
8155
8419
|
}
|
|
8156
|
-
let ctx;
|
|
8157
8420
|
try {
|
|
8158
|
-
|
|
8159
|
-
tenantLabel = ctx.tenant;
|
|
8421
|
+
authorize(principal, "read");
|
|
8160
8422
|
} catch (err) {
|
|
8161
|
-
if (err instanceof
|
|
8162
|
-
send(res,
|
|
8163
|
-
finish(
|
|
8423
|
+
if (err instanceof AuthorizationError) {
|
|
8424
|
+
send(res, 403, { error: "forbidden" }, cors);
|
|
8425
|
+
finish(403);
|
|
8164
8426
|
return;
|
|
8165
8427
|
}
|
|
8166
8428
|
throw err;
|
|
8167
8429
|
}
|
|
8430
|
+
let ctx;
|
|
8431
|
+
if (rbacMode) {
|
|
8432
|
+
ctx = { tenant: principal.tenant };
|
|
8433
|
+
tenantLabel = principal.tenant;
|
|
8434
|
+
} else {
|
|
8435
|
+
try {
|
|
8436
|
+
ctx = resolveTenant(req, url, opts.tenant ?? {});
|
|
8437
|
+
tenantLabel = ctx.tenant;
|
|
8438
|
+
} catch (err) {
|
|
8439
|
+
if (err instanceof InvalidTenantError) {
|
|
8440
|
+
send(res, 400, { error: "invalid tenant" }, cors);
|
|
8441
|
+
finish(400);
|
|
8442
|
+
return;
|
|
8443
|
+
}
|
|
8444
|
+
throw err;
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8168
8447
|
if (graphqlEnabled && path === "/graphql") {
|
|
8169
8448
|
if (req.method === "GET") {
|
|
8170
8449
|
const g = handleGraphqlGet();
|
|
@@ -8226,6 +8505,8 @@ function dispatchRest(ctx, path, url, deps) {
|
|
|
8226
8505
|
return handleDiff(ctx, url, deps);
|
|
8227
8506
|
case "/v1/sessions":
|
|
8228
8507
|
return handleSessions(ctx, deps);
|
|
8508
|
+
case "/v1/backstage/catalog":
|
|
8509
|
+
return handleBackstageCatalog(ctx, deps);
|
|
8229
8510
|
default: {
|
|
8230
8511
|
const m = DEPENDENCIES_RE.exec(path);
|
|
8231
8512
|
if (m) return handleDependencies(ctx, decodeURIComponent(m[1]), url, deps);
|
|
@@ -8234,6 +8515,30 @@ function dispatchRest(ctx, path, url, deps) {
|
|
|
8234
8515
|
}
|
|
8235
8516
|
}
|
|
8236
8517
|
|
|
8518
|
+
// src/auth/types.ts
|
|
8519
|
+
var import_zod9 = require("zod");
|
|
8520
|
+
var ROLES = ["viewer", "operator", "admin"];
|
|
8521
|
+
var RoleSchema = import_zod9.z.enum(ROLES);
|
|
8522
|
+
var ACTIONS = ["read", "discovery", "admin"];
|
|
8523
|
+
var ActionSchema = import_zod9.z.enum(ACTIONS);
|
|
8524
|
+
var PrincipalSchema = import_zod9.z.object({
|
|
8525
|
+
subject: import_zod9.z.string().min(1),
|
|
8526
|
+
tenant: import_zod9.z.string().min(1),
|
|
8527
|
+
role: RoleSchema
|
|
8528
|
+
});
|
|
8529
|
+
var CredentialConfigSchema = import_zod9.z.object({
|
|
8530
|
+
token: import_zod9.z.string().min(1),
|
|
8531
|
+
subject: import_zod9.z.string().min(1),
|
|
8532
|
+
tenant: import_zod9.z.string().optional(),
|
|
8533
|
+
role: RoleSchema.default("viewer")
|
|
8534
|
+
});
|
|
8535
|
+
var AuthConfigSchema = import_zod9.z.object({
|
|
8536
|
+
/** Seed credentials (merged into the SQLite store on startup). */
|
|
8537
|
+
credentials: import_zod9.z.array(CredentialConfigSchema).optional(),
|
|
8538
|
+
/** Reject unauthenticated requests even on loopback (default: loopback dev stays open). */
|
|
8539
|
+
required: import_zod9.z.boolean().optional()
|
|
8540
|
+
});
|
|
8541
|
+
|
|
8237
8542
|
// src/api/start.ts
|
|
8238
8543
|
var import_node_fs5 = require("fs");
|
|
8239
8544
|
var import_node_path5 = require("path");
|
|
@@ -8261,6 +8566,7 @@ function parseApiArgs(argv) {
|
|
|
8261
8566
|
else if (a === "--db") opts.dbPath = argv[++i];
|
|
8262
8567
|
else if (a === "--session") opts.session = argv[++i];
|
|
8263
8568
|
else if (a === "--tenant" || a === "--org") opts.tenant = argv[++i];
|
|
8569
|
+
else if (a === "--auth-required") opts.authRequired = true;
|
|
8264
8570
|
else if (a === "--help" || a === "-h") opts.help = true;
|
|
8265
8571
|
}
|
|
8266
8572
|
return opts;
|
|
@@ -8276,11 +8582,13 @@ async function startApi(opts = {}) {
|
|
|
8276
8582
|
const host2 = opts.host ?? "127.0.0.1";
|
|
8277
8583
|
const port = opts.port ?? 3737;
|
|
8278
8584
|
const version = readVersion();
|
|
8585
|
+
const authStore = new SqliteCredentialStore(db);
|
|
8279
8586
|
const server = await runApi({
|
|
8280
8587
|
host: host2,
|
|
8281
8588
|
port,
|
|
8282
8589
|
backend,
|
|
8283
8590
|
version,
|
|
8591
|
+
auth: { store: authStore, ...opts.authRequired ? { required: true } : {} },
|
|
8284
8592
|
...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
|
|
8285
8593
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
8286
8594
|
...token ? { token } : {},
|
|
@@ -8775,13 +9083,13 @@ function createClaudeProvider() {
|
|
|
8775
9083
|
}
|
|
8776
9084
|
|
|
8777
9085
|
// src/providers/shell.ts
|
|
8778
|
-
var
|
|
9086
|
+
var import_zod10 = require("zod");
|
|
8779
9087
|
function createBashTool() {
|
|
8780
9088
|
const shell = IS_WIN ? "powershell" : "posix";
|
|
8781
9089
|
return {
|
|
8782
9090
|
name: "Bash",
|
|
8783
9091
|
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:
|
|
9092
|
+
inputShape: { command: import_zod10.z.string().describe("The read-only shell command to run") },
|
|
8785
9093
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
8786
9094
|
handler: async (args) => {
|
|
8787
9095
|
const command = String(args["command"] ?? "").trim();
|
|
@@ -9705,7 +10013,7 @@ var MERMAID_CLASSES = {
|
|
|
9705
10013
|
saas_tool: "fill:#2a1a2a,stroke:#9a3a9a,color:#daf",
|
|
9706
10014
|
unknown: "fill:#2a2a2a,stroke:#5a5a5a,color:#aaa"
|
|
9707
10015
|
};
|
|
9708
|
-
function
|
|
10016
|
+
function sanitize2(id) {
|
|
9709
10017
|
return id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
9710
10018
|
}
|
|
9711
10019
|
function nodeLabel(node) {
|
|
@@ -9747,14 +10055,14 @@ function generateTopologyMermaid(nodes, edges) {
|
|
|
9747
10055
|
const label = LAYER_LABELS[layerKey] ?? layerKey;
|
|
9748
10056
|
lines.push(` subgraph ${layerKey}["${label}"]`);
|
|
9749
10057
|
for (const node of layerNodes) {
|
|
9750
|
-
lines.push(` ${
|
|
10058
|
+
lines.push(` ${sanitize2(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, "")}`);
|
|
9751
10059
|
}
|
|
9752
10060
|
lines.push(" end");
|
|
9753
10061
|
lines.push("");
|
|
9754
10062
|
}
|
|
9755
10063
|
for (const edge of edges) {
|
|
9756
|
-
const src =
|
|
9757
|
-
const tgt =
|
|
10064
|
+
const src = sanitize2(edge.sourceId);
|
|
10065
|
+
const tgt = sanitize2(edge.targetId);
|
|
9758
10066
|
const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;
|
|
9759
10067
|
const arrow = edge.confidence < 0.6 ? `-. "${label}" .->` : `-->|"${label}"|`;
|
|
9760
10068
|
lines.push(` ${src} ${arrow} ${tgt}`);
|
|
@@ -9780,12 +10088,12 @@ function generateDependencyMermaid(nodes, edges) {
|
|
|
9780
10088
|
}
|
|
9781
10089
|
lines.push("");
|
|
9782
10090
|
for (const node of usedNodes) {
|
|
9783
|
-
lines.push(` ${
|
|
10091
|
+
lines.push(` ${sanitize2(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, "")}`);
|
|
9784
10092
|
}
|
|
9785
10093
|
lines.push("");
|
|
9786
10094
|
for (const edge of depEdges) {
|
|
9787
10095
|
const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;
|
|
9788
|
-
lines.push(` ${
|
|
10096
|
+
lines.push(` ${sanitize2(edge.sourceId)} -->|"${label}"| ${sanitize2(edge.targetId)}`);
|
|
9789
10097
|
}
|
|
9790
10098
|
return lines.join("\n");
|
|
9791
10099
|
}
|
|
@@ -9836,44 +10144,21 @@ function generateDiffMermaid(diff) {
|
|
|
9836
10144
|
ensureEndpoint(e.targetId);
|
|
9837
10145
|
}
|
|
9838
10146
|
for (const { node, cls, suffix } of entries.values()) {
|
|
9839
|
-
lines.push(` ${
|
|
10147
|
+
lines.push(` ${sanitize2(node.id)}${diffNodeLabel(node, suffix)}:::${cls}`);
|
|
9840
10148
|
}
|
|
9841
10149
|
lines.push("");
|
|
9842
10150
|
for (const e of diff.edges.added) {
|
|
9843
10151
|
const label = EDGE_LABELS[e.relationship] ?? e.relationship;
|
|
9844
|
-
lines.push(` ${
|
|
10152
|
+
lines.push(` ${sanitize2(e.sourceId)} ==>|"+ ${label}"| ${sanitize2(e.targetId)}`);
|
|
9845
10153
|
}
|
|
9846
10154
|
for (const e of diff.edges.removed) {
|
|
9847
10155
|
const label = EDGE_LABELS[e.relationship] ?? e.relationship;
|
|
9848
|
-
lines.push(` ${
|
|
10156
|
+
lines.push(` ${sanitize2(e.sourceId)} -.->|"- ${label}"| ${sanitize2(e.targetId)}`);
|
|
9849
10157
|
}
|
|
9850
10158
|
return lines.join("\n");
|
|
9851
10159
|
}
|
|
9852
10160
|
function exportBackstageYAML(nodes, edges, org) {
|
|
9853
|
-
|
|
9854
|
-
const docs = [];
|
|
9855
|
-
for (const node of nodes) {
|
|
9856
|
-
const isComponent = ["web_service", "container", "pod"].includes(node.type);
|
|
9857
|
-
const isAPI = node.type === "api_endpoint";
|
|
9858
|
-
const kind = isComponent ? "Component" : isAPI ? "API" : "Resource";
|
|
9859
|
-
const deps = edges.filter((e) => e.sourceId === node.id).map((e) => ` - resource:default/${sanitize(e.targetId)}`);
|
|
9860
|
-
const doc = [
|
|
9861
|
-
`apiVersion: backstage.io/v1alpha1`,
|
|
9862
|
-
`kind: ${kind}`,
|
|
9863
|
-
`metadata:`,
|
|
9864
|
-
` name: ${sanitize(node.id)}`,
|
|
9865
|
-
` annotations:`,
|
|
9866
|
-
` cartography/discovered-at: "${node.discoveredAt}"`,
|
|
9867
|
-
` cartography/confidence: "${node.confidence}"`,
|
|
9868
|
-
`spec:`,
|
|
9869
|
-
` type: ${node.type}`,
|
|
9870
|
-
` lifecycle: production`,
|
|
9871
|
-
` owner: ${node.owner ?? owner}`,
|
|
9872
|
-
...deps.length > 0 ? [" dependsOn:", ...deps] : []
|
|
9873
|
-
].join("\n");
|
|
9874
|
-
docs.push(doc);
|
|
9875
|
-
}
|
|
9876
|
-
return docs.join("\n---\n");
|
|
10161
|
+
return entitiesToYaml(toBackstageEntities(nodes, edges, org !== void 0 ? { org } : {}));
|
|
9877
10162
|
}
|
|
9878
10163
|
function exportJSON(db, sessionId) {
|
|
9879
10164
|
const nodes = db.getNodes(sessionId);
|
|
@@ -11293,9 +11578,9 @@ async function runOnce(cfg, db) {
|
|
|
11293
11578
|
}
|
|
11294
11579
|
|
|
11295
11580
|
// src/sync/hash.ts
|
|
11296
|
-
var
|
|
11581
|
+
var import_node_crypto7 = require("crypto");
|
|
11297
11582
|
function shareHash(kind, payload) {
|
|
11298
|
-
return (0,
|
|
11583
|
+
return (0, import_node_crypto7.createHash)("sha256").update(stableStringify({ kind, payload })).digest("hex");
|
|
11299
11584
|
}
|
|
11300
11585
|
|
|
11301
11586
|
// src/sync/classify.ts
|
|
@@ -11339,7 +11624,7 @@ function classify2(input) {
|
|
|
11339
11624
|
}
|
|
11340
11625
|
|
|
11341
11626
|
// src/sync/push.ts
|
|
11342
|
-
var
|
|
11627
|
+
var import_node_crypto8 = require("crypto");
|
|
11343
11628
|
var PUSH_SCHEMA_VERSION = 1;
|
|
11344
11629
|
var DEFAULT_BATCH = 100;
|
|
11345
11630
|
var DEFAULT_RETRIES = 4;
|
|
@@ -11353,7 +11638,7 @@ function defaultSleep(ms) {
|
|
|
11353
11638
|
}
|
|
11354
11639
|
function batchKey(items) {
|
|
11355
11640
|
const hashes = items.map((i) => i.contentHash).sort();
|
|
11356
|
-
return (0,
|
|
11641
|
+
return (0, import_node_crypto8.createHash)("sha256").update(stableStringify(hashes)).digest("hex");
|
|
11357
11642
|
}
|
|
11358
11643
|
async function pushDeltas(config, items, opts = {}) {
|
|
11359
11644
|
const central = config.centralDb;
|
|
@@ -11559,6 +11844,10 @@ function checkClaudePrerequisites() {
|
|
|
11559
11844
|
}
|
|
11560
11845
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11561
11846
|
0 && (module.exports = {
|
|
11847
|
+
ACTIONS,
|
|
11848
|
+
ActionSchema,
|
|
11849
|
+
AuthConfigSchema,
|
|
11850
|
+
AuthorizationError,
|
|
11562
11851
|
CLIENTS,
|
|
11563
11852
|
CONFIDENCE,
|
|
11564
11853
|
CartographyDB,
|
|
@@ -11567,6 +11856,7 @@ function checkClaudePrerequisites() {
|
|
|
11567
11856
|
ConditionSchema,
|
|
11568
11857
|
ConfigError,
|
|
11569
11858
|
ControlResultSchema,
|
|
11859
|
+
CredentialConfigSchema,
|
|
11570
11860
|
CsvCostSource,
|
|
11571
11861
|
DEFAULT_ANOMALY_THRESHOLDS,
|
|
11572
11862
|
DEFAULT_SERVER_NAME,
|
|
@@ -11586,8 +11876,11 @@ function checkClaudePrerequisites() {
|
|
|
11586
11876
|
PRIVATE_IP,
|
|
11587
11877
|
PUSH_SCHEMA_VERSION,
|
|
11588
11878
|
PagerDutySink,
|
|
11879
|
+
PrincipalSchema,
|
|
11589
11880
|
ProviderRegistry,
|
|
11590
11881
|
RELATION_TO_DIRECTION,
|
|
11882
|
+
ROLES,
|
|
11883
|
+
RoleSchema,
|
|
11591
11884
|
RuleCheckSchema,
|
|
11592
11885
|
RulesetSchema,
|
|
11593
11886
|
SCAN_ARG_PATTERNS,
|
|
@@ -11598,10 +11891,12 @@ function checkClaudePrerequisites() {
|
|
|
11598
11891
|
ScannerShape,
|
|
11599
11892
|
SharingLevelSchema,
|
|
11600
11893
|
SlackSink,
|
|
11894
|
+
SqliteCredentialStore,
|
|
11601
11895
|
SqliteQueryBackend,
|
|
11602
11896
|
SqliteStoreBackend,
|
|
11603
11897
|
StdoutSink,
|
|
11604
11898
|
TENANT_HEADER,
|
|
11899
|
+
TenantMismatchError,
|
|
11605
11900
|
VectorStore,
|
|
11606
11901
|
WebhookSink,
|
|
11607
11902
|
applyInstall,
|
|
@@ -11609,7 +11904,9 @@ function checkClaudePrerequisites() {
|
|
|
11609
11904
|
assertReadOnly,
|
|
11610
11905
|
assertSafeBind,
|
|
11611
11906
|
assertSafeScanArg,
|
|
11907
|
+
assertSameTenant,
|
|
11612
11908
|
assignColors,
|
|
11909
|
+
authorize,
|
|
11613
11910
|
bearerToken,
|
|
11614
11911
|
bookmarksScanner,
|
|
11615
11912
|
buildCartographyToolHandlers,
|
|
@@ -11617,6 +11914,7 @@ function checkClaudePrerequisites() {
|
|
|
11617
11914
|
buildOpenApiDocument,
|
|
11618
11915
|
buildReport,
|
|
11619
11916
|
buildSinks,
|
|
11917
|
+
can,
|
|
11620
11918
|
centralDbFromEnv,
|
|
11621
11919
|
checkBearer,
|
|
11622
11920
|
checkPrerequisites,
|
|
@@ -11665,6 +11963,7 @@ function checkClaudePrerequisites() {
|
|
|
11665
11963
|
diffTopology,
|
|
11666
11964
|
edgesToConnections,
|
|
11667
11965
|
enrichCosts,
|
|
11966
|
+
entitiesToYaml,
|
|
11668
11967
|
evaluateCheck,
|
|
11669
11968
|
evaluateRule,
|
|
11670
11969
|
evidenceLine,
|
|
@@ -11693,6 +11992,7 @@ function checkClaudePrerequisites() {
|
|
|
11693
11992
|
globalId,
|
|
11694
11993
|
groupByDomain,
|
|
11695
11994
|
handleGraphqlGet,
|
|
11995
|
+
hashToken,
|
|
11696
11996
|
hexCorners,
|
|
11697
11997
|
hexDistance,
|
|
11698
11998
|
hexNeighbors,
|
|
@@ -11759,6 +12059,7 @@ function checkClaudePrerequisites() {
|
|
|
11759
12059
|
renderDiff,
|
|
11760
12060
|
resolveEffectiveLevel,
|
|
11761
12061
|
resolveNlQuery,
|
|
12062
|
+
resolvePrincipal,
|
|
11762
12063
|
resolveSharingLevel,
|
|
11763
12064
|
resolveTenant,
|
|
11764
12065
|
revalidateAnonymized,
|
|
@@ -11778,6 +12079,7 @@ function checkClaudePrerequisites() {
|
|
|
11778
12079
|
safetyHook,
|
|
11779
12080
|
sanitizeUntrusted,
|
|
11780
12081
|
sanitizeValue,
|
|
12082
|
+
scopeReads,
|
|
11781
12083
|
scoreTopology,
|
|
11782
12084
|
securityRelevantChange,
|
|
11783
12085
|
serializeConfig,
|
|
@@ -11791,6 +12093,7 @@ function checkClaudePrerequisites() {
|
|
|
11791
12093
|
startApi,
|
|
11792
12094
|
stripSensitive,
|
|
11793
12095
|
timingSafeEqual,
|
|
12096
|
+
toBackstageEntities,
|
|
11794
12097
|
validateScanner,
|
|
11795
12098
|
vscodeDeeplink,
|
|
11796
12099
|
zodToJsonSchema
|