Skip to content

blog: support tags - #47

Merged
gapry merged 2 commits into
mainfrom
tags
Mar 28, 2026
Merged

gapry merged 2 commits into
mainfrom
tags

Conversation

@gapry

@gapry gapry commented Mar 28, 2026

Copy link
Copy Markdown
Owner

No description provided.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

This PR adds tags functionality to the blog, including tag extraction from markdown frontmatter, tag cloud page, and tag filtering. The implementation is mostly well-structured, but there are critical security and logic issues that must be fixed before merging.

Critical Issues (Must Fix):

  1. Security vulnerability: Path traversal risk in tag directory creation - unsanitized tag names from markdown are used in file paths
  2. Logic error: Missing bounds check when accessing parts[1] in tag routing will cause runtime crashes

Required Changes:

  • Sanitize tag names before using them in file paths
  • Add proper validation for tag route parameters
  • Remove debug console.log statements
  • Remove unused variables

Please address these issues before merging.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread src/App.jsx Outdated
Comment on lines +29 to +30
console.log("Current Path Parts:", window.location.pathname.split('/').filter(Boolean));
console.log("All Posts Data:", allPosts)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remove debug console.log statements before merging to production. These expose internal application state and routing information in browser console.

Suggested change
console.log("Current Path Parts:", window.location.pathname.split('/').filter(Boolean));
console.log("All Posts Data:", allPosts)
const params = new URLSearchParams(window.location.search);

Comment thread post.config.js
Comment on lines +85 to +98
allTags.forEach(tag => {
const tagDir = path.join(distDir, 'tag', tag);

if (!fs.existsSync(tagDir)) {
fs.mkdirSync(tagDir, {
recursive: true
});
}

fs.copyFileSync(
path.join(distDir, 'index.html'),
path.join(tagDir, 'index.html')
);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 Security Vulnerability: Tag names from markdown files are used directly in file path construction without sanitization, creating a path traversal vulnerability. An attacker could craft markdown with malicious tags like ../../etc/passwd to write files outside the intended directory.1

Suggested change
allTags.forEach(tag => {
const tagDir = path.join(distDir, 'tag', tag);
if (!fs.existsSync(tagDir)) {
fs.mkdirSync(tagDir, {
recursive: true
});
}
fs.copyFileSync(
path.join(distDir, 'index.html'),
path.join(tagDir, 'index.html')
);
});
allTags.forEach(tag => {
const sanitizedTag = tag.replace(/[^a-zA-Z0-9-_]/g, '-');
const tagDir = path.join(distDir, 'tag', sanitizedTag);
if (!fs.existsSync(tagDir)) {
fs.mkdirSync(tagDir, {
recursive: true
});
}
fs.copyFileSync(
path.join(distDir, 'index.html'),
path.join(tagDir, 'index.html')
);
});

Footnotes

  1. CWE-22: Path Traversal - https://cwe.mitre.org/data/definitions/22.html ↩

Comment thread src/pages/Home/Home.jsx Outdated
Comment on lines +9 to +10
const isTagPage = window.location.pathname.includes('/tag/');
const currentTag = isTagPage ? decodeURIComponent(window.location.pathname.split('/').filter(Boolean).pop()) : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remove unused variables isTagPage and currentTag. These are declared but never referenced in the component.

Suggested change
const isTagPage = window.location.pathname.includes('/tag/');
const currentTag = isTagPage ? decodeURIComponent(window.location.pathname.split('/').filter(Boolean).pop()) : null;
useEffect(() => {

Comment thread src/App.jsx
Comment on lines +49 to +57
if (parts[0] === 'tag' && parts[1]) {
const tagName = decodeURIComponent(parts[1]);
const filtered = allPosts.filter(p => p.tags && p.tags.includes(tagName));

document.title = `Tag: ${tagName} | ${siteConfig.siteName}`;
setPosts(filtered);
setStatus('home');
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 Logic Error: Missing bounds check before accessing parts[1] will cause runtime error when URL is /tag/ without a tag name. This crashes the routing logic.

Suggested change
if (parts[0] === 'tag' && parts[1]) {
const tagName = decodeURIComponent(parts[1]);
const filtered = allPosts.filter(p => p.tags && p.tags.includes(tagName));
document.title = `Tag: ${tagName} | ${siteConfig.siteName}`;
setPosts(filtered);
setStatus('home');
return;
}
if (parts.length === 2 && parts[0] === 'tag') {
const tagName = decodeURIComponent(parts[1]);
const filtered = allPosts.filter(p => p.tags && p.tags.includes(tagName));
document.title = `Tag: ${tagName} | ${siteConfig.siteName}`;
setPosts(filtered);
setStatus('home');
return;
}

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements a tagging system for the blog, adding tag extraction to the build process and creating a new Tags page and tag-filtered views. The UI now displays tag pills in posts and links in the header and home feed. Feedback focuses on code cleanliness, specifically removing duplicate CSS rules, debug logs, and unused variables or props, as well as improving CSS specificity by avoiding the use of !important.

Comment thread src/pages/Home/Home.css Outdated
Comment on lines 99 to 104
.site-header {
width: 100%;
border-bottom: 1px solid #333;
padding: 1.5rem 0 0.5rem 0;
margin-bottom: 0;
} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There are duplicate styles for the .site-header class here and in src/components/Header/Header.css. This can lead to unpredictable styling depending on CSS import order. Since .site-header is part of the Header component, its styles should be defined only in src/components/Header/Header.css. Please remove these rules from this file to avoid conflicts and keep the styling organized.

Comment thread src/App.jsx Outdated
Comment on lines +29 to +31
console.log("Current Path Parts:", window.location.pathname.split('/').filter(Boolean));
console.log("All Posts Data:", allPosts)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

These console.log statements appear to be for debugging purposes. They should be removed before merging to keep the console clean in production.

Comment thread src/App.jsx Outdated
<Analytics />
<div className="app-shell">
<Header />
<Header allPosts={posts} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The Header component is being passed the allPosts prop, but it doesn't seem to be using it. If this prop is not needed, it should be removed to avoid passing unnecessary data and to keep the component's API clean.

Suggested change
<Header allPosts={posts} />
<Header />

.markdown-body > h1,
.markdown-body > h2,
.markdown-body > p {
margin-top: 0.8rem !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using !important should be avoided as it can make CSS difficult to maintain and debug by breaking the natural cascade. It's better to increase selector specificity or reorder rules if you need to override styles.

Suggested change
margin-top: 0.8rem !important;
margin-top: 0.8rem;

Comment thread src/pages/Home/Home.jsx Outdated
Comment on lines +9 to +10
const isTagPage = window.location.pathname.includes('/tag/');
const currentTag = isTagPage ? decodeURIComponent(window.location.pathname.split('/').filter(Boolean).pop()) : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The isTagPage and currentTag variables are declared but never used within the component. This appears to be dead code and should be removed to improve code clarity.

@gapry

gapry commented Mar 28, 2026

Copy link
Copy Markdown
Owner Author

/q review

@gapry

gapry commented Mar 28, 2026

Copy link
Copy Markdown
Owner Author

/gemini review

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review Summary

This PR adds tag support to the blog, implementing tag extraction from markdown files, tag cloud page, and tag filtering. However, there are critical security vulnerabilities that must be fixed before merge.

Critical Issues (Must Fix)

Security Vulnerabilities:

  1. Path Traversal in post.config.js - Tag names from markdown files are used directly in filesystem paths without sanitization, enabling directory traversal attacks
  2. URL Injection in Tags.jsx and Home.jsx - Tag names embedded in URLs without encoding can create malformed URLs or enable injection attacks

Crash Risk:
3. Array Bounds Check in App.jsx - Missing validation on parts[0] when array may be empty causes runtime error

Required Actions

All four comments contain commit-ready fixes that address these blocking issues. Please apply these security fixes before merging.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Comment thread src/App.jsx
return;
}

if (parts[0] === 'tag' && parts[1]) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 Crash Risk: Missing bounds check on parts[0] when parts array is empty. Accessing parts[0] without verifying array length causes runtime error when URL path is malformed or empty after filtering.

Suggested change
if (parts[0] === 'tag' && parts[1]) {
if (parts.length >= 2 && parts[0] === 'tag' && parts[1]) {

Comment thread post.config.js
const allTags = [...new Set(allPosts.flatMap(p => p.tags))];

allTags.forEach(tag => {
const tagDir = path.join(distDir, 'tag', tag);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 Security Vulnerability: Tag names are used directly in filesystem paths without sanitization, enabling path traversal attacks. A malicious markdown file with tags: ../../malicious could write files outside the intended directory structure, potentially overwriting critical system files or injecting malicious content.

Suggested change
const tagDir = path.join(distDir, 'tag', tag);
const sanitizedTag = tag.replace(/[^a-zA-Z0-9_-]/g, '-');
const tagDir = path.join(distDir, 'tag', sanitizedTag);

Comment thread src/pages/Tags/Tags.jsx
<div className="tags-page">
<div className="tags-cloud-wrapper">
{tagStats.map(([tag, count]) => (
<a key={tag} href={`/tag/${tag}`} className="tag-cloud-item">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 Security Vulnerability: Tag names are embedded in URLs without encoding. Tags containing special characters (spaces, slashes, etc.) will create malformed URLs or enable injection attacks. Use encodeURIComponent(tag) to properly escape special characters in the URL.

Suggested change
<a key={tag} href={`/tag/${tag}`} className="tag-cloud-item">
<a key={tag} href={`/tag/${encodeURIComponent(tag)}`} className="tag-cloud-item">

Comment thread src/pages/Home/Home.jsx
{post.tags && post.tags.length > 0 && (
<div className="post-tags">
{post.tags.map(tag => (
<a key={tag} href={`/tag/${tag}`} className="tag-link">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 Security Vulnerability: Tag names are embedded in URLs without encoding. Tags containing special characters (spaces, slashes, etc.) will create malformed URLs or enable injection attacks. Use encodeURIComponent(tag) to properly escape special characters in the URL.

Suggested change
<a key={tag} href={`/tag/${tag}`} className="tag-link">
<a key={tag} href={`/tag/${encodeURIComponent(tag)}`} className="tag-link">

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a tagging system for blog posts, including frontmatter parsing in the build script, new routing logic in the React application, and a dedicated tags cloud page. It also includes several CSS adjustments to the header and markdown rendering. Feedback focuses on removing redundant file generation in the build script, avoiding the use of !important in CSS to maintain better style specificity, and reducing code duplication by passing tags as props instead of re-parsing frontmatter in the frontend.

Comment thread post.config.js
path.join(tagsPageDir, 'index.html')
);

const PagesComponents = ['404.html', 'about.html', 'tags.html'];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

You are creating dist/tags/index.html in lines 100-107, and here you are also creating dist/tags.html. This is redundant. The routing logic will handle /tags with tags/index.html, so you can remove 'tags.html' from this array.

Suggested change
const PagesComponents = ['404.html', 'about.html', 'tags.html'];
const PagesComponents = ['404.html', 'about.html'];

}

.markdown-body h2 {
margin-top: 0px !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using !important is generally discouraged as it can lead to CSS specificity issues and make maintenance harder. It seems this rule is intended to override another style. A better approach would be to increase the selector's specificity or adjust the conflicting style directly.

Comment on lines +38 to +50
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);

let tags = [];
let cleanContent = content;

if (frontmatterMatch) {
const frontmatter = frontmatterMatch[1];
const tagsLine = frontmatter.match(/^tags:\s*(.*)$/m);
if (tagsLine) {
tags = tagsLine[1].split(',').map(t => t.trim()).filter(Boolean);
}
cleanContent = content.replace(/^---\s*\n[\s\S]*?\n---\s*\n/, '').trim();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This frontmatter parsing logic is also present in post.config.js. To avoid this duplication, you could enhance the App component to pass the tags array (which it can get from the post metadata in posts.json) as a prop to MarkdownRenderer. This component would then only need to strip the frontmatter from the content, not parse it again. This would make the component's responsibility clearer and reduce code duplication.

Comment thread src/styles/App.css
.markdown-body > h2:first-child,
.markdown-body > h3:first-child,
.markdown-body > p:first-child {
margin-top: 0.5rem !important;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using !important should be avoided as it breaks the natural cascade of stylesheets and can lead to maintenance headaches. Please try to achieve the desired styling by using a more specific selector or refactoring the CSS to avoid the need for !important.

@gapry
gapry merged commit 388ba7c into main Mar 28, 2026
2 checks passed
@gapry
gapry deleted the tags branch March 28, 2026 20:45
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.

1 participant