---
title: "Platform Updates — April 2026"
slug: "platform-update-2026-04-30"
date: "2026-04-30"
tag: "Application"
summary: "72 production image releases (1.0.0 → 1.7.2): abuse-resistant authentication and SSRF/XSS hardening across the platform, MCP tool-output sanitization, agent memory and voice/screen-share in chat, the Mentor→Agent rebrand, and a new provisioning CLI."
author: "ibl.ai Engineering"
---

Production image releases `1.0.0` → `1.7.2` (72 releases, April 2026).

| service | image / pin | version span |
|---|---|---|
| dm | `iblai-dm-pro` | `4.212.1` → `4.241.5` |
| os | `iblai-os-spa` | `0.41.2` → `0.58.0` |
| lms | `iblai-lms-spa` | `0.10.7` → `0.19.0` |
| edx | `iblai-edx-pro` | `sumac.2.12.4` → `sumac.2.15.12` |
| cli | `iblai/infra-cli` | `1.3.0` |

---

## `iblai-dm-pro` — `4.212.1` → `4.241.5`

**Abuse-resistant authentication.** The backend added per-account login throttling on `/oauth/token/` to blunt credential stuffing: failed attempts are tracked by username (case-insensitive) regardless of source IP, a successful login resets the counter, and an exhausted budget returns `429`. The rate is tunable via `ACCOUNT_LOGIN_THROTTLE_RATE` (default `5/min`).

**SSRF and injection hardening across the agent app.** `ibl-ai-mentor-app` closed several server-side request forgery and injection paths: `RetrieverTrainView` (`/documents/retriever-train/`) now runs `validate_url_for_ssrf()` before fetching, matching `/documents/train/` (CWE-918); the `type=google_drive` short-circuit that bypassed URL validation was removed so `is_url_scrapeable()` always enforces strict hostname checks for Google Drive and Dropbox; and Git clone URLs pass through a new `validate_github_token()` that rejects dangerous characters enabling URL redirection or command injection (CWE-88/918).

**Confused-deputy fix in chat sessions.** `ChatSessionView` now authorizes the URL `user_id` against the authenticated caller (CWE-862) — only the owning user or a platform admin may create a session attributed to a given username, closing a spoofing path into downstream RBAC and paywall checks.

**Bot integrations locked down.** `ibl-ai-bot-app` `1.4.0` validates all Slack and Discord webhook URLs through SSRF checks and host-allowlist enforcement, routes outbound Slack responses through `safe_post`, and hardens Slack token verification to reject empty or `None` tokens.

**MCP tool-output sanitization (zero trust).** `4.241.5` shipped an `MCPContentSanitizer` interceptor that sanitizes every MCP tool result: HTML is stripped via Django's `strip_tags()` to prevent XSS, and content is wrapped with markers so the LLM can distinguish data from instructions (prompt-injection defense). The logic lives in the new `ibl_ai_mentor/utils/mcp_security.py`.

**Provider-aware chat history.** A pluggable chat-history transformer system (`ChatHistoryTransformer` + `ChatHistoryTransformerRegistry`) normalizes messages per provider/model. A GPT-5-family transformer strips Responses-API ephemeral kwargs (`reasoning`, `tool_outputs`, `__openai_function_call_ids__`) and flattens annotated content blocks to plain text to prevent 400 "missing index" errors on replay. The graph agent's `ChatHistoryMiddleware` now packs intermediate tool-calling messages and results into the final `AIMessage`'s `additional_kwargs["__intermediate_messages__"]` and unpacks them on load, and session-detail/chat-history endpoints expose `additional_kwargs` and `tool_calls` so the frontend can render the full tool-call trace. `ibl-ai-langchain-ext` `1.6.0` resolves and applies the registered transformers on save/load based on the agent's `llm_provider`/`llm_name`.

**Unified sellable-items search.** `ibl-dm-search-app` `0.10.24` introduced a V2 endpoint `GET /api/ai-search/sellable-items/` — a single cross-model search over courses, programs, pathways, and agents. It authenticates from the request token (not query params), requires `platform_key` for tenant scoping, enforces the `Ibl.Billing/CanSellItems/action` policy (agents additionally filtered by `Ibl.Mentor/Mentors/read`), hydrates paywall status from `ItemPaywallConfig` (lowest price, active pricing, Stripe product info), and returns faceted, paginated results filterable by item type and paywall status (`none`, `enabled`, `disabled`).

**Fresh personalized data.** `ibl-dm-search-app` `0.10.23` disabled the browser `Cache-Control: max-age=300` on the personalized agents endpoint; responses now set `Cache-Control: no-store` so browsers always fetch fresh data, while the server-side Django cache is unchanged.

**Reporting pipeline (`ibl-dm-axd-app`).** The recommendation-history report is now capped at 1M rows with chunked DataFrame accumulation for bounded memory, drops the redundant `fullname` column, adds a `created_at` field to `ReportTask` to separate request time from Celery pickup time (`started` is now nullable), and gains a `retry_selected_reports` admin action for bulk retry of failed reports. Report-download views were refactored onto shared validation utilities (`validate_task_state`, `validate_column_order`, `get_download_handler`) with structured JSON error responses for invalid column requests.

**Document-embedding performance.** `ibl-ai-mentor-app` `3.203.5` fixed the document-embedding list endpoint to paginate at the queryset level before serialization (#1564) — previously the full queryset was serialized first, causing timeouts on agents with hundreds of documents.

**Deployment-facing schema changes.** DM `4.239.0` (shipped transitively via `4.240.0`) added `MentorSettings.enable_claw` and `AgentSkill.unique_id` columns; because the chat consumer queries `MentorSettings` on every request, migrations must run before the new code goes live or chat crashes with `UndefinedColumn`. `4.233.0` also required a migration for the notification app. Housekeeping removed `ibl-ai-finetuning-app` and the deprecated `export-chathistory` endpoint.

## `iblai-os-spa` — `0.41.2` → `0.58.0`

**Voice, screen share, and read-aloud in chat.** The canvas view gained screen-sharing and voice-call modals (with mobile coverage), and chat added a speaker button that reads AI messages aloud, switching to a stop icon while speaking. A loading spinner now covers cached-session message retrieval.

**Proactive and embedded messaging.** The SPA added proactive messaging driven by the postMessage API on progress events, and now passes metadata from `CONTEXT_UPDATE` iframe messages — the plumbing that lets a host page steer an embedded agent.

**Agent memory.** Memory management was built out substantially: a new agent-memory API, memory categories, per-student memory, and support for anonymous users to retain memory (user schema relaxed so full name is optional/nullable).

**RBAC and audit.** The Access tab added an analytics-viewer role (with unit and Playwright coverage), and audit logs were added for analytics and edit-agent actions, wired to real usernames with RBAC on the audit view.

**Datasets and prompts.** Dataset ingestion added a CSV resource type in the edit-agent modal and Markdown file support; the suggested-prompts feature gained delete, run, and pagination with non-admin RBAC coverage.

**Mentor → Agent rebrand.** `0.58.0` replaced "Mentor" with "Agent" across the SPA UI and polished remaining "mentorAI" brand mentions, and `0.57.0` removed the "My Mentors" trigger from the header and platform navbar (dropping the associated modal, shortcut, and navigation helpers).

**Desktop and build.** The Windows build workflow added optional NSIS EXE and MSIX installer outputs (speeded up with `rust-cache` and pre-built `cargo-binstall`); `0.47.1` then removed `electron`/`electron-builder` as the desktop shell consolidated on Tauri. A `playwright-e2e-engineer` agent was added for E2E testing.

**Security and dependencies.** SSRF protection was added to `WebCache`'s `fetch_from_network`, CodeQL alerts (SSRF, TLS, redirect, sanitization) were addressed, `permissions: {}` was locked onto all workflows, and Next.js was bumped through `15.5.15` with additional overrides for transitive vulnerabilities.

## `iblai-lms-spa` — `0.10.7` → `0.19.0`

**Agent-aware courseware.** The inner-courseware view added an agent-and-course tab mode, and switching a unit on the agent tab now sends a silent postMessage to the embedded agent iframe so the agent stays in sync with the learner's position; embedded-agent spacing was unified top and bottom.

**Course outline and content.** The course outline replaced its current-unit icon with per-unit progress checks, and a scrolling issue on the course-about page was fixed.

**Analytics reporting.** Analytics-report E2E helpers were imported from the SDK Playwright package, and an analytics-tab groups dropdown got its correct required action/RBAC.

**Tenancy and resilience.** Several tenant-switching bugs were resolved — suppressing spurious auth redirects during tenant switch and logout, removing a 100ms delay that caused double navigation, and setting the suppression flag synchronously before the first `await`. Client error boundaries (`error.tsx`, `global-error.tsx`) now redirect exceptions to `/error/500`, and courses accessed from the wrong tenant were fixed.

**Accessibility.** Icon-only buttons received aria-labels, a reusable `AccessiblePaginate` wrapper was added, "My Courses" moved to a semantic `section`, and viewport scaling and contrast were improved.

## `iblai-edx-pro` — `sumac.2.12.4` → `sumac.2.15.12`

**Cross-site scripting fixes in XBlocks and LTI.** `2.15.3` fixed a stored XSS in LTI deep-linking (`render_link.html`) where `attrs.url`/`targetName`/`windowFeatures` were interpolated into a JS-string context inside an `onclick` attribute; all three now pass through Django's `escapejs` (`lti-consumer-xblock` `7.4.2`). `2.15.2` fixed stored XSS in the Ratings XBlock, where `student_view`/`studio_view` rendered via unescaped `html_content.format(...)`; both now build an explicit context with `html.escape()` on every staff-editable field (`rating-xblock` `1.0.3`).

**Authentication and redirect hardening.** `2.15.1` fixed an OAuth-SSO open redirect by validating `state_data["next"]` through `is_safe_login_or_logout_redirect()` against `LOGIN_REDIRECT_WHITELIST`. `2.15.0` made the SSO backend fail loudly on a missing/placeholder Fernet key — the LMS now raises `ImproperlyConfigured` at startup rather than defaulting to `"BAD_FERNET_KEY"`, so operators must confirm every tenant has a generated `IBL_FERNET_KEY` before deploy. `2.13.0` replaced the `startswith("auth.")`/`startswith("login.")` magic-link Origin/Referer heuristic with a `CsrfExemptDomain` hostname allowlist, blocking account-takeover via crafted Origins like `auth.iblai.app@evil.com`.

**Brute-force and DoS throttling.** `2.15.6` swapped the magic-link verification-code generator from `random.choice` (Mersenne Twister) to `secrets.choice` (CSPRNG) and added a per-token `failed_code_attempts` counter that invalidates the token after `MAGIC_LINK_MAX_FAILED_CODE_ATTEMPTS` (default 3), preserving the 15-minute TTL. `2.15.7` wrapped the OIDC `/openid/token` view in a two-layer throttle — an outer per-IP cap (`OIDC_TOKEN_RATE`, default `200/m`) and an inner failure-only counter keyed on `(ip, client_id)` (`OIDC_TOKEN_FAILURE_RATE`, default `10/m`) — so `client_secret` brute force and DoS are mitigated without breaking corporate NAT or `refresh_token` traffic.

**Payment-webhook removal.** `2.14.0` removed the deprecated, unsigned `POST /api/ibl/stripe/checkout/webhook/` endpoint (no `Stripe-Signature` verification, which allowed forged `checkout.session.completed` events to grant free enrollment); Stripe checkout is now handled exclusively by the Data Manager, and tenants must drop `ENABLE_IBL_STRIPE_WEBHOOK_ENDPOINT` from LMS settings. `2.14.1` added defense-in-depth to the Manager proxy URL construction (netloc-equality assertion, `@` dropped from the proxy URL regex).

**Redirector, enrollment, and completion fixes.** `2.15.8` preserves the block id when `jump_to` URLs are rewritten to the SPA, redirecting deep links to `{IBL_SPA_COURSE_UNIT_URL}<course_id>&unit=<block_id>` instead of dumping every bookmark on the course-detail page. `2.15.9` fixed an `ImportError` from `ugettext` (removed in Django 4.0) that silently disabled the enrollment-API patches on Sumac — swapped to `gettext` with an AST-level regression test scanning post-Olive patch modules. A completion-API fix corrected section/subsection status that was stuck at `not_started` when a unit held completed leaves. `2.15.4` fixed a CMS `/api-docs/` 500 on Sumac by monkey-patching the upstream `ObjectTagSerializer` and disambiguating shared v0/v1 grading serializer ref names for drf_yasg.

**Diagnostics.** `2.15.12` added minimal per-user trace logging for the DM user-platform proxy flow (`/api/ibl/users/manage/platform/`), with explicit response trace fields on `ibl-edx-request-router-app` (`0.5.8`) and `ibl-edx-user-management-api` (`0.9.8`) to speed cross-log debugging without behavior change.

## `iblai/infra-cli` — `1.3.0`

**infra-cli `1.3.0`.** The standalone provisioning CLI added a `resetup` command (re-configure existing environments with a new domain and rotated secrets), a non-interactive AMI-based `launch` command for CI/CD, and `launch-env` (launch from a `.env` file with interactive confirmation). It integrates `iblai-prod-images` so ops is installed via `iblai-images[sumac]` with every image version pinned by the package rather than hardcoded tags, syncs PostgreSQL and MySQL passwords after secret rotation (capture-before-rotate), verifies all SPA launches over HTTP with retries, shows the current Ansible task description instead of just "Running," ships a `.env.example` template, and adds an ingress system for pre-provisioned domain-endpoint management with lock backends.

**Companion `ibl-cli-ops` pin (bundled through the prod-images releases).** The ops CLI advanced from `4.0.2` to `5.9.0` over the month. Highlights: an ECR image-mirroring command (`ibl ecr list` / `ibl ecr sync` with `--dry-run`) plus a switch of all third-party Docker images (edx, reverse-proxy, dm, prometheus stacks) to IBL's private ECR with previously untagged images pinned. `5.0.x` purged leaked secrets from the entire git history and resolved code-scanning and Dependabot alerts (`cryptography`, `pyopenssl`, `urllib3`, etc.), and `5.0.0` made `--node-id` required on the memory-monitor commands. Later releases added DM resource tuning (ASGI backpressure via `ASGI_BACKLOG`, `--preload`, worker recycling, celery autoscale caps, and edX LMS/CMS `--concurrency` limits), Langfuse Sentry defaults, `CORS_*` defaults for `ibl-dm-pro` under `IBL_DM`, EDX Redis-TLS support (`ENABLE_REDIS_TLS`, `REDIS_PASSWORD`, `rediss://` rewrite), tutor-plugin exposure of the new magic-link and OIDC throttle settings, and a fix for a parallel-update race where concurrent `ibl dm update`/`ibl dm up` runs could persist `ENABLE_HTTPS: false` and corrupt the request-router plugin. A new `hq` image was introduced at `1.6.8` and advanced to `1.0.109`.
