@datasynx/agentic-ai-cartography 2.11.0 → 2.12.1
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/README.md +142 -2
- package/dist/{chunk-FFUUNSWP.js → chunk-OIDAXUW5.js} +212 -200
- package/dist/chunk-OIDAXUW5.js.map +1 -0
- package/dist/cli.js +95 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +87 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -3
- package/dist/index.d.ts +94 -3
- package/dist/index.js +80 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +1 -1
- package/llms-full.txt +305 -25
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-FFUUNSWP.js.map +0 -1
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
[What it does](#what-it-does) ·
|
|
43
43
|
[Cross-platform](#cross-platform-support) ·
|
|
44
44
|
[Features](#features) ·
|
|
45
|
+
[Platform & ecosystem](#platform--ecosystem) ·
|
|
45
46
|
[CLI commands](#commands) ·
|
|
46
47
|
[Architecture](#architecture) ·
|
|
47
48
|
[Safety](#safety) ·
|
|
@@ -64,7 +65,7 @@ The topology is exposed with **progressive disclosure** so agents never blow the
|
|
|
64
65
|
context window:
|
|
65
66
|
|
|
66
67
|
- **Resources** (read-only context): `cartography://graph/summary` (low-token index — read first), `cartography://nodes/{id}`, `cartography://services`, `cartography://databases`, `cartography://dependencies/{id}`.
|
|
67
|
-
- **Tools** (parameterized queries): `query_infrastructure`, `search_topology` (semantic), `get_dependencies` (recursive graph traversal), `list_services`, `get_node`, `get_summary`, `run_discovery`.
|
|
68
|
+
- **Tools** (parameterized queries): `query_infrastructure`, `search_topology` (semantic), `get_dependencies` (recursive graph traversal), `query_natural_language` (plain-English topology questions, LLM-free), `correlate_topology` (collapse the same resource across clouds/on-prem), `get_cost_summary`, `score_compliance`, `classify_drift`, `list_services`, `get_node`, `get_summary`, `run_discovery`.
|
|
68
69
|
- **Prompts**: `audit-attack-surface`, `map-service-dependencies`, `onboard-to-system`.
|
|
69
70
|
|
|
70
71
|
### Quick start
|
|
@@ -257,11 +258,125 @@ Cartography runs natively on **Linux**, **macOS**, and **Windows** — no WSL re
|
|
|
257
258
|
| **Database Discovery** | PostgreSQL, MySQL, MongoDB, Redis, SQLite file scan. Windows: `Get-Service` for DB engine detection |
|
|
258
259
|
| **Cloud Scanning** | AWS (EC2/RDS/EKS/S3), GCP (Compute/GKE/Cloud Run), Azure (AKS/WebApps), Kubernetes |
|
|
259
260
|
| **Human-in-the-Loop** | Chat with the agent mid-discovery: type `"hubspot windsurf"` to search for specific tools |
|
|
261
|
+
| **Terraform Import** | First-class `terraform-state` scanner — `*.tfstate` JSON → authoritative IaC nodes/edges, reconciled with observed reality (no `terraform` CLI, no extra credentials) |
|
|
262
|
+
| **Multi-Cloud Correlation** | Collapse the same logical resource discovered across AWS/GCP/Azure/on-prem into canonical entities + confidence-scored `same_as` links (`correlate_topology`, pure & deterministic) |
|
|
263
|
+
| **Intelligence Layer** | Cost attribution (FinOps rollups), compliance scoring (CIS/SOC2/ISO 27001 starters), anomaly detection (orphans / shadow IT), severity-classified drift |
|
|
260
264
|
| **Export Formats** | Mermaid topology, D3.js interactive graph, Backstage YAML, JSON |
|
|
261
265
|
| **Safety First** | Strict read-only **allowlist** (not a denylist): only known-safe commands run — shell-aware for POSIX *and* PowerShell, enforced at the command runner as defense-in-depth. 100% read-only |
|
|
262
266
|
|
|
263
267
|
---
|
|
264
268
|
|
|
269
|
+
## Platform & Ecosystem
|
|
270
|
+
|
|
271
|
+
Beyond the MCP server, Cartography ships a **read-only platform** for teams and an
|
|
272
|
+
**ecosystem** of integrations. Everything below is opt-in, read-only by default, and never
|
|
273
|
+
phones home — the same locked constraints as the core.
|
|
274
|
+
|
|
275
|
+
### REST / GraphQL API + web dashboard (Phase 4)
|
|
276
|
+
|
|
277
|
+
`cartography api` (and the `cartography-api` binary) exposes the topology over a **read-only**
|
|
278
|
+
HTTP API — REST under `/v1/...` with a published **OpenAPI 3.1** document, and **GraphQL** at
|
|
279
|
+
`/graphql` (SDL + introspection, no `Mutation` type). It reuses the MCP transport's constant-time
|
|
280
|
+
bearer auth + DNS-rebinding hardening and the same tenant-scoped query layer — no new runtime
|
|
281
|
+
dependency (Node's built-in `http`).
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
cartography api # loopback dev — REST + /graphql + dashboard, no token
|
|
285
|
+
curl -s http://127.0.0.1:3737/v1/summary | jq .totals
|
|
286
|
+
# Exposed: a non-loopback bind REQUIRES both --allowed-hosts and --token
|
|
287
|
+
cartography api --host 0.0.0.0 --allowed-hosts cartograph.internal:3737 --token "$TOKEN"
|
|
288
|
+
cartography api --no-graphql # REST only
|
|
289
|
+
cartography api --no-dashboard # disable the / and /app web UI
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
The same server hosts a self-contained **web dashboard** at `/` and `/app` — a live, interactive
|
|
293
|
+
Canvas topology view with node drill-down (no CDN, no build step, zero new dependency). It fetches
|
|
294
|
+
the live `/v1/*` API, so it inherits the API's auth + RBAC/tenant scoping for free.
|
|
295
|
+
See **[docs/how-to/api-server.md](docs/how-to/api-server.md)** and
|
|
296
|
+
**[docs/how-to/web-dashboard.md](docs/how-to/web-dashboard.md)**.
|
|
297
|
+
|
|
298
|
+
### Role-based access control (Phase 4)
|
|
299
|
+
|
|
300
|
+
`cartography auth` layers **identity + roles** over the HTTP surfaces. A bearer token resolves to a
|
|
301
|
+
principal `{ subject, tenant, role }`; the server returns **401** for an unknown token, **403** for
|
|
302
|
+
an insufficient role, and **pins every read to the principal's tenant** — no cross-tenant read by
|
|
303
|
+
spoofing a header. Roles are `viewer` ⊂ `operator` (adds `run_discovery`) ⊂ `admin` (adds credential
|
|
304
|
+
admin). Fully backward-compatible: with no credentials configured, the server behaves exactly as
|
|
305
|
+
before. Tokens are stored **hashed** (sha256), printed once on creation.
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
cartography auth add alice --role operator --tenant acme # prints the bearer token ONCE
|
|
309
|
+
cartography auth list # subjects/roles/tenants, never the token
|
|
310
|
+
cartography auth revoke alice
|
|
311
|
+
cartography api --auth-required # require auth even on loopback
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
See **[docs/how-to/rbac.md](docs/how-to/rbac.md)**.
|
|
315
|
+
|
|
316
|
+
### Central collector → production (Phase 4)
|
|
317
|
+
|
|
318
|
+
`cartography mcp --server-mode` runs the binary as a self-hostable **central collector** that pools
|
|
319
|
+
every employee's *consented* discovery into one org-wide topology — consent-first, never phones home.
|
|
320
|
+
It exposes an authenticated `POST /ingest` write route (the consent-gated push envelope, server-side
|
|
321
|
+
anonymization re-validation via `--anon-mode reject|strip`, per-org rate limit → `429 + Retry-After`)
|
|
322
|
+
and an org-wide merged `get_summary`. Public `GET /healthz` (liveness) / `GET /readyz` (readiness)
|
|
323
|
+
probes and a `deploy/` Docker bundle make it orchestrator-ready.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
export CARTOGRAPHY_CENTRAL_TOKEN=$(openssl rand -base64 32)
|
|
327
|
+
cartography mcp --server-mode --host 0.0.0.0 \
|
|
328
|
+
--allowed-hosts cartograph.internal:3737 --org acme --anon-mode reject
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
For an org-wide store at **10K+ nodes**, opt into a **Neo4j/Memgraph graph-DB backend** for the
|
|
332
|
+
collector's merge + summary path (`neo4j-driver` is an optional dependency; if absent or unreachable
|
|
333
|
+
it **degrades to SQLite**, never fails):
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
cartography mcp --server-mode --store-backend graph \
|
|
337
|
+
--graph-url bolt://graph.internal:7687 --graph-user neo4j --graph-password "$NEO4J_PASSWORD"
|
|
338
|
+
# or via env: CARTOGRAPHY_GRAPH_URL / _USER / _PASSWORD (kept out of process listings)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
See **[docs/how-to/self-host-collector.md](docs/how-to/self-host-collector.md)**.
|
|
342
|
+
|
|
343
|
+
### Backstage live data source (Phase 4)
|
|
344
|
+
|
|
345
|
+
Cartography maps discovered infrastructure to **Backstage catalog entities** (`Component`/`API`/
|
|
346
|
+
`Resource` + `dependsOn` relations). Export a static `catalog-info.yaml` snapshot, or consume the
|
|
347
|
+
**live, tenant-scoped** endpoint `GET /v1/backstage/catalog` on the API server (re-mapped on every
|
|
348
|
+
request, RBAC-pinned). A reference `CartographEntityProvider` lives in
|
|
349
|
+
[`examples/backstage-plugin/`](examples/backstage-plugin/). See
|
|
350
|
+
**[docs/how-to/backstage.md](docs/how-to/backstage.md)**.
|
|
351
|
+
|
|
352
|
+
### Drift alerting — Slack / PagerDuty / Jira (Phase 4)
|
|
353
|
+
|
|
354
|
+
`cartography drift` classifies topology drift into `info`/`warning`/`critical` and fans it out to
|
|
355
|
+
the **sinks** configured under the `drift` block of `cartography.config.json` — `stdout`, `slack`,
|
|
356
|
+
`pagerduty`, `jira`, or a generic `webhook`. With no config it prints one redacted JSON line and makes
|
|
357
|
+
**no outbound request**. Every sink is hardened identically (`https:`-or-loopback only, bounded
|
|
358
|
+
timeout, body always credential-redacted, only host:port logged, one failing sink never blocks the
|
|
359
|
+
others). See **[docs/how-to/drift-and-ci.md](docs/how-to/drift-and-ci.md)**.
|
|
360
|
+
|
|
361
|
+
### Kubernetes operator (Phase 5)
|
|
362
|
+
|
|
363
|
+
`cartography operator` runs the **deterministic, LLM-free** discovery continuously **inside** a
|
|
364
|
+
cluster and reports drift between reconcile cycles — a "continuous CMDB for Kubernetes". It's a thin
|
|
365
|
+
reconcile loop (no CRD, no controller-runtime, no agent loop), read-only (`kubectl get …` via the
|
|
366
|
+
allowlist only). The `deploy/k8s/` manifests ship a **read-only** ServiceAccount + ClusterRole
|
|
367
|
+
(`get/list/watch` only — no write verbs).
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
kubectl apply -f deploy/k8s/operator.yaml # in-cluster, single-replica Deployment
|
|
371
|
+
cartography operator --once # local dev against your kube-context
|
|
372
|
+
cartography operator --interval 300 # long-running reconcile loop
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
See **[docs/how-to/k8s-operator.md](docs/how-to/k8s-operator.md)** and
|
|
376
|
+
**[docs/how-to/terraform-import.md](docs/how-to/terraform-import.md)**.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
265
380
|
## Requirements
|
|
266
381
|
|
|
267
382
|
- **Node.js >= 20** (Linux, macOS, or Windows) — that's it for the MCP server and the
|
|
@@ -349,10 +464,31 @@ datasynx-cartography drift [base] [current] Severity-classified drift ale
|
|
|
349
464
|
--webhook <url> Outbound webhook sink (opt-in; token via CARTOGRAPHY_DRIFT_TOKEN)
|
|
350
465
|
datasynx-cartography bookmarks View all browser bookmarks
|
|
351
466
|
datasynx-cartography seed [--file <path>] Manually add infrastructure nodes
|
|
467
|
+
datasynx-cartography cost --file <csv> Enrich nodes with owner/cost (FinOps)
|
|
468
|
+
datasynx-cartography compliance [session] --ruleset <name> Grade against baseline/cis/soc2/iso27001
|
|
469
|
+
datasynx-cartography consent <…> Per-employee sharing policy + anonymization
|
|
470
|
+
datasynx-cartography sync <status|review|push> Opt-in central-DB outbound pipeline
|
|
471
|
+
datasynx-cartography schedule --config <file> Recurring headless discovery + drift
|
|
472
|
+
datasynx-cartography prune [--older-than <days>] Prune old sessions / compact the audit trail
|
|
352
473
|
datasynx-cartography doctor Check all requirements + cloud CLIs
|
|
353
474
|
datasynx-cartography docs Full feature reference
|
|
354
475
|
```
|
|
355
476
|
|
|
477
|
+
### Platform & Ecosystem
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
datasynx-cartography mcp [--server-mode] [--http] MCP server / central collector (Phase 4)
|
|
481
|
+
datasynx-cartography api [--no-graphql] [--no-dashboard] REST + GraphQL + web dashboard (Phase 4)
|
|
482
|
+
--host <h> --port <n> --token <secret> --allowed-hosts <list> (non-loopback needs both)
|
|
483
|
+
--tenant <id> / --org <id> Tenant whose topology to serve
|
|
484
|
+
datasynx-cartography auth add <subject> --role <viewer|operator|admin> --tenant <id> RBAC (Phase 4)
|
|
485
|
+
datasynx-cartography auth list | revoke <subject>
|
|
486
|
+
datasynx-cartography operator [--once] [--interval <sec>] Kubernetes operator (Phase 5)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
> The `cartography-mcp` and `cartography-api` binaries start the MCP server and the API server
|
|
490
|
+
> directly (used by `server.json` / containers via `npx`).
|
|
491
|
+
|
|
356
492
|
---
|
|
357
493
|
|
|
358
494
|
## Output Files
|
|
@@ -380,7 +516,11 @@ datasynx-output/
|
|
|
380
516
|
|
|
381
517
|
The **MCP server is the headline interface** — LLM-agnostic and the same SQLite graph
|
|
382
518
|
underneath every entry point. Discovery (deterministic scanners or the optional Claude
|
|
383
|
-
loop) writes the graph; any MCP host reads it.
|
|
519
|
+
loop) writes the graph; any MCP host reads it. The Phase 4 **platform** adds read-only
|
|
520
|
+
HTTP surfaces over that same graph — the REST/GraphQL API (`cartography api`), the web
|
|
521
|
+
dashboard, RBAC (`cartography auth`), the self-hostable central collector
|
|
522
|
+
(`mcp --server-mode`, with an optional Neo4j/Memgraph backend), and a live Backstage
|
|
523
|
+
data source — all sharing the MCP transport's bearer auth + tenant scoping.
|
|
384
524
|
|
|
385
525
|
```
|
|
386
526
|
┌──────────────────────────────────────────┐
|
|
@@ -888,201 +888,6 @@ function localDiscoveryFn(registry, plugins) {
|
|
|
888
888
|
};
|
|
889
889
|
}
|
|
890
890
|
|
|
891
|
-
// src/compliance/rulesets/baseline.ts
|
|
892
|
-
var baseline = RulesetSchema.parse({
|
|
893
|
-
name: "baseline",
|
|
894
|
-
version: "1.0.0",
|
|
895
|
-
framework: "baseline",
|
|
896
|
-
description: "Deterministic baseline hygiene controls scored against signals available today.",
|
|
897
|
-
rules: [
|
|
898
|
-
{
|
|
899
|
-
id: "BASE-1",
|
|
900
|
-
control: "BASE-1",
|
|
901
|
-
framework: "baseline",
|
|
902
|
-
severity: "medium",
|
|
903
|
-
title: "Asset has an owner",
|
|
904
|
-
rationale: "Every asset should have a clear owning team/person for accountability.",
|
|
905
|
-
scope: {},
|
|
906
|
-
check: { any: [
|
|
907
|
-
{ field: "owner", op: "present" },
|
|
908
|
-
{ field: "domain", op: "present" },
|
|
909
|
-
{ field: "tags", op: "matches", pattern: "owner_key" },
|
|
910
|
-
{ field: "metadataKeys", op: "matches", pattern: "owner_key" }
|
|
911
|
-
] }
|
|
912
|
-
},
|
|
913
|
-
{
|
|
914
|
-
id: "BASE-2",
|
|
915
|
-
control: "BASE-2",
|
|
916
|
-
framework: "baseline",
|
|
917
|
-
severity: "low",
|
|
918
|
-
title: "Service/data asset is assigned a business domain",
|
|
919
|
-
rationale: "Domain assignment enables blast-radius and ownership analysis.",
|
|
920
|
-
scope: { groups: ["data", "web"] },
|
|
921
|
-
check: { field: "domain", op: "present" }
|
|
922
|
-
},
|
|
923
|
-
{
|
|
924
|
-
id: "BASE-3",
|
|
925
|
-
control: "BASE-3",
|
|
926
|
-
framework: "baseline",
|
|
927
|
-
severity: "high",
|
|
928
|
-
title: "Critical asset is discovered with adequate confidence",
|
|
929
|
-
rationale: "Low-confidence critical assets indicate incomplete or unreliable discovery.",
|
|
930
|
-
scope: { groups: ["data", "infra"] },
|
|
931
|
-
check: { field: "confidence", op: "gte", value: 0.5 }
|
|
932
|
-
},
|
|
933
|
-
{
|
|
934
|
-
id: "BASE-4",
|
|
935
|
-
control: "BASE-4",
|
|
936
|
-
framework: "baseline",
|
|
937
|
-
severity: "critical",
|
|
938
|
-
title: "No embedded credentials / plaintext DSN in metadata",
|
|
939
|
-
rationale: "A connection string with embedded credentials is a direct secret-exposure risk.",
|
|
940
|
-
scope: {},
|
|
941
|
-
check: { not: { field: "metadataValues", op: "matches", pattern: "dsn_with_credentials" } }
|
|
942
|
-
},
|
|
943
|
-
{
|
|
944
|
-
id: "BASE-5",
|
|
945
|
-
control: "BASE-5",
|
|
946
|
-
framework: "baseline",
|
|
947
|
-
severity: "medium",
|
|
948
|
-
title: "Data store carries an acceptable quality score",
|
|
949
|
-
rationale: "Where a quality score exists, a low score flags an under-governed data store.",
|
|
950
|
-
scope: { groups: ["data"] },
|
|
951
|
-
applicableWhen: { field: "qualityScore", op: "present" },
|
|
952
|
-
check: { field: "qualityScore", op: "gte", value: 50 }
|
|
953
|
-
}
|
|
954
|
-
]
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
// src/compliance/rulesets/cis.ts
|
|
958
|
-
var cis = RulesetSchema.parse({
|
|
959
|
-
name: "cis",
|
|
960
|
-
version: "0.1.0",
|
|
961
|
-
framework: "CIS",
|
|
962
|
-
description: "CIS starter subset \u2014 illustrative controls, not a certified benchmark.",
|
|
963
|
-
rules: [
|
|
964
|
-
{
|
|
965
|
-
id: "CIS-1.1",
|
|
966
|
-
control: "CIS-1.1",
|
|
967
|
-
framework: "CIS",
|
|
968
|
-
severity: "critical",
|
|
969
|
-
title: "No plaintext credentials in service configuration",
|
|
970
|
-
rationale: "CIS hardening forbids embedded secrets in config/metadata.",
|
|
971
|
-
scope: {},
|
|
972
|
-
check: { not: { field: "metadataValues", op: "matches", pattern: "dsn_with_credentials" } }
|
|
973
|
-
},
|
|
974
|
-
{
|
|
975
|
-
id: "CIS-2.1",
|
|
976
|
-
control: "CIS-2.1",
|
|
977
|
-
framework: "CIS",
|
|
978
|
-
severity: "high",
|
|
979
|
-
title: "Data store is not publicly exposed",
|
|
980
|
-
rationale: "Data stores should not bind to public/0.0.0.0 addresses.",
|
|
981
|
-
scope: { groups: ["data"] },
|
|
982
|
-
check: { not: { field: "metadataValues", op: "matches", pattern: "public_exposure" } }
|
|
983
|
-
},
|
|
984
|
-
{
|
|
985
|
-
id: "CIS-3.1",
|
|
986
|
-
control: "CIS-3.1",
|
|
987
|
-
framework: "CIS",
|
|
988
|
-
severity: "medium",
|
|
989
|
-
title: "Asset inventory has an accountable owner",
|
|
990
|
-
rationale: "CIS asset management requires an assigned owner.",
|
|
991
|
-
scope: {},
|
|
992
|
-
check: { any: [{ field: "owner", op: "present" }, { field: "metadataKeys", op: "matches", pattern: "owner_key" }] }
|
|
993
|
-
}
|
|
994
|
-
]
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
// src/compliance/rulesets/soc2.ts
|
|
998
|
-
var soc2 = RulesetSchema.parse({
|
|
999
|
-
name: "soc2",
|
|
1000
|
-
version: "0.1.0",
|
|
1001
|
-
framework: "SOC2",
|
|
1002
|
-
description: "SOC 2 starter subset \u2014 illustrative controls, not a certified control set.",
|
|
1003
|
-
rules: [
|
|
1004
|
-
{
|
|
1005
|
-
id: "CC6.1",
|
|
1006
|
-
control: "CC6.1",
|
|
1007
|
-
framework: "SOC2",
|
|
1008
|
-
severity: "critical",
|
|
1009
|
-
title: "Logical access \u2014 no embedded credentials",
|
|
1010
|
-
rationale: "CC6.1 logical access controls preclude plaintext secrets in config.",
|
|
1011
|
-
scope: {},
|
|
1012
|
-
check: { not: { field: "metadataValues", op: "matches", pattern: "dsn_with_credentials" } }
|
|
1013
|
-
},
|
|
1014
|
-
{
|
|
1015
|
-
id: "CC6.6",
|
|
1016
|
-
control: "CC6.6",
|
|
1017
|
-
framework: "SOC2",
|
|
1018
|
-
severity: "high",
|
|
1019
|
-
title: "Boundary protection \u2014 data stores not public",
|
|
1020
|
-
rationale: "CC6.6 requires protection of system boundaries from external access.",
|
|
1021
|
-
scope: { groups: ["data"] },
|
|
1022
|
-
check: { not: { field: "metadataValues", op: "matches", pattern: "public_exposure" } }
|
|
1023
|
-
},
|
|
1024
|
-
{
|
|
1025
|
-
id: "CC1.2",
|
|
1026
|
-
control: "CC1.2",
|
|
1027
|
-
framework: "SOC2",
|
|
1028
|
-
severity: "medium",
|
|
1029
|
-
title: "Accountability \u2014 asset has an owner",
|
|
1030
|
-
rationale: "CC1.2 establishes accountability; assets need an assigned owner.",
|
|
1031
|
-
scope: {},
|
|
1032
|
-
check: { any: [{ field: "owner", op: "present" }, { field: "domain", op: "present" }] }
|
|
1033
|
-
}
|
|
1034
|
-
]
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
// src/compliance/rulesets/iso27001.ts
|
|
1038
|
-
var iso27001 = RulesetSchema.parse({
|
|
1039
|
-
name: "iso27001",
|
|
1040
|
-
version: "0.1.0",
|
|
1041
|
-
framework: "ISO27001",
|
|
1042
|
-
description: "ISO/IEC 27001 Annex A starter subset \u2014 illustrative, not certified.",
|
|
1043
|
-
rules: [
|
|
1044
|
-
{
|
|
1045
|
-
id: "A.8.1",
|
|
1046
|
-
control: "A.8.1",
|
|
1047
|
-
framework: "ISO27001",
|
|
1048
|
-
severity: "medium",
|
|
1049
|
-
title: "Inventory of assets \u2014 ownership assigned",
|
|
1050
|
-
rationale: "A.8.1 requires an inventory of assets each with an identified owner.",
|
|
1051
|
-
scope: {},
|
|
1052
|
-
check: { any: [{ field: "owner", op: "present" }, { field: "metadataKeys", op: "matches", pattern: "owner_key" }] }
|
|
1053
|
-
},
|
|
1054
|
-
{
|
|
1055
|
-
id: "A.8.12",
|
|
1056
|
-
control: "A.8.12",
|
|
1057
|
-
framework: "ISO27001",
|
|
1058
|
-
severity: "critical",
|
|
1059
|
-
title: "Data leakage prevention \u2014 no embedded secrets",
|
|
1060
|
-
rationale: "A.8.12 mandates measures against data leakage such as exposed credentials.",
|
|
1061
|
-
scope: {},
|
|
1062
|
-
check: { not: { field: "metadataValues", op: "matches", pattern: "dsn_with_credentials" } }
|
|
1063
|
-
},
|
|
1064
|
-
{
|
|
1065
|
-
id: "A.8.20",
|
|
1066
|
-
control: "A.8.20",
|
|
1067
|
-
framework: "ISO27001",
|
|
1068
|
-
severity: "high",
|
|
1069
|
-
title: "Network security \u2014 data stores not publicly exposed",
|
|
1070
|
-
rationale: "A.8.20 requires networks to be secured; data stores should not be public.",
|
|
1071
|
-
scope: { groups: ["data"] },
|
|
1072
|
-
check: { not: { field: "metadataValues", op: "matches", pattern: "public_exposure" } }
|
|
1073
|
-
}
|
|
1074
|
-
]
|
|
1075
|
-
});
|
|
1076
|
-
|
|
1077
|
-
// src/compliance/rulesets/registry.ts
|
|
1078
|
-
var RULESETS = { baseline, cis, soc2, iso27001 };
|
|
1079
|
-
function getRuleset(name) {
|
|
1080
|
-
return RULESETS[name];
|
|
1081
|
-
}
|
|
1082
|
-
function listRulesets() {
|
|
1083
|
-
return Object.values(RULESETS).map((r) => ({ name: r.name, version: r.version, framework: r.framework, ruleCount: r.rules.length }));
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
891
|
// src/sinks/stdout.ts
|
|
1087
892
|
var StdoutSink = class {
|
|
1088
893
|
name = "stdout";
|
|
@@ -1439,6 +1244,201 @@ async function runDrift(db, config, opts = {}) {
|
|
|
1439
1244
|
return alert;
|
|
1440
1245
|
}
|
|
1441
1246
|
|
|
1247
|
+
// src/compliance/rulesets/baseline.ts
|
|
1248
|
+
var baseline = RulesetSchema.parse({
|
|
1249
|
+
name: "baseline",
|
|
1250
|
+
version: "1.0.0",
|
|
1251
|
+
framework: "baseline",
|
|
1252
|
+
description: "Deterministic baseline hygiene controls scored against signals available today.",
|
|
1253
|
+
rules: [
|
|
1254
|
+
{
|
|
1255
|
+
id: "BASE-1",
|
|
1256
|
+
control: "BASE-1",
|
|
1257
|
+
framework: "baseline",
|
|
1258
|
+
severity: "medium",
|
|
1259
|
+
title: "Asset has an owner",
|
|
1260
|
+
rationale: "Every asset should have a clear owning team/person for accountability.",
|
|
1261
|
+
scope: {},
|
|
1262
|
+
check: { any: [
|
|
1263
|
+
{ field: "owner", op: "present" },
|
|
1264
|
+
{ field: "domain", op: "present" },
|
|
1265
|
+
{ field: "tags", op: "matches", pattern: "owner_key" },
|
|
1266
|
+
{ field: "metadataKeys", op: "matches", pattern: "owner_key" }
|
|
1267
|
+
] }
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
id: "BASE-2",
|
|
1271
|
+
control: "BASE-2",
|
|
1272
|
+
framework: "baseline",
|
|
1273
|
+
severity: "low",
|
|
1274
|
+
title: "Service/data asset is assigned a business domain",
|
|
1275
|
+
rationale: "Domain assignment enables blast-radius and ownership analysis.",
|
|
1276
|
+
scope: { groups: ["data", "web"] },
|
|
1277
|
+
check: { field: "domain", op: "present" }
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
id: "BASE-3",
|
|
1281
|
+
control: "BASE-3",
|
|
1282
|
+
framework: "baseline",
|
|
1283
|
+
severity: "high",
|
|
1284
|
+
title: "Critical asset is discovered with adequate confidence",
|
|
1285
|
+
rationale: "Low-confidence critical assets indicate incomplete or unreliable discovery.",
|
|
1286
|
+
scope: { groups: ["data", "infra"] },
|
|
1287
|
+
check: { field: "confidence", op: "gte", value: 0.5 }
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
id: "BASE-4",
|
|
1291
|
+
control: "BASE-4",
|
|
1292
|
+
framework: "baseline",
|
|
1293
|
+
severity: "critical",
|
|
1294
|
+
title: "No embedded credentials / plaintext DSN in metadata",
|
|
1295
|
+
rationale: "A connection string with embedded credentials is a direct secret-exposure risk.",
|
|
1296
|
+
scope: {},
|
|
1297
|
+
check: { not: { field: "metadataValues", op: "matches", pattern: "dsn_with_credentials" } }
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
id: "BASE-5",
|
|
1301
|
+
control: "BASE-5",
|
|
1302
|
+
framework: "baseline",
|
|
1303
|
+
severity: "medium",
|
|
1304
|
+
title: "Data store carries an acceptable quality score",
|
|
1305
|
+
rationale: "Where a quality score exists, a low score flags an under-governed data store.",
|
|
1306
|
+
scope: { groups: ["data"] },
|
|
1307
|
+
applicableWhen: { field: "qualityScore", op: "present" },
|
|
1308
|
+
check: { field: "qualityScore", op: "gte", value: 50 }
|
|
1309
|
+
}
|
|
1310
|
+
]
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
// src/compliance/rulesets/cis.ts
|
|
1314
|
+
var cis = RulesetSchema.parse({
|
|
1315
|
+
name: "cis",
|
|
1316
|
+
version: "0.1.0",
|
|
1317
|
+
framework: "CIS",
|
|
1318
|
+
description: "CIS starter subset \u2014 illustrative controls, not a certified benchmark.",
|
|
1319
|
+
rules: [
|
|
1320
|
+
{
|
|
1321
|
+
id: "CIS-1.1",
|
|
1322
|
+
control: "CIS-1.1",
|
|
1323
|
+
framework: "CIS",
|
|
1324
|
+
severity: "critical",
|
|
1325
|
+
title: "No plaintext credentials in service configuration",
|
|
1326
|
+
rationale: "CIS hardening forbids embedded secrets in config/metadata.",
|
|
1327
|
+
scope: {},
|
|
1328
|
+
check: { not: { field: "metadataValues", op: "matches", pattern: "dsn_with_credentials" } }
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
id: "CIS-2.1",
|
|
1332
|
+
control: "CIS-2.1",
|
|
1333
|
+
framework: "CIS",
|
|
1334
|
+
severity: "high",
|
|
1335
|
+
title: "Data store is not publicly exposed",
|
|
1336
|
+
rationale: "Data stores should not bind to public/0.0.0.0 addresses.",
|
|
1337
|
+
scope: { groups: ["data"] },
|
|
1338
|
+
check: { not: { field: "metadataValues", op: "matches", pattern: "public_exposure" } }
|
|
1339
|
+
},
|
|
1340
|
+
{
|
|
1341
|
+
id: "CIS-3.1",
|
|
1342
|
+
control: "CIS-3.1",
|
|
1343
|
+
framework: "CIS",
|
|
1344
|
+
severity: "medium",
|
|
1345
|
+
title: "Asset inventory has an accountable owner",
|
|
1346
|
+
rationale: "CIS asset management requires an assigned owner.",
|
|
1347
|
+
scope: {},
|
|
1348
|
+
check: { any: [{ field: "owner", op: "present" }, { field: "metadataKeys", op: "matches", pattern: "owner_key" }] }
|
|
1349
|
+
}
|
|
1350
|
+
]
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
// src/compliance/rulesets/soc2.ts
|
|
1354
|
+
var soc2 = RulesetSchema.parse({
|
|
1355
|
+
name: "soc2",
|
|
1356
|
+
version: "0.1.0",
|
|
1357
|
+
framework: "SOC2",
|
|
1358
|
+
description: "SOC 2 starter subset \u2014 illustrative controls, not a certified control set.",
|
|
1359
|
+
rules: [
|
|
1360
|
+
{
|
|
1361
|
+
id: "CC6.1",
|
|
1362
|
+
control: "CC6.1",
|
|
1363
|
+
framework: "SOC2",
|
|
1364
|
+
severity: "critical",
|
|
1365
|
+
title: "Logical access \u2014 no embedded credentials",
|
|
1366
|
+
rationale: "CC6.1 logical access controls preclude plaintext secrets in config.",
|
|
1367
|
+
scope: {},
|
|
1368
|
+
check: { not: { field: "metadataValues", op: "matches", pattern: "dsn_with_credentials" } }
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
id: "CC6.6",
|
|
1372
|
+
control: "CC6.6",
|
|
1373
|
+
framework: "SOC2",
|
|
1374
|
+
severity: "high",
|
|
1375
|
+
title: "Boundary protection \u2014 data stores not public",
|
|
1376
|
+
rationale: "CC6.6 requires protection of system boundaries from external access.",
|
|
1377
|
+
scope: { groups: ["data"] },
|
|
1378
|
+
check: { not: { field: "metadataValues", op: "matches", pattern: "public_exposure" } }
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
id: "CC1.2",
|
|
1382
|
+
control: "CC1.2",
|
|
1383
|
+
framework: "SOC2",
|
|
1384
|
+
severity: "medium",
|
|
1385
|
+
title: "Accountability \u2014 asset has an owner",
|
|
1386
|
+
rationale: "CC1.2 establishes accountability; assets need an assigned owner.",
|
|
1387
|
+
scope: {},
|
|
1388
|
+
check: { any: [{ field: "owner", op: "present" }, { field: "domain", op: "present" }] }
|
|
1389
|
+
}
|
|
1390
|
+
]
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
// src/compliance/rulesets/iso27001.ts
|
|
1394
|
+
var iso27001 = RulesetSchema.parse({
|
|
1395
|
+
name: "iso27001",
|
|
1396
|
+
version: "0.1.0",
|
|
1397
|
+
framework: "ISO27001",
|
|
1398
|
+
description: "ISO/IEC 27001 Annex A starter subset \u2014 illustrative, not certified.",
|
|
1399
|
+
rules: [
|
|
1400
|
+
{
|
|
1401
|
+
id: "A.8.1",
|
|
1402
|
+
control: "A.8.1",
|
|
1403
|
+
framework: "ISO27001",
|
|
1404
|
+
severity: "medium",
|
|
1405
|
+
title: "Inventory of assets \u2014 ownership assigned",
|
|
1406
|
+
rationale: "A.8.1 requires an inventory of assets each with an identified owner.",
|
|
1407
|
+
scope: {},
|
|
1408
|
+
check: { any: [{ field: "owner", op: "present" }, { field: "metadataKeys", op: "matches", pattern: "owner_key" }] }
|
|
1409
|
+
},
|
|
1410
|
+
{
|
|
1411
|
+
id: "A.8.12",
|
|
1412
|
+
control: "A.8.12",
|
|
1413
|
+
framework: "ISO27001",
|
|
1414
|
+
severity: "critical",
|
|
1415
|
+
title: "Data leakage prevention \u2014 no embedded secrets",
|
|
1416
|
+
rationale: "A.8.12 mandates measures against data leakage such as exposed credentials.",
|
|
1417
|
+
scope: {},
|
|
1418
|
+
check: { not: { field: "metadataValues", op: "matches", pattern: "dsn_with_credentials" } }
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
id: "A.8.20",
|
|
1422
|
+
control: "A.8.20",
|
|
1423
|
+
framework: "ISO27001",
|
|
1424
|
+
severity: "high",
|
|
1425
|
+
title: "Network security \u2014 data stores not publicly exposed",
|
|
1426
|
+
rationale: "A.8.20 requires networks to be secured; data stores should not be public.",
|
|
1427
|
+
scope: { groups: ["data"] },
|
|
1428
|
+
check: { not: { field: "metadataValues", op: "matches", pattern: "public_exposure" } }
|
|
1429
|
+
}
|
|
1430
|
+
]
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
// src/compliance/rulesets/registry.ts
|
|
1434
|
+
var RULESETS = { baseline, cis, soc2, iso27001 };
|
|
1435
|
+
function getRuleset(name) {
|
|
1436
|
+
return RULESETS[name];
|
|
1437
|
+
}
|
|
1438
|
+
function listRulesets() {
|
|
1439
|
+
return Object.values(RULESETS).map((r) => ({ name: r.name, version: r.version, framework: r.framework, ruleCount: r.rules.length }));
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
1442
|
// src/orgkey.ts
|
|
1443
1443
|
import { randomBytes, hkdfSync } from "crypto";
|
|
1444
1444
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync, statSync } from "fs";
|
|
@@ -1494,6 +1494,8 @@ function reversalKey(orgKey) {
|
|
|
1494
1494
|
import { createHmac, createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
|
|
1495
1495
|
var PRIVATE_IP = /\b(?:10(?:\.\d{1,3}){3}|192\.168(?:\.\d{1,3}){2}|172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})\b/g;
|
|
1496
1496
|
var HOSTNAME = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}\b/gi;
|
|
1497
|
+
var BARE_INTERNAL_HOST = /^[a-z0-9]+(?:-[a-z0-9]+)+$|^[a-z]+\d+$|^\d+[a-z]+$/i;
|
|
1498
|
+
var ANON_TOKEN = /^anon:(?:host|user|path|ip):[a-z2-7]+$/;
|
|
1497
1499
|
var POSIX_PATH = /(?:^|(?<=\s|=|:|"|'|\())(\/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+)/g;
|
|
1498
1500
|
var WIN_PATH = /\b[A-Za-z]:\\[A-Za-z0-9._\\-]+/g;
|
|
1499
1501
|
var B32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
|
|
@@ -1548,8 +1550,18 @@ function pseudonymizeString(s, orgKey, db) {
|
|
|
1548
1550
|
(_m, user, host) => `${pseudonymizeFragment(user, "user", orgKey, db)}@${pseudonymizeFragment(host, "host", orgKey, db)}`
|
|
1549
1551
|
);
|
|
1550
1552
|
out = out.replace(HOSTNAME, (m) => pseudonymizeFragment(m, "host", orgKey, db));
|
|
1553
|
+
const trimmed = out.trim();
|
|
1554
|
+
if (out === s && !ANON_TOKEN.test(trimmed) && BARE_INTERNAL_HOST.test(trimmed)) {
|
|
1555
|
+
out = pseudonymizeFragment(trimmed, "host", orgKey, db);
|
|
1556
|
+
}
|
|
1551
1557
|
return out;
|
|
1552
1558
|
}
|
|
1559
|
+
function pseudonymizeId(id, orgKey, db) {
|
|
1560
|
+
const segments = id.split(":");
|
|
1561
|
+
if (segments.length <= 1) return pseudonymizeString(id, orgKey, db);
|
|
1562
|
+
const [type, ...rest] = segments;
|
|
1563
|
+
return [type, ...rest.map((seg) => pseudonymizeString(seg, orgKey, db))].join(":");
|
|
1564
|
+
}
|
|
1553
1565
|
function pseudonymize(value, orgKey, db) {
|
|
1554
1566
|
if (typeof value === "string") return pseudonymizeString(value, orgKey, db);
|
|
1555
1567
|
if (Array.isArray(value)) return value.map((v) => pseudonymize(v, orgKey, db));
|
|
@@ -1800,7 +1812,7 @@ function correlateTopology(nodes, _edges = []) {
|
|
|
1800
1812
|
|
|
1801
1813
|
// src/mcp/server.ts
|
|
1802
1814
|
var SERVER_NAME = "cartography";
|
|
1803
|
-
var SERVER_VERSION = "2.
|
|
1815
|
+
var SERVER_VERSION = "2.12.1";
|
|
1804
1816
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
1805
1817
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
1806
1818
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -2812,8 +2824,6 @@ var FQDN = /\b(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}\b/gi;
|
|
|
2812
2824
|
var POSIX_PATH2 = /(?:^|(?<=\s|=|:|"|'|\())(\/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+)/g;
|
|
2813
2825
|
var WIN_PATH2 = /\b[A-Za-z]:\\[A-Za-z0-9._\\-]+/g;
|
|
2814
2826
|
var HOME_USER = /(?:\/home\/|\/Users\/|[A-Za-z]:\\Users\\)([A-Za-z0-9._-]+)/g;
|
|
2815
|
-
var BARE_INTERNAL_HOST = /^[a-z0-9]+(?:-[a-z0-9]+)+$|^[a-z]+\d+$|^\d+[a-z]+$/i;
|
|
2816
|
-
var ANON_TOKEN = /^anon:(?:host|user|path|ip):[a-z2-7]+$/;
|
|
2817
2827
|
function violationsInString(s, path) {
|
|
2818
2828
|
const out = [];
|
|
2819
2829
|
const trimmed = s.trim();
|
|
@@ -3139,17 +3149,19 @@ async function startMcp(opts = {}) {
|
|
|
3139
3149
|
}
|
|
3140
3150
|
|
|
3141
3151
|
export {
|
|
3152
|
+
ScannerRegistry,
|
|
3142
3153
|
isPersonalHost,
|
|
3143
3154
|
runLocalDiscovery,
|
|
3155
|
+
runDrift,
|
|
3144
3156
|
getRuleset,
|
|
3145
3157
|
listRulesets,
|
|
3146
|
-
runDrift,
|
|
3147
3158
|
loadOrgKey,
|
|
3148
3159
|
rotateOrgKey,
|
|
3149
3160
|
pseudonymizeString,
|
|
3161
|
+
pseudonymizeId,
|
|
3150
3162
|
pseudonymize,
|
|
3151
3163
|
reversePseudonym,
|
|
3152
3164
|
parseMcpArgs,
|
|
3153
3165
|
startMcp
|
|
3154
3166
|
};
|
|
3155
|
-
//# sourceMappingURL=chunk-
|
|
3167
|
+
//# sourceMappingURL=chunk-OIDAXUW5.js.map
|