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.
Improvements
Section titled “Improvements”- 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 callAngularAdapterService.Preload()after authentication.
Bug Fixes
Section titled “Bug Fixes”- 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. ClientExtraFilter,OrderByandOverrideExcludeFiltersubqueries, new to screening on 5.51, must read an entity base view the user can read, andExecuteAdhocQueryauthorizes every table it reads the same way.GetDatavalidates 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,TestQuerySQLand 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.findByand the view-name lookup escape client-supplied values, andfindByrejects 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 reachFunction,processand 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 withTemplateSandboxError.RunTemplateandTemplate.Runrefuse scope-limited sessions and callers without Read onMJ: TemplatesandMJ: Template Contents. SafeExpressionEvaluatorvalidates expressions against an AST allowlist (@memberjunction/global): the textual denylist could be bypassed by assembling names from strings, reaching theFunctionconstructor 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 asname == 'constructor'is accepted again.RunActionauthorizes 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 throughMJ: Action Authorizations, unless the action checks the caller itself. Scope-limited sessions are refused.Calculate Expressionevaluates with an allowlisted math parser instead ofnew Function,ConditionalusesSafeExpressionEvaluator, andRun Ad-hoc Queryapplies the same checks asExecuteAdhocQuery.- Non-Owners can no longer elevate themselves through
MJ: Users(@memberjunction/server,@memberjunction/core-entities-server,@memberjunction/ng-explorer-settings): auto-provisioned users received theDeveloperrole by default, which could update anyMJ: Usersrow, including setting one’s ownTypetoOwner. ThenewUserRolesdefault is now['UI'], and a server-side guard refuses, for any non-Owner, creating or deleting users, changingTypeorName, 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 holdingDeveloperorIntegrationcould 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): thestatusUpdatessubscription trusted the client-suppliedsessionId, so a subscriber who obtained another user’ssessionIdreceived 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 anddata:URLs. Sanitization now happens inMarkdownService.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/predictcould execute code in the sidecar. Only the globals the sidecar’s estimators need are resolved, and refusals return 400. Existing stored artifacts load unchanged. InstanceConnectionStringkeeps each connection’s identity (@memberjunction/sqlserver-dataprovider): on mssql v11 and later the getter returnedmssql://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 getsallow-same-origin, the Gmail provider strips CR and LF from header values, and Nunjucks runs withdev: false, so render errors no longer carry stack traces. - The realtime relay re-checks
CanRunon every call (@memberjunction/server,@memberjunction/ai-agents): the relay took authority from a session’sConfig, which the session owner can edit through the generic entity API. Every relayed tool call now re-checksCanRunon the target agent, and run ids inConfigare 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[].Filtervalues are screened like a RunViewExtraFilter, 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 as1=1) OR (1=1could 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): thecacheInvalidationsubscription reaches every connected client, and every save attached the full row, so sessions received rows they had no right to read.recordDatais now sent only for entities listed incacheSettings.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 oncacheInvalidation, 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 inMentionParserbacktracked 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 ownTOP,LIMITorOFFSET … FETCHis kept alongside a caller’sMaxRows, CTEs and set operations page correctly, composition handles trailing;, hints, template tags and colliding CTE names, and ad-hoc SQL withMaxRowspages 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):tsvectorandtsquerywere typed as GraphQLInt, so once full-text search filled the column every load of that entity failed. The standalone artifact tab now handlesopenEntityRecordandnavigationRequestevents. - Validation refusals reach the user as prose (
@memberjunction/core,@memberjunction/server):CompleteMessagerendered everyValidationErrorInfoas 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 endCancelled. 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,topPandtopKare dropped for models that reject them. Earlier models now actually receivetopPandtopK, and an explicittemperature: 0is 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’srecordFilter.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 Notes
Section titled “Upgrade Notes”- 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): thenewUserRolesdefault is['UI']. If you relied on the default forDeveloper, setuserHandling.newUserRolesexplicitly. - 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. IfcontextUserForNewUserCreationorcontextUserForProvisioningnames a non-Owner user, JWT auto-provisioning and magic-link provisioning now fail; point them at an Owner-type user before upgrading.AssignUserRolesActionfails 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 throughRunActiononly when it is linked to an authorization one of their roles holds, or when the action checks the caller itself.SkipActionLogis ignored. Calculate Expression accepts arithmetic only. - Audit stored expressions (
@memberjunction/global):SafeExpressionEvaluatornow refusesin,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 clientExtraFilter,OrderByorOverrideExcludeFilterthat subqueries a base table, a catalog, or an entity the user cannot read fails withInvalid 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,TestQuerySQLand 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 withMaxRowsalone is now capped in the database. - Set
recordDataBroadcastEntitiesif you rely onrecordData(@memberjunction/server): add the entities tocacheSettings.recordDataBroadcastEntitiesinmj.config.cjs;['*']restores the old behaviour. With the default, a remote save of anAutoRefreshentity triggers a full reload of that engine’s config instead of an in-place update. - Template runs need permission (
@memberjunction/templates,@memberjunction/server):RunTemplateandTemplate.Runrefuse scope-limited sessions and callers without Read onMJ: TemplatesandMJ: Template Contents, and templates that use inherited names (toString,constructor,prototype) in refused positions fail withTemplateSandboxError. - 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, PostgreSQLFILTERandEXTRACT,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 sotsvectorcolumns are typed as strings.