@softize/opus 24.0.1 → 24.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
|
|
|
7
7
|
`opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
|
|
8
8
|
mudança cobra do seu código.
|
|
9
9
|
|
|
10
|
+
## 24.1.0 — 2026-10-02
|
|
11
|
+
|
|
12
|
+
O `Markdown` deixa de carregar imagem com endereço absoluto (`https:`, `http:` ou `//host`). No
|
|
13
|
+
lugar dela fica o texto alternativo, num `span` com `data-slot="markdown-external-image"`, sem link.
|
|
14
|
+
O conteúdo vem de modelo, e um `<img>` é buscado pelo navegador sem clique: um modelo induzido por
|
|
15
|
+
injeção de prompt poderia levar dados da conversa no endereço da imagem (ADR 0050). Imagem com
|
|
16
|
+
endereço relativo e as imagens `data:` que o markdown-it já aceitava continuam renderizando. A
|
|
17
|
+
regra vale para a instância compartilhada, então chat, painéis e documentação mudam juntos. Quem
|
|
18
|
+
exibia imagem absoluta da própria origem passa a usar o endereço relativo.
|
|
19
|
+
|
|
10
20
|
## 24.0.1 — 2026-10-01
|
|
11
21
|
|
|
12
22
|
Em `DialogHeader` e `DrawerHeader` com navegação e título, os dois passam a formar um grupo com
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ADR 0050 — Markdown não carrega imagem externa
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-10-02.
|
|
5
|
+
- **Complementa:** a decisão registrada em `markdown.tsx` de renderizar com `html: false`, porque o
|
|
6
|
+
conteúdo vem de pessoas e de modelos.
|
|
7
|
+
|
|
8
|
+
## Contexto
|
|
9
|
+
|
|
10
|
+
O `Markdown` é o renderer único do pacote: o chat, os painéis e a documentação usam a mesma
|
|
11
|
+
instância do markdown-it. Ele já escapa HTML do fonte e o markdown-it já recusa links
|
|
12
|
+
`javascript:` e `data:text/html`. Faltava uma terceira via: `` virava um
|
|
13
|
+
`<img>` que o navegador busca sozinho, sem nenhum clique.
|
|
14
|
+
|
|
15
|
+
Isso importa porque boa parte desse conteúdo é escrita por um modelo. Um modelo induzido por
|
|
16
|
+
injeção de prompt (um documento, uma resposta de ferramenta, uma mensagem de terceiro) pode montar
|
|
17
|
+
o endereço de uma imagem com dados da conversa e deixar que o navegador de quem lê os entregue ao
|
|
18
|
+
host. Num relatório salvo, isso acontece também com quem abre o relatório depois.
|
|
19
|
+
|
|
20
|
+
Nenhum consumidor conhecido depende de imagem externa no Markdown: a documentação do pacote não usa
|
|
21
|
+
imagem em Markdown, e a plataforma Grand Brasil não tem imagem externa em documentos, Base de
|
|
22
|
+
conhecimento nem relatórios.
|
|
23
|
+
|
|
24
|
+
## Decisão
|
|
25
|
+
|
|
26
|
+
1. O `Markdown` não renderiza `<img>` para endereço absoluto, com esquema (`https:`, `http:`) ou
|
|
27
|
+
relativo ao protocolo (`//host`). No lugar da imagem fica o texto alternativo dela, num
|
|
28
|
+
`span` com `data-slot="markdown-external-image"`, sem link: seguir o endereço exigiria um
|
|
29
|
+
clique, mas um link montado pelo modelo não deve ser oferecido como se fosse da imagem.
|
|
30
|
+
2. Endereço relativo (`/caminho`, `./arquivo.png`) continua renderizando a imagem, porque é servido
|
|
31
|
+
pela própria aplicação. As imagens em `data:` que o markdown-it já aceita (`png`, `gif`, `jpeg`,
|
|
32
|
+
`webp`) também continuam: não fazem requisição.
|
|
33
|
+
3. A regra vale para a instância compartilhada `markdownIt`, então chat, painéis e documentação
|
|
34
|
+
seguem o mesmo comportamento. Não há opção para liberar imagem externa; um consumidor que
|
|
35
|
+
precise disso traz o caso concreto para uma decisão nova.
|
|
36
|
+
|
|
37
|
+
## Consequências
|
|
38
|
+
|
|
39
|
+
- Uma resposta ou um relatório com imagem externa mostra o texto alternativo no lugar da imagem.
|
|
40
|
+
- A defesa não depende de cada aplicação configurar uma Content Security Policy com `img-src`,
|
|
41
|
+
embora uma CSP continue sendo uma segunda camada recomendada.
|
|
42
|
+
- Imagem absoluta da própria origem também deixa de carregar. Não há uso conhecido; se aparecer,
|
|
43
|
+
o caminho é usar o endereço relativo.
|
|
44
|
+
- A imagem relativa continua fazendo uma requisição à própria origem, com os cookies de quem lê. Se
|
|
45
|
+
a aplicação tiver um redirecionamento aberto ou um proxy de imagem, o mesmo vetor volta por ali.
|
|
46
|
+
É mais um motivo para manter a CSP com `img-src` como segunda camada.
|
|
47
|
+
|
|
48
|
+
## Alternativas consideradas
|
|
49
|
+
|
|
50
|
+
- **Bloquear toda imagem.** Fecharia também o resíduo da imagem relativa, mas tiraria imagens que a
|
|
51
|
+
própria aplicação serve (anexos, ilustrações de documentação) sem um vetor conhecido que o
|
|
52
|
+
justifique hoje.
|
|
53
|
+
- **Transformar a imagem externa em link.** Mantém o endereço acessível, mas oferece com aparência
|
|
54
|
+
legítima um link montado pelo modelo; o texto alternativo basta para a leitura.
|
|
55
|
+
- **Lista de origens permitidas ou proxy de imagem.** Exigiriam configuração por aplicação e um
|
|
56
|
+
serviço novo para um uso que não existe. Ficam para quando um consumidor trouxer o caso.
|
|
57
|
+
- **Só CSP na aplicação.** Depende de cada consumidor configurar e não vale fora do navegador da
|
|
58
|
+
aplicação (por exemplo, na documentação publicada). Fica como segunda camada, não como a defesa.
|
|
59
|
+
|
|
60
|
+
## Verificação
|
|
61
|
+
|
|
62
|
+
- Teste do `Markdown`: imagem `https:`, `//host`, com maiúsculas, com esquema em entidade, por
|
|
63
|
+
referência, dentro de link e em célula de tabela não geram `<img>` e mostram o texto alternativo;
|
|
64
|
+
imagem relativa e `data:image/png` continuam gerando `<img>`.
|
package/package.json
CHANGED
|
@@ -17,6 +17,23 @@ import MarkdownIt from 'markdown-it'
|
|
|
17
17
|
* opções próprias divergiriam no primeiro edge case, e "mesmo motor" viraria promessa. */
|
|
18
18
|
export const markdownIt = new MarkdownIt({ html: false, linkify: true })
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Imagem com endereço absoluto não carrega (ADR 0050). O conteúdo vem de modelo, e um `<img>` é
|
|
22
|
+
* buscado pelo navegador sem clique: um modelo induzido por injeção de prompt poderia levar dados
|
|
23
|
+
* da conversa no endereço. Fica o texto alternativo; imagem relativa e `data:` seguem normais.
|
|
24
|
+
*/
|
|
25
|
+
const EXTERNAL_IMAGE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i
|
|
26
|
+
const renderImage = markdownIt.renderer.rules.image
|
|
27
|
+
markdownIt.renderer.rules.image = (tokens, idx, options, env, self) => {
|
|
28
|
+
const token = tokens[idx]!
|
|
29
|
+
const src = token.attrGet('src') ?? ''
|
|
30
|
+
if (EXTERNAL_IMAGE.test(src) && !src.toLowerCase().startsWith('data:')) {
|
|
31
|
+
const alt = self.renderInlineAsText(token.children ?? [], options, env)
|
|
32
|
+
return `<span data-slot="markdown-external-image">${markdownIt.utils.escapeHtml(alt)}</span>`
|
|
33
|
+
}
|
|
34
|
+
return renderImage!(tokens, idx, options, env, self)
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
export interface MarkdownProps {
|
|
21
38
|
content: string
|
|
22
39
|
className?: string
|