@datasynx/agentic-ai-cartography 2.9.0 → 2.11.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-LRUWWHMQ.js → chunk-FFUUNSWP.js} +276 -6
- package/dist/chunk-FFUUNSWP.js.map +1 -0
- package/dist/{chunk-YVV6NIT2.js → chunk-LO6YFS6H.js} +2 -1
- package/dist/{chunk-W4Q3TXHR.js → chunk-PD67MOKR.js} +2 -2
- package/dist/cli.js +3 -3
- package/dist/index.cjs +307 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -1
- package/dist/index.d.ts +112 -1
- package/dist/index.js +287 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +2 -2
- package/llms-full.txt +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-LRUWWHMQ.js.map +0 -1
- /package/dist/{chunk-YVV6NIT2.js.map → chunk-LO6YFS6H.js.map} +0 -0
- /package/dist/{chunk-W4Q3TXHR.js.map → chunk-PD67MOKR.js.map} +0 -0
|
@@ -3391,6 +3391,7 @@ export {
|
|
|
3391
3391
|
k8sScanner,
|
|
3392
3392
|
databasesScanner,
|
|
3393
3393
|
stripSensitive,
|
|
3394
|
+
redactSecrets,
|
|
3394
3395
|
redactValue,
|
|
3395
3396
|
buildCartographyToolHandlers,
|
|
3396
3397
|
createCartographyTools,
|
|
@@ -3414,4 +3415,4 @@ export {
|
|
|
3414
3415
|
AuthorizationError,
|
|
3415
3416
|
authorize
|
|
3416
3417
|
};
|
|
3417
|
-
//# sourceMappingURL=chunk-
|
|
3418
|
+
//# sourceMappingURL=chunk-LO6YFS6H.js.map
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
defaultAllowedHosts,
|
|
11
11
|
normalizeTenant,
|
|
12
12
|
resolvePrincipal
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-LO6YFS6H.js";
|
|
14
14
|
import {
|
|
15
15
|
ANOMALY_KINDS,
|
|
16
16
|
ANOMALY_SEVERITIES,
|
|
@@ -1409,4 +1409,4 @@ export {
|
|
|
1409
1409
|
parseApiArgs,
|
|
1410
1410
|
startApi
|
|
1411
1411
|
};
|
|
1412
|
-
//# sourceMappingURL=chunk-
|
|
1412
|
+
//# sourceMappingURL=chunk-PD67MOKR.js.map
|
package/dist/cli.js
CHANGED
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
runDrift,
|
|
12
12
|
runLocalDiscovery,
|
|
13
13
|
startMcp
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-FFUUNSWP.js";
|
|
15
15
|
import {
|
|
16
16
|
entitiesToYaml,
|
|
17
17
|
startApi,
|
|
18
18
|
toBackstageEntities
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-PD67MOKR.js";
|
|
20
20
|
import {
|
|
21
21
|
CartographyDB,
|
|
22
22
|
buildCartographyToolHandlers,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
redactValue,
|
|
29
29
|
stableStringify,
|
|
30
30
|
stripSensitive
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-LO6YFS6H.js";
|
|
32
32
|
import {
|
|
33
33
|
ConfigFileSchema,
|
|
34
34
|
CostEntrySchema,
|
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(src_exports, {
|
|
|
36
36
|
AuthorizationError: () => AuthorizationError,
|
|
37
37
|
CLIENTS: () => CLIENTS,
|
|
38
38
|
CONFIDENCE: () => CONFIDENCE,
|
|
39
|
+
CORRELATION_CONFIDENCE: () => CORRELATION_CONFIDENCE,
|
|
39
40
|
CartographyDB: () => CartographyDB,
|
|
40
41
|
ComplianceReportSchema: () => ComplianceReportSchema,
|
|
41
42
|
ComplianceRuleSchema: () => ComplianceRuleSchema,
|
|
@@ -122,6 +123,7 @@ __export(src_exports, {
|
|
|
122
123
|
computeIdentity: () => computeIdentity,
|
|
123
124
|
connectionsScanner: () => connectionsScanner,
|
|
124
125
|
contentHash: () => contentHash,
|
|
126
|
+
correlateTopology: () => correlateTopology,
|
|
125
127
|
createBashTool: () => createBashTool,
|
|
126
128
|
createCartographyTools: () => createCartographyTools,
|
|
127
129
|
createClaudeProvider: () => createClaudeProvider,
|
|
@@ -169,6 +171,7 @@ __export(src_exports, {
|
|
|
169
171
|
exportJGF: () => exportJGF,
|
|
170
172
|
exportJSON: () => exportJSON,
|
|
171
173
|
extractListeningPorts: () => extractListeningPorts,
|
|
174
|
+
extractSignals: () => extractSignals,
|
|
172
175
|
filterBySeverity: () => filterBySeverity,
|
|
173
176
|
findAnonViolations: () => findAnonViolations,
|
|
174
177
|
formatComplianceText: () => formatComplianceText,
|
|
@@ -235,6 +238,7 @@ __export(src_exports, {
|
|
|
235
238
|
parseNginxUpstreams: () => parseNginxUpstreams,
|
|
236
239
|
parseNlQuery: () => parseNlQuery,
|
|
237
240
|
parseScanHint: () => parseScanHint,
|
|
241
|
+
parseTerraformState: () => parseTerraformState,
|
|
238
242
|
pixelToHex: () => pixelToHex,
|
|
239
243
|
planInstall: () => planInstall,
|
|
240
244
|
portsScanner: () => portsScanner,
|
|
@@ -284,6 +288,8 @@ __export(src_exports, {
|
|
|
284
288
|
stableStringify: () => stableStringify,
|
|
285
289
|
startApi: () => startApi,
|
|
286
290
|
stripSensitive: () => stripSensitive,
|
|
291
|
+
terraformScanner: () => terraformScanner,
|
|
292
|
+
terraformTypeToNode: () => terraformTypeToNode,
|
|
287
293
|
timingSafeEqual: () => timingSafeEqual,
|
|
288
294
|
toBackstageEntities: () => toBackstageEntities,
|
|
289
295
|
validateScanner: () => validateScanner,
|
|
@@ -6134,9 +6140,146 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
|
|
|
6134
6140
|
return executeNlQuery(db, sessionId, search, parseNlQuery(raw), opts);
|
|
6135
6141
|
}
|
|
6136
6142
|
|
|
6143
|
+
// src/correlation/signals.ts
|
|
6144
|
+
var IPV4 = /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g;
|
|
6145
|
+
var DNS = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.){1,8}[a-z]{2,24}\b/gi;
|
|
6146
|
+
var ENDPOINT = /\b((?:[a-z0-9][a-z0-9.-]{0,253})):(\d{1,5})\b/gi;
|
|
6147
|
+
function isPrivateIp(ip) {
|
|
6148
|
+
return /^(?:10\.|127\.|169\.254\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/.test(ip);
|
|
6149
|
+
}
|
|
6150
|
+
function sources(node) {
|
|
6151
|
+
const out = [node.id, node.name];
|
|
6152
|
+
const meta = node.metadata;
|
|
6153
|
+
if (meta) {
|
|
6154
|
+
for (const k of ["host", "hostname", "ip", "address", "dns", "dnsName", "endpoint", "fqdn", "publicIp", "privateIp", "url"]) {
|
|
6155
|
+
const v = meta[k];
|
|
6156
|
+
if (typeof v === "string") out.push(v);
|
|
6157
|
+
}
|
|
6158
|
+
}
|
|
6159
|
+
return out;
|
|
6160
|
+
}
|
|
6161
|
+
var KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["aws", "gcp", "azure", "k8s", "kubernetes", "localhost", "docker"]);
|
|
6162
|
+
function parseProvider(id) {
|
|
6163
|
+
const seg = id.split(":");
|
|
6164
|
+
if (seg.length >= 3 && KNOWN_PROVIDERS.has(seg[1])) return seg[1];
|
|
6165
|
+
return void 0;
|
|
6166
|
+
}
|
|
6167
|
+
function extractSignals(node) {
|
|
6168
|
+
const text = sources(node).join(" \n ");
|
|
6169
|
+
const publicIps = /* @__PURE__ */ new Set();
|
|
6170
|
+
const privateIps = /* @__PURE__ */ new Set();
|
|
6171
|
+
const dnsNames = /* @__PURE__ */ new Set();
|
|
6172
|
+
const endpoints = /* @__PURE__ */ new Set();
|
|
6173
|
+
for (const m of text.matchAll(IPV4)) (isPrivateIp(m[0]) ? privateIps : publicIps).add(m[0]);
|
|
6174
|
+
for (const m of text.matchAll(DNS)) dnsNames.add(m[0].toLowerCase());
|
|
6175
|
+
for (const m of text.matchAll(ENDPOINT)) endpoints.add(`${m[1].toLowerCase()}:${m[2]}`);
|
|
6176
|
+
const provider = parseProvider(node.id);
|
|
6177
|
+
const sortUniq = (s) => [...s].sort();
|
|
6178
|
+
return {
|
|
6179
|
+
...provider ? { provider } : {},
|
|
6180
|
+
endpoints: sortUniq(endpoints),
|
|
6181
|
+
publicIps: sortUniq(publicIps),
|
|
6182
|
+
privateIps: sortUniq(privateIps),
|
|
6183
|
+
// The DNS regex requires an alphabetic TLD, so pure IPv4s never land here.
|
|
6184
|
+
dnsNames: sortUniq(dnsNames)
|
|
6185
|
+
};
|
|
6186
|
+
}
|
|
6187
|
+
|
|
6188
|
+
// src/correlation/correlate.ts
|
|
6189
|
+
var CORRELATION_CONFIDENCE = {
|
|
6190
|
+
"global-identity": 1,
|
|
6191
|
+
"dns-name": 0.95,
|
|
6192
|
+
"public-ip": 0.9,
|
|
6193
|
+
"endpoint": 0.85,
|
|
6194
|
+
"private-ip": 0.5
|
|
6195
|
+
};
|
|
6196
|
+
var MERGE_THRESHOLD = 0.85;
|
|
6197
|
+
var DSU = class {
|
|
6198
|
+
parent;
|
|
6199
|
+
constructor(n) {
|
|
6200
|
+
this.parent = Array.from({ length: n }, (_, i) => i);
|
|
6201
|
+
}
|
|
6202
|
+
find(x) {
|
|
6203
|
+
while (this.parent[x] !== x) {
|
|
6204
|
+
this.parent[x] = this.parent[this.parent[x]];
|
|
6205
|
+
x = this.parent[x];
|
|
6206
|
+
}
|
|
6207
|
+
return x;
|
|
6208
|
+
}
|
|
6209
|
+
union(a, b) {
|
|
6210
|
+
const ra = this.find(a), rb = this.find(b);
|
|
6211
|
+
if (ra !== rb) this.parent[Math.max(ra, rb)] = Math.min(ra, rb);
|
|
6212
|
+
}
|
|
6213
|
+
};
|
|
6214
|
+
function correlateTopology(nodes, _edges = []) {
|
|
6215
|
+
const n = nodes.length;
|
|
6216
|
+
const signals = nodes.map(extractSignals);
|
|
6217
|
+
const dsu = new DSU(n);
|
|
6218
|
+
const correlations = [];
|
|
6219
|
+
const merged = /* @__PURE__ */ new Set();
|
|
6220
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
6221
|
+
const add = (sig, val, i) => {
|
|
6222
|
+
const k = `${sig}|${val}`;
|
|
6223
|
+
const arr = buckets.get(k);
|
|
6224
|
+
if (arr) arr.push(i);
|
|
6225
|
+
else buckets.set(k, [i]);
|
|
6226
|
+
};
|
|
6227
|
+
nodes.forEach((node, i) => {
|
|
6228
|
+
if (node.globalId) add("global-identity", node.globalId, i);
|
|
6229
|
+
for (const d of signals[i].dnsNames) add("dns-name", d, i);
|
|
6230
|
+
for (const ip of signals[i].publicIps) add("public-ip", ip, i);
|
|
6231
|
+
for (const e of signals[i].endpoints) add("endpoint", e, i);
|
|
6232
|
+
});
|
|
6233
|
+
const order = ["global-identity", "dns-name", "public-ip", "endpoint"];
|
|
6234
|
+
for (const sig of order) {
|
|
6235
|
+
const conf = CORRELATION_CONFIDENCE[sig];
|
|
6236
|
+
const keys = [...buckets.keys()].filter((k) => k.startsWith(`${sig}|`)).sort();
|
|
6237
|
+
for (const key of keys) {
|
|
6238
|
+
const members = buckets.get(key).slice().sort((x, y) => nodes[x].id.localeCompare(nodes[y].id));
|
|
6239
|
+
const value = key.slice(sig.length + 1);
|
|
6240
|
+
for (let j = 1; j < members.length; j++) {
|
|
6241
|
+
const a = members[0], b = members[j];
|
|
6242
|
+
if (conf < MERGE_THRESHOLD) continue;
|
|
6243
|
+
dsu.union(a, b);
|
|
6244
|
+
merged.add(a);
|
|
6245
|
+
merged.add(b);
|
|
6246
|
+
const [s, t] = [nodes[a].id, nodes[b].id].sort();
|
|
6247
|
+
correlations.push({ sourceId: s, targetId: t, relationship: "same_as", signal: sig, confidence: conf, evidence: `[${sig}] shared ${value}` });
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
6252
|
+
for (let i = 0; i < n; i++) {
|
|
6253
|
+
const r = dsu.find(i);
|
|
6254
|
+
const arr = clusters.get(r);
|
|
6255
|
+
if (arr) arr.push(i);
|
|
6256
|
+
else clusters.set(r, [i]);
|
|
6257
|
+
}
|
|
6258
|
+
const canonical = [];
|
|
6259
|
+
let crossCloud = 0;
|
|
6260
|
+
for (const idxs of clusters.values()) {
|
|
6261
|
+
const memberNodes = idxs.map((i) => nodes[i]);
|
|
6262
|
+
const members = memberNodes.map((m) => m.id).sort();
|
|
6263
|
+
const providers = [...new Set(idxs.map((i) => signals[i].provider).filter((p) => !!p))].sort();
|
|
6264
|
+
const rep = memberNodes.reduce((a, b) => a.id < b.id ? a : b);
|
|
6265
|
+
const memberIds = new Set(members);
|
|
6266
|
+
const internal = correlations.filter((c) => memberIds.has(c.sourceId) && memberIds.has(c.targetId));
|
|
6267
|
+
const confidence = internal.length ? Math.min(...internal.map((c) => c.confidence)) : 1;
|
|
6268
|
+
if (providers.length > 1) crossCloud += 1;
|
|
6269
|
+
canonical.push({ id: rep.id, type: rep.type, name: rep.name, members, providers, confidence });
|
|
6270
|
+
}
|
|
6271
|
+
canonical.sort((a, b) => a.id.localeCompare(b.id));
|
|
6272
|
+
correlations.sort((a, b) => b.confidence - a.confidence || a.sourceId.localeCompare(b.sourceId) || a.targetId.localeCompare(b.targetId));
|
|
6273
|
+
return {
|
|
6274
|
+
canonical,
|
|
6275
|
+
correlations,
|
|
6276
|
+
summary: { rawNodes: n, canonicalNodes: canonical.length, collapsed: n - canonical.length, crossCloud }
|
|
6277
|
+
};
|
|
6278
|
+
}
|
|
6279
|
+
|
|
6137
6280
|
// src/mcp/server.ts
|
|
6138
6281
|
var SERVER_NAME = "cartography";
|
|
6139
|
-
var SERVER_VERSION = "2.
|
|
6282
|
+
var SERVER_VERSION = "2.11.0";
|
|
6140
6283
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
6141
6284
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
6142
6285
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -6296,6 +6439,15 @@ function createMcpServer(opts = {}) {
|
|
|
6296
6439
|
return json(db.getGraphSummary(sid));
|
|
6297
6440
|
}
|
|
6298
6441
|
);
|
|
6442
|
+
server.registerTool(
|
|
6443
|
+
"correlate_topology",
|
|
6444
|
+
{ title: "Correlate multi-cloud topology", description: "Collapse the same logical resource discovered across clouds/on-prem (by global identity or a shared DNS name / public IP / endpoint) into canonical entities with confidence-scored same_as links. Read-only, non-destructive (5.1).", inputSchema: {}, annotations: readOnly },
|
|
6445
|
+
() => {
|
|
6446
|
+
const sid = resolveSession();
|
|
6447
|
+
if (!sid) return json({ error: "No discovery session found." });
|
|
6448
|
+
return json(correlateTopology(db.getNodes(sid), db.getEdges(sid)));
|
|
6449
|
+
}
|
|
6450
|
+
);
|
|
6299
6451
|
server.registerTool(
|
|
6300
6452
|
"get_cost_summary",
|
|
6301
6453
|
{ title: "Get cost summary", description: "FinOps rollup: cost by domain and owner, currency/period-bucketed (3.3).", inputSchema: {}, annotations: readOnly },
|
|
@@ -7537,9 +7689,132 @@ var serviceConfigScanner = {
|
|
|
7537
7689
|
}
|
|
7538
7690
|
};
|
|
7539
7691
|
|
|
7692
|
+
// src/scanners/terraform.ts
|
|
7693
|
+
var import_node_fs5 = require("fs");
|
|
7694
|
+
var TYPE_RULES = [
|
|
7695
|
+
[/(db_instance|_rds|sql_database|sql_instance|database_instance|cosmosdb|dynamodb|spanner|bigtable|documentdb|redshift)/, "database_server"],
|
|
7696
|
+
[/(elasticache|_redis|memcached|memorystore)/, "cache_server"],
|
|
7697
|
+
[/(s3_bucket|storage_bucket|gcs_bucket|storage_account|_blob)/, "database"],
|
|
7698
|
+
[/(_sqs|_queue|servicebus_queue)/, "queue"],
|
|
7699
|
+
[/(_sns|_topic|pubsub_topic|servicebus_topic)/, "topic"],
|
|
7700
|
+
[/(kafka|_msk|event_hub|kinesis)/, "message_broker"],
|
|
7701
|
+
[/(_eks|_gke|_aks|kubernetes_cluster|container_cluster)/, "k8s_cluster"],
|
|
7702
|
+
[/(ecs_|_container|fargate)/, "container"],
|
|
7703
|
+
[/(lambda|cloud_function|cloudfunctions|function_app|cloud_run)/, "web_service"],
|
|
7704
|
+
[/(_lb$|load_balancer|_alb|_elb|application_gateway)/, "web_service"],
|
|
7705
|
+
[/(api_gateway|apigateway)/, "api_endpoint"],
|
|
7706
|
+
[/(_instance|virtual_machine|_vm$|compute_instance)/, "host"]
|
|
7707
|
+
];
|
|
7708
|
+
function terraformTypeToNode(tfType) {
|
|
7709
|
+
const t = tfType.toLowerCase();
|
|
7710
|
+
for (const [re, nt] of TYPE_RULES) if (re.test(t)) return nt;
|
|
7711
|
+
return "unknown";
|
|
7712
|
+
}
|
|
7713
|
+
var IDENTITY_ATTRS = ["id", "arn", "region", "location", "instance_type", "engine", "machine_type"];
|
|
7714
|
+
var OWNER_TAGS = ["Owner", "owner", "Team", "team"];
|
|
7715
|
+
var SAFE_TAG_KEYS = /* @__PURE__ */ new Set(["Name", "name", "Owner", "owner", "Team", "team", "Env", "env", "Environment", "environment", "Service", "service", "Component", "component", "App", "app", "Project", "project", "Tier", "tier", "Role", "role"]);
|
|
7716
|
+
var SECRET_KEY = /pass|secret|token|key|pwd|cred|private/i;
|
|
7717
|
+
function attrTags(tags) {
|
|
7718
|
+
if (!tags || typeof tags !== "object") return [];
|
|
7719
|
+
return Object.entries(tags).filter(([k]) => SAFE_TAG_KEYS.has(k) && !SECRET_KEY.test(k)).map(([k, v]) => `${k}:${redactSecrets(String(v))}`);
|
|
7720
|
+
}
|
|
7721
|
+
function parseTerraformState(json2) {
|
|
7722
|
+
let state;
|
|
7723
|
+
try {
|
|
7724
|
+
state = JSON.parse(json2);
|
|
7725
|
+
} catch {
|
|
7726
|
+
return { nodes: [], edges: [] };
|
|
7727
|
+
}
|
|
7728
|
+
const resources = Array.isArray(state?.resources) ? state.resources : [];
|
|
7729
|
+
const nodes = [];
|
|
7730
|
+
const edges = [];
|
|
7731
|
+
const addrToId = /* @__PURE__ */ new Map();
|
|
7732
|
+
for (const raw of resources) {
|
|
7733
|
+
const r = raw;
|
|
7734
|
+
if (r.mode && r.mode !== "managed") continue;
|
|
7735
|
+
if (typeof r.type !== "string" || typeof r.name !== "string") continue;
|
|
7736
|
+
const address = `${r.type}.${r.name}`;
|
|
7737
|
+
const nt = terraformTypeToNode(r.type);
|
|
7738
|
+
const id = `${nt}:terraform:${address}`;
|
|
7739
|
+
if (addrToId.has(address)) continue;
|
|
7740
|
+
addrToId.set(address, id);
|
|
7741
|
+
const inst = Array.isArray(r.instances) ? r.instances[0] : void 0;
|
|
7742
|
+
const attrs = inst?.attributes ?? {};
|
|
7743
|
+
const identity = { source: "terraform", tfType: r.type };
|
|
7744
|
+
for (const k of IDENTITY_ATTRS) if (attrs[k] !== void 0) identity[k] = attrs[k];
|
|
7745
|
+
const owner = OWNER_TAGS.map((k) => attrs.tags?.[k]).find((v) => typeof v === "string");
|
|
7746
|
+
nodes.push({
|
|
7747
|
+
id,
|
|
7748
|
+
type: nt,
|
|
7749
|
+
name: address,
|
|
7750
|
+
discoveredVia: "terraform-state",
|
|
7751
|
+
confidence: 0.9,
|
|
7752
|
+
// IaC is authoritative declared intent.
|
|
7753
|
+
metadata: redactValue(identity),
|
|
7754
|
+
tags: attrTags(attrs.tags),
|
|
7755
|
+
...owner ? { owner } : {}
|
|
7756
|
+
});
|
|
7757
|
+
}
|
|
7758
|
+
for (const raw of resources) {
|
|
7759
|
+
const r = raw;
|
|
7760
|
+
if (r.mode && r.mode !== "managed") continue;
|
|
7761
|
+
if (typeof r.type !== "string" || typeof r.name !== "string") continue;
|
|
7762
|
+
const srcId = addrToId.get(`${r.type}.${r.name}`);
|
|
7763
|
+
if (!srcId) continue;
|
|
7764
|
+
const inst = Array.isArray(r.instances) ? r.instances[0] : void 0;
|
|
7765
|
+
const deps = Array.isArray(inst?.dependencies) ? inst.dependencies : [];
|
|
7766
|
+
for (const dep of deps) {
|
|
7767
|
+
if (typeof dep !== "string") continue;
|
|
7768
|
+
const tgtId = addrToId.get(dep) ?? addrToId.get(dep.split("[")[0]);
|
|
7769
|
+
if (!tgtId || tgtId === srcId) continue;
|
|
7770
|
+
edges.push({ sourceId: srcId, targetId: tgtId, relationship: "depends_on", evidence: evidenceLine("config-declared", `terraform depends_on ${dep}`), confidence: 0.85 });
|
|
7771
|
+
}
|
|
7772
|
+
}
|
|
7773
|
+
return { nodes, edges };
|
|
7774
|
+
}
|
|
7775
|
+
function stateDirs() {
|
|
7776
|
+
return [".", "./terraform", "./infra", "./infrastructure", "./deploy", "./terraform/environments"];
|
|
7777
|
+
}
|
|
7778
|
+
function hintPath(hint) {
|
|
7779
|
+
if (!hint) return void 0;
|
|
7780
|
+
const m = /(?:^|[\s,])tfstate=([^\s,]+)/.exec(hint);
|
|
7781
|
+
return m ? m[1] : void 0;
|
|
7782
|
+
}
|
|
7783
|
+
function resolveStatePath(ctx) {
|
|
7784
|
+
const explicit = hintPath(ctx.hint);
|
|
7785
|
+
if (explicit) return explicit;
|
|
7786
|
+
const found = (ctx.findFiles ?? findFiles)(stateDirs(), ["*.tfstate"], 4, 20).split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
7787
|
+
return found[0];
|
|
7788
|
+
}
|
|
7789
|
+
function readStateFile(path) {
|
|
7790
|
+
try {
|
|
7791
|
+
return (0, import_node_fs5.readFileSync)(path, "utf8");
|
|
7792
|
+
} catch {
|
|
7793
|
+
return "";
|
|
7794
|
+
}
|
|
7795
|
+
}
|
|
7796
|
+
var terraformScanner = {
|
|
7797
|
+
id: "terraform-state",
|
|
7798
|
+
title: "Terraform state (IaC)",
|
|
7799
|
+
platforms: "all",
|
|
7800
|
+
// No shell commands — the state file is read directly via node:fs, so an
|
|
7801
|
+
// operator-supplied path can never inject a command (no `cat "${path}"` interpolation).
|
|
7802
|
+
detect(ctx) {
|
|
7803
|
+
return resolveStatePath(ctx) !== void 0;
|
|
7804
|
+
},
|
|
7805
|
+
async scan(ctx) {
|
|
7806
|
+
const path = resolveStatePath(ctx);
|
|
7807
|
+
if (!path) return { nodes: [], edges: [] };
|
|
7808
|
+
const json2 = (ctx.readFile ?? readStateFile)(path);
|
|
7809
|
+
if (!json2) return { nodes: [], edges: [] };
|
|
7810
|
+
const result = parseTerraformState(json2);
|
|
7811
|
+
return { ...result, report: `terraform-state: ${result.nodes.length} resources, ${result.edges.length} dependencies from ${path}` };
|
|
7812
|
+
}
|
|
7813
|
+
};
|
|
7814
|
+
|
|
7540
7815
|
// src/scanners/registry.ts
|
|
7541
7816
|
function defaultRegistry() {
|
|
7542
|
-
return new ScannerRegistry().register(bookmarksScanner).register(installedAppsScanner).register(portsScanner).register(cloudAwsScanner).register(cloudGcpScanner).register(cloudAzureScanner).register(k8sScanner).register(databasesScanner).register(connectionsScanner).register(serviceConfigScanner);
|
|
7817
|
+
return new ScannerRegistry().register(bookmarksScanner).register(installedAppsScanner).register(portsScanner).register(cloudAwsScanner).register(cloudGcpScanner).register(cloudAzureScanner).register(k8sScanner).register(databasesScanner).register(connectionsScanner).register(serviceConfigScanner).register(terraformScanner);
|
|
7543
7818
|
}
|
|
7544
7819
|
|
|
7545
7820
|
// src/scanners/loader.ts
|
|
@@ -8907,14 +9182,14 @@ var AuthConfigSchema = import_zod9.z.object({
|
|
|
8907
9182
|
});
|
|
8908
9183
|
|
|
8909
9184
|
// src/api/start.ts
|
|
8910
|
-
var
|
|
9185
|
+
var import_node_fs6 = require("fs");
|
|
8911
9186
|
var import_node_path5 = require("path");
|
|
8912
9187
|
var import_node_url = require("url");
|
|
8913
9188
|
var import_meta = {};
|
|
8914
9189
|
function readVersion() {
|
|
8915
9190
|
try {
|
|
8916
9191
|
const dir = import_meta.dirname ?? (0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
8917
|
-
return JSON.parse((0,
|
|
9192
|
+
return JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path5.resolve)(dir, "..", "package.json"), "utf-8")).version ?? "0.0.0";
|
|
8918
9193
|
} catch {
|
|
8919
9194
|
return "0.0.0";
|
|
8920
9195
|
}
|
|
@@ -9045,7 +9320,7 @@ function defaultServerEntry(opts = {}) {
|
|
|
9045
9320
|
}
|
|
9046
9321
|
|
|
9047
9322
|
// src/installer/install.ts
|
|
9048
|
-
var
|
|
9323
|
+
var import_node_fs7 = require("fs");
|
|
9049
9324
|
var import_node_path6 = require("path");
|
|
9050
9325
|
var import_node_os4 = require("os");
|
|
9051
9326
|
function currentOs() {
|
|
@@ -9061,8 +9336,8 @@ function planInstall(spec, ctx, opts) {
|
|
|
9061
9336
|
if (!path) {
|
|
9062
9337
|
throw new Error(`${spec.label} does not support the "${ctx.scope}" scope.`);
|
|
9063
9338
|
}
|
|
9064
|
-
const fileExists = (0,
|
|
9065
|
-
const before = fileExists ? (0,
|
|
9339
|
+
const fileExists = (0, import_node_fs7.existsSync)(path);
|
|
9340
|
+
const before = fileExists ? (0, import_node_fs7.readFileSync)(path, "utf8") : "";
|
|
9066
9341
|
const existing = parseConfig(before, spec.format);
|
|
9067
9342
|
const merged = spec.apply(existing, opts.serverName ?? DEFAULT_SERVER_NAME, opts.entry);
|
|
9068
9343
|
const after = serializeConfig(merged, spec.format);
|
|
@@ -9079,8 +9354,8 @@ function planInstall(spec, ctx, opts) {
|
|
|
9079
9354
|
};
|
|
9080
9355
|
}
|
|
9081
9356
|
function applyInstall(plan) {
|
|
9082
|
-
(0,
|
|
9083
|
-
(0,
|
|
9357
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(plan.path), { recursive: true });
|
|
9358
|
+
(0, import_node_fs7.writeFileSync)(plan.path, plan.after, "utf8");
|
|
9084
9359
|
}
|
|
9085
9360
|
function renderDiff(before, after) {
|
|
9086
9361
|
if (before === after) return " (no changes)";
|
|
@@ -9928,7 +10203,7 @@ Use ask_user when you need context from the user.`;
|
|
|
9928
10203
|
}
|
|
9929
10204
|
|
|
9930
10205
|
// src/cost.ts
|
|
9931
|
-
var
|
|
10206
|
+
var import_node_fs8 = require("fs");
|
|
9932
10207
|
var import_node_path8 = require("path");
|
|
9933
10208
|
function splitCsvLine(line) {
|
|
9934
10209
|
const out = [];
|
|
@@ -10007,7 +10282,7 @@ var CsvCostSource = class {
|
|
|
10007
10282
|
}
|
|
10008
10283
|
id;
|
|
10009
10284
|
async fetch() {
|
|
10010
|
-
const text = (0,
|
|
10285
|
+
const text = (0, import_node_fs8.readFileSync)((0, import_node_path8.resolve)(this.opts.filePath), "utf-8");
|
|
10011
10286
|
const records = parseCostCsv(text);
|
|
10012
10287
|
const match = this.opts.match ?? "nodeId";
|
|
10013
10288
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -10049,7 +10324,7 @@ async function enrichCosts(db, sessionId, source) {
|
|
|
10049
10324
|
}
|
|
10050
10325
|
|
|
10051
10326
|
// src/exporter.ts
|
|
10052
|
-
var
|
|
10327
|
+
var import_node_fs9 = require("fs");
|
|
10053
10328
|
var import_node_path9 = require("path");
|
|
10054
10329
|
|
|
10055
10330
|
// src/hex.ts
|
|
@@ -11719,28 +11994,28 @@ function exportComplianceReport(report, format) {
|
|
|
11719
11994
|
return lines.join("\n");
|
|
11720
11995
|
}
|
|
11721
11996
|
function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml", "html", "map", "discovery"]) {
|
|
11722
|
-
(0,
|
|
11997
|
+
(0, import_node_fs9.mkdirSync)(outputDir, { recursive: true });
|
|
11723
11998
|
const nodes = db.getNodes(sessionId);
|
|
11724
11999
|
const edges = db.getEdges(sessionId);
|
|
11725
12000
|
const jgfPath = (0, import_node_path9.join)(outputDir, "cartography-graph.jgf.json");
|
|
11726
|
-
(0,
|
|
12001
|
+
(0, import_node_fs9.writeFileSync)(jgfPath, exportJGF(nodes, edges));
|
|
11727
12002
|
if (formats.includes("mermaid")) {
|
|
11728
|
-
(0,
|
|
11729
|
-
(0,
|
|
12003
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
|
|
12004
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
|
|
11730
12005
|
}
|
|
11731
12006
|
if (formats.includes("json")) {
|
|
11732
|
-
(0,
|
|
12007
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog.json"), exportJSON(db, sessionId));
|
|
11733
12008
|
}
|
|
11734
12009
|
if (formats.includes("yaml")) {
|
|
11735
|
-
(0,
|
|
12010
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
|
|
11736
12011
|
}
|
|
11737
12012
|
if (formats.includes("html") || formats.includes("map") || formats.includes("discovery")) {
|
|
11738
|
-
(0,
|
|
12013
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "discovery.html"), exportDiscoveryApp(nodes, edges));
|
|
11739
12014
|
}
|
|
11740
12015
|
if (formats.includes("cost")) {
|
|
11741
12016
|
const summary = db.getGraphSummary(sessionId);
|
|
11742
|
-
(0,
|
|
11743
|
-
(0,
|
|
12017
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-by-domain.csv"), exportCostCSV(summary));
|
|
12018
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-summary.json"), exportCostSummary(summary));
|
|
11744
12019
|
}
|
|
11745
12020
|
}
|
|
11746
12021
|
|
|
@@ -11772,7 +12047,7 @@ function formatComplianceText(report) {
|
|
|
11772
12047
|
}
|
|
11773
12048
|
|
|
11774
12049
|
// src/config.ts
|
|
11775
|
-
var
|
|
12050
|
+
var import_node_fs10 = require("fs");
|
|
11776
12051
|
var ConfigError = class extends Error {
|
|
11777
12052
|
constructor(message) {
|
|
11778
12053
|
super(message);
|
|
@@ -11797,7 +12072,7 @@ function loadConfig(path) {
|
|
|
11797
12072
|
function readConfigFile(path) {
|
|
11798
12073
|
let raw;
|
|
11799
12074
|
try {
|
|
11800
|
-
raw = (0,
|
|
12075
|
+
raw = (0, import_node_fs10.readFileSync)(path, "utf-8");
|
|
11801
12076
|
} catch (err) {
|
|
11802
12077
|
throw new ConfigError(
|
|
11803
12078
|
`Cannot read config file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -12155,14 +12430,14 @@ function runSyncClassify(db, sessionId, config, opts = {}) {
|
|
|
12155
12430
|
|
|
12156
12431
|
// src/preflight.ts
|
|
12157
12432
|
var import_node_child_process2 = require("child_process");
|
|
12158
|
-
var
|
|
12433
|
+
var import_node_fs11 = require("fs");
|
|
12159
12434
|
var import_node_path10 = require("path");
|
|
12160
12435
|
function isOAuthLoggedIn() {
|
|
12161
12436
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
|
|
12162
12437
|
const credFile = (0, import_node_path10.join)(home, ".claude", ".credentials.json");
|
|
12163
|
-
if (!(0,
|
|
12438
|
+
if (!(0, import_node_fs11.existsSync)(credFile)) return false;
|
|
12164
12439
|
try {
|
|
12165
|
-
const creds = JSON.parse((0,
|
|
12440
|
+
const creds = JSON.parse((0, import_node_fs11.readFileSync)(credFile, "utf8"));
|
|
12166
12441
|
const oauth = creds["claudeAiOauth"];
|
|
12167
12442
|
return typeof oauth?.["accessToken"] === "string" && oauth["accessToken"].length > 0;
|
|
12168
12443
|
} catch {
|
|
@@ -12220,6 +12495,7 @@ function checkClaudePrerequisites() {
|
|
|
12220
12495
|
AuthorizationError,
|
|
12221
12496
|
CLIENTS,
|
|
12222
12497
|
CONFIDENCE,
|
|
12498
|
+
CORRELATION_CONFIDENCE,
|
|
12223
12499
|
CartographyDB,
|
|
12224
12500
|
ComplianceReportSchema,
|
|
12225
12501
|
ComplianceRuleSchema,
|
|
@@ -12306,6 +12582,7 @@ function checkClaudePrerequisites() {
|
|
|
12306
12582
|
computeIdentity,
|
|
12307
12583
|
connectionsScanner,
|
|
12308
12584
|
contentHash,
|
|
12585
|
+
correlateTopology,
|
|
12309
12586
|
createBashTool,
|
|
12310
12587
|
createCartographyTools,
|
|
12311
12588
|
createClaudeProvider,
|
|
@@ -12353,6 +12630,7 @@ function checkClaudePrerequisites() {
|
|
|
12353
12630
|
exportJGF,
|
|
12354
12631
|
exportJSON,
|
|
12355
12632
|
extractListeningPorts,
|
|
12633
|
+
extractSignals,
|
|
12356
12634
|
filterBySeverity,
|
|
12357
12635
|
findAnonViolations,
|
|
12358
12636
|
formatComplianceText,
|
|
@@ -12419,6 +12697,7 @@ function checkClaudePrerequisites() {
|
|
|
12419
12697
|
parseNginxUpstreams,
|
|
12420
12698
|
parseNlQuery,
|
|
12421
12699
|
parseScanHint,
|
|
12700
|
+
parseTerraformState,
|
|
12422
12701
|
pixelToHex,
|
|
12423
12702
|
planInstall,
|
|
12424
12703
|
portsScanner,
|
|
@@ -12468,6 +12747,8 @@ function checkClaudePrerequisites() {
|
|
|
12468
12747
|
stableStringify,
|
|
12469
12748
|
startApi,
|
|
12470
12749
|
stripSensitive,
|
|
12750
|
+
terraformScanner,
|
|
12751
|
+
terraformTypeToNode,
|
|
12471
12752
|
timingSafeEqual,
|
|
12472
12753
|
toBackstageEntities,
|
|
12473
12754
|
validateScanner,
|