Skip to content

v5.51.4: Security fixes ported from 6.1, plus memory and query fixes

A security patch for the certified 5.x line that ports the 6.0 and 6.1 security fixes needing no migration: SQL and filter screening, per-action RunAction authorization, user and role elevation guards, and sandboxed templates and expressions. Plus memory, query rendering and AI-provider fixes. No migrations.

Upgrade Notes apply: several requests 5.51.3 accepted are now refused, non-Owner users need Action Authorizations to run actions, and shared Redis caches must be flushed on upgrade.

Upgrading to 6.1 is the recommended path. 6.1 has every fix. One fix, which stops public web-widget guests reading each other’s agent runs, needs a database migration and so is not in 5.51.x. Deployers who run the public widget on 5.51 should add the Widget Guest row filters by hand until they move to 6.1; deployers without the public widget are not exposed.

  • The artifacts module no longer preloads React and Babel from a CDN (@memberjunction/ng-artifacts, @memberjunction/ng-explorer-core): apps that only import a realtime overlay, such as a public voice widget, make no third-party CDN requests at bootstrap. MJ Explorer keeps a warm cache by preloading after login; other hosts can call AngularAdapterService.Preload() after authentication.
  • Data-layer injection and read-authorization gaps closed (@memberjunction/core, @memberjunction/server, @memberjunction/sqlserver-dataprovider, @memberjunction/sql-dialect, @memberjunction/sql-parser, @memberjunction/global, @memberjunction/codegen-lib): composite-key values are escaped or validated before they reach record-name and dependency SQL. Client ExtraFilter, OrderBy and OverrideExcludeFilter subqueries, new to screening on 5.51, must read an entity base view the user can read, and ExecuteAdhocQuery authorizes every table it reads the same way. GetData validates resolved SQL as a single read. SQL Server identifier quoting and REST paging parameters are hardened, and CodeGen writes descriptions as JSON strings.
  • Caller-supplied SQL is confined to the read-only login (@memberjunction/server, @memberjunction/generic-database-provider, @memberjunction/sql-dialect, @memberjunction/postgresql-dataprovider): ad-hoc SQL runs through the read-only provider with a database-enforced timeout, and on PostgreSQL inside a rolled-back read-only transaction on its own pool. Ad-hoc SQL, TestQuerySQL and query specs are refused when they call functions that run SQL from a string, read files or reach other databases. MJAPI warns at startup when the PostgreSQL read-only login can read base tables or server files.
  • Scope-limited sessions cannot run ad-hoc SQL, and server-built filters escape their values (@memberjunction/server): ad-hoc SQL bypasses entity permissions and row-level security, so anonymous and resource-scoped magic-link sessions could read the whole database outside their scope. They are now refused. findBy and the view-name lookup escape client-supplied values, and findBy rejects non-numeric values for numeric and boolean fields.
  • Template rendering is sandboxed, and on-demand template runs are authorized (@memberjunction/templates, @memberjunction/server): a template could reach Function, process and the shared rendering environment, and use them to run code on the server or alter later renders. Rendering now checks each template’s parse tree before it compiles and blocks those objects while it renders; a template that breaks a rule fails with TemplateSandboxError. RunTemplate and Template.Run refuse scope-limited sessions and callers without Read on MJ: Templates and MJ: Template Contents.
  • SafeExpressionEvaluator validates expressions against an AST allowlist (@memberjunction/global): the textual denylist could be bypassed by assembling names from strings, reaching the Function constructor from any metadata-authored expression such as field rules and flow or loop agent conditions. Every node is now checked structurally before compilation. Legitimate data such as name == 'constructor' is accepted again.
  • RunAction authorizes each action, and its actions no longer run caller code (@memberjunction/server, @memberjunction/actions, @memberjunction/core-actions, @memberjunction/database-designer-actions): an Owner may run any action; any other user needs a role holding an authorization linked to the action through MJ: Action Authorizations, unless the action checks the caller itself. Scope-limited sessions are refused. Calculate Expression evaluates with an allowlisted math parser instead of new Function, Conditional uses SafeExpressionEvaluator, and Run Ad-hoc Query applies the same checks as ExecuteAdhocQuery.
  • Non-Owners can no longer elevate themselves through MJ: Users (@memberjunction/server, @memberjunction/core-entities-server, @memberjunction/ng-explorer-settings): auto-provisioned users received the Developer role by default, which could update any MJ: Users row, including setting one’s own Type to Owner. The newUserRoles default is now ['UI'], and a server-side guard refuses, for any non-Owner, creating or deleting users, changing Type or Name, and editing another user’s row. Settings > User Management now reports a refused activation change instead of showing success.
  • Non-Owners can no longer elevate themselves through roles (@memberjunction/core-entities-server, @memberjunction/core-actions, @memberjunction/ng-explorer-settings, @memberjunction/server-bootstrap, @memberjunction/server-bootstrap-lite): a non-Owner holding Developer or Integration could grant themselves any role or create a new one. A non-Owner may now grant, move or revoke only roles they hold, and may not create, change or delete roles. Explorer’s bulk role assignment reports rows refused on the client.
  • Row-level security honours the permission row’s Can* flags (@memberjunction/core): a permission row that did not grant an operation was read as an unfiltered grant, so a user whose tenant role bound Create, Update or Delete to a row filter was exempt from it on the client path and could write into another organization. A leftover filter on a non-granting row also widened the user’s combined filter. Both checks now consider only rows that grant the operation.
  • Status-update pushes go only to the authenticated connection (@memberjunction/server): the statusUpdates subscription trusted the client-supplied sessionId, so a subscriber who obtained another user’s sessionId received their agent-run progress and notifications. Delivery now also requires the connection’s authenticated user to match.
  • Rendered markdown is sanitized with DOMPurify (@memberjunction/ng-markdown): the component’s hand-written deny-list missed some script end tags, on* handlers and data: URLs. Sanitization now happens in MarkdownService.parse(), so every consumer is covered, and code-block unwrapping no longer runs handlers in the live document.
  • Predictive Studio loads model artifacts through an allow-list unpickler (@memberjunction/predictive-studio-sidecar): a crafted artifact submitted to /predict could execute code in the sidecar. Only the globals the sidecar’s estimators need are resolved, and refusals return 400. Existing stored artifacts load unchanged.
  • InstanceConnectionString keeps each connection’s identity (@memberjunction/sqlserver-dataprovider): on mssql v11 and later the getter returned mssql://localhost:1433/ for every connection, so shared Redis result caches collided across processes connected to different databases and one process could serve another’s cached rows.
  • Artifact iframes, Gmail headers and template errors are hardened (@memberjunction/ng-artifacts, @memberjunction/communication-gmail, @memberjunction/templates): the JSON artifact viewer’s HTML iframe no longer gets allow-same-origin, the Gmail provider strips CR and LF from header values, and Nunjucks runs with dev: false, so render errors no longer carry stack traces.
  • The realtime relay re-checks CanRun on every call (@memberjunction/server, @memberjunction/ai-agents): the relay took authority from a session’s Config, which the session owner can edit through the generic entity API. Every relayed tool call now re-checks CanRun on the target agent, and run ids in Config are used only when they belong to the session.
  • RunView aggregate expressions cannot carry stacked SQL (@memberjunction/sql-dialect, @memberjunction/sql-parser, @memberjunction/generic-database-provider, @memberjunction/server): each expression must be a single aggregate call over the entity’s columns, and the query runs SQL rebuilt from the checked parse tree. A refused expression returns a per-aggregate error; the rows and other aggregates still load.
  • Record-name lookups bind their keys and apply row-level security (@memberjunction/core, @memberjunction/server): key values are bound as SQL parameters, only primary-key fields are accepted as key names, and the caller’s read row filter applies. The GraphQL resolver no longer looks a name up without an acting user.
  • Dataset reads apply the caller’s permissions (@memberjunction/core, @memberjunction/generic-database-provider, @memberjunction/server): ItemFilters[].Filter values are screened like a RunView ExtraFilter, and the provider requires read permission on each item’s entity and applies the caller’s read row filter.
  • SQL data context items are gated like ad-hoc SQL (@memberjunction/server, @memberjunction/data-context-server, @memberjunction/core-entities-server): they run only for the context’s owner or an Owner-type user, only on the read-only login, and only when the SQL is a single read over entity base views the caller can read in full. A refused item fails on its own and the other items still load. Only Owner-type users can save an item that holds SQL.
  • Every client RunView clause path is screened (@memberjunction/generic-database-provider, @memberjunction/server, @memberjunction/lists, @memberjunction/core-entities-server, @memberjunction/sql-parser, @memberjunction/sql-dialect): the clause screen now also guards batched cached RunViews, ad-hoc list sources, the REST view routes, and a saved view’s stored filter and sort. A subquery over an entity the user reads under a row filter or with denied fields is refused.
  • A client filter cannot close the server’s parentheses (@memberjunction/sql-parser, @memberjunction/generic-database-provider, @memberjunction/server): a fragment such as 1=1) OR (1=1 could take row-level security out of force. Client and stored view filters must now each be one balanced expression, read with the dialect’s lexer, before they are combined with server-side filters.
  • SQL screens read quotes and comments the way the database does (@memberjunction/global, @memberjunction/core, @memberjunction/generic-database-provider, @memberjunction/server): a quote inside a bracket identifier, a comment or a PostgreSQL string could hide a stacked statement from the validators. A dialect-aware scanner, ScanSQLForScreening, now classifies the text and rejects anything it cannot read.
  • Cache-invalidation events no longer broadcast row data by default (@memberjunction/server, @memberjunction/core, @memberjunction/core-entities, @memberjunction/ng-core-entity-forms, @memberjunction/ng-dashboards): the cacheInvalidation subscription reaches every connected client, and every save attached the full row, so sessions received rows they had no right to read. recordData is now sent only for entities listed in cacheSettings.recordDataBroadcastEntities. The conversation engine and the forms that used the row re-read it as the signed-in user.
  • Row-filtered subscribers no longer receive the keys of records they cannot read (@memberjunction/server): a session whose every read of an entity is row-filtered now gets the entity, action and time on cacheInvalidation, with no key.
  • MCP sign-in pins JWT algorithms, and the integration run reader blocks path traversal (@memberjunction/ai-mcp-server, @memberjunction/integration-progress-artifacts): the Azure AD v1 verification path now uses the same algorithm allow-list as its sibling, and run IDs containing separators or .. are rejected.
  • Chat mentions are parsed in linear time (@memberjunction/conversations-runtime): two patterns in MentionParser backtracked quadratically on crafted input. An unclosed @{ earlier in a message no longer swallows a valid mention after it.
  • MJAPI no longer retains a provider on every GraphQL request (@memberjunction/core): each provider subscribed to the process-wide event bus and never unsubscribed, pinning its metadata graph. On large tenants this retained hundreds of megabytes per agent run and ended in memory-guard kills.
  • Metadata reloads no longer serialize the whole metadata graph to in-process storage (@memberjunction/core, @memberjunction/redis-provider, @memberjunction/graphql-dataprovider, @memberjunction/mobile-app, @memberjunction/testing-integration): on a server without Redis the snapshot could never be read back, yet each reload built it, and overlapping reloads exhausted the heap. Storage that does not persist across processes now skips the snapshot, and telemetry buffers are bounded.
  • Saved, ad-hoc and composed queries render, page and cap correctly (@memberjunction/sql-parser, @memberjunction/sql-dialect, @memberjunction/generic-database-provider, @memberjunction/core, @memberjunction/core-entities-server, @memberjunction/postgresql-dataprovider, @memberjunction/server, @memberjunction/integration-test-suite, @memberjunction/sqlserver-dataprovider, @memberjunction/graphql-dataprovider, @memberjunction/query-processor): a query’s own TOP, LIMIT or OFFSET … FETCH is kept alongside a caller’s MaxRows, CTEs and set operations page correctly, composition handles trailing ;, hints, template tags and colliding CTE names, and ad-hoc SQL with MaxRows pages in the database on SQL Server and PostgreSQL.
  • PostgreSQL full-text columns no longer break record loads, and shared artifacts open records (@memberjunction/sql-dialect, @memberjunction/ng-explorer-core): tsvector and tsquery were typed as GraphQL Int, so once full-text search filled the column every load of that entity failed. The standalone artifact tab now handles openEntityRecord and navigationRequest events.
  • Validation refusals reach the user as prose (@memberjunction/core, @memberjunction/server): CompleteMessage rendered every ValidationErrorInfo as a JSON blob, and update and delete failures reported “Unknown error”. Both now show the validation message.
  • A cancelled prompt stops instead of failing over (@memberjunction/ai-prompts, @memberjunction/ai-agents, @memberjunction/ai-bedrock): cancelling no longer walks every model candidate, and prompt and agent runs end Cancelled. Failover attempt counts are saved correctly, and Bedrock streaming calls no longer fail with “stream is not async iterable”.
  • Gemini and Vertex honour effortLevel, and agent responses no longer stick at “In-Progress” (@memberjunction/ai-gemini, @memberjunction/ai-agents): the thinking setting now reaches Google on every request, and thinking tokens count toward completion tokens and cost. A race between progress saves and the final update left some conversation responses showing “In-Progress” after the run finished.
  • Gemini 3.6 and later no longer receive sampling fields (@memberjunction/ai-gemini, @memberjunction/ai-vertex): temperature, topP and topK are dropped for models that reject them. Earlier models now actually receive topP and topK, and an explicit temperature: 0 is sent as 0.
  • Duplicate detection finishes on large entities and matches what vector sync stored (@memberjunction/ai-vector-dupe, @memberjunction/ai-vector-sync, @memberjunction/core, @memberjunction/ai-agents): detection held every result in memory and exhausted the heap on large entities, and checked only the first 1,000 records. Its query text now renders related-entity template data the same way sync does, and an entity document’s recordFilter.extraFilter, which nothing read before, now applies to both sync and detection.
  • The Tasks tab and task search work for users with row-level security on conversations (@memberjunction/ng-conversations): they filtered with a subquery over the conversation views, which the clause screen refuses for row-filtered users. They now read the user’s conversation and message IDs with their own RunView, and the Tasks tab rebuilds the filter on every load and refresh.
  • Realtime recordings include the agent’s voice on Gemini and other PCM-playback drivers (@memberjunction/ai-realtime-client, @memberjunction/ng-conversations): agent audio from these drivers is now mixed into the recording. If it cannot be mixed, microphone capture is kept, and recorder setup resources are released when recording stops.
  • Upgrade every MemberJunction package to 5.51.4 together, and plan the move to 6.1. There are no migrations.
  • Flush shared Redis caches on upgrade (@memberjunction/sqlserver-dataprovider): entries written while every connection shared one identity may belong to another database.
  • Auto-provisioned users now get only UI (@memberjunction/server): the newUserRoles default is ['UI']. If you relied on the default for Developer, set userHandling.newUserRoles explicitly.
  • Non-Owners can no longer administer users or roles (@memberjunction/core-entities-server): Explorer’s user- and role-management screens refuse saves for non-Owner administrators. If contextUserForNewUserCreation or contextUserForProvisioning names a non-Owner user, JWT auto-provisioning and magic-link provisioning now fail; point them at an Owner-type user before upgrading. AssignUserRolesAction fails when its context user does not hold the role being granted.
  • Link actions to Action Authorizations before upgrading (@memberjunction/server, @memberjunction/actions): non-Owner users can run an action through RunAction only when it is linked to an authorization one of their roles holds, or when the action checks the caller itself. SkipActionLog is ignored. Calculate Expression accepts arithmetic only.
  • Audit stored expressions (@memberjunction/global): SafeExpressionEvaluator now refuses in, instanceof, regex literals, and string and array methods outside its safe list (.split(), .replace(), .slice(), .substring(), .match(), .join()). No shipped metadata uses them; check field rules and flow or loop agent conditions you authored.
  • PostgreSQL deployers: set the read-only credentials (@memberjunction/postgresql-dataprovider, @memberjunction/server): ad-hoc SQL now runs on its own read-only pool and login, and MJAPI warns at startup if that login can read base tables or server files. SQL data context items and the Run Ad-hoc Query action also need the read-only login on SQL Server (dbReadOnlyUsername / dbReadOnlyPassword).
  • Client subqueries must read entity base views (@memberjunction/server, @memberjunction/generic-database-provider): a client ExtraFilter, OrderBy or OverrideExcludeFilter that subqueries a base table, a catalog, or an entity the user cannot read fails with Invalid ExtraFilter: subquery must use an entity base view…. Rewrite it against the entity’s base view. First-party clients already comply. Every table ad-hoc SQL reads must likewise be an entity base view the caller can read with no row filter.
  • Caller-supplied SQL is narrower (@memberjunction/server): ad-hoc SQL, TestQuerySQL and query specs that call the refused functions fail. A per-call timeout can shorten the server’s limit, never lengthen it, and a saved query with MaxRows alone is now capped in the database.
  • Set recordDataBroadcastEntities if you rely on recordData (@memberjunction/server): add the entities to cacheSettings.recordDataBroadcastEntities in mj.config.cjs; ['*'] restores the old behaviour. With the default, a remote save of an AutoRefresh entity triggers a full reload of that engine’s config instead of an in-place update.
  • Template runs need permission (@memberjunction/templates, @memberjunction/server): RunTemplate and Template.Run refuse scope-limited sessions and callers without Read on MJ: Templates and MJ: Template Contents, and templates that use inherited names (toString, constructor, prototype) in refused positions fail with TemplateSandboxError.
  • The other 6.1.6 screening changes apply here too (@memberjunction/generic-database-provider, @memberjunction/server): some RunView aggregate formulas are refused (arithmetic around the call, CONVERT, window clauses, PostgreSQL FILTER and EXTRACT, COUNT_BIG(*), TRY_CAST); non-custom saved views whose filter or sort reads a base table, catalog or unreadable entity fail when they run; client filters must be one balanced expression; and on PostgreSQL unquoted non-ASCII names and dollar-quoted strings in filters are refused.
  • Record names, datasets and cache reloads follow the caller’s access (@memberjunction/core, @memberjunction/server): a record outside the caller’s read row filter shows its key instead of its name, datasets read as the caller, and row-filtered users reload an entity’s whole cache on save instead of one record.
  • Review effort levels on Gemini and Vertex prompts (@memberjunction/ai-gemini): prompts that set an effort level now think at that level, which changes token use and cost.
  • Re-run CodeGen if you use PostgreSQL full-text search (@memberjunction/sql-dialect): regenerate the affected entities so tsvector columns are typed as strings.