docs(ai): spec for #37153 escape dotAI viewtool output by default - #37533
Open
hassandotcms wants to merge 1 commit into
Open
hassandotcms wants to merge 1 commit into
hassandotcms wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spec only (PR 1 of 2) for #37153. Part of epic #37255 (dotAI Security Hardening). Full issue detail is in
dotCMS/private-issues#675.Problem
The
$aiVelocity viewtool hands the AI provider's reply to templates exactly as returned. Nothing escapes it, and dotCMS Velocity does not auto-escape references. A template that prints the model's answer ($summary.openAiResponse.choices.get(0).message.content, as documented) renders whatever HTML the model emitted. A model steered by indexed content or a crafted prompt can emit<img src=x onerror=...>, and the script runs for every visitor of that page. The unsafe path is the default.Proposed fix
Escape by default, by source. Every call through
$aireturns a copy of the payload in which exactly four things are HTML-escaped with the OWASP encoder:raw,generateText,generateImage; theopenAiResponsesubtree ofsummarize);query;originalPrompt;dotCMSResults[].matches[].extractedText, because the embeddings API stores every visitor's query text in an index namedcache, and a template can search that index.Nothing else changes. Contentlet fields in search results, including
title, are returned as today, the same as$dotcontentgives them. Payload shape and type are unchanged, so existing property paths keep resolving.One opt-in:
$ai.unsafe. Same tool, escaping off, exactly today's behavior for every method. No per-method*Unescapedsiblings, no config flag.Not in scope
REST endpoints (
/api/v1/ai/*) are untouched. No sanitizing, no config kill-switch, no changes to what the model is asked (#37152), retrieval permissions (#37151), rate limiting (#37155) or stack traces (#37154, same classes, no dependency either way).Impact
Template contract change on the success path only. Model output that contains markup renders as text until the author switches to
$ai.unsafe. Templates that already escape the output themselves will double-encode. A release note for template authors is a deliverable of PR 2. No API, schema or data changes. Rollback-safe.Please review
specs/37153-ai-viewtool-escape-output/spec.md. The payload-shapes block near the top shows every field the spec refers to. On approval, planning and implementation follow in PR 2.This PR fixes: #37153