neotoma 0.21.0 → 0.21.4

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.
Files changed (52) hide show
  1. package/dist/actions.d.ts +18 -0
  2. package/dist/actions.d.ts.map +1 -1
  3. package/dist/actions.js +152 -36
  4. package/dist/actions.js.map +1 -1
  5. package/dist/cli/discovery.d.ts.map +1 -1
  6. package/dist/cli/discovery.js +34 -1
  7. package/dist/cli/discovery.js.map +1 -1
  8. package/dist/cli/index.d.ts.map +1 -1
  9. package/dist/cli/index.js +66 -19
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/cli/onboarding_transcript_import.d.ts +18 -0
  12. package/dist/cli/onboarding_transcript_import.d.ts.map +1 -1
  13. package/dist/cli/onboarding_transcript_import.js +98 -0
  14. package/dist/cli/onboarding_transcript_import.js.map +1 -1
  15. package/dist/cli/transcript_parser.d.ts +29 -1
  16. package/dist/cli/transcript_parser.d.ts.map +1 -1
  17. package/dist/cli/transcript_parser.js +117 -3
  18. package/dist/cli/transcript_parser.js.map +1 -1
  19. package/dist/docs/api/rest_api.md +24 -0
  20. package/dist/docs/developer/cli/transcript_ingestion.md +90 -0
  21. package/dist/docs/developer/cli_reference.md +38 -11
  22. package/dist/docs/security/advisories/2026-08-07-ed25519-bearer-forged-key-auth-bypass.md +67 -0
  23. package/dist/docs/security/advisories/2026-08-07-sort-by-order-by-sql-injection.md +64 -0
  24. package/dist/docs/security/advisories/README.md +2 -0
  25. package/dist/docs/testing/automated_test_catalog.md +22 -12
  26. package/dist/repositories/sqlite/local_db_adapter.d.ts +1 -0
  27. package/dist/repositories/sqlite/local_db_adapter.d.ts.map +1 -1
  28. package/dist/repositories/sqlite/local_db_adapter.js +33 -3
  29. package/dist/repositories/sqlite/local_db_adapter.js.map +1 -1
  30. package/dist/server.d.ts.map +1 -1
  31. package/dist/server.js +15 -2
  32. package/dist/server.js.map +1 -1
  33. package/dist/services/entity_queries.d.ts +4 -0
  34. package/dist/services/entity_queries.d.ts.map +1 -1
  35. package/dist/services/entity_queries.js +30 -0
  36. package/dist/services/entity_queries.js.map +1 -1
  37. package/dist/services/schema_registry.d.ts +7 -0
  38. package/dist/services/schema_registry.d.ts.map +1 -1
  39. package/dist/services/schema_registry.js +13 -0
  40. package/dist/services/schema_registry.js.map +1 -1
  41. package/dist/services/sessions/seed_schema.d.ts +25 -0
  42. package/dist/services/sessions/seed_schema.d.ts.map +1 -0
  43. package/dist/services/sessions/seed_schema.js +139 -0
  44. package/dist/services/sessions/seed_schema.js.map +1 -0
  45. package/dist/shared/action_schemas.d.ts +40 -40
  46. package/dist/shared/contract_mappings.d.ts.map +1 -1
  47. package/dist/shared/contract_mappings.js +20 -1
  48. package/dist/shared/contract_mappings.js.map +1 -1
  49. package/dist/shared/openapi_types.d.ts +154 -3
  50. package/dist/shared/openapi_types.d.ts.map +1 -1
  51. package/openapi.yaml +111 -2
  52. package/package.json +1 -1
package/dist/actions.d.ts CHANGED
@@ -187,6 +187,24 @@ export declare function routeAcceptsAnonymousIssueSubmit(req: Pick<express.Reque
187
187
  * be resolved — prevents silent mis-attribution.
188
188
  */
189
189
  export declare function resolveGuestUserId(req: express.Request, principal: RoutePrincipal): Promise<string | null>;
190
+ /**
191
+ * Helper to extract authenticated user_id from request.
192
+ * Supports: middleware-set user (Bearer / OAuth / dev-local / AAuth admission),
193
+ * session token, Ed25519 bearer.
194
+ *
195
+ * Stronger AAuth Admission plan: admitted AAuth callers carry
196
+ * `req.authenticatedUserId` resolved from the matching `agent_grant`'s
197
+ * owner. This helper applies the same `user_id` mismatch rules to them
198
+ * as to OAuth/Bearer — payload-driven user pivots are rejected. The
199
+ * local-dev override remains available only for the local dev user;
200
+ * AAuth admission can never resolve to that id, so admitted agents are
201
+ * structurally locked to their grant owner.
202
+ * @param req - Express request object
203
+ * @param providedUserId - Optional user_id from request body/query
204
+ * @returns Authenticated user_id
205
+ * @throws Error if not authenticated or user_id mismatch
206
+ */
207
+ export declare function getAuthenticatedUserId(req: express.Request, providedUserId?: string): Promise<string>;
190
208
  type StoreRelationshipRef = {
191
209
  relationship_type: string;
192
210
  source_index?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
package/dist/actions.js CHANGED
@@ -628,7 +628,14 @@ app.get("/.well-known/oauth-authorization-server", (req, res) => {
628
628
  token_endpoint_auth_methods_supported: ["none"],
629
629
  });
630
630
  });
631
- app.get("/.well-known/oauth-protected-resource", async (req, res) => {
631
+ // Both the bare path and the resource-suffixed form are served (#2049).
632
+ // RFC 9728 §3.1 locates a resource's metadata by appending the resource's own
633
+ // path, so a client whose resource is `/mcp` probes
634
+ // `/.well-known/oauth-protected-resource/mcp`. Express does not match that
635
+ // against the bare route, so it previously fell through to the catch-all auth
636
+ // guard and 401'd — the same bootstrap failure as the deadlock described below,
637
+ // reached by a different path. Registering both makes either probe order work.
638
+ app.get(["/.well-known/oauth-protected-resource", "/.well-known/oauth-protected-resource/mcp"], async (req, res) => {
632
639
  const forwardedProto = req.header("x-forwarded-proto")?.split(",")[0]?.trim();
633
640
  const forwardedHost = req.header("x-forwarded-host")?.split(",")[0]?.trim();
634
641
  const host = forwardedHost || req.header("host");
@@ -640,8 +647,18 @@ app.get("/.well-known/oauth-protected-resource", async (req, res) => {
640
647
  has_authorization: Boolean((req.headers["authorization"] || req.headers["Authorization"])),
641
648
  has_connection_id: Boolean(req.headers["x-connection-id"] || req.headers["X-Connection-Id"]),
642
649
  });
643
- // oauth-repro pattern: return 401 on protected resource endpoint when unauthenticated
644
650
  // Validate X-Connection-Id here so Cursor gets consistent invalid_token signal (helps trigger Connect prompt)
651
+ //
652
+ // This document is NOT gated on authentication (#2049). RFC 9728 §3 makes
653
+ // protected-resource metadata the unauthenticated bootstrap entry point: its
654
+ // entire job is to tell a caller with no credentials where to obtain them. An
655
+ // earlier revision returned 401 here when neither Authorization nor
656
+ // X-Connection-Id was present, with a `WWW-Authenticate` naming THIS URL as
657
+ // the place to look — a self-referential deadlock. A client 401'd at /mcp
658
+ // follows the header here, is 401'd again pointing back here, and the login
659
+ // flow never starts, so no credential can resolve it. The sibling
660
+ // /.well-known/oauth-authorization-server was always public (RFC 8414 requires
661
+ // the same); that asymmetry was the bug, not the design.
645
662
  const authHeader = (req.headers["authorization"] || req.headers["Authorization"]);
646
663
  const connectionIdHeader = req.headers["x-connection-id"] || req.headers["X-Connection-Id"];
647
664
  // If X-Connection-Id is sent, validate it. Invalid/expired connection → 401 with error=invalid_token
@@ -660,17 +677,35 @@ app.get("/.well-known/oauth-protected-resource", async (req, res) => {
660
677
  });
661
678
  }
662
679
  }
663
- if (!authHeader?.startsWith("Bearer ") && !connectionIdHeader) {
664
- res.setHeader("WWW-Authenticate", `Bearer resource_metadata="${base}/.well-known/oauth-protected-resource"`);
665
- return res.status(401).json({
666
- error: "Unauthorized: Authentication required",
667
- });
668
- }
669
680
  res.setHeader("Content-Type", "application/json");
670
681
  res.json({
682
+ // RFC 9728 §3: `resource` is the identifier the client binds its token
683
+ // request to. Without it a client that validates the metadata document
684
+ // cannot confirm the document describes the resource it is trying to
685
+ // reach, and has to guess.
686
+ resource: `${base}/mcp`,
671
687
  authorization_servers: [base],
672
688
  });
673
689
  });
690
+ // OIDC discovery is not offered — return an explicit 404 rather than letting this
691
+ // path fall through to the catch-all auth guard, which answers 401 (#2049).
692
+ //
693
+ // A 401 on a route that does not exist tells an OIDC-first client "this endpoint
694
+ // is real, you are just unauthenticated", sending it into the same dead end the
695
+ // protected-resource deadlock created: it retries against a document that will
696
+ // never serve it. A 404 says "not offered here", which correctly makes the client
697
+ // fall back to RFC 8414 /.well-known/oauth-authorization-server.
698
+ //
699
+ // Deliberately NOT a synthetic 200 alias of the OAuth authorization-server
700
+ // metadata: that would advertise a second, parallel discovery contract Neotoma
701
+ // does not actually implement. Revisit only if a real OIDC stack is scoped.
702
+ app.get("/.well-known/openid-configuration", (_req, res) => {
703
+ res.status(404).json({
704
+ error: "not_found",
705
+ error_description: "This server does not offer OpenID Connect discovery. Use " +
706
+ "/.well-known/oauth-authorization-server (RFC 8414) instead.",
707
+ });
708
+ });
674
709
  // AAuth resource server metadata. Exposed publicly so AAuth client libraries
675
710
  // can auto-configure issuer / supported algs / signature window without an
676
711
  // out-of-band conversation. Neotoma is verifier-only: it never hosts agent
@@ -3358,28 +3393,42 @@ app.use(async (req, res, next) => {
3358
3393
  });
3359
3394
  }
3360
3395
  const bearerToken = headerAuth.slice("Bearer ".length).trim();
3361
- // Try to validate as Ed25519 bearer token first
3396
+ // Ed25519 bearer path.
3397
+ //
3398
+ // SECURITY (advisory 2026-08-07-ed25519-bearer-forged-key-auth-bypass):
3399
+ // The bearer token IS a public key (base64url of 32 raw bytes). Possession
3400
+ // of a public key must NEVER authenticate — anyone can present any public
3401
+ // key. Two invariants close the prior forged-key bypass:
3402
+ //
3403
+ // 1. A valid request Signature is MANDATORY. It proves possession of the
3404
+ // corresponding private key. A request without a signature is rejected,
3405
+ // not silently accepted (the old `if (signature && req.body)` made the
3406
+ // check optional, so an unsigned forged key sailed through).
3407
+ // 2. The key must map to a PRE-PROVISIONED userId. `ensurePublicKeyRegistered`
3408
+ // auto-registers any syntactically-valid 32-byte key, so its `true` return
3409
+ // means "well-formed", not "known". Only a key that was registered WITH a
3410
+ // userId (getUserIdFromBearerToken) is a real principal; a bare
3411
+ // auto-registered key resolves to `undefined` and must fall through to the
3412
+ // reject path — never to a caller-supplied `user_id` in getAuthenticatedUserId.
3362
3413
  const registered = ensurePublicKeyRegistered(bearerToken);
3363
- if (registered && isBearerTokenValid(bearerToken)) {
3364
- // Optional: Verify signature if provided
3365
- const { signature } = parseAuthHeader(headerAuth);
3366
- if (signature && req.body) {
3367
- const bodyString = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
3368
- const isValid = verifyRequest(bodyString, signature, bearerToken);
3369
- if (!isValid) {
3370
- logWarn("AuthInvalidSignature", req);
3371
- return sendError(res, 403, "AUTH_INVALID", "Invalid request signature");
3372
- }
3414
+ const registeredUserId = registered ? getUserIdFromBearerToken(bearerToken) : undefined;
3415
+ const { signature: ed25519Signature } = parseAuthHeader(headerAuth);
3416
+ if (registered && isBearerTokenValid(bearerToken) && registeredUserId && ed25519Signature) {
3417
+ // Signature is required and must verify over the exact request body.
3418
+ const bodyString = typeof req.body === "string" ? req.body : JSON.stringify(req.body ?? "");
3419
+ if (!verifyRequest(bodyString, ed25519Signature, bearerToken)) {
3420
+ logWarn("AuthInvalidSignature", req);
3421
+ return sendError(res, 403, "AUTH_INVALID", "Invalid request signature");
3373
3422
  }
3374
3423
  // Attach public key to request for encryption service
3375
3424
  req.publicKey = getPublicKey(bearerToken);
3376
3425
  req.bearerToken = bearerToken;
3377
- // If this token was registered with a userId, set it so getAuthenticatedUserId works without query param
3378
- const registeredUserId = getUserIdFromBearerToken(bearerToken);
3379
- if (registeredUserId) {
3380
- stampUserPrincipal(req, registeredUserId);
3381
- }
3382
- logger.info(`[Auth] ${req.method} ${req.path} auth_method=ed25519_bearer user_id=${req.authenticatedUserId ?? "(from token)"}`);
3426
+ // Bind the request to the pre-provisioned principal. The caller cannot
3427
+ // override this via a `user_id` param — getAuthenticatedUserId keys off the
3428
+ // stamped principal, and the Ed25519 fall-through that trusted a
3429
+ // caller-supplied user_id has been removed.
3430
+ stampUserPrincipal(req, registeredUserId);
3431
+ logger.info(`[Auth] ${req.method} ${req.path} auth_method=ed25519_bearer user_id=${registeredUserId}`);
3383
3432
  }
3384
3433
  else {
3385
3434
  // Try to validate as session token
@@ -3484,7 +3533,10 @@ app.get("/me", async (req, res) => {
3484
3533
  * @returns Authenticated user_id
3485
3534
  * @throws Error if not authenticated or user_id mismatch
3486
3535
  */
3487
- async function getAuthenticatedUserId(req, providedUserId) {
3536
+ // Exported for the security regression test that asserts the fail-closed
3537
+ // behaviour for unresolved Bearer principals
3538
+ // (advisory 2026-08-07-ed25519-bearer-forged-key-auth-bypass).
3539
+ export async function getAuthenticatedUserId(req, providedUserId) {
3488
3540
  const principal = requestPrincipal(req);
3489
3541
  if (principal?.kind === "guest") {
3490
3542
  throw new Error("Not authenticated - guest principal cannot resolve a user_id");
@@ -3510,16 +3562,17 @@ async function getAuthenticatedUserId(req, providedUserId) {
3510
3562
  }
3511
3563
  return authenticatedUserId;
3512
3564
  }
3513
- const headerAuth = req.headers.authorization || "";
3514
- if (!headerAuth.startsWith("Bearer ")) {
3515
- throw new Error("Not authenticated - missing Bearer token");
3516
- }
3517
- // Ed25519 bearer token - user_id must be provided
3518
- if (!providedUserId) {
3519
- throw new Error("user_id required when using Ed25519 bearer token");
3520
- }
3521
- // For Ed25519 tokens, we trust the provided user_id (token validation happens in middleware)
3522
- return providedUserId;
3565
+ // No principal was resolved by the auth middleware. Fail closed.
3566
+ //
3567
+ // SECURITY (advisory 2026-08-07-ed25519-bearer-forged-key-auth-bypass):
3568
+ // Previously this tail returned the caller-supplied `user_id` for any request
3569
+ // carrying a Bearer header, on the assumption that "token validation happens
3570
+ // in middleware". A forged public key was accepted by that middleware without
3571
+ // proving possession of the private key, so this line handed an attacker the
3572
+ // scope of their choice (e.g. the shared nil-UUID owner). A request that
3573
+ // reaches here has NOT been authenticated to any principal — there is no
3574
+ // trustworthy `user_id` to return, regardless of what the caller provided.
3575
+ throw new Error("Not authenticated - no resolved principal for request");
3523
3576
  }
3524
3577
  /**
3525
3578
  * Helper to handle errors in API endpoints, including authentication errors
@@ -3583,6 +3636,24 @@ function handleApiError(req, res, error, defaultMessage, errorCode = "DB_QUERY_F
3583
3636
  .status(error.status)
3584
3637
  .json(buildErrorEnvelope(error.code, error.message, error.toErrorEnvelopeDetails()));
3585
3638
  }
3639
+ // SECURITY (advisory 2026-08-07-sort-by-order-by-sql-injection): a rejected
3640
+ // snapshot field name (sort_by / snapshot_filters) or an unsafe column
3641
+ // reference caught by the sqlite adapter is a client input error, not a server
3642
+ // fault — surface it as a 400 so it is not masked as a 500 DB_QUERY_FAILED, and
3643
+ // so the negative test asserts a stable status. Matched by name to avoid a
3644
+ // static import cycle with the dynamically-imported query module.
3645
+ if (error instanceof Error &&
3646
+ (error.name === "InvalidSnapshotFieldError" ||
3647
+ error.message.startsWith("Unsafe column reference rejected:"))) {
3648
+ // Log the full message (with the rejected value) server-side for debugging,
3649
+ // but do NOT echo the caller-supplied field string back in the response —
3650
+ // reflecting rejected input, even sanitized, is needless. Return a generic
3651
+ // detail that names the constraint. (Vanellus review, PR #2129, non-blocking.)
3652
+ logWarn(logContext || "InvalidQueryField", req, { detail: error.message });
3653
+ return res.status(400).json(buildErrorEnvelope("INVALID_QUERY_FIELD", "Invalid query field name.", {
3654
+ detail: "Query field names (sort_by / snapshot_filters keys) must be bare identifiers matching ^[A-Za-z_][A-Za-z0-9_]*$.",
3655
+ }));
3656
+ }
3586
3657
  logError(logContext || "APIError", req, error);
3587
3658
  // SECURITY: do not echo raw Error.message to clients in production; SQLite /
3588
3659
  // filesystem errors frequently leak paths and schema. In development we keep
@@ -9609,6 +9680,15 @@ export async function startHTTPServer() {
9609
9680
  catch (err) {
9610
9681
  logger.warn(`[Plans] failed to seed plan schema: ${err.message}`);
9611
9682
  }
9683
+ // Seed `agent_session` + `session_transcript` schemas (transcript ingest).
9684
+ try {
9685
+ const { seedSessionSchemas } = await import("./services/sessions/seed_schema.js");
9686
+ await seedSessionSchemas();
9687
+ logger.info("[Sessions] agent_session + session_transcript schemas seeded");
9688
+ }
9689
+ catch (err) {
9690
+ logger.warn(`[Sessions] failed to seed session schemas: ${err.message}`);
9691
+ }
9612
9692
  // Seed `skill` schema (harness-mirrored skills, slash-command palette).
9613
9693
  try {
9614
9694
  const { seedSkillSchema } = await import("./services/skills/seed_schema.js");
@@ -9775,6 +9855,42 @@ export async function startHTTPServer() {
9775
9855
  // Only auto-start if not disabled AND if this is the main module
9776
9856
  const isMainModule = import.meta.url === `file://${process.argv[1]}`;
9777
9857
  if (process.env.NEOTOMA_ACTIONS_DISABLE_AUTOSTART !== "1" && isMainModule) {
9858
+ // Exit diagnostics. A long-running server should never reach `beforeExit`:
9859
+ // that event only fires when the event loop has drained, i.e. nothing is
9860
+ // left holding the process open. In production this happened silently and
9861
+ // repeatedly — the process exited with code 0 every ~12 minutes with no
9862
+ // error and no signal, and because the exit was CLEAN the platform read it
9863
+ // as success and declined to restart, turning it into an outage
9864
+ // (neotoma#2094).
9865
+ //
9866
+ // Logging the active resources at that moment names what the loop was (and
9867
+ // was no longer) waiting on, which is the one piece of evidence needed to
9868
+ // find the handle that stops holding the server up. `exit` is logged too so
9869
+ // the code and reason are attributable even when the cause is external.
9870
+ process.on("beforeExit", (code) => {
9871
+ let resources = [];
9872
+ try {
9873
+ const getResources = process
9874
+ .getActiveResourcesInfo;
9875
+ if (typeof getResources === "function")
9876
+ resources = getResources.call(process);
9877
+ }
9878
+ catch {
9879
+ /* diagnostics must never themselves throw */
9880
+ }
9881
+ console.error(`[neotoma] beforeExit code=${code} — event loop drained; a long-running ` +
9882
+ `server should not reach here. activeResources=${JSON.stringify(resources)} ` +
9883
+ `uptime=${process.uptime().toFixed(1)}s`);
9884
+ });
9885
+ process.on("exit", (code) => {
9886
+ console.error(`[neotoma] exit code=${code} uptime=${process.uptime().toFixed(1)}s`);
9887
+ });
9888
+ for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
9889
+ process.on(sig, () => {
9890
+ console.error(`[neotoma] received ${sig} uptime=${process.uptime().toFixed(1)}s`);
9891
+ process.exit(0);
9892
+ });
9893
+ }
9778
9894
  startHTTPServer().catch((err) => {
9779
9895
  console.error("Failed to start HTTP server:", err);
9780
9896
  process.exit(1);