This release includes 1 security fix for security teams reviewing exposed deployments.
Topics
+3 more
Affected surfaces
ReleasePort's take
Light signalTenant isolation vulnerabilities in /list_relationships and /retrieve_graph_neighborhood endpoints have been fixed, addressing GHSA-wrr4-782v-jhwh.
Why it matters: Patch to v0.14.0 immediately to remediate the GHSA-wrr4-782v-jhwh vulnerability affecting runtime data layer endpoints.
Summary
AI summaryUpdates Behavior changes, Highlights, and Tests and validation across a mixed release.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Security | Medium |
Tenant isolation fix for /list_relationships and /retrieve_graph_neighborhood addresses GHSA-wrr4-782v-jhwh vulnerability. Tenant isolation fix for /list_relationships and /retrieve_graph_neighborhood addresses GHSA-wrr4-782v-jhwh vulnerability. Source: llm_adapter@2026-05-22 Confidence: high |
— |
| Feature | Low |
Entity search boosts results by 280 points when query token matches an entity type name. Entity search boosts results by 280 points when query token matches an entity type name. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Feature | Low |
Chat bookkeeping entities are excluded from product search by default unless explicitly filtered. Chat bookkeeping entities are excluded from product search by default unless explicitly filtered. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Feature | Low |
Issue‑filing consent preference persists across sessions via a stored `preference` entity. Issue‑filing consent preference persists across sessions via a stored `preference` entity. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Feature | Low |
/review skill integrated into automated PR review workflow and used as a hard gate in release step 3.6. /review skill integrated into automated PR review workflow and used as a hard gate in release step 3.6. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Feature | Low |
`update_schema_incremental` returns structured error responses with stable hints when no schema exists or identity config is missing. `update_schema_incremental` returns structured error responses with stable hints when no schema exists or identity config is missing. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Performance | Low |
Graph neighborhood traversal endpoints now support pagination with `limit` and `offset` parameters. Graph neighborhood traversal endpoints now support pagination with `limit` and `offset` parameters. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Performance | Low |
Docker build context trimmed dramatically by new `.dockerignore`, reducing image build time. Docker build context trimmed dramatically by new `.dockerignore`, reducing image build time. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Bugfix | Low |
`neotoma schemas repair-plural-types` works correctly from global or `npx` installs after fixing dynamic import path. `neotoma schemas repair-plural-types` works correctly from global or `npx` installs after fixing dynamic import path. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Bugfix | Low |
`mirrorEntity` profile render no longer adds spurious `## body` heading in frontmatter_content mode. `mirrorEntity` profile render no longer adds spurious `## body` heading in frontmatter_content mode. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Refactor | Low |
`preference` entity schema is now registered at boot with proper canonical‑name derivation and merge policy. `preference` entity schema is now registered at boot with proper canonical‑name derivation and merge policy. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Refactor | Low |
OpenAPI schema updated with pagination fields and request/response shapes for `/retrieve_graph_neighborhood` and `/list_relationships` without breaking changes. OpenAPI schema updated with pagination fields and request/response shapes for `/retrieve_graph_neighborhood` and `/list_relationships` without breaking changes. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
Full changelog
v0.14.0 ships substantial improvements across search, ingestion, agent ergonomics, and security. Headlines: smarter entity search that ranks by entity-type intent and hides chat bookkeeping; persistent issue-filing consent; paginated graph neighborhood traversal; structured cold-start error responses on update_schema_incremental; per-user data scoping on relationship query endpoints (GHSA-wrr4-782v-jhwh); a /review skill wired into the automated PR review workflow; the preference entity schema is now registered; and neotoma schemas repair-plural-types is fixed for global / npx installs.
Highlights
- Smarter entity search. When a query token names an entity type (e.g. "plans", "tasks", "transactions"),
retrieve_entitiesnow boosts that type's results by 280 points and filters by type instead of matching the token against snapshot text — so "newest plans" actually surfaces plans, not chat messages mentioning the word "plans". Singular/plural normalization included. - Chat bookkeeping no longer pollutes product search. The Inspector header search and
/searchautomatically excludeconversation,conversation_message, and related bookkeeping entity types unless the caller explicitly filters by one of those types. Passexclude_bookkeeping: trueonretrieve_entitiesto apply the same filter from any caller. - Issue-filing preference persists across sessions. On first encounter, agents ask once and store your choice (
always/ask/never) as apreferenceentity so future sessions skip the prompt.neotoma issues config --modestill sets the runtime flag; the preference entity is the cross-session layer that feeds it. - Paginate large graph neighborhood results.
retrieve_graph_neighborhoodandPOST /retrieve_graph_neighborhoodnow acceptlimit(default 100, max 500) andoffsetparameters and returntotal_countandhas_more, so callers can page through densely-connected nodes without truncation. - Tenant isolation fix on relationship query endpoints. GHSA-wrr4-782v-jhwh —
/list_relationshipsand/retrieve_graph_neighborhoodpreviously authenticated callers but did not scope database queries to the requesting user. An authenticated user with a known cross-user entity ID could read another user's relationship metadata. Severity Low under current single-tenant deployments; escalates to Medium for multi-tenant. Fixed with explicit.eq("user_id", userId)on every query and a newtenant_isolation_matrix.test.tsgate. /reviewskill wired into automated PR review. The PR review workflow now invokes a versioned/reviewskill that walks the full architectural + product-principles + agent-instruction coherence checklist instead of a hand-rolled prompt. The same skill runs as a hard gate inside/releaseStep 3.6.preferenceentity schema registered. The schema is now seeded at boot sopreferenceentities (used by the issue-filing consent flow, future agent settings) have proper canonical-name derivation, last-write merge policy, and field declarations.neotoma schemas repair-plural-typesworks from a global or npx install. A dynamic import in the compiled CLI used the wrong relative path (../../services/instead of../services/), which silently failed after installation. Fixed, with new dist-level smoke tests covering this class of bug going forward.
What changed for npm package users
CLI (neotoma)
neotoma schemas repair-plural-types— fixed: the dist-level dynamic import path forplural_type_repairwas one directory level off (../../services/plural_type_repair.jsinstead of../services/plural_type_repair.js). The bug was invisible to TypeScript compilation and source-level tests because both resolve fromsrc/; only a subprocess spawned against the compileddist/catches it. New smoke tests attests/contract/cli_handler_dist_smoke.test.tscover this class of regression forschemas repair-plural-types,schemas audit,schemas list, andstatus.
Runtime / data layer
- Entity search ranking and bookkeeping exclusion (
retrieve_entities,POST /retrieve_entities) — substantial rework ofsrc/shared/action_handlers/entity_handlers.ts(~430 lines):- New
ENTITY_TYPE_KEYWORD_BOOST = 280. When a search token equals an entity_type's name (canonical or singular-normalized viasuggestSingular), matching entities of that type get ranked 280 points higher in the result list. - New
exclude_bookkeepingparameter onretrieve_entities(boolean, default false on direct API calls; default true for Inspector header and/search). When set,conversation,conversation_message, and relatedBOOKKEEPING_ENTITY_TYPESare omitted from results. - New
buildEntityTypeFilterTokens(searchTokens, knownEntityTypes)andtextTokensForEntityMatch(searchTokens, typeFilterTokens)helpers: when the query names a known entity_type, that token is removed from snapshot-text matching and becomes a type filter instead. The remaining tokens still match snapshot content. - New
buildEntityLexicalSearchText(canonicalName, snapshot, rawFragmentText)builds the canonical lexical haystack (canonical name + snapshot + raw fragments) used for ranking. lexicalSearchEntityIdsnow resolves active entity types fromschema_registryto drive the type-filter logic; falls back to a warning log if the schema registry query fails.- Net effect: a query like "newest plans" now narrows the candidate query to
entity_type='plan'and boosts ranking accordingly, rather than fuzzy-matching the literal string "plans" against every entity's snapshot.
- New
retrieve_graph_neighborhoodpagination — acceptslimit(1–500, default 100) andoffset(default 0) query parameters. Response now includestotal_count(total relationships for the node before pagination) andhas_more. Use these to page through high-degree nodes.list_relationshipsfilter expansion + OpenAPI declaration —src/actions.ts: the handler now supportssource_entity_idandtarget_entity_idas direct filter params in addition to the legacyentity_id + directionpattern. Pagination (limit,offset) added. The request schema requires at least one ofentity_id,source_entity_id,target_entity_id, orrelationship_type. Optionaluser_idfilter added.openapi.yamlnow declares the full request and response shape including the closedrelationship_typeenum (matchingRelationshipTypeSchema) and ananyOf"at least one identifying field" constraint. Non-breaking addition.- Per-user data scoping on
/list_relationshipsand/retrieve_graph_neighborhood(GHSA-wrr4-782v-jhwh) — both endpoints now resolvegetAuthenticatedUserIdand apply.eq("user_id", userId)to every relationship, entity, observation, and source lookup. Closes #365 and the tenant-isolation portion of #366. Test gate added:tests/security/tenant_isolation_matrix.test.ts. update_schema_incrementalcold-start handling — when called for an entity_type with no registered or code-defined schema, previously threw an opaqueMcpError(-32603); now returns a structured non-throwing response witherror_code: ERR_NO_SCHEMA_FOR_ENTITY_TYPE,no_schema_for_entity_type: true, and a stablehint("Pickregister_schemafor the new entity_type. The MCP tool isregister_schema; the HTTP route isPOST /register_schema...") that no longer interpolates per-type strings. When the entity_type has an existing schema that lacks bothcanonical_name_fieldsandidentity_opt_out, the R2 error is now surfaced asERR_SCHEMA_MISSING_IDENTITY_CONFIGwith a hint to callregister_schemawith a complete schema definition.preferenceentity schema — registered at boot undersrc/services/schema_definitions.ts. Fields:title(canonical name source),value(last-write merge policy),scope,description. Backs the cross-session issue-filing consent flow and is available for any future agent-settings entity. Closes #339.- Profile mirror render correctness (
rebuildProfile/mirrorEntity) —mirrorEntitynow dispatches torenderProfileEntitywhenrender_modeisfrontmatter_contentorcontent_only, matchingrebuildProfilebehavior. Previously the live-write path always calledrenderEntityMarkdown, ignoringrender_mode,frontmatter_fields, andcontent_field. Whencontent_fieldwas"body", this produced a spurious## bodyheading in the output. Fixed via dynamic import ofrenderProfileEntityfromcanonical_markdown.js. Closes #262. - Graceful
entity_typesfallback in search (src/shared/action_handlers/entity_handlers.ts) — whenschema_registryqueries fail (cold start, transient DB error),resolveEntityTypesForTypeFiltersnow logs a warning and returns an empty set rather than throwing. Search falls back to the un-typed match path instead of failing the whole request. Closes #342.
Shipped artifacts
openapi.yaml— substantial additions:POST /retrieve_graph_neighborhoodgainslimit,offset,user_idrequest fields andtotal_count,has_more,node_id,node_type,entity,relationships,related_entities,observations,sourcesresponse fields.POST /list_relationshipsgains a fully-declared request body (entity_id,source_entity_id,target_entity_id,direction,relationship_typeclosed enum,limit,offset,user_id) withanyOf"at least one identifying field" constraint, plus full response shape (relationships,total,limit,offset).
.dockerignore— substantially trimmed: build context reduced from ~15 GB to ~50 MB by excluding non-source dirs (backups,data_backups,tmp,site_pages,writ,packages,mcp,public,frontend,.claude,.vitest). Faster image builds; no runtime behavior change.
API surface & contracts
OpenAPI — no breaking changes. ~35 non-breaking additions covering POST /retrieve_graph_neighborhood (12 fields) and POST /list_relationships (request body + response body + closed enum + anyOf constraint). npm run openapi:bc-diff -- --base v0.13.0 --head HEAD reports no breaking changes.
Request schemas — ListRelationshipsRequestSchema (src/shared/action_schemas.ts) refactored: entity_id becomes optional, source_entity_id / target_entity_id / user_id added as optional, .refine() requires at least one identifying field, limit is bounded 1..500, offset bounded ≥ 0. Existing callers passing only entity_id continue to work.
MCP tool surface — retrieve_graph_neighborhood tool gains limit (integer, 1–500, default 100) and offset (integer, min 0, default 0) parameters.
Behavior changes
- Entity search results reorder. Queries containing entity-type names ("plans", "tasks", "transactions") will now return type-filtered results ranked by a 280-point keyword boost, where previously they returned mixed-type fuzzy matches. Snapshots and downstream callers that depended on the old fuzzy behavior may see different result orderings or smaller result sets when the query happens to name a type.
- Inspector header search hides chat bookkeeping by default. Conversations and conversation messages no longer appear in product search results unless the caller explicitly filters by
conversationorconversation_message. neotoma schemas repair-plural-typesnow executes its handler correctly from a globalnpm install -g neotomaornpx neotomainvocation. Previously the dynamic import silently threwERR_MODULE_NOT_FOUNDwhen invoked against the dist tree.update_schema_incrementalon an unregistered type returns a structured error with stablehinttext instead of an uncaught internal error. Callers should check forerror_code: ERR_NO_SCHEMA_FOR_ENTITY_TYPEand redirect toregister_schema. Hint text no longer interpolates per-type strings (stable across calls).mirrorEntityprofile render —## bodysection heading no longer appears spuriously whencontent_fieldis"body"infrontmatter_contentmode.- Graph neighborhood traversal — responses are now paginated; callers relying on all relationships being present in a single response must check
has_moreand page whentrue. - Relationship query endpoints scope to authenticated user.
/list_relationshipsand/retrieve_graph_neighborhoodnow return only the authenticated user's relationships/graph. Multi-tenant callers that previously received cross-user rows (a bug) will see strictly smaller result sets. Single-tenant deployments are unaffected.
Agent-facing instruction changes (ship to every client)
The MCP instructions (docs/developer/mcp/instructions.md) shipped with this release include two changes:
-
Issue-filing consent uses a stored preference entity. The
[ISSUE REPORTING]section's mode-discovery flow now first callsretrieve_entitieswithentity_type: "preference"and filtertitle: "issue_filing_consent"before prompting the user. If a matching entity exists, itsvaluefield (always/ask/never) resolves the mode without re-prompting. On first consent, the agent persists both the preference entity (cross-session memory) andneotoma issues config --mode(runtime config flag). The QA-driven filing section mirrors this flow. -
describe_entity_typeadded as the first option for schema-check. The schema-check rule in[ENTITY TYPES & SCHEMA]now listsdescribe_entity_typeas the preferred introspection call (returns the fullSchemaDefinition: fields,canonical_name_fields,temporal_fields,reference_fields,aliases,merge_policies) before falling back toget_schema_recommendationsor a snapshot inspection.
The previous draft [IDENTITY & ATTRIBUTION] "Blocked-plan recovery at session start" rule referencing a check_blocked_plans tool was reverted (#349) because the underlying tool was never implemented (#297 closed in favor of an existing-primitives approach; see #193 follow-up).
Plugin / hooks / SDK changes
The /review skill is now invoked end-to-end by .github/workflows/claude_pr_review.yml instead of a hand-rolled prompt. Path triggers widened to include docs/developer/mcp/instructions.md, docs/developer/cli_agent_instructions.md, docs/foundation/**, docs/architecture/**, docs/subsystems/**, .claude/skills/**, .claude/rules/**. /release Step 3.6 also runs /review on the full release diff as a hard gate. Closes #348.
Security hardening
The diff classifier (npm run security:classify-diff -- --base v0.13.0 --head HEAD) reports sensitive=true because openapi.yaml and src/actions.ts appear in the monitored surface set. Two distinct security-relevant changes shipped:
-
Tenant isolation fix (GHSA-wrr4-782v-jhwh) — Per-user data scoping added to
/list_relationshipsand/retrieve_graph_neighborhoodHTTP handlers and their MCP counterparts. Closes #365, addresses the tenant-isolation portion of #366. Test gate added:tests/security/tenant_isolation_matrix.test.ts(6 tests) seeds two users and asserts cross-user reads are blocked. Seedocs/security/advisories/2026-05-21-relationship-endpoint-tenant-isolation.mdfor the full advisory. -
change_guardrails_rules.mdcMUST 5 (Authorization) extended — explicit.eq("user_id", userId)requirement on every query against user-owned tables, plus mandatory tenant-isolation matrix coverage for new query endpoints. Prevents recurrence of the tenant isolation gap class.
Surfaces touched (full inspection):
openapi.yaml— adds pagination, response-enrichment, and request-body declarations onPOST /retrieve_graph_neighborhoodandPOST /list_relationships. No changes to security blocks, protected routes, or auth scheme declarations.src/actions.ts—list_relationshipshandler refactor (filter expansion + pagination) + per-user scoping on both/list_relationshipsand/retrieve_graph_neighborhood. No changes toisLocalRequest,forwardedForValues,isProductionEnvironment, or any auth-gating logic.src/server.ts— corresponding per-user scoping on the MCP versions oflistRelationshipsandretrieveGraphNeighborhood.src/shared/action_handlers/entity_handlers.ts— search ranking and bookkeeping-exclusion changes. The new helpers (shouldExcludeBookkeepingFromSearch,searchTokenMatchesEntityType,entityTypeKeywordBoost,buildEntityTypeFilterTokens) are pure query/ranking functions with no auth surface. Query construction continues to filter byuser_idthrough the standard authenticated path.
Security review: docs/releases/in_progress/v0.14.0/security_review.md — verdict yes, no security-relevant findings beyond the documented advisory.
Post-deploy probes: docs/releases/in_progress/v0.14.0/post_deploy_security_probes.md (populated after Step 5).
Gate results:
- G1
npm run security:classify-diff—sensitive=true. - G2
npm run security:lint— 0 errors (112 pre-existing warnings, none introduced by this release). - G3a
npm run security:manifest:check— in sync (108 routes). - G3b
npm run test:security:auth-matrix— 16 passed, 1 skipped. - G3c
tests/security/tenant_isolation_matrix.test.ts(new) — 6 passed. - G4
npm run security:ai-review— review file filled, sign-offyes.
Docs site & CI / tooling
docs/subsystems/entity_field_semantics.md— new canonical doc (141 lines) definingsource(originating system slug),source_url(URL),source_ref(upstream external ID), anddata_source(audit string). Includes canonical slug table and forbidden-value examples.docs/subsystems/record_types.md— datasetsourcedescription tightened to referenceentity_field_semantics.md.docs/developer/mcp/instructions.md— adds a source-field rule to the agent instructions: source is a slug, never a URL or person name; usesource_urlandsource_reffor those.docs/doc_dependencies.yaml— registersentity_field_semantics.mdas upstream forschema_definitions.ts,instructions.md, andrecord_types.md.src/services/schema_definitions.ts— inline comments on thesourcefields ofincome,note, andagent_taskdeclarations now point to the canonical doc.docs/reference/error_codes.md— new Schema Registry Errors section documentingERR_NO_SCHEMA_FOR_ENTITY_TYPEandERR_SCHEMA_MISSING_IDENTITY_CONFIG. Closes #344.docs/security/advisories/README.md— new entry for GHSA-wrr4-782v-jhwh.docs/architecture/change_guardrails_rules.mdc— MUST 5 (Authorization) extended per security hardening above..claude/skills/review/SKILL.md— new (335 lines), the/reviewskill body that the PR review workflow invokes..github/workflows/claude_pr_review.yml— invokes the skill viadirect_prompt, widens path triggers, broadenssubstantialgate detection.docs/testing/automated_test_catalog.mdregenerated to include all new test files.
Fixes
- Tenant isolation on relationship query endpoints (GHSA-wrr4-782v-jhwh) — see Security hardening section above.
neotoma schemas repair-plural-typesdist import (#304, #311) — import path../../services/plural_type_repair.js→../services/plural_type_repair.jsin the compiled CLI. Invisible to TypeScript and source-level tests; caught only by runningnode dist/cli/index.js.update_schema_incrementalopaque error on unregistered types (#269) — replaced uncaughtMcpError(-32603)with structuredERR_NO_SCHEMA_FOR_ENTITY_TYPE/ERR_SCHEMA_MISSING_IDENTITY_CONFIGresponses with actionable hints. Hint text stabilized to remove per-type string interpolation (#345).mirrorEntityspurious## bodyheading (#262) —mirrorEntitylive-write path now dispatches torenderProfileEntityfor profile render modes, matchingrebuildProfilebehavior.- OpenAPI declaration drift on
/list_relationships(#340) — the v0.14.0 handler refactor added request and response fields that were not declared inopenapi.yaml. Now declared with closedrelationship_typeenum andanyOfconstraint. schema_registrycold-start search failure (#342) —resolveEntityTypesForTypeFiltersnow logs a warning and falls back gracefully instead of throwing.- Removed broken
check_blocked_plansinstruction (#337) — the[IDENTITY & ATTRIBUTION]rule referenced a tool that was never implemented; removed pending a proper recovery-workflow design (see #193 follow-up).
Tests and validation
- New
tests/contract/cli_handler_dist_smoke.test.ts(92 lines) — spawnsnode dist/cli/index.js <command>as a real subprocess and asserts noERR_MODULE_NOT_FOUNDin stderr. Coversschemas repair-plural-types,schemas audit,schemas list, andstatus. Catches the class of regression from issue #304 that is invisible to TypeScript and source-level tests. - New
tests/integration/graph_neighborhood_pagination.test.ts(177 lines) — real HTTP server + real DB integration test. Seeds a center node with multiple spoke relationships, then assertslimittruncates results,offsetskips correctly,total_countreflects the full set, andhas_moreflips when the final page is reached. - New
tests/integration/update_schema_incremental_cold_start.test.ts(130 lines) — covers theERR_NO_SCHEMA_FOR_ENTITY_TYPEandERR_SCHEMA_MISSING_IDENTITY_CONFIGcold-start paths against a realschema_registryrow. - New
tests/security/tenant_isolation_matrix.test.ts(6 tests) — companion to the auth topology matrix. Seeds two users and asserts authenticated endpoints scope responses to the requesting user. - New
src/shared/action_handlers/entity_handlers.test.ts(32 tests, #343) — unit coverage for the pure ranking and bookkeeping helpers introduced by the search rework (ENTITY_TYPE_KEYWORD_BOOSTmagnitude lock-in,shouldExcludeBookkeepingFromSearch,searchTokenMatchesEntityType,entityTypeKeywordBoost,buildEntityTypeFilterTokens,textTokensForEntityMatch,buildEntityLexicalSearchText,compareSearchRank). - New contract test block in
tests/contract/openapi_schema.test.tsforlist_relationships— asserts declared request fields and the closedrelationship_typeenum match the handler'sRelationshipTypeSchema. - Regression test in
src/services/canonical_markdown.test.ts— asserts## bodynever appears whencontent_fieldis"body"infrontmatter_contentmode (#262).
Breaking changes
No breaking changes.
Security Fixes
- GHSA-wrr4-782v-jhwh — per‑user data scoping added to `/list_relationships` and `/retrieve_graph_neighborhood` endpoints, preventing cross‑tenant reads
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About markmhendrickson/neotoma
Deterministic state layer for AI agents. Stores versioned entities (contacts, tasks, transactions, decisions) with immutable observations, full provenance, and schema-first extraction. Local-first SQLite, cross-client memory across Claude, Cursor, ChatGPT, and OpenClaw. Website
Related context
Related tools
Earlier breaking changes
- v0.12.1 Inspector build prepublish now exits non-zero if inspector submodule is missing, breaking ad-hoc npm pack runs without init.
- v0.12.0 Access policy source precedence: env > SchemaMetadata.guest_access_policy > config
- v0.12.0 Legacy feedback subsystem completely removed; issues subsystem is replacement
- v0.12.0 MCP submit_issue requires reporter_git_sha or reporter_app_version
Beta — feedback welcome: [email protected]