Skip to content

bug: fix stored XSS in todo content (marked 4.3.0 + sanitize-html) - #1109

Open
devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/sfind-2de74fd9-marked-sanitize-html
Open

devin-ai-integration[bot] wants to merge 4 commits into
mainfrom
devin/sfind-2de74fd9-marked-sanitize-html

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Resumo

Corrige o XSS armazenado via conteúdo do todo (finding sfind-2de74fd9447e4e70a2fafe668d3c5c44, médio). views/index.ejs imprime marked(todo.content) com <%- %>, e o sanitize: true do marked 0.3.5 é contornável: [x](javascript&#58this;alert(1&#41;) passa pelo unescape() do marked (vira javascriptalert(1)), mas o navegador decodifica &#58 como : e o link fica javascript:this;alert(1).

Mudança (só app.js + dependências; o template continua igual):

// antes
marked.setOptions({ sanitize: true });
app.locals.marked = marked;

// depois
app.locals.marked = src => sanitizeHtml(marked.parse(String(src)), {
  allowedTags: defaults + ['img', 'del', 'input'],
  allowedAttributes: { a: ['href','title'], img: ['src','alt','title'], input: ['type','checked','disabled'] },
  transformTags: { input: sempre vira <input type="checkbox" disabled [checked]> },
  allowedSchemes: ['http','https','mailto'], allowedSchemesByTag: { img: ['http','https'] },
  exclusiveFilter: img sem src http(s) absoluto é removido,
  allowProtocolRelative: false,
});
  • marked 0.3.5 → 4.3.0 (const { marked } = require('marked')), sanitize-html 2.17.1 novo; package-lock.json atualizado.
  • HTML cru do markdown (<script>, <img onerror>, <a href="javascript:">) agora é removido pelo sanitizer, em vez de virar texto escapado como no sanitize: true. Markdown normal (negrito, itálico, ~~tachado~~, task list - [x], links http(s), imagens http(s)) continua renderizando; qualquer <input> cru vira checkbox desabilitado sem outros atributos.
  • Nenhuma mudança de contrato de API (/create e a renderização de / iguais).

Duplicidade: é o mesmo diff da branch devin/1793687177-fix-marked-xss (3 commits cherry-picked) + 1 commit para preservar <del> e checkboxes de task list (apontado pelo Devin Review) e se sobrepõe ao upgrade do marked em #1077 / #1088. Aberto como PR novo porque é um ticket novo; só um deve ser mergeado.

Como testar

Rodei o app real (node app.js, com um stub em memória do Mongoose, porque o mongod 7 local não fala com o mongoose 4.2.4), postei 13 payloads via POST /create, peguei o HTML de GET / e cliquei em cada link no Chrome (Playwright via CDP), registrando todo alert().

main (marked 0.3.5) este PR
[x](javascript&#58this;alert('J'&#41;) href="javascript:this;alert('J')", o clique dispara alert("J") href="javascript&#58this;..." (URL relativa, sem esquema javascript:), sem alert
[x](javascript&#58;alert(1)) (payload do finding) link removido pelo marked <a>x</a> sem href
[x](data:text/html;base64,...) href="data:..." <a>x</a>
<img src=x onerror=...>, <script> texto escapado removido
**bold** [safe](https://example.com) ![img](https://...) renderiza renderiza igual
total de diálogos 1 0

Para reproduzir: suba o app, poste [x](javascript&#58this;alert(1&#41;) em /create, abra / e clique no link: no main aparece o alert; neste PR o href não tem esquema javascript:.

depois

Devin-Org: engineering

Link to Devin session: https://devin-gtm.devinenterprise.com/sessions/ea0ed0b9b4c9421fa387909a7ccbdf8b
Open in Devin Desktop: https://devin-gtm.devinenterprise.com/desktop/session/ea0ed0b9b4c9421fa387909a7ccbdf8b?variant=devin


Devin Review

Status Commit
⚪ Not started —

Run Devin Review

Enable auto-review to review your pull requests automatically.

Devin Review (Staging)


Devin Review

Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

Refs: sfind-2de74fd9447e4e70a2fafe668d3c5c44
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

Refs: sfind-2de74fd9447e4e70a2fafe668d3c5c44
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

Refs: sfind-2de74fd9447e4e70a2fafe668d3c5c44
@devin-ai-integration

Copy link
Copy Markdown
Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@socket-security

socket-security Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedmarked@​0.3.5 ⏵ 4.3.0100100 +31100 +198 +280 -19
Addedsanitize-html@​2.17.11009010093100

View full report

devin-ai-integration[bot]

This comment was marked as resolved.

Allow <del>, and <input> rewritten to a disabled checkbox (only type/checked/disabled survive), so GFM ~~text~~ and - [x] items render after sanitize-html.

Refs: sfind-2de74fd9447e4e70a2fafe668d3c5c44
Signed-off-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants