diff --git a/apps/blog/app/[param]/[p2]/[permlink]/content.tsx b/apps/blog/app/[param]/[p2]/[permlink]/content.tsx
index 89e37f1700746bd19abd7664192edadc835c6099..afc33ddb1acecc99c5a3e7be697c7c260f0de574 100644
--- a/apps/blog/app/[param]/[p2]/[permlink]/content.tsx
+++ b/apps/blog/app/[param]/[p2]/[permlink]/content.tsx
@@ -362,6 +362,7 @@ const PostContent = () => {
mainPost={postData.depth === 0}
body={crossPostData?.body ?? postData.body}
author={postData.author}
+ permlink={postData.permlink}
className={postClassName}
/>
diff --git a/apps/blog/features/post-rendering/comment-list-item.tsx b/apps/blog/features/post-rendering/comment-list-item.tsx
index a085f6d381ebab5eaba00d93579a556b02e99f06..ac02bcb7b437cb52656ca5aec127d9cf415c6112 100644
--- a/apps/blog/features/post-rendering/comment-list-item.tsx
+++ b/apps/blog/features/post-rendering/comment-list-item.tsx
@@ -329,6 +329,7 @@ const CommentListItem = ({
diff --git a/apps/blog/features/post-rendering/rendererContainer.tsx b/apps/blog/features/post-rendering/rendererContainer.tsx
index adf27b701a27879a8fc0a27564b29114b8932545..150ab1d20f1717758c25e106a83999132474e4f8 100644
--- a/apps/blog/features/post-rendering/rendererContainer.tsx
+++ b/apps/blog/features/post-rendering/rendererContainer.tsx
@@ -11,6 +11,7 @@ import { isUrlWhitelisted } from '@hive/ui/config/lists/phishing';
const RendererContainer = ({
body,
author,
+ permlink,
dataTestid,
communityDescription,
mainPost,
@@ -18,6 +19,7 @@ const RendererContainer = ({
}: {
body: string;
author: string;
+ permlink?: string;
dataTestid?: string;
communityDescription?: boolean;
className?: string;
@@ -82,8 +84,11 @@ const RendererContainer = ({
}, [body, hiveRenderer]);
const htmlBody = useMemo(() => {
- if (body) return hiveRenderer.render(body);
- }, [hiveRenderer, body]);
+ if (body) {
+ const postContext = author || permlink ? { author, permlink } : undefined;
+ return hiveRenderer.render(body, postContext);
+ }
+ }, [hiveRenderer, body, author, permlink]);
return !htmlBody ? (
diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts
index 92643a7cf3d970bc678ef4af1ed6326e440ce7fa..4c067a90465a04a95b0b4c572a0e3435e6fd0ecd 100644
--- a/packages/renderer/src/index.ts
+++ b/packages/renderer/src/index.ts
@@ -8,6 +8,7 @@ export {TwitterPlugin} from './renderers/default/plugins/TwitterPlugin';
export {InstagramPlugin} from './renderers/default/plugins/InstagramPlugin';
export {TablePlugin} from './renderers/default/plugins/TablePlugin';
export type {RendererPlugin} from './renderers/default/plugins/RendererPlugin';
+export type {PostContext} from './renderers/default/sanitization/TagTransformingSanitizer';
export const HiveContentRenderer = {
DefaultRenderer,
diff --git a/packages/renderer/src/renderers/default/DefaultRenderer.ts b/packages/renderer/src/renderers/default/DefaultRenderer.ts
index eb04654c093e23ef3cfbf8be1d4c59546a71ca4d..ae3e3b977ecad90da17b19e92efe2f8905d863ff 100644
--- a/packages/renderer/src/renderers/default/DefaultRenderer.ts
+++ b/packages/renderer/src/renderers/default/DefaultRenderer.ts
@@ -6,7 +6,7 @@ import {Localization, LocalizationOptions} from './Localization';
import type {RendererPlugin} from './plugins/RendererPlugin';
import remarkableSpoiler from './plugins/SpoilerPlugin';
import {PreliminarySanitizer} from './sanitization/PreliminarySanitizer';
-import {TagTransformingSanitizer} from './sanitization/TagTransformingSanitizer';
+import {TagTransformingSanitizer, PostContext} from './sanitization/TagTransformingSanitizer';
/**
* DefaultRenderer is a configurable HTML/Markdown renderer that provides:
@@ -68,22 +68,24 @@ export class DefaultRenderer {
/**
* Renders the input text to HTML
* @param input - Markdown or HTML text to render
+ * @param postContext - Optional context about the post (author/permlink) for logging
* @returns Rendered and processed HTML
* @throws Will throw if input is empty or invalid
*/
- public render(input: string): string {
+ public render(input: string, postContext?: PostContext): string {
// Validate input
ow(input, 'input', ow.string.nonEmpty);
- return this.doRender(input);
+ return this.doRender(input, postContext);
}
/**
* Renders the input text to HTML with a specific locale
* @param text - Markdown or HTML text to render
+ * @param postContext - Optional context about the post for logging
* @returns Rendered and processed HTML
* @throws Will throw if input is empty or invalid
*/
- private doRender(text: string): string {
+ private doRender(text: string, postContext?: PostContext): string {
// Pre-process with plugins
text = this.runPluginPhase('preProcess', text);
// Preliminary sanitization
@@ -98,7 +100,7 @@ export class DefaultRenderer {
// Parse the HTML and sanitize it
text = this.domParser.parse(text).getParsedDocumentAsString();
// Check for script tags and other security issues
- text = this.sanitize(text);
+ text = this.sanitize(text, postContext);
// Check for security issues
SecurityChecker.checkSecurity(text, {allowScriptTag: this.options.allowInsecureScriptTags});
// Embed assets and resize them
@@ -183,6 +185,7 @@ export class DefaultRenderer {
/**
* Sanitizes the HTML text by removing potentially harmful content
* @param text - The HTML text to sanitize
+ * @param postContext - Optional context about the post for logging
* @returns Sanitized HTML text
* @remarks
* This method can be skipped if skipSanitization option is set to true.
@@ -191,12 +194,12 @@ export class DefaultRenderer {
* - Transform certain tags according to renderer options
* - Apply security policies to links and embedded content
*/
- private sanitize(text: string): string {
+ private sanitize(text: string, postContext?: PostContext): string {
if (this.options.skipSanitization) {
return text;
}
- return this.tagTransformingSanitizer.sanitize(text);
+ return this.tagTransformingSanitizer.sanitize(text, postContext);
}
/**
* Validates the renderer options
diff --git a/packages/renderer/src/renderers/default/sanitization/TagTransformingSanitizer.ts b/packages/renderer/src/renderers/default/sanitization/TagTransformingSanitizer.ts
index 2e7ec9b99e170a08e7b77e6503c0f6550fe8015a..6b66754473d5002559feb62cbf103a038c1fa94f 100644
--- a/packages/renderer/src/renderers/default/sanitization/TagTransformingSanitizer.ts
+++ b/packages/renderer/src/renderers/default/sanitization/TagTransformingSanitizer.ts
@@ -11,6 +11,7 @@ export class TagTransformingSanitizer {
private options: TagsSanitizerOptions;
private localization: LocalizationOptions;
private sanitizationErrors: string[] = [];
+ private currentPostContext?: PostContext;
public constructor(options: TagsSanitizerOptions, localization: LocalizationOptions) {
this.validate(options);
@@ -25,12 +26,22 @@ export class TagTransformingSanitizer {
* Uses the sanitize-html library with custom configuration for tag transformation.
*
* @param text - The HTML content to sanitize
+ * @param postContext - Optional context about the post being rendered (for logging)
* @returns A sanitized version of the HTML content with transformed tags and removed unsafe content
*/
- public sanitize(text: string): string {
+ public sanitize(text: string, postContext?: PostContext): string {
+ this.currentPostContext = postContext;
return sanitize(text, this.generateSanitizeConfig());
}
+ private formatPostContext(): string {
+ if (!this.currentPostContext) return '';
+ const { author, permlink } = this.currentPostContext;
+ if (author && permlink) return ` in @${author}/${permlink}`;
+ if (author) return ` by @${author}`;
+ return '';
+ }
+
public getErrors(): string[] {
return this.sanitizationErrors;
}
@@ -98,7 +109,7 @@ export class TagTransformingSanitizer {
return iframeToBeReturned;
}
}
- Log.log().warn('Blocked, did not match iframe "src" white list urls:', tagName, attributes);
+ Log.log().warn(`Blocked iframe (not whitelisted)${this.formatPostContext()}: src="${srcAtty || '(empty)'}"`);
this.sanitizationErrors.push('Invalid iframe URL: ' + srcAtty);
const retTag: sanitize.Tag = {tagName: 'div', text: `(Unsupported ${srcAtty})`, attribs: {}};
@@ -117,7 +128,7 @@ export class TagTransformingSanitizer {
const {src, alt} = attribs;
// eslint-disable-next-line security/detect-unsafe-regex
if (!/^(https?:)?\/\//i.test(src)) {
- Log.log().warn('Blocked, image tag src does not appear to be a url', tagName, attribs);
+ Log.log().warn(`Blocked image (invalid src)${this.formatPostContext()}: src="${src || '(empty)'}"`);
this.sanitizationErrors.push('An image in this post did not save properly.');
const retTagOnNoUrl: sanitize.Tag = {
tagName: 'img',
@@ -228,3 +239,8 @@ export interface TagsSanitizerOptions {
isLinkSafeFn: (url: string) => boolean;
addExternalCssClassToMatchingLinksFn: (url: string) => boolean;
}
+
+export interface PostContext {
+ author?: string;
+ permlink?: string;
+}