@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 CHANGED
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  parseApiArgs,
4
4
  startApi
5
- } from "./chunk-7VZH5PFV.js";
6
- import "./chunk-7QEBFMN4.js";
7
- import "./chunk-WCR47QA2.js";
5
+ } from "./chunk-NQXZUWOI.js";
6
+ import "./chunk-GA4427LB.js";
7
+ import "./chunk-QQOQBE2A.js";
8
8
  import "./chunk-2SZ5QHGH.js";
9
9
 
10
10
  // src/api-bin.ts
@@ -7,7 +7,7 @@ import {
7
7
  NODE_TYPES,
8
8
  NODE_TYPE_GROUPS,
9
9
  SharingLevelSchema
10
- } from "./chunk-WCR47QA2.js";
10
+ } from "./chunk-QQOQBE2A.js";
11
11
  import {
12
12
  HOME,
13
13
  IS_LINUX,
@@ -744,8 +744,17 @@ function stripSensitive(target) {
744
744
  const stripped = `${url.hostname}${url.port ? ":" + url.port : ""}`;
745
745
  return stripped || raw;
746
746
  } catch {
747
- const stripped = raw.replace(/\/.*$/, "").replace(/\?.*$/, "").replace(/@.*:/, ":");
748
- return stripped || raw;
747
+ let s = raw;
748
+ const slash = s.indexOf("/");
749
+ if (slash >= 0) s = s.slice(0, slash);
750
+ const q = s.indexOf("?");
751
+ if (q >= 0) s = s.slice(0, q);
752
+ const at = s.indexOf("@");
753
+ if (at >= 0) {
754
+ const colon = s.lastIndexOf(":");
755
+ if (colon > at) s = s.slice(0, at) + ":" + s.slice(colon + 1);
756
+ }
757
+ return s || raw;
749
758
  }
750
759
  }
751
760
  var SCAN_ARG_PATTERNS = {
@@ -763,7 +772,7 @@ function assertSafeScanArg(kind, value) {
763
772
  return value;
764
773
  }
765
774
  function redactSecrets(value) {
766
- return value.replace(/([a-z][a-z0-9+.-]*:\/\/[^:@/\s]+):[^@/\s]+@/gi, "$1:***@");
775
+ return value.replace(/([a-z][a-z0-9+.-]{0,63}:\/\/[^:@/\s]{1,256}):[^@/\s]{1,256}@/gi, "$1:***@");
767
776
  }
768
777
  function redactValue(value) {
769
778
  if (typeof value === "string") return redactSecrets(value);
@@ -1765,7 +1774,10 @@ CREATE TABLE IF NOT EXISTS activity_events (
1765
1774
  duration_ms INTEGER,
1766
1775
  command TEXT,
1767
1776
  result_bytes INTEGER,
1768
- tenant TEXT NOT NULL DEFAULT 'local'
1777
+ tenant TEXT NOT NULL DEFAULT 'local',
1778
+ actor_subject TEXT,
1779
+ actor_role TEXT,
1780
+ actor_tenant TEXT
1769
1781
  );
1770
1782
 
1771
1783
  CREATE TABLE IF NOT EXISTS tasks (
@@ -1874,6 +1886,16 @@ CREATE INDEX IF NOT EXISTS idx_nodes_tenant_content ON nodes(tenant, content_has
1874
1886
  CREATE INDEX IF NOT EXISTS idx_contrib_org ON node_contributors(organization, global_id);
1875
1887
  CREATE INDEX IF NOT EXISTS idx_nodes_owner ON nodes(session_id, owner);
1876
1888
  `;
1889
+ var AUTH_SCHEMA = `
1890
+ CREATE TABLE IF NOT EXISTS auth_credentials (
1891
+ token_hash TEXT PRIMARY KEY,
1892
+ subject TEXT NOT NULL,
1893
+ tenant TEXT NOT NULL DEFAULT 'local',
1894
+ role TEXT NOT NULL,
1895
+ created_at TEXT NOT NULL
1896
+ );
1897
+ CREATE INDEX IF NOT EXISTS idx_auth_subject ON auth_credentials(subject);
1898
+ `;
1877
1899
  var CartographyDB = class {
1878
1900
  db;
1879
1901
  /** 3.6 anomaly settings; defaults apply when no `anomaly` config is supplied. */
@@ -1893,7 +1915,8 @@ var CartographyDB = class {
1893
1915
  const version = this.db.pragma("user_version", { simple: true });
1894
1916
  if (version === 0) {
1895
1917
  this.db.exec(SCHEMA);
1896
- this.db.pragma("user_version = 14");
1918
+ this.db.exec(AUTH_SCHEMA);
1919
+ this.db.pragma("user_version = 15");
1897
1920
  return;
1898
1921
  } else if (version === 1) {
1899
1922
  const cols = this.db.prepare("PRAGMA table_info(nodes)").all().map((c) => c.name);
@@ -2079,6 +2102,18 @@ var CartographyDB = class {
2079
2102
  }
2080
2103
  this.db.pragma("user_version = 14");
2081
2104
  }
2105
+ const v14 = this.db.pragma("user_version", { simple: true });
2106
+ if (v14 < 15) {
2107
+ this.db.exec(AUTH_SCHEMA);
2108
+ const ev = this.db.prepare("PRAGMA table_info(activity_events)").all();
2109
+ if (ev.length > 0) {
2110
+ const cols = ev.map((c) => c.name);
2111
+ if (!cols.includes("actor_subject")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_subject TEXT");
2112
+ if (!cols.includes("actor_role")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_role TEXT");
2113
+ if (!cols.includes("actor_tenant")) this.db.exec("ALTER TABLE activity_events ADD COLUMN actor_tenant TEXT");
2114
+ }
2115
+ this.db.pragma("user_version = 15");
2116
+ }
2082
2117
  }
2083
2118
  close() {
2084
2119
  this.db.pragma("optimize");
@@ -2509,13 +2544,13 @@ var CartographyDB = class {
2509
2544
  });
2510
2545
  }
2511
2546
  // ── Events ──────────────────────────────
2512
- insertEvent(sessionId, event, taskId) {
2547
+ insertEvent(sessionId, event, taskId, actor) {
2513
2548
  const id = crypto.randomUUID();
2514
2549
  const tenant = this.tenantOf(sessionId);
2515
2550
  this.db.prepare(`
2516
2551
  INSERT INTO activity_events
2517
- (id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant)
2518
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2552
+ (id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port, command, result_bytes, tenant, actor_subject, actor_role, actor_tenant)
2553
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2519
2554
  `).run(
2520
2555
  id,
2521
2556
  sessionId,
@@ -2529,9 +2564,52 @@ var CartographyDB = class {
2529
2564
  event.port ?? null,
2530
2565
  event.command ?? null,
2531
2566
  event.resultBytes ?? null,
2532
- tenant
2567
+ tenant,
2568
+ actor?.subject ?? null,
2569
+ actor?.role ?? null,
2570
+ actor?.tenant ?? null
2533
2571
  );
2534
2572
  }
2573
+ // ── RBAC credential store (4.5) ─────────────
2574
+ /** Number of stored credentials. `0` ⇒ no RBAC configured (fall back to shared/loopback). */
2575
+ countCredentials() {
2576
+ return this.db.prepare("SELECT COUNT(*) AS n FROM auth_credentials").get().n;
2577
+ }
2578
+ /** Look up a credential by its sha256 token hash. */
2579
+ findCredentialByHash(tokenHash) {
2580
+ const r = this.db.prepare("SELECT * FROM auth_credentials WHERE token_hash = ?").get(tokenHash);
2581
+ if (!r) return void 0;
2582
+ return {
2583
+ tokenHash: r["token_hash"],
2584
+ subject: r["subject"],
2585
+ tenant: r["tenant"],
2586
+ role: r["role"],
2587
+ createdAt: r["created_at"]
2588
+ };
2589
+ }
2590
+ /** Upsert a credential (idempotent on the token hash). Stores only the hash, never the raw token. */
2591
+ addCredential(rec) {
2592
+ this.db.prepare(`
2593
+ INSERT INTO auth_credentials (token_hash, subject, tenant, role, created_at)
2594
+ VALUES (?, ?, ?, ?, ?)
2595
+ ON CONFLICT(token_hash) DO UPDATE SET subject = excluded.subject, tenant = excluded.tenant, role = excluded.role
2596
+ `).run(rec.tokenHash, rec.subject, rec.tenant, rec.role, (/* @__PURE__ */ new Date()).toISOString());
2597
+ }
2598
+ /** List all credentials (token hashes only — the raw token is unrecoverable). */
2599
+ listCredentials() {
2600
+ const rows = this.db.prepare("SELECT * FROM auth_credentials ORDER BY created_at").all();
2601
+ return rows.map((r) => ({
2602
+ tokenHash: r["token_hash"],
2603
+ subject: r["subject"],
2604
+ tenant: r["tenant"],
2605
+ role: r["role"],
2606
+ createdAt: r["created_at"]
2607
+ }));
2608
+ }
2609
+ /** Revoke every credential for a subject. Returns the number removed. */
2610
+ revokeCredentialsBySubject(subject) {
2611
+ return this.db.prepare("DELETE FROM auth_credentials WHERE subject = ?").run(subject).changes;
2612
+ }
2535
2613
  getEvents(sessionId, since) {
2536
2614
  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);
2537
2615
  return rows.map((r) => {
@@ -3208,6 +3286,9 @@ var CartographyDB = class {
3208
3286
  }
3209
3287
  };
3210
3288
 
3289
+ // src/auth/identity.ts
3290
+ import { createHash as createHash2 } from "crypto";
3291
+
3211
3292
  // src/api/auth.ts
3212
3293
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
3213
3294
  function isLoopbackHost(host) {
@@ -3228,11 +3309,6 @@ function bearerToken(header) {
3228
3309
  const token = rest.trimStart();
3229
3310
  return token.length > 0 ? token : void 0;
3230
3311
  }
3231
- function checkBearer(authorizationHeader, token) {
3232
- if (!token) return true;
3233
- const provided = bearerToken(authorizationHeader);
3234
- return provided !== void 0 && timingSafeEqual(provided, token);
3235
- }
3236
3312
  function assertSafeBind(opts) {
3237
3313
  if (isLoopbackHost(opts.host)) return;
3238
3314
  if (opts.allowedHosts === void 0) {
@@ -3250,6 +3326,54 @@ function defaultAllowedHosts(host, port) {
3250
3326
  return [`${host}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];
3251
3327
  }
3252
3328
 
3329
+ // src/auth/identity.ts
3330
+ function hashToken(token) {
3331
+ return createHash2("sha256").update(token, "utf8").digest("hex");
3332
+ }
3333
+ var SqliteCredentialStore = class {
3334
+ constructor(db) {
3335
+ this.db = db;
3336
+ }
3337
+ count() {
3338
+ return this.db.countCredentials();
3339
+ }
3340
+ findByHash(tokenHash) {
3341
+ return this.db.findCredentialByHash(tokenHash);
3342
+ }
3343
+ };
3344
+ function resolvePrincipal(presentedToken, opts) {
3345
+ const tenant = opts.defaultTenant ?? DEFAULT_TENANT;
3346
+ if (opts.store && opts.store.count() > 0) {
3347
+ if (!presentedToken) return void 0;
3348
+ const rec = opts.store.findByHash(hashToken(presentedToken));
3349
+ return rec ? { subject: rec.subject, tenant: rec.tenant, role: rec.role } : void 0;
3350
+ }
3351
+ if (opts.sharedToken) {
3352
+ if (!presentedToken || !timingSafeEqual(presentedToken, opts.sharedToken)) return void 0;
3353
+ return { subject: "shared-token", tenant, role: "admin" };
3354
+ }
3355
+ if (opts.required) return void 0;
3356
+ return { subject: "anonymous", tenant, role: "admin" };
3357
+ }
3358
+
3359
+ // src/auth/rbac.ts
3360
+ var ROLE_RANK = { viewer: 1, operator: 2, admin: 3 };
3361
+ var ACTION_MIN_ROLE = { read: "viewer", discovery: "operator", admin: "admin" };
3362
+ function can(role, action) {
3363
+ return ROLE_RANK[role] >= ROLE_RANK[ACTION_MIN_ROLE[action]];
3364
+ }
3365
+ var AuthorizationError = class extends Error {
3366
+ constructor(action, role) {
3367
+ super(`forbidden: role '${role}' may not perform '${action}'`);
3368
+ this.action = action;
3369
+ this.role = role;
3370
+ this.name = "AuthorizationError";
3371
+ }
3372
+ };
3373
+ function authorize(principal, action) {
3374
+ if (!can(principal.role, action)) throw new AuthorizationError(action, principal.role);
3375
+ }
3376
+
3253
3377
  export {
3254
3378
  sanitizeUntrusted,
3255
3379
  cloudAwsScanner,
@@ -3271,8 +3395,13 @@ export {
3271
3395
  globalId,
3272
3396
  deriveSessionName,
3273
3397
  CartographyDB,
3274
- checkBearer,
3398
+ bearerToken,
3275
3399
  assertSafeBind,
3276
- defaultAllowedHosts
3400
+ defaultAllowedHosts,
3401
+ hashToken,
3402
+ SqliteCredentialStore,
3403
+ resolvePrincipal,
3404
+ AuthorizationError,
3405
+ authorize
3277
3406
  };
3278
- //# sourceMappingURL=chunk-7QEBFMN4.js.map
3407
+ //# sourceMappingURL=chunk-GA4427LB.js.map