@datasynx/agentic-ai-cartography 2.3.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 +3 -3
- package/dist/{chunk-7QEBFMN4.js → chunk-GA4427LB.js} +147 -18
- package/dist/chunk-GA4427LB.js.map +1 -0
- package/dist/{chunk-7VZH5PFV.js → chunk-NQXZUWOI.js} +42 -12
- package/dist/chunk-NQXZUWOI.js.map +1 -0
- package/dist/{chunk-WCR47QA2.js → chunk-QQOQBE2A.js} +16 -5
- package/dist/chunk-QQOQBE2A.js.map +1 -0
- package/dist/{chunk-B2AKONVW.js → chunk-RYQ4KQCK.js} +253 -56
- package/dist/chunk-RYQ4KQCK.js.map +1 -0
- package/dist/cli.js +89 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +502 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +390 -11
- package/dist/index.d.ts +390 -11
- package/dist/index.js +475 -73
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +3 -3
- package/dist/{types-TJWXAQ2L.js → types-5L3AGZLG.js} +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-7QEBFMN4.js.map +0 -1
- package/dist/chunk-7VZH5PFV.js.map +0 -1
- package/dist/chunk-B2AKONVW.js.map +0 -1
- package/dist/chunk-WCR47QA2.js.map +0 -1
- /package/dist/{types-TJWXAQ2L.js.map → types-5L3AGZLG.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -156,15 +156,26 @@ var SECURITY_METADATA_KEYS = [
|
|
|
156
156
|
var DriftConfigSchema = z.object({
|
|
157
157
|
minSeverity: z.enum(SEVERITIES).default("info"),
|
|
158
158
|
sinks: z.array(z.object({
|
|
159
|
-
type: z.enum(["stdout", "webhook"]),
|
|
159
|
+
type: z.enum(["stdout", "webhook", "slack", "pagerduty", "jira"]),
|
|
160
160
|
url: z.string().url().optional(),
|
|
161
161
|
token: z.string().optional(),
|
|
162
|
-
timeoutMs: z.number().int().positive().optional()
|
|
162
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
163
|
+
routingKey: z.string().optional(),
|
|
164
|
+
email: z.string().optional(),
|
|
165
|
+
project: z.string().optional(),
|
|
166
|
+
issueType: z.string().optional()
|
|
163
167
|
})).default([{ type: "stdout" }])
|
|
164
168
|
}).superRefine((cfg, ctx) => {
|
|
165
169
|
for (const [i, s] of cfg.sinks.entries()) {
|
|
166
|
-
|
|
167
|
-
ctx.addIssue({ code: "custom", path: ["sinks", i, "url"], message:
|
|
170
|
+
const requireUrl = (msg) => {
|
|
171
|
+
if (!s.url) ctx.addIssue({ code: "custom", path: ["sinks", i, "url"], message: msg });
|
|
172
|
+
};
|
|
173
|
+
if (s.type === "webhook") requireUrl("webhook sink requires a url");
|
|
174
|
+
if (s.type === "slack") requireUrl("slack sink requires a webhook url");
|
|
175
|
+
if (s.type === "jira") {
|
|
176
|
+
requireUrl("jira sink requires a base url");
|
|
177
|
+
if (!s.email) ctx.addIssue({ code: "custom", path: ["sinks", i, "email"], message: "jira sink requires an email" });
|
|
178
|
+
if (!s.project) ctx.addIssue({ code: "custom", path: ["sinks", i, "project"], message: "jira sink requires a project key" });
|
|
168
179
|
}
|
|
169
180
|
}
|
|
170
181
|
});
|
|
@@ -1778,8 +1789,17 @@ function stripSensitive(target) {
|
|
|
1778
1789
|
const stripped = `${url.hostname}${url.port ? ":" + url.port : ""}`;
|
|
1779
1790
|
return stripped || raw;
|
|
1780
1791
|
} catch {
|
|
1781
|
-
|
|
1782
|
-
|
|
1792
|
+
let s = raw;
|
|
1793
|
+
const slash = s.indexOf("/");
|
|
1794
|
+
if (slash >= 0) s = s.slice(0, slash);
|
|
1795
|
+
const q = s.indexOf("?");
|
|
1796
|
+
if (q >= 0) s = s.slice(0, q);
|
|
1797
|
+
const at = s.indexOf("@");
|
|
1798
|
+
if (at >= 0) {
|
|
1799
|
+
const colon = s.lastIndexOf(":");
|
|
1800
|
+
if (colon > at) s = s.slice(0, at) + ":" + s.slice(colon + 1);
|
|
1801
|
+
}
|
|
1802
|
+
return s || raw;
|
|
1783
1803
|
}
|
|
1784
1804
|
}
|
|
1785
1805
|
var SCAN_ARG_PATTERNS = {
|
|
@@ -1797,7 +1817,7 @@ function assertSafeScanArg(kind, value) {
|
|
|
1797
1817
|
return value;
|
|
1798
1818
|
}
|
|
1799
1819
|
function redactSecrets(value) {
|
|
1800
|
-
return value.replace(/([a-z][a-z0-9+.-]
|
|
1820
|
+
return value.replace(/([a-z][a-z0-9+.-]{0,63}:\/\/[^:@/\s]{1,256}):[^@/\s]{1,256}@/gi, "$1:***@");
|
|
1801
1821
|
}
|
|
1802
1822
|
function redactValue(value) {
|
|
1803
1823
|
if (typeof value === "string") return redactSecrets(value);
|
|
@@ -2795,7 +2815,10 @@ CREATE TABLE IF NOT EXISTS activity_events (
|
|
|
2795
2815
|
duration_ms INTEGER,
|
|
2796
2816
|
command TEXT,
|
|
2797
2817
|
result_bytes INTEGER,
|
|
2798
|
-
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
|
|
2799
2822
|
);
|
|
2800
2823
|
|
|
2801
2824
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -2904,6 +2927,16 @@ CREATE INDEX IF NOT EXISTS idx_nodes_tenant_content ON nodes(tenant, content_has
|
|
|
2904
2927
|
CREATE INDEX IF NOT EXISTS idx_contrib_org ON node_contributors(organization, global_id);
|
|
2905
2928
|
CREATE INDEX IF NOT EXISTS idx_nodes_owner ON nodes(session_id, owner);
|
|
2906
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
|
+
`;
|
|
2907
2940
|
var CartographyDB = class {
|
|
2908
2941
|
db;
|
|
2909
2942
|
/** 3.6 anomaly settings; defaults apply when no `anomaly` config is supplied. */
|
|
@@ -2923,7 +2956,8 @@ var CartographyDB = class {
|
|
|
2923
2956
|
const version = this.db.pragma("user_version", { simple: true });
|
|
2924
2957
|
if (version === 0) {
|
|
2925
2958
|
this.db.exec(SCHEMA);
|
|
2926
|
-
this.db.
|
|
2959
|
+
this.db.exec(AUTH_SCHEMA);
|
|
2960
|
+
this.db.pragma("user_version = 15");
|
|
2927
2961
|
return;
|
|
2928
2962
|
} else if (version === 1) {
|
|
2929
2963
|
const cols = this.db.prepare("PRAGMA table_info(nodes)").all().map((c) => c.name);
|
|
@@ -3109,6 +3143,18 @@ var CartographyDB = class {
|
|
|
3109
3143
|
}
|
|
3110
3144
|
this.db.pragma("user_version = 14");
|
|
3111
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
|
+
}
|
|
3112
3158
|
}
|
|
3113
3159
|
close() {
|
|
3114
3160
|
this.db.pragma("optimize");
|
|
@@ -3539,13 +3585,13 @@ var CartographyDB = class {
|
|
|
3539
3585
|
});
|
|
3540
3586
|
}
|
|
3541
3587
|
// ── Events ──────────────────────────────
|
|
3542
|
-
insertEvent(sessionId, event, taskId) {
|
|
3588
|
+
insertEvent(sessionId, event, taskId, actor) {
|
|
3543
3589
|
const id = crypto.randomUUID();
|
|
3544
3590
|
const tenant = this.tenantOf(sessionId);
|
|
3545
3591
|
this.db.prepare(`
|
|
3546
3592
|
INSERT INTO activity_events
|
|
3547
|
-
(id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant)
|
|
3548
|
-
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3549
3595
|
`).run(
|
|
3550
3596
|
id,
|
|
3551
3597
|
sessionId,
|
|
@@ -3559,9 +3605,52 @@ var CartographyDB = class {
|
|
|
3559
3605
|
event.port ?? null,
|
|
3560
3606
|
event.command ?? null,
|
|
3561
3607
|
event.resultBytes ?? null,
|
|
3562
|
-
tenant
|
|
3608
|
+
tenant,
|
|
3609
|
+
actor?.subject ?? null,
|
|
3610
|
+
actor?.role ?? null,
|
|
3611
|
+
actor?.tenant ?? null
|
|
3563
3612
|
);
|
|
3564
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
|
+
}
|
|
3565
3654
|
getEvents(sessionId, since) {
|
|
3566
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);
|
|
3567
3656
|
return rows.map((r) => {
|
|
@@ -4895,6 +4984,41 @@ var StdoutSink = class {
|
|
|
4895
4984
|
|
|
4896
4985
|
// src/sinks/webhook.ts
|
|
4897
4986
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
4987
|
+
async function postJson(opts) {
|
|
4988
|
+
const doFetch = opts.fetchImpl ?? (typeof fetch === "function" ? fetch : void 0);
|
|
4989
|
+
if (!doFetch) {
|
|
4990
|
+
logWarn("sink unavailable: global fetch missing", { sink: opts.sinkName });
|
|
4991
|
+
return;
|
|
4992
|
+
}
|
|
4993
|
+
if (!opts.url) {
|
|
4994
|
+
logWarn("sink unavailable: no url configured", { sink: opts.sinkName });
|
|
4995
|
+
return;
|
|
4996
|
+
}
|
|
4997
|
+
if (!isSecureWebhookUrl(opts.url)) {
|
|
4998
|
+
logWarn("sink refused: insecure scheme (use https:// or a loopback host)", {
|
|
4999
|
+
sink: opts.sinkName,
|
|
5000
|
+
host: stripSensitive(opts.url)
|
|
5001
|
+
});
|
|
5002
|
+
return;
|
|
5003
|
+
}
|
|
5004
|
+
try {
|
|
5005
|
+
const res = await doFetch(opts.url, {
|
|
5006
|
+
method: "POST",
|
|
5007
|
+
headers: { "content-type": "application/json", ...opts.headers ?? {} },
|
|
5008
|
+
body: JSON.stringify(opts.body),
|
|
5009
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 1e4)
|
|
5010
|
+
});
|
|
5011
|
+
if (!res.ok) {
|
|
5012
|
+
logError("sink delivery failed", { sink: opts.sinkName, host: stripSensitive(opts.url), status: res.status });
|
|
5013
|
+
}
|
|
5014
|
+
} catch (err) {
|
|
5015
|
+
logError("sink delivery failed", {
|
|
5016
|
+
sink: opts.sinkName,
|
|
5017
|
+
host: stripSensitive(opts.url),
|
|
5018
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
5019
|
+
});
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
4898
5022
|
function isSecureWebhookUrl(url, env = process.env) {
|
|
4899
5023
|
if (env.CARTOGRAPHY_ALLOW_INSECURE_SYNC === "1") return true;
|
|
4900
5024
|
let parsed;
|
|
@@ -4913,59 +5037,177 @@ var WebhookSink = class {
|
|
|
4913
5037
|
}
|
|
4914
5038
|
name = "webhook";
|
|
4915
5039
|
async emit(alert) {
|
|
4916
|
-
if (typeof fetch !== "function") {
|
|
4917
|
-
logWarn("webhook sink unavailable: global fetch missing", { sink: this.name });
|
|
4918
|
-
return;
|
|
4919
|
-
}
|
|
4920
5040
|
const { url, token, timeoutMs } = this.opts;
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
5041
|
+
await postJson({
|
|
5042
|
+
url,
|
|
5043
|
+
body: redactValue(alert),
|
|
5044
|
+
...token ? { headers: { authorization: `Bearer ${token}` } } : {},
|
|
5045
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
5046
|
+
sinkName: this.name
|
|
5047
|
+
});
|
|
5048
|
+
}
|
|
5049
|
+
};
|
|
5050
|
+
|
|
5051
|
+
// src/sinks/providers.ts
|
|
5052
|
+
var MAX_ITEMS2 = 20;
|
|
5053
|
+
var SEVERITY_EMOJI = { info: "\u{1F7E2}", warning: "\u{1F7E1}", critical: "\u{1F534}" };
|
|
5054
|
+
function headline(alert) {
|
|
5055
|
+
const s = alert.summary;
|
|
5056
|
+
return `${s.nodesAdded}+ / ${s.nodesRemoved}- / ${s.nodesChanged}~ nodes, ${s.edgesAdded}+ / ${s.edgesRemoved}- edges`;
|
|
5057
|
+
}
|
|
5058
|
+
function itemLine(it) {
|
|
5059
|
+
const sec = it.securityFields?.length ? ` [security: ${it.securityFields.join(", ")}]` : "";
|
|
5060
|
+
const fields = it.changedFields?.length ? ` (${it.changedFields.join(", ")})` : "";
|
|
5061
|
+
return `${it.severity.toUpperCase()} \xB7 ${it.kind} \xB7 ${it.label}${fields}${sec}`;
|
|
5062
|
+
}
|
|
5063
|
+
function bodyText(alert) {
|
|
5064
|
+
const lines = alert.items.slice(0, MAX_ITEMS2).map(itemLine);
|
|
5065
|
+
const more = alert.items.length > MAX_ITEMS2 ? [`\u2026and ${alert.items.length - MAX_ITEMS2} more`] : [];
|
|
5066
|
+
return [headline(alert), "", ...lines, ...more].join("\n");
|
|
5067
|
+
}
|
|
5068
|
+
function formatSlack(alert) {
|
|
5069
|
+
const title = `${SEVERITY_EMOJI[alert.severity]} Topology drift \u2014 ${alert.severity}`;
|
|
5070
|
+
return {
|
|
5071
|
+
text: `${title}: ${headline(alert)}`,
|
|
5072
|
+
blocks: [
|
|
5073
|
+
{ type: "header", text: { type: "plain_text", text: title, emoji: true } },
|
|
5074
|
+
{ type: "section", text: { type: "mrkdwn", text: "```" + bodyText(alert) + "```" } },
|
|
5075
|
+
{ type: "context", elements: [{ type: "mrkdwn", text: `base ${alert.base.sessionId} \u2192 current ${alert.current.sessionId} \xB7 ${alert.generatedAt}` }] }
|
|
5076
|
+
]
|
|
5077
|
+
};
|
|
5078
|
+
}
|
|
5079
|
+
var PD_SEVERITY = {
|
|
5080
|
+
info: "info",
|
|
5081
|
+
warning: "warning",
|
|
5082
|
+
critical: "critical"
|
|
5083
|
+
};
|
|
5084
|
+
function formatPagerDuty(alert, routingKey) {
|
|
5085
|
+
return {
|
|
5086
|
+
routing_key: routingKey,
|
|
5087
|
+
event_action: "trigger",
|
|
5088
|
+
// Stable per base→current pair so repeated alerts for the same delta de-duplicate.
|
|
5089
|
+
dedup_key: `cartograph-drift:${alert.base.sessionId}:${alert.current.sessionId}`,
|
|
5090
|
+
payload: {
|
|
5091
|
+
summary: `Cartograph topology drift (${alert.severity}): ${headline(alert)}`,
|
|
5092
|
+
source: "cartograph",
|
|
5093
|
+
severity: PD_SEVERITY[alert.severity],
|
|
5094
|
+
timestamp: alert.generatedAt,
|
|
5095
|
+
custom_details: {
|
|
5096
|
+
summary: alert.summary,
|
|
5097
|
+
items: alert.items.slice(0, MAX_ITEMS2).map((it) => ({
|
|
5098
|
+
kind: it.kind,
|
|
5099
|
+
ref: it.ref,
|
|
5100
|
+
severity: it.severity,
|
|
5101
|
+
...it.changedFields ? { changedFields: it.changedFields } : {},
|
|
5102
|
+
...it.securityFields ? { securityFields: it.securityFields } : {}
|
|
5103
|
+
}))
|
|
4944
5104
|
}
|
|
4945
|
-
} catch (err) {
|
|
4946
|
-
logError("webhook sink failed", {
|
|
4947
|
-
sink: this.name,
|
|
4948
|
-
host: stripSensitive(url),
|
|
4949
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
4950
|
-
});
|
|
4951
5105
|
}
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5108
|
+
function formatJira(alert, opts) {
|
|
5109
|
+
return {
|
|
5110
|
+
fields: {
|
|
5111
|
+
project: { key: opts.project },
|
|
5112
|
+
issuetype: { name: opts.issueType ?? "Task" },
|
|
5113
|
+
summary: `Cartograph topology drift (${alert.severity}): ${headline(alert)}`,
|
|
5114
|
+
description: bodyText(alert) + `
|
|
5115
|
+
|
|
5116
|
+
base ${alert.base.sessionId} \u2192 current ${alert.current.sessionId}
|
|
5117
|
+
generated ${alert.generatedAt}`
|
|
5118
|
+
}
|
|
5119
|
+
};
|
|
5120
|
+
}
|
|
5121
|
+
|
|
5122
|
+
// src/sinks/provider-sink.ts
|
|
5123
|
+
var PAGERDUTY_ENQUEUE_URL = "https://events.pagerduty.com/v2/enqueue";
|
|
5124
|
+
function deliver(name, url, body, opts, headers) {
|
|
5125
|
+
return postJson({
|
|
5126
|
+
url,
|
|
5127
|
+
body,
|
|
5128
|
+
sinkName: name,
|
|
5129
|
+
...headers ? { headers } : {},
|
|
5130
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
5131
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
5132
|
+
});
|
|
5133
|
+
}
|
|
5134
|
+
var SlackSink = class {
|
|
5135
|
+
constructor(opts) {
|
|
5136
|
+
this.opts = opts;
|
|
5137
|
+
}
|
|
5138
|
+
name = "slack";
|
|
5139
|
+
async emit(alert) {
|
|
5140
|
+
await deliver(this.name, this.opts.url, formatSlack(redactValue(alert)), this.opts);
|
|
5141
|
+
}
|
|
5142
|
+
};
|
|
5143
|
+
var PagerDutySink = class {
|
|
5144
|
+
constructor(opts) {
|
|
5145
|
+
this.opts = opts;
|
|
5146
|
+
}
|
|
5147
|
+
name = "pagerduty";
|
|
5148
|
+
async emit(alert) {
|
|
5149
|
+
const body = formatPagerDuty(redactValue(alert), this.opts.routingKey);
|
|
5150
|
+
await deliver(this.name, this.opts.url || PAGERDUTY_ENQUEUE_URL, body, this.opts);
|
|
5151
|
+
}
|
|
5152
|
+
};
|
|
5153
|
+
var JiraSink = class {
|
|
5154
|
+
constructor(opts) {
|
|
5155
|
+
this.opts = opts;
|
|
5156
|
+
}
|
|
5157
|
+
name = "jira";
|
|
5158
|
+
async emit(alert) {
|
|
5159
|
+
const body = formatJira(redactValue(alert), {
|
|
5160
|
+
project: this.opts.project,
|
|
5161
|
+
...this.opts.issueType ? { issueType: this.opts.issueType } : {}
|
|
5162
|
+
});
|
|
5163
|
+
const auth = Buffer.from(`${this.opts.email}:${this.opts.token}`).toString("base64");
|
|
5164
|
+
const base = this.opts.url.replace(/\/+$/, "");
|
|
5165
|
+
await deliver(this.name, `${base}/rest/api/2/issue`, body, this.opts, { authorization: `Basic ${auth}` });
|
|
4952
5166
|
}
|
|
4953
5167
|
};
|
|
4954
5168
|
|
|
4955
5169
|
// src/sinks/index.ts
|
|
4956
5170
|
function buildSinks(drift) {
|
|
4957
5171
|
const configs = drift?.sinks && drift.sinks.length > 0 ? drift.sinks : [{ type: "stdout" }];
|
|
5172
|
+
const envSecret = process.env.CARTOGRAPHY_DRIFT_TOKEN;
|
|
4958
5173
|
const sinks = [];
|
|
4959
5174
|
for (const s of configs) {
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
5175
|
+
const timeoutMs = s.timeoutMs;
|
|
5176
|
+
switch (s.type) {
|
|
5177
|
+
case "webhook":
|
|
5178
|
+
if (!s.url) {
|
|
5179
|
+
logWarn("drift sink skipped: webhook requires a url", { sink: s.type });
|
|
5180
|
+
break;
|
|
5181
|
+
}
|
|
5182
|
+
sinks.push(new WebhookSink({ url: s.url, token: s.token ?? envSecret, timeoutMs }));
|
|
5183
|
+
break;
|
|
5184
|
+
case "slack":
|
|
5185
|
+
if (!s.url) {
|
|
5186
|
+
logWarn("drift sink skipped: slack requires a webhook url", { sink: s.type });
|
|
5187
|
+
break;
|
|
5188
|
+
}
|
|
5189
|
+
sinks.push(new SlackSink({ url: s.url, timeoutMs }));
|
|
5190
|
+
break;
|
|
5191
|
+
case "pagerduty": {
|
|
5192
|
+
const routingKey = s.routingKey ?? s.token ?? envSecret;
|
|
5193
|
+
if (!routingKey) {
|
|
5194
|
+
logWarn("drift sink skipped: pagerduty requires a routingKey (or CARTOGRAPHY_DRIFT_TOKEN)", { sink: s.type });
|
|
5195
|
+
break;
|
|
5196
|
+
}
|
|
5197
|
+
sinks.push(new PagerDutySink({ url: s.url ?? PAGERDUTY_ENQUEUE_URL, routingKey, timeoutMs }));
|
|
5198
|
+
break;
|
|
5199
|
+
}
|
|
5200
|
+
case "jira": {
|
|
5201
|
+
const token = s.token ?? envSecret;
|
|
5202
|
+
if (!s.url || !s.email || !s.project || !token) {
|
|
5203
|
+
logWarn("drift sink skipped: jira requires url, email, project and a token", { sink: s.type });
|
|
5204
|
+
break;
|
|
5205
|
+
}
|
|
5206
|
+
sinks.push(new JiraSink({ url: s.url, email: s.email, token, project: s.project, issueType: s.issueType, timeoutMs }));
|
|
5207
|
+
break;
|
|
5208
|
+
}
|
|
5209
|
+
default:
|
|
5210
|
+
sinks.push(new StdoutSink());
|
|
4969
5211
|
}
|
|
4970
5212
|
}
|
|
4971
5213
|
return sinks.length > 0 ? sinks : [new StdoutSink()];
|
|
@@ -5087,6 +5329,36 @@ async function runDrift(db, config, opts = {}) {
|
|
|
5087
5329
|
return alert;
|
|
5088
5330
|
}
|
|
5089
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
|
+
|
|
5090
5362
|
// src/compliance/rulesets/baseline.ts
|
|
5091
5363
|
var baseline = RulesetSchema.parse({
|
|
5092
5364
|
name: "baseline",
|
|
@@ -5374,7 +5646,7 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
|
|
|
5374
5646
|
|
|
5375
5647
|
// src/mcp/server.ts
|
|
5376
5648
|
var SERVER_NAME = "cartography";
|
|
5377
|
-
var SERVER_VERSION = "2.
|
|
5649
|
+
var SERVER_VERSION = "2.5.0";
|
|
5378
5650
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
5379
5651
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
5380
5652
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -5776,6 +6048,14 @@ function createMcpServer(opts = {}) {
|
|
|
5776
6048
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
|
|
5777
6049
|
},
|
|
5778
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
|
+
}
|
|
5779
6059
|
let sid = resolveSession();
|
|
5780
6060
|
if (args.update) {
|
|
5781
6061
|
if (!sid) return json({ error: "No session to update; run discovery first." });
|
|
@@ -5918,7 +6198,41 @@ function defaultAllowedHosts(host2, port) {
|
|
|
5918
6198
|
return [`${host2}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];
|
|
5919
6199
|
}
|
|
5920
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
|
+
|
|
5921
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
|
+
}
|
|
5922
6236
|
async function runStdio(server) {
|
|
5923
6237
|
const transport = new StdioServerTransport();
|
|
5924
6238
|
await server.connect(transport);
|
|
@@ -5963,6 +6277,14 @@ async function runHttp(factory, opts = {}) {
|
|
|
5963
6277
|
assertSafeBind({ host: host2, port, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...opts.token ? { token: opts.token } : {} });
|
|
5964
6278
|
const allowedHosts = opts.allowedHosts ?? defaultAllowedHosts(host2, port);
|
|
5965
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
|
+
});
|
|
5966
6288
|
const transports = /* @__PURE__ */ new Map();
|
|
5967
6289
|
const httpServer = http.createServer(async (req, res) => {
|
|
5968
6290
|
try {
|
|
@@ -5972,7 +6294,8 @@ async function runHttp(factory, opts = {}) {
|
|
|
5972
6294
|
res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
|
|
5973
6295
|
return;
|
|
5974
6296
|
}
|
|
5975
|
-
|
|
6297
|
+
const principal = resolveAuth(req.headers["authorization"]);
|
|
6298
|
+
if (!principal) {
|
|
5976
6299
|
res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" }).end('{"error":"unauthorized"}');
|
|
5977
6300
|
return;
|
|
5978
6301
|
}
|
|
@@ -5999,8 +6322,12 @@ async function runHttp(factory, opts = {}) {
|
|
|
5999
6322
|
const sessionId = req.headers["mcp-session-id"];
|
|
6000
6323
|
const existing = sessionId ? transports.get(sessionId) : void 0;
|
|
6001
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
|
+
}
|
|
6002
6329
|
const body2 = req.method === "POST" ? await readJsonBody(req) : void 0;
|
|
6003
|
-
await existing.handleRequest(req, res, body2);
|
|
6330
|
+
await existing.transport.handleRequest(req, res, body2);
|
|
6004
6331
|
return;
|
|
6005
6332
|
}
|
|
6006
6333
|
if (req.method !== "POST") {
|
|
@@ -6014,13 +6341,13 @@ async function runHttp(factory, opts = {}) {
|
|
|
6014
6341
|
allowedHosts,
|
|
6015
6342
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
6016
6343
|
onsessioninitialized: (id) => {
|
|
6017
|
-
transports.set(id, transport);
|
|
6344
|
+
transports.set(id, { transport, principal });
|
|
6018
6345
|
}
|
|
6019
6346
|
});
|
|
6020
6347
|
transport.onclose = () => {
|
|
6021
6348
|
if (transport.sessionId) transports.delete(transport.sessionId);
|
|
6022
6349
|
};
|
|
6023
|
-
await factory().connect(transport);
|
|
6350
|
+
await factory(principal).connect(transport);
|
|
6024
6351
|
await transport.handleRequest(req, res, body);
|
|
6025
6352
|
} catch (err) {
|
|
6026
6353
|
process.stderr.write(`[cartography-mcp] HTTP request failed: ${err instanceof Error ? err.message : String(err)}
|
|
@@ -7647,6 +7974,8 @@ async function runApi(opts) {
|
|
|
7647
7974
|
const token = opts.token;
|
|
7648
7975
|
const graphqlEnabled = opts.graphql !== false;
|
|
7649
7976
|
const defaultTenant = opts.tenant?.defaultTenant ?? DEFAULT_TENANT;
|
|
7977
|
+
const authStore = opts.auth?.store;
|
|
7978
|
+
const rbacMode = !!(authStore && authStore.count() > 0);
|
|
7650
7979
|
const log2 = opts.log ?? (() => {
|
|
7651
7980
|
});
|
|
7652
7981
|
const restDeps = { backend: opts.backend, version: opts.version };
|
|
@@ -7705,23 +8034,44 @@ async function runApi(opts) {
|
|
|
7705
8034
|
finish(r.status);
|
|
7706
8035
|
return;
|
|
7707
8036
|
}
|
|
7708
|
-
|
|
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) {
|
|
7709
8044
|
send(res, 401, { error: "unauthorized" }, { "www-authenticate": "Bearer", ...cors });
|
|
7710
8045
|
finish(401);
|
|
7711
8046
|
return;
|
|
7712
8047
|
}
|
|
7713
|
-
let ctx;
|
|
7714
8048
|
try {
|
|
7715
|
-
|
|
7716
|
-
tenantLabel = ctx.tenant;
|
|
8049
|
+
authorize(principal, "read");
|
|
7717
8050
|
} catch (err) {
|
|
7718
|
-
if (err instanceof
|
|
7719
|
-
send(res,
|
|
7720
|
-
finish(
|
|
8051
|
+
if (err instanceof AuthorizationError) {
|
|
8052
|
+
send(res, 403, { error: "forbidden" }, cors);
|
|
8053
|
+
finish(403);
|
|
7721
8054
|
return;
|
|
7722
8055
|
}
|
|
7723
8056
|
throw err;
|
|
7724
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
|
+
}
|
|
7725
8075
|
if (graphqlEnabled && path === "/graphql") {
|
|
7726
8076
|
if (req.method === "GET") {
|
|
7727
8077
|
const g = handleGraphqlGet();
|
|
@@ -7791,6 +8141,30 @@ function dispatchRest(ctx, path, url, deps) {
|
|
|
7791
8141
|
}
|
|
7792
8142
|
}
|
|
7793
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
|
+
|
|
7794
8168
|
// src/api/start.ts
|
|
7795
8169
|
import { readFileSync as readFileSync4 } from "fs";
|
|
7796
8170
|
import { dirname as dirname3, resolve } from "path";
|
|
@@ -7817,6 +8191,7 @@ function parseApiArgs(argv) {
|
|
|
7817
8191
|
else if (a === "--db") opts.dbPath = argv[++i];
|
|
7818
8192
|
else if (a === "--session") opts.session = argv[++i];
|
|
7819
8193
|
else if (a === "--tenant" || a === "--org") opts.tenant = argv[++i];
|
|
8194
|
+
else if (a === "--auth-required") opts.authRequired = true;
|
|
7820
8195
|
else if (a === "--help" || a === "-h") opts.help = true;
|
|
7821
8196
|
}
|
|
7822
8197
|
return opts;
|
|
@@ -7832,11 +8207,13 @@ async function startApi(opts = {}) {
|
|
|
7832
8207
|
const host2 = opts.host ?? "127.0.0.1";
|
|
7833
8208
|
const port = opts.port ?? 3737;
|
|
7834
8209
|
const version = readVersion();
|
|
8210
|
+
const authStore = new SqliteCredentialStore(db);
|
|
7835
8211
|
const server = await runApi({
|
|
7836
8212
|
host: host2,
|
|
7837
8213
|
port,
|
|
7838
8214
|
backend,
|
|
7839
8215
|
version,
|
|
8216
|
+
auth: { store: authStore, ...opts.authRequired ? { required: true } : {} },
|
|
7840
8217
|
...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
|
|
7841
8218
|
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
7842
8219
|
...token ? { token } : {},
|
|
@@ -8331,13 +8708,13 @@ function createClaudeProvider() {
|
|
|
8331
8708
|
}
|
|
8332
8709
|
|
|
8333
8710
|
// src/providers/shell.ts
|
|
8334
|
-
import { z as
|
|
8711
|
+
import { z as z10 } from "zod";
|
|
8335
8712
|
function createBashTool() {
|
|
8336
8713
|
const shell = IS_WIN ? "powershell" : "posix";
|
|
8337
8714
|
return {
|
|
8338
8715
|
name: "Bash",
|
|
8339
8716
|
description: "Run a read-only shell command (inspect ports, processes, config). Mutating or destructive commands are blocked by the read-only allowlist.",
|
|
8340
|
-
inputShape: { command:
|
|
8717
|
+
inputShape: { command: z10.string().describe("The read-only shell command to run") },
|
|
8341
8718
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
8342
8719
|
handler: async (args) => {
|
|
8343
8720
|
const command = String(args["command"] ?? "").trim();
|
|
@@ -10849,9 +11226,9 @@ async function runOnce(cfg, db) {
|
|
|
10849
11226
|
}
|
|
10850
11227
|
|
|
10851
11228
|
// src/sync/hash.ts
|
|
10852
|
-
import { createHash as
|
|
11229
|
+
import { createHash as createHash3 } from "crypto";
|
|
10853
11230
|
function shareHash(kind, payload) {
|
|
10854
|
-
return
|
|
11231
|
+
return createHash3("sha256").update(stableStringify({ kind, payload })).digest("hex");
|
|
10855
11232
|
}
|
|
10856
11233
|
|
|
10857
11234
|
// src/sync/classify.ts
|
|
@@ -10895,7 +11272,7 @@ function classify2(input) {
|
|
|
10895
11272
|
}
|
|
10896
11273
|
|
|
10897
11274
|
// src/sync/push.ts
|
|
10898
|
-
import { createHash as
|
|
11275
|
+
import { createHash as createHash4 } from "crypto";
|
|
10899
11276
|
var PUSH_SCHEMA_VERSION = 1;
|
|
10900
11277
|
var DEFAULT_BATCH = 100;
|
|
10901
11278
|
var DEFAULT_RETRIES = 4;
|
|
@@ -10909,7 +11286,7 @@ function defaultSleep(ms) {
|
|
|
10909
11286
|
}
|
|
10910
11287
|
function batchKey(items) {
|
|
10911
11288
|
const hashes = items.map((i) => i.contentHash).sort();
|
|
10912
|
-
return
|
|
11289
|
+
return createHash4("sha256").update(stableStringify(hashes)).digest("hex");
|
|
10913
11290
|
}
|
|
10914
11291
|
async function pushDeltas(config, items, opts = {}) {
|
|
10915
11292
|
const central = config.centralDb;
|
|
@@ -11114,6 +11491,10 @@ function checkClaudePrerequisites() {
|
|
|
11114
11491
|
}
|
|
11115
11492
|
}
|
|
11116
11493
|
export {
|
|
11494
|
+
ACTIONS,
|
|
11495
|
+
ActionSchema,
|
|
11496
|
+
AuthConfigSchema,
|
|
11497
|
+
AuthorizationError,
|
|
11117
11498
|
CLIENTS,
|
|
11118
11499
|
CONFIDENCE,
|
|
11119
11500
|
CartographyDB,
|
|
@@ -11122,6 +11503,7 @@ export {
|
|
|
11122
11503
|
ConditionSchema,
|
|
11123
11504
|
ConfigError,
|
|
11124
11505
|
ControlResultSchema,
|
|
11506
|
+
CredentialConfigSchema,
|
|
11125
11507
|
CsvCostSource,
|
|
11126
11508
|
DEFAULT_ANOMALY_THRESHOLDS,
|
|
11127
11509
|
DEFAULT_SERVER_NAME,
|
|
@@ -11130,16 +11512,22 @@ export {
|
|
|
11130
11512
|
INGEST_SCHEMA_VERSION,
|
|
11131
11513
|
IngestEnvelopeSchema,
|
|
11132
11514
|
InvalidTenantError,
|
|
11515
|
+
JiraSink,
|
|
11133
11516
|
LOOPBACK_HOSTS2 as LOOPBACK_HOSTS,
|
|
11134
11517
|
MCP_BIN,
|
|
11135
11518
|
NotFoundError,
|
|
11136
11519
|
PACKAGE_NAME,
|
|
11520
|
+
PAGERDUTY_ENQUEUE_URL,
|
|
11137
11521
|
PERSONAL,
|
|
11138
11522
|
PORT_MAP,
|
|
11139
11523
|
PRIVATE_IP,
|
|
11140
11524
|
PUSH_SCHEMA_VERSION,
|
|
11525
|
+
PagerDutySink,
|
|
11526
|
+
PrincipalSchema,
|
|
11141
11527
|
ProviderRegistry,
|
|
11142
11528
|
RELATION_TO_DIRECTION,
|
|
11529
|
+
ROLES,
|
|
11530
|
+
RoleSchema,
|
|
11143
11531
|
RuleCheckSchema,
|
|
11144
11532
|
RulesetSchema,
|
|
11145
11533
|
SCAN_ARG_PATTERNS,
|
|
@@ -11149,10 +11537,13 @@ export {
|
|
|
11149
11537
|
ScannerRegistry,
|
|
11150
11538
|
ScannerShape,
|
|
11151
11539
|
SharingLevelSchema,
|
|
11540
|
+
SlackSink,
|
|
11541
|
+
SqliteCredentialStore,
|
|
11152
11542
|
SqliteQueryBackend,
|
|
11153
11543
|
SqliteStoreBackend,
|
|
11154
11544
|
StdoutSink,
|
|
11155
11545
|
TENANT_HEADER,
|
|
11546
|
+
TenantMismatchError,
|
|
11156
11547
|
VectorStore,
|
|
11157
11548
|
WebhookSink,
|
|
11158
11549
|
applyInstall,
|
|
@@ -11160,7 +11551,9 @@ export {
|
|
|
11160
11551
|
assertReadOnly,
|
|
11161
11552
|
assertSafeBind,
|
|
11162
11553
|
assertSafeScanArg,
|
|
11554
|
+
assertSameTenant,
|
|
11163
11555
|
assignColors,
|
|
11556
|
+
authorize,
|
|
11164
11557
|
bearerToken,
|
|
11165
11558
|
bookmarksScanner,
|
|
11166
11559
|
buildCartographyToolHandlers,
|
|
@@ -11168,6 +11561,7 @@ export {
|
|
|
11168
11561
|
buildOpenApiDocument,
|
|
11169
11562
|
buildReport,
|
|
11170
11563
|
buildSinks,
|
|
11564
|
+
can,
|
|
11171
11565
|
centralDbFromEnv,
|
|
11172
11566
|
checkBearer,
|
|
11173
11567
|
checkPrerequisites,
|
|
@@ -11233,6 +11627,9 @@ export {
|
|
|
11233
11627
|
filterBySeverity,
|
|
11234
11628
|
findAnonViolations,
|
|
11235
11629
|
formatComplianceText,
|
|
11630
|
+
formatJira,
|
|
11631
|
+
formatPagerDuty,
|
|
11632
|
+
formatSlack,
|
|
11236
11633
|
generateDependencyMermaid,
|
|
11237
11634
|
generateDiffMermaid,
|
|
11238
11635
|
generateTopologyMermaid,
|
|
@@ -11241,6 +11638,7 @@ export {
|
|
|
11241
11638
|
globalId,
|
|
11242
11639
|
groupByDomain,
|
|
11243
11640
|
handleGraphqlGet,
|
|
11641
|
+
hashToken,
|
|
11244
11642
|
hexCorners,
|
|
11245
11643
|
hexDistance,
|
|
11246
11644
|
hexNeighbors,
|
|
@@ -11255,6 +11653,7 @@ export {
|
|
|
11255
11653
|
isPersonalHost,
|
|
11256
11654
|
isReadOnlyCommand,
|
|
11257
11655
|
isRemembered,
|
|
11656
|
+
isSecureWebhookUrl,
|
|
11258
11657
|
k8sScanner,
|
|
11259
11658
|
keyMetaOf,
|
|
11260
11659
|
layoutClusters,
|
|
@@ -11293,6 +11692,7 @@ export {
|
|
|
11293
11692
|
pixelToHex,
|
|
11294
11693
|
planInstall,
|
|
11295
11694
|
portsScanner,
|
|
11695
|
+
postJson,
|
|
11296
11696
|
previewShare,
|
|
11297
11697
|
pseudonymize,
|
|
11298
11698
|
pseudonymizeFragment,
|
|
@@ -11305,6 +11705,7 @@ export {
|
|
|
11305
11705
|
renderDiff,
|
|
11306
11706
|
resolveEffectiveLevel,
|
|
11307
11707
|
resolveNlQuery,
|
|
11708
|
+
resolvePrincipal,
|
|
11308
11709
|
resolveSharingLevel,
|
|
11309
11710
|
resolveTenant,
|
|
11310
11711
|
revalidateAnonymized,
|
|
@@ -11324,6 +11725,7 @@ export {
|
|
|
11324
11725
|
safetyHook,
|
|
11325
11726
|
sanitizeUntrusted,
|
|
11326
11727
|
sanitizeValue,
|
|
11728
|
+
scopeReads,
|
|
11327
11729
|
scoreTopology,
|
|
11328
11730
|
securityRelevantChange,
|
|
11329
11731
|
serializeConfig,
|