Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 26 additions & 17 deletions blog/sections/Template.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { BlogPost } from "../types.ts";
import { CSS } from "../static/css.ts";
import { renderSection } from "../../website/pages/Page.tsx";
import { AppContext } from "../mod.ts";

export interface Props {
Expand Down Expand Up @@ -42,27 +41,37 @@ export default function Template(
<link href="/styles.css" rel="stylesheet" />
<style dangerouslySetInnerHTML={{ __html: CSS }} />
<div class="deco-post-preview">
<h1>{title}</h1>
<p class="text-xl">{excerpt}</p>
<p>
{date
? new Date(`${date}T00:00:00`).toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})
: ""}
</p>
<h1 class="text-4xl font-bold mb-4">{title}</h1>
<p class="text-xl mb-4 italic">{excerpt}</p>

{image && (
<img
class="w-full rounded-2xl bg-cover"
class="w-full rounded-2xl mb-8"
src={image}
alt={alt ?? title}
/>
)}
<div dangerouslySetInnerHTML={{ __html: content as string }} />
<div class="content-sections">
{sections?.map(renderSection)}

<div
class="prose max-w-none mb-10"
dangerouslySetInnerHTML={{ __html: content as string }}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether HTML content is sanitized before render.

# 1) Find all raw HTML injection points.
rg -n -C3 'dangerouslySetInnerHTML' --type=ts --type=tsx

# 2) Find sanitization utilities/usages in the blog rendering pipeline.
rg -n -C3 'sanitize|sanitiz|DOMPurify|xss|rehype-sanitize|sanitize-html' --type=ts --type=tsx

# 3) Trace where blog post content is loaded/transformed before Template.
rg -n -C3 '\bcontent\b' blog --type=ts --type=tsx

Repository: deco-cx/apps

Length of output: 140


🏁 Script executed:

#!/bin/bash
# 1) Find all raw HTML injection points in blog with correct file pattern
echo "=== Searching for dangerouslySetInnerHTML ==="
rg -n -C3 'dangerouslySetInnerHTML' -g '*.tsx' -g '*.ts' blog/

# 2) Find sanitization utilities in the entire blog directory
echo "=== Searching for sanitization patterns ==="
rg -n 'sanitize|DOMPurify|xss|rehype-sanitize|sanitize-html' -g '*.tsx' -g '*.ts' blog/

# 3) Search for imports/requires in Template.tsx specifically
echo "=== Template.tsx imports ==="
head -50 blog/sections/Template.tsx

Repository: deco-cx/apps

Length of output: 2246


🏁 Script executed:

#!/bin/bash
# Find the loader function and BlogPost type definition
echo "=== BlogPost type definition ==="
cat blog/types.ts

echo ""
echo "=== Searching for loader function in Template.tsx ==="
rg -n 'loader|export' blog/sections/Template.tsx

echo ""
echo "=== Looking for loader file in blog directory ==="
find blog -name '*loader*' -o -name '*load*' 2>/dev/null | head -20

echo ""
echo "=== Check blog directory structure ==="
ls -la blog/

Repository: deco-cx/apps

Length of output: 6561


🏁 Script executed:

#!/bin/bash
# Read the loader function from Template.tsx
echo "=== Loader function from Template.tsx ==="
sed -n '81,130p' blog/sections/Template.tsx

echo ""
echo "=== Check loaders directory ==="
ls -la blog/loaders/

echo ""
echo "=== Search for rich-text or content processing ==="
rg -n 'rich-text|content.*sanitize|sanitize.*content' -g '*.ts' -g '*.tsx' blog/

Repository: deco-cx/apps

Length of output: 1108


🏁 Script executed:

#!/bin/bash
# Check mod.ts and other framework setup
echo "=== mod.ts (framework setup) ==="
cat blog/mod.ts

echo ""
echo "=== Search for other rich-text format usages ==="
rg -n '@format rich-text' -g '*.ts' -g '*.tsx' 

echo ""
echo "=== Check if Deco has sanitization in dependencies ==="
find . -name 'deno.json' -o -name 'deno.lock' -o -name 'package.json' 2>/dev/null | head -5

Repository: deco-cx/apps

Length of output: 1283


🏁 Script executed:

#!/bin/bash
# Check deno.json for `@deco/deco` and any sanitization dependencies
echo "=== deno.json (first 100 lines) ==="
head -100 deno.json

echo ""
echo "=== admin/widgets.ts rich-text context ==="
rg -n -B5 -A5 '@format rich-text' admin/widgets.ts

Repository: deco-cx/apps

Length of output: 3139


🏁 Script executed:

#!/bin/bash
# Final verification: search for any comments about content safety
echo "=== Search for content safety comments/docs ==="
rg -n -i 'sanitize|xss|danger|safe|trust|content.*html' -g '*.ts' -g '*.tsx' blog/ | head -30

echo ""
echo "=== Check if similar patterns exist elsewhere ==="
rg -n 'dangerouslySetInnerHTML' -g '*.tsx' -g '*.ts' . --max-count=10

Repository: deco-cx/apps

Length of output: 4359


🏁 Script executed:

#!/bin/bash
# Critical: Found that spire app DOES sanitize. Let's find the sanitizeHtml function
echo "=== Finding sanitizeHtml implementation ==="
rg -n 'function sanitizeHtml|export.*sanitizeHtml|const sanitizeHtml' -g '*.ts' -g '*.tsx'

echo ""
echo "=== Check spire app's sanitization imports ==="
head -30 spire/sections/blocks/Paragraph.tsx

Repository: deco-cx/apps

Length of output: 779


🏁 Script executed:

#!/bin/bash
# Check the sanitizeHtml implementation
echo "=== sanitizeHtml implementation ==="
cat spire/utils/sanitizeHtml.ts

echo ""
echo "=== Check if blog app imports or uses sanitizeHtml ==="
rg -n 'sanitizeHtml' blog/

Repository: deco-cx/apps

Length of output: 1468


Sanitize content before dangerouslySetInnerHTML (Line 57).

Rendering post.content directly here is an XSS risk. The content comes from the CMS (@format rich-text) and is injected without sanitization. A sanitizeHtml utility already exists in this codebase at spire/utils/sanitizeHtml.ts that can be reused to strip XSS vectors (scripts, event handlers, dangerous protocols).

🧰 Tools
🪛 ast-grep (0.42.2)

[warning] 56-56: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@blog/sections/Template.tsx` around lines 55 - 58, The JSX currently injects
raw HTML via dangerouslySetInnerHTML using the content variable in Template.tsx;
import the existing sanitizeHtml utility from spire/utils/sanitizeHtml.ts and
run content through it before passing into dangerouslySetInnerHTML (e.g., const
safeHtml = sanitizeHtml(content as string)), then use safeHtml in the __html
field to strip scripts, event handlers and dangerous protocols and eliminate the
XSS risk.


<div class="content-sections flex flex-col gap-8">
{sections?.map((section, index) => {
if (!section || !section.Component) {
console.warn(`Section na posição ${index} está indefinida.`);
return null;
}

const { Component, props } = section;

return (
<div key={index} class="section-wrapper">
<Component {...props} />
</div>
);
})}
</div>
</div>
</>
Expand All @@ -74,4 +83,4 @@ export const loader = (props: Props, _req: Request, ctx: AppContext) => {
...props,
pageSlug: ctx.pageSlug,
};
};
};
22 changes: 19 additions & 3 deletions blog/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ImageWidget } from "../admin/widgets.ts";
import { PageInfo, Person, Thing } from "../commerce/types.ts";
import { type Section } from "@deco/deco/blocks";
import { Product } from "../commerce/types.ts";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether relatedProducts is already modeled/consumed elsewhere.
rg -n -C3 --type=ts '\brelatedProducts\b'
rg -n -C3 --type=ts '\binterface\s+BlogPost\b'
rg -n -C3 --type=ts '`@options` .*Product|ProductList|Catalog'

Repository: deco-cx/apps

Length of output: 50370


🏁 Script executed:

cat -n blog/types.ts

Repository: deco-cx/apps

Length of output: 7062


Add the missing relatedProducts field to complete the post contract.

Line 4 imports Product, but the BlogPost interface only includes relatedPosts and lacks a relatedProducts field. This type gap can break editor schema integration for related products functionality.

Proposed change
 export interface BlogPost {
   relatedPosts?: BlogPost[];
+  /**
+   * `@title` Produtos Relacionados
+   * `@description` Selecione produtos para exibir no post
+   * `@format` dynamic-options
+   * `@options` <use the same product loader path used by this PR>
+   */
+  relatedProducts?: Product[];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@blog/types.ts` at line 4, The BlogPost type is missing the relatedProducts
field though Product is imported; update the BlogPost interface to include a
relatedProducts property (e.g., relatedProducts?: Product[] or relatedProducts:
Product[] as appropriate for optionality) alongside the existing relatedPosts to
satisfy the post contract and editor schema; locate the BlogPost interface and
add the relatedProducts field using the imported Product type.


/**
* @titleBy name
Expand All @@ -17,8 +18,18 @@ export interface Author {
export interface Category {
name: string;
slug: string;
/**
Comment thread
pedrophzp marked this conversation as resolved.
* @title Categoria Pai
* @description Selecione se esta categoria for uma subcategoria de outra
* @format dynamic-options
* @options apps/blog/loaders/CategoryList.ts
*/
parent?: Category;
}

/**
* @titleBy title
*/
export interface BlogPost {
title: string;
excerpt: string;
Expand Down Expand Up @@ -52,13 +63,18 @@ export interface BlogPost {
content?: string;
/**
* @title Sections
* @label hidden
* @changeable true
* @description Adicione seções de conteúdo (PostTitle, PostText, PostImage)
*/
sections?: Section[];
/**
* @title Posts Relacionados
* @description Busque posts já cadastrados pelo título
* @format dynamic-options
* @options apps/blog/loaders/BlogPostList.ts
*/
relatedPosts?: BlogPost[];
/**
* @title Carousel in post content
* @description add a carousel in the middle of the post. Must be implemented in frontEnd
*/
imageCarousel?: ImageCarousel;
/**
Expand Down