Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis pull request introduces comprehensive AI assistant functionality to the Berta CMS platform. It adds a Laravel-based AI SDK integration with configuration, a backend API endpoint for AI chat, an Angular-based chat UI component with NGXS state management, comprehensive test coverage, and supporting documentation and configuration files across multiple development tools (Claude, Cursor, Codex). Changes
Sequence DiagramsequenceDiagram
participant User as User
participant UI as AI Chat UI<br/>(Component)
participant Store as NGXS Store<br/>(State)
participant Service as AI Assistant<br/>Service
participant API as Backend API<br/>(StateController)
participant Agent as AssistantAgent<br/>(Laravel)
User->>UI: Click AI icon / Open chat
UI->>Store: Dispatch ToggleAiAssistantAction
Store->>Store: Toggle isOpen flag
User->>UI: Type message + Send
UI->>Store: Dispatch SendAiMessageAction
Store->>Store: Append user message, set isLoading
Store->>Service: chat(message, history,<br/>site, template, changeHistory)
Service->>API: POST /ai-chat with payload
API->>Agent: Process message via<br/>AssistantAgent::fake/real
Agent->>Agent: Generate reply with<br/>design_changes &<br/>settings_changes
Agent-->>API: Return structured response
API-->>Service: Return AiChatResponse
Service-->>Store: Observable<AiChatResponse>
Store->>Store: Dispatch AiMessageReceivedAction
Store->>Store: Append assistant reply
alt If design_changes exist
Store->>Store: Dispatch UpdateSiteTemplateSettingsAction
end
alt If settings_changes exist
Store->>Store: Dispatch UpdateSiteSettingsAction
end
Store->>Store: Update changeHistory for undo support
Store->>Store: Set isLoading = false
Store-->>UI: Update observables (messages, isLoading)
UI->>UI: Auto-scroll to bottom
UI-->>User: Display assistant reply & applied changes
User->>UI: Click Clear button
UI->>Store: Dispatch ClearAiChatAction
Store->>Store: Reset to defaults (empty messages,<br/>isOpen: false, etc.)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
_api_app/app/Http/Middleware/SetupMiddleware.php (2)
26-50:⚠️ Potential issue | 🟡 MinorAdd missing return type and curly braces for control structures.
The method violates coding guidelines:
- Missing explicit return type declaration (should be
: void)- Control structures on lines 33-39 lack curly braces
As per coding guidelines: "Always use curly braces for control structures, even for single-line statements" and "Always use explicit return type declarations for methods and functions."
📝 Proposed fix
- protected function updateEnvFile() + protected function updateEnvFile(): void { $env_example = $this->parseEnvFile(base_path() . '/.env.example'); $env = $this->parseEnvFile(base_path() . '/.env'); $env_new = []; foreach ($env_example as $key => $value) { - if (isset($env[$key])) { + if (isset($env[$key])) { $env_new[$key] = $env[$key]; - } elseif ($key == 'APP_KEY' || $key == 'APP_ID') { + } elseif ($key == 'APP_KEY' || $key == 'APP_ID') { $env_new[$key] = Helpers::uuid_v4(); - } else { + } else { $env_new[$key] = $value; + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@_api_app/app/Http/Middleware/SetupMiddleware.php` around lines 26 - 50, The updateEnvFile method is missing an explicit return type and uses control structures without braces; update the method signature to include a void return type (function updateEnvFile(): void) and add curly braces for the if/elseif/else blocks inside the loop that builds $env_new (the checks using isset($env[$key]), $key == 'APP_KEY' || $key == 'APP_ID', and the final else), ensuring each branch has braces, and keep the rest of the logic (parseEnvFile calls, Helpers::uuid_v4(), building $content and file_put_contents) unchanged.
52-74:⚠️ Potential issue | 🟡 MinorAdd missing type declarations and curly braces for control structures.
The method violates coding guidelines:
- Missing explicit return type declaration (should be
: array)- Missing parameter type hint for
$file(should bestring)- Control structures on lines 56-58 and 62-64 lack curly braces
As per coding guidelines: "Always use curly braces for control structures, even for single-line statements", "Always use explicit return type declarations for methods and functions", and "Use appropriate PHP type hints for method parameters."
📝 Proposed fix
- protected function parseEnvFile($file) + protected function parseEnvFile(string $file): array { $res = []; - if (! file_exists($file)) { + if (! file_exists($file)) { return $res; + } $content = trim(file_get_contents(realpath($file))); - if (empty($content)) { + if (empty($content)) { return $res; + } $rows = preg_split('/\s+/', $content); // Loop through given data foreach ((array) $rows as $key => $value) { [$key, $value] = explode('=', $value, 2); $res[$key] = $value; } return $res; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@_api_app/app/Http/Middleware/SetupMiddleware.php` around lines 52 - 74, The parseEnvFile method lacks a parameter type and explicit return type and uses single-line control structures without braces; update the function signature parseEnvFile($file) to parseEnvFile(string $file): array, add curly braces for the if (! file_exists($file)) { ... }, if (empty($content)) { ... } and the foreach loop body foreach ((array) $rows as $key => $value) { ... }, and ensure the method still returns the $res array at the end.
🧹 Nitpick comments (8)
_api_app/app/Http/Controllers/StateController.php (1)
136-147: LGTM!Adding the explicit
JsonResponsereturn type aligns with coding guidelines and improves type safety.For consistency, consider adding return type declarations to other methods in this controller (
get(),getMeta(),getSentryDSN()) in a follow-up PR.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@_api_app/app/Http/Controllers/StateController.php` around lines 136 - 147, The getLocaleSettings method now has an explicit JsonResponse return type; apply the same consistency to the other controller methods by adding appropriate return type declarations: update get(), getMeta(), and getSentryDSN() to declare their return types (e.g., JsonResponse or Response as appropriate for each method's actual return value) so signatures match getLocaleSettings and improve type safety and consistency across the controller._api_app/app/Http/Middleware/SetupMiddleware.php (1)
66-71: Consider using a robust .env parser library.The current parsing logic splits on whitespace and may fail for:
- Multi-line values
- Values containing spaces
- Malformed lines without
=separatorConsider using a well-tested library like
vlucas/phpdotenv(already a Laravel dependency) or Laravel's config layer instead of custom parsing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@_api_app/app/Http/Middleware/SetupMiddleware.php` around lines 66 - 71, Replace the fragile custom parser in SetupMiddleware (the preg_split on $content, the foreach over $rows and the explode('=',$value,2) that populates $res) with a robust .env parsing approach: use vlucas/phpdotenv (or Laravel's built-in env/config loader) to parse $content into key/value pairs and populate the $res array, handling multi-line values, spaces and malformed lines safely; update the code to call Dotenv or the appropriate loader API instead of manually splitting by whitespace and exploding on '=' and remove the fragile foreach parsing logic.editor/src/app/ai-assistant/ai-assistant.state.ts (2)
175-189: Multiple dispatch calls could be batched for efficiency.Each iteration dispatches a separate action. While functionally correct, NGXS allows dispatching an array of actions in a single call for better performance.
♻️ Proposed refactor to batch dispatches
- for (const change of action.designChanges) { - dispatch( - new UpdateSiteTemplateSettingsAction(change.group, { - [change.setting]: change.value, - }), - ); - } - - for (const change of action.settingsChanges) { - dispatch( - new UpdateSiteSettingsAction(change.group, { - [change.setting]: change.value, - }), - ); - } + const actions = [ + ...action.designChanges.map( + (change) => + new UpdateSiteTemplateSettingsAction(change.group, { + [change.setting]: change.value, + }), + ), + ...action.settingsChanges.map( + (change) => + new UpdateSiteSettingsAction(change.group, { + [change.setting]: change.value, + }), + ), + ]; + + if (actions.length > 0) { + dispatch(actions); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@editor/src/app/ai-assistant/ai-assistant.state.ts` around lines 175 - 189, Currently each loop calls dispatch per change which is inefficient; instead map action.designChanges to an array of new UpdateSiteTemplateSettingsAction(change.group, {[change.setting]: change.value}) and map action.settingsChanges to an array of new UpdateSiteSettingsAction(change.group, {[change.setting]: change.value}), concatenate those two arrays (preserving order) and call dispatch once with the combined array; locate the loops where action.designChanges and action.settingsChanges are iterated and replace them with the single batched dispatch call using the generated action instances.
127-131: Consider providing user-facing error feedback.When the AI request fails, the error is logged to console and loading state is cleared, but the user receives no visible indication of what went wrong. Consider adding an error message to the messages array or dispatching a notification action.
💡 Proposed enhancement for error feedback
catchError((error) => { console.error('AI assistant error:', error); - patchState({ isLoading: false }); + const errorMessage: AiMessage = { + role: 'assistant', + content: 'Sorry, something went wrong. Please try again.', + }; + patchState({ + messages: [...getState().messages, errorMessage], + isLoading: false, + }); return EMPTY; }),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@editor/src/app/ai-assistant/ai-assistant.state.ts` around lines 127 - 131, The catchError block in the AI assistant observable only logs and clears isLoading, so add user-facing feedback by appending an error message to the messages state or dispatching a notification action: inside the catchError in ai-assistant.state.ts update patchState to both set isLoading: false and push a new message object into the messages array (e.g., a system/error message with a clear text like "AI request failed") or call your notification action (e.g., dispatch(NotificationActions.showError(...))). Ensure you reference the same patchState and messages state shape used elsewhere so the UI displays the error to the user.editor/src/app/ai-assistant/ai-assistant.actions.ts (1)
10-18: Consider reusingAiChangeIteminterface to reduce duplication.The inline type
{ group: string; setting: string; value: string; previous_value?: string | null }[]duplicates theAiChangeIteminterface defined inai-assistant.service.ts. Reusing the interface improves maintainability.♻️ Proposed refactor to reuse interface
+import { AiChangeItem } from './ai-assistant.service'; + export class AiMessageReceivedAction { static readonly type = 'AI_ASSISTANT:MESSAGE_RECEIVED'; constructor( public reply: string, - public designChanges: { group: string; setting: string; value: string; previous_value?: string | null }[], - public settingsChanges: { group: string; setting: string; value: string; previous_value?: string | null }[], + public designChanges: AiChangeItem[], + public settingsChanges: AiChangeItem[], public isUndo: boolean = false, ) {} }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@editor/src/app/ai-assistant/ai-assistant.actions.ts` around lines 10 - 18, The inline change item type in AiMessageReceivedAction duplicates the AiChangeItem interface; update the constructor signatures for designChanges and settingsChanges to use AiChangeItem[] instead of the inline type and add an import for AiChangeItem where it’s defined (so AiMessageReceivedAction references the shared interface), ensuring any nullable previous_value semantics match AiChangeItem.editor/src/app/rerender/template-rerender.service.ts (1)
96-99: Minor inconsistency: null check not used for other cases.The
if (info.siteTexts)guard is present here but not for other cases likeBANNERS_SETTINGS,SETTINGS, orENTRY_LAYOUT(lines 84-92). SincesiteTextsis a required field inSiteSettingChildrenHandler(per context snippet 1), this check is redundant. However, if this is intentional defensive coding for a newly added field, it's acceptable.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@editor/src/app/rerender/template-rerender.service.ts` around lines 96 - 99, The SITE_TEXTS branch currently has an extra conditional guard (if (info.siteTexts)) while other branches like BANNERS_SETTINGS, SETTINGS, and ENTRY_LAYOUT directly push their fields; to keep consistency remove the redundant null-check and change the SITE_TEXTS case to simply push info.siteTexts into compList (i.e., compList.push(info.siteTexts);) so it matches the pattern used in the other cases and relies on the type contract of SiteSettingChildrenHandler._api_app/.env.example (1)
9-10: Consider alphabetically ordering environment variables.The static analysis tool suggests placing
AI_DEFAULT_PROVIDERandANTHROPIC_API_KEYbeforeAPI_PREFIXfor alphabetical consistency. This is a minor style consideration that improves maintainability as the file grows.♻️ Suggested ordering
APP_ENV=production APP_DEBUG=false +AI_DEFAULT_PROVIDER=anthropic +ANTHROPIC_API_KEY= CACHE_STORE=file APP_KEY=[YOUR_APP_KEY] APP_ID=[YOUR_APP_ID] API_PREFIX=_api SENTRY_DSN= SENTRY_FRONTEND_DSN= -AI_DEFAULT_PROVIDER=anthropic -ANTHROPIC_API_KEY=🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@_api_app/.env.example` around lines 9 - 10, Reorder the environment variable declarations so they are alphabetized: move AI_DEFAULT_PROVIDER and ANTHROPIC_API_KEY so they appear before API_PREFIX in .env.example; ensure the variables (AI_DEFAULT_PROVIDER, ANTHROPIC_API_KEY, API_PREFIX) are in ascending alphabetical order and keep the rest of the file formatting unchanged._api_app/.github/skills/ai-sdk-development/SKILL.md (1)
1-413: Consider a single-source approach for duplicated skill docs.This content is duplicated across
.github/.cursor/.agentsvariants, which will be hard to keep in sync. A template/generation step would reduce drift.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@_api_app/.github/skills/ai-sdk-development/SKILL.md` around lines 1 - 413, The ai-sdk-development SKILL.md is duplicated across multiple variants; consolidate into a single source (e.g., a template or canonical SKILL.md for the ai-sdk-development skill) and update the duplicated copies to be generated or included from that canonical file; modify the generator/script (or add a small build step/CI job) to produce the variant files from the canonical ai-sdk-development SKILL.md (or SKILL.md.template) and update references to the skill name "ai-sdk-development" so all variants pull from the single source.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@_api_app/.codex/config.toml`:
- Around line 1-12: The config contains hardcoded absolute paths (cwd and
SITE_PATH in the mcp_servers sections: mcp_servers.laravel-boost,
mcp_servers.herd, and mcp_servers.herd.env) which break for other developers;
replace those absolute paths with relative paths or environment-variable
references (e.g., use "./_api_app" or a variable like ${SITE_PATH}) or remove
the file from VCS and add a template (e.g., config.toml.example) and add this
file to .gitignore so each developer can configure their own local cwd and
SITE_PATH values.
In `@_api_app/AGENTS.md`:
- Line 248: Replace the incorrect skill name reference "developing-with-ai-sdk"
with the defined skill name "ai-sdk-development" so activation uses the
canonical identifier; update any occurrence in the AGENTS.md text (the line that
currently reads Activate the `developing-with-ai-sdk` skill) to read Activate
the `ai-sdk-development` skill to ensure the skill activates correctly.
In `@_api_app/CLAUDE.md`:
- Around line 248-249: The skill name string in the CLAUDE.md content is
incorrect: replace the lone occurrence of "developing-with-ai-sdk" with the
canonical skill id "ai-sdk-development" to match boost.json and the SKILL.md
frontmatter; verify any other occurrences in this file use "ai-sdk-development"
so the skill activation string in the documentation matches the
boost.json/SKILL.md definition.
In `@_api_app/opencode.json`:
- Around line 20-22: The SITE_PATH in the "environment" object is a hardcoded,
user-specific absolute path; replace it with a portable solution by: change
SITE_PATH to use a relative path or an environment variable reference (e.g. read
from process env at runtime) and remove or template the absolute value in
opencode.json so it no longer contains "/Users/uldis/..."; alternatively, mark
this file as developer-specific by adding it to .gitignore or provide a sample
file (e.g. opencode.example.json) with a placeholder for SITE_PATH so other
developers/CI can set their own path.
In `@_api_app/tests/Feature/AiChatControllerTest.php`:
- Around line 10-16: The test "returns 401 when unauthenticated" uses
assertStatus(401) — change this to the more specific assertion by replacing
assertStatus(401) with assertUnauthorized() on the response from
post(route('ai_chat'), [...]) so the test uses the preferred Laravel assertion;
locate the anonymous test function (it('returns 401 when unauthenticated', ...))
and update the assertion accordingly.
In `@editor/src/app/ai-assistant/ai-assistant.component.ts`:
- Around line 40-51: The template is inserting rendered HTML via
[innerHTML]="msg.content | markdown" which relies on the markdown pipe to bypass
Angular XSS protections; update the markdown.pipe (used by
ai-assistant.component template) to treat AI-generated content as untrusted:
parse markdown to HTML, run the HTML through a robust sanitizer (e.g.,
DOMPurify) inside the MarkdownPipe, and only then, if necessary, use
DomSanitizer.bypassSecurityTrustHtml on the already-sanitized output; ensure the
pipe is named MarkdownPipe (or the exported pipe symbol used in the template)
and that ai-assistant.component continues to consume messages$ / msg.content
without changing template bindings.
In `@editor/src/app/pipes/markdown.pipe.ts`:
- Line 9: The transform method should guard against null/undefined input before
calling marked.parse; update transform(value: string): SafeHtml in the markdown
pipe to return a safe empty string or a default SafeHtml (e.g.,
sanitizer.bypassSecurityTrustHtml('')) when value is null/undefined, and only
call marked.parse(value) when value is a non-empty string; ensure the return
type SafeHtml is preserved and reference marked.parse and transform to locate
the change.
- Around line 9-17: The transform method in markdown.pipe.ts uses marked.parse
with a custom Renderer and then calls this.sanitizer.bypassSecurityTrustHtml
without sanitizing or escaping attributes, which allows raw HTML, script tags
and attribute injection (href, title, text) — fix by installing and importing
DOMPurify, run DOMPurify.sanitize on the string returned by marked.parse (pass a
safe ALLOWED_TAGS/ALLOWED_ATTRS config and allow target/rel if needed), stop
interpolating raw attribute values by ensuring marked's renderer produces safe
attributes (or escape href/title/text before constructing the <a>), and only
call this.sanitizer.bypassSecurityTrustHtml on the sanitized output; keep
references to transform, Renderer, marked.parse and
this.sanitizer.bypassSecurityTrustHtml when making the change.
---
Outside diff comments:
In `@_api_app/app/Http/Middleware/SetupMiddleware.php`:
- Around line 26-50: The updateEnvFile method is missing an explicit return type
and uses control structures without braces; update the method signature to
include a void return type (function updateEnvFile(): void) and add curly braces
for the if/elseif/else blocks inside the loop that builds $env_new (the checks
using isset($env[$key]), $key == 'APP_KEY' || $key == 'APP_ID', and the final
else), ensuring each branch has braces, and keep the rest of the logic
(parseEnvFile calls, Helpers::uuid_v4(), building $content and
file_put_contents) unchanged.
- Around line 52-74: The parseEnvFile method lacks a parameter type and explicit
return type and uses single-line control structures without braces; update the
function signature parseEnvFile($file) to parseEnvFile(string $file): array, add
curly braces for the if (! file_exists($file)) { ... }, if (empty($content)) {
... } and the foreach loop body foreach ((array) $rows as $key => $value) { ...
}, and ensure the method still returns the $res array at the end.
---
Nitpick comments:
In `@_api_app/.env.example`:
- Around line 9-10: Reorder the environment variable declarations so they are
alphabetized: move AI_DEFAULT_PROVIDER and ANTHROPIC_API_KEY so they appear
before API_PREFIX in .env.example; ensure the variables (AI_DEFAULT_PROVIDER,
ANTHROPIC_API_KEY, API_PREFIX) are in ascending alphabetical order and keep the
rest of the file formatting unchanged.
In `@_api_app/.github/skills/ai-sdk-development/SKILL.md`:
- Around line 1-413: The ai-sdk-development SKILL.md is duplicated across
multiple variants; consolidate into a single source (e.g., a template or
canonical SKILL.md for the ai-sdk-development skill) and update the duplicated
copies to be generated or included from that canonical file; modify the
generator/script (or add a small build step/CI job) to produce the variant files
from the canonical ai-sdk-development SKILL.md (or SKILL.md.template) and update
references to the skill name "ai-sdk-development" so all variants pull from the
single source.
In `@_api_app/app/Http/Controllers/StateController.php`:
- Around line 136-147: The getLocaleSettings method now has an explicit
JsonResponse return type; apply the same consistency to the other controller
methods by adding appropriate return type declarations: update get(), getMeta(),
and getSentryDSN() to declare their return types (e.g., JsonResponse or Response
as appropriate for each method's actual return value) so signatures match
getLocaleSettings and improve type safety and consistency across the controller.
In `@_api_app/app/Http/Middleware/SetupMiddleware.php`:
- Around line 66-71: Replace the fragile custom parser in SetupMiddleware (the
preg_split on $content, the foreach over $rows and the explode('=',$value,2)
that populates $res) with a robust .env parsing approach: use vlucas/phpdotenv
(or Laravel's built-in env/config loader) to parse $content into key/value pairs
and populate the $res array, handling multi-line values, spaces and malformed
lines safely; update the code to call Dotenv or the appropriate loader API
instead of manually splitting by whitespace and exploding on '=' and remove the
fragile foreach parsing logic.
In `@editor/src/app/ai-assistant/ai-assistant.actions.ts`:
- Around line 10-18: The inline change item type in AiMessageReceivedAction
duplicates the AiChangeItem interface; update the constructor signatures for
designChanges and settingsChanges to use AiChangeItem[] instead of the inline
type and add an import for AiChangeItem where it’s defined (so
AiMessageReceivedAction references the shared interface), ensuring any nullable
previous_value semantics match AiChangeItem.
In `@editor/src/app/ai-assistant/ai-assistant.state.ts`:
- Around line 175-189: Currently each loop calls dispatch per change which is
inefficient; instead map action.designChanges to an array of new
UpdateSiteTemplateSettingsAction(change.group, {[change.setting]: change.value})
and map action.settingsChanges to an array of new
UpdateSiteSettingsAction(change.group, {[change.setting]: change.value}),
concatenate those two arrays (preserving order) and call dispatch once with the
combined array; locate the loops where action.designChanges and
action.settingsChanges are iterated and replace them with the single batched
dispatch call using the generated action instances.
- Around line 127-131: The catchError block in the AI assistant observable only
logs and clears isLoading, so add user-facing feedback by appending an error
message to the messages state or dispatching a notification action: inside the
catchError in ai-assistant.state.ts update patchState to both set isLoading:
false and push a new message object into the messages array (e.g., a
system/error message with a clear text like "AI request failed") or call your
notification action (e.g., dispatch(NotificationActions.showError(...))). Ensure
you reference the same patchState and messages state shape used elsewhere so the
UI displays the error to the user.
In `@editor/src/app/rerender/template-rerender.service.ts`:
- Around line 96-99: The SITE_TEXTS branch currently has an extra conditional
guard (if (info.siteTexts)) while other branches like BANNERS_SETTINGS,
SETTINGS, and ENTRY_LAYOUT directly push their fields; to keep consistency
remove the redundant null-check and change the SITE_TEXTS case to simply push
info.siteTexts into compList (i.e., compList.push(info.siteTexts);) so it
matches the pattern used in the other cases and relies on the type contract of
SiteSettingChildrenHandler.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4272ee19-9253-4285-827b-de9b8dcbc0b0
⛔ Files ignored due to path filters (2)
_api_app/composer.lockis excluded by!**/*.lockeditor/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (50)
CLAUDE.mdWARP.md_api_app/.agents/skills/ai-sdk-development/SKILL.md_api_app/.agents/skills/pest-testing/SKILL.md_api_app/.claude/skills/ai-sdk-development/SKILL.md_api_app/.claude/skills/pest-testing/SKILL.md_api_app/.codex/config.toml_api_app/.cursor/skills/ai-sdk-development/SKILL.md_api_app/.cursor/skills/pest-testing/SKILL.md_api_app/.env.example_api_app/.github/skills/ai-sdk-development/SKILL.md_api_app/.github/skills/pest-testing/SKILL.md_api_app/AGENTS.md_api_app/CLAUDE.md_api_app/app/Http/Controllers/StateController.php_api_app/app/Http/Middleware/Authenticate.php_api_app/app/Http/Middleware/SetupMiddleware.php_api_app/app/User/UserModel.php_api_app/boost.json_api_app/bootstrap/providers.php_api_app/composer.json_api_app/config/ai.php_api_app/config/auth.php_api_app/config/sanctum.php_api_app/config/twigbridge.php_api_app/database/factories/UserFactory.php_api_app/opencode.json_api_app/tests/Feature/AiChatControllerTest.php_api_app/tests/Pest.phpeditor/package.jsoneditor/src/app/ai-assistant/ai-assistant.actions.tseditor/src/app/ai-assistant/ai-assistant.component.tseditor/src/app/ai-assistant/ai-assistant.module.tseditor/src/app/ai-assistant/ai-assistant.service.tseditor/src/app/ai-assistant/ai-assistant.state.tseditor/src/app/app.component.tseditor/src/app/app.module.tseditor/src/app/header/header.component.tseditor/src/app/pipes/markdown.pipe.tseditor/src/app/pipes/pipes.module.tseditor/src/app/pipes/safe.pipe.tseditor/src/app/rerender/default-template-rerender.service.tseditor/src/app/rerender/mashup/mashup-template-rerender.service.tseditor/src/app/rerender/messy/messy-template-rerender.service.tseditor/src/app/rerender/template-rerender.service.tseditor/src/app/rerender/types/components.tseditor/src/app/rerender/white/white-template-rerender.service.tseditor/src/app/sites/sections/site-sections.module.tseditor/src/app/sites/settings/site-settings.state.tseditor/src/app/user/user.state.ts
💤 Files with no reviewable changes (2)
- WARP.md
- _api_app/app/Http/Middleware/Authenticate.php
_api_app/.codex/config.toml
Outdated
| [mcp_servers.laravel-boost] | ||
| command = "php" | ||
| args = ["artisan", "boost:mcp"] | ||
| cwd = "/Users/uldis/projects/berta/berta/_api_app" | ||
|
|
||
| [mcp_servers.herd] | ||
| command = "php" | ||
| args = ["/Applications/Herd.app/Contents/Resources/herd-mcp.phar"] | ||
| cwd = "/Users/uldis/projects/berta/berta/_api_app" | ||
|
|
||
| [mcp_servers.herd.env] | ||
| SITE_PATH = "/Users/uldis/projects/berta/berta/_api_app" |
There was a problem hiding this comment.
Hardcoded absolute paths will break for other developers.
The configuration contains machine-specific paths (/Users/uldis/projects/berta/berta/_api_app) that won't work for other team members. Consider using relative paths or removing this file from version control if it's meant for local development only.
🔧 Suggested fix using relative paths
[mcp_servers.laravel-boost]
command = "php"
args = ["artisan", "boost:mcp"]
-cwd = "/Users/uldis/projects/berta/berta/_api_app"
+cwd = "."
[mcp_servers.herd]
command = "php"
args = ["/Applications/Herd.app/Contents/Resources/herd-mcp.phar"]
-cwd = "/Users/uldis/projects/berta/berta/_api_app"
+cwd = "."
[mcp_servers.herd.env]
-SITE_PATH = "/Users/uldis/projects/berta/berta/_api_app"
+SITE_PATH = "."Alternatively, add this file to .gitignore and provide a .codex/config.toml.example template instead.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| [mcp_servers.laravel-boost] | |
| command = "php" | |
| args = ["artisan", "boost:mcp"] | |
| cwd = "/Users/uldis/projects/berta/berta/_api_app" | |
| [mcp_servers.herd] | |
| command = "php" | |
| args = ["/Applications/Herd.app/Contents/Resources/herd-mcp.phar"] | |
| cwd = "/Users/uldis/projects/berta/berta/_api_app" | |
| [mcp_servers.herd.env] | |
| SITE_PATH = "/Users/uldis/projects/berta/berta/_api_app" | |
| [mcp_servers.laravel-boost] | |
| command = "php" | |
| args = ["artisan", "boost:mcp"] | |
| cwd = "." | |
| [mcp_servers.herd] | |
| command = "php" | |
| args = ["/Applications/Herd.app/Contents/Resources/herd-mcp.phar"] | |
| cwd = "." | |
| [mcp_servers.herd.env] | |
| SITE_PATH = "." |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@_api_app/.codex/config.toml` around lines 1 - 12, The config contains
hardcoded absolute paths (cwd and SITE_PATH in the mcp_servers sections:
mcp_servers.laravel-boost, mcp_servers.herd, and mcp_servers.herd.env) which
break for other developers; replace those absolute paths with relative paths or
environment-variable references (e.g., use "./_api_app" or a variable like
${SITE_PATH}) or remove the file from VCS and add a template (e.g.,
config.toml.example) and add this file to .gitignore so each developer can
configure their own local cwd and SITE_PATH values.
|
|
||
| ## Test Enforcement | ||
| - This application uses the Laravel AI SDK (`laravel/ai`) for all AI functionality. | ||
| - Activate the `developing-with-ai-sdk` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter). |
There was a problem hiding this comment.
Fix incorrect AI skill name reference.
Line 248 references developing-with-ai-sdk, but the defined skill name is ai-sdk-development (see Line 29). This mismatch can prevent correct skill activation.
🛠️ Suggested fix
-- Activate the `developing-with-ai-sdk` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter).
+- Activate the `ai-sdk-development` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter).📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - Activate the `developing-with-ai-sdk` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter). | |
| - Activate the `ai-sdk-development` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter). |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@_api_app/AGENTS.md` at line 248, Replace the incorrect skill name reference
"developing-with-ai-sdk" with the defined skill name "ai-sdk-development" so
activation uses the canonical identifier; update any occurrence in the AGENTS.md
text (the line that currently reads Activate the `developing-with-ai-sdk` skill)
to read Activate the `ai-sdk-development` skill to ensure the skill activates
correctly.
| - Activate the `developing-with-ai-sdk` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter). | ||
|
|
There was a problem hiding this comment.
Skill name mismatch — should be ai-sdk-development.
The skill name here is developing-with-ai-sdk, but in boost.json and the SKILL.md frontmatter, it's defined as ai-sdk-development. This inconsistency may prevent proper skill activation.
Proposed fix
## Laravel AI SDK
- This application uses the Laravel AI SDK (`laravel/ai`) for all AI functionality.
-- Activate the `developing-with-ai-sdk` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter).
+- Activate the `ai-sdk-development` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter).📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - Activate the `developing-with-ai-sdk` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter). | |
| - Activate the `ai-sdk-development` skill when building, editing, updating, debugging, or testing AI agents, text generation, chat, streaming, structured output, tools, image generation, audio, transcription, embeddings, reranking, vector stores, files, conversation memory, or any AI provider integration (OpenAI, Anthropic, Gemini, Cohere, Groq, xAI, ElevenLabs, Jina, OpenRouter). |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@_api_app/CLAUDE.md` around lines 248 - 249, The skill name string in the
CLAUDE.md content is incorrect: replace the lone occurrence of
"developing-with-ai-sdk" with the canonical skill id "ai-sdk-development" to
match boost.json and the SKILL.md frontmatter; verify any other occurrences in
this file use "ai-sdk-development" so the skill activation string in the
documentation matches the boost.json/SKILL.md definition.
| it('returns 401 when unauthenticated', function () { | ||
| post(route('ai_chat'), [ | ||
| 'message' => 'Make the background blue', | ||
| 'site' => '', | ||
| 'template' => 'default', | ||
| ])->assertStatus(401); | ||
| })->skip(! $pluginInstalled, 'AiAssistant plugin not installed'); |
There was a problem hiding this comment.
Use assertUnauthorized() instead of assertStatus(401).
As per coding guidelines, prefer specific response assertion methods over generic assertStatus().
Proposed fix
it('returns 401 when unauthenticated', function () {
post(route('ai_chat'), [
'message' => 'Make the background blue',
'site' => '',
'template' => 'default',
- ])->assertStatus(401);
+ ])->assertUnauthorized();
})->skip(! $pluginInstalled, 'AiAssistant plugin not installed');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@_api_app/tests/Feature/AiChatControllerTest.php` around lines 10 - 16, The
test "returns 401 when unauthenticated" uses assertStatus(401) — change this to
the more specific assertion by replacing assertStatus(401) with
assertUnauthorized() on the response from post(route('ai_chat'), [...]) so the
test uses the preferred Laravel assertion; locate the anonymous test function
(it('returns 401 when unauthenticated', ...)) and update the assertion
accordingly.
| @for (msg of messages$ | async; track $index) { | ||
| <div | ||
| class="ai-message" | ||
| [class.ai-message--user]="msg.role === 'user'" | ||
| [class.ai-message--assistant]="msg.role === 'assistant'" | ||
| > | ||
| @if (msg.role === 'assistant') { | ||
| <span [innerHTML]="msg.content | markdown"></span> | ||
| } @else { | ||
| {{ msg.content }} | ||
| } | ||
| </div> |
There was a problem hiding this comment.
Note: This template relies on the markdown pipe which has security concerns.
The markdown pipe used on line 47 bypasses Angular's XSS protection without proper sanitization. Please address the security issues flagged in the markdown.pipe.ts review before shipping this feature. AI-generated content should be treated as untrusted input.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@editor/src/app/ai-assistant/ai-assistant.component.ts` around lines 40 - 51,
The template is inserting rendered HTML via [innerHTML]="msg.content | markdown"
which relies on the markdown pipe to bypass Angular XSS protections; update the
markdown.pipe (used by ai-assistant.component template) to treat AI-generated
content as untrusted: parse markdown to HTML, run the HTML through a robust
sanitizer (e.g., DOMPurify) inside the MarkdownPipe, and only then, if
necessary, use DomSanitizer.bypassSecurityTrustHtml on the already-sanitized
output; ensure the pipe is named MarkdownPipe (or the exported pipe symbol used
in the template) and that ai-assistant.component continues to consume messages$
/ msg.content without changing template bindings.
| export class MarkdownPipe implements PipeTransform { | ||
| constructor(private sanitizer: DomSanitizer) {} | ||
|
|
||
| transform(value: string): SafeHtml { |
There was a problem hiding this comment.
Add null/undefined guard for input value.
If value is null or undefined, marked.parse() may throw or produce unexpected output.
🛡️ Proposed fix
transform(value: string): SafeHtml {
+ if (!value) {
+ return '';
+ }
const renderer = new Renderer();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@editor/src/app/pipes/markdown.pipe.ts` at line 9, The transform method should
guard against null/undefined input before calling marked.parse; update
transform(value: string): SafeHtml in the markdown pipe to return a safe empty
string or a default SafeHtml (e.g., sanitizer.bypassSecurityTrustHtml('')) when
value is null/undefined, and only call marked.parse(value) when value is a
non-empty string; ensure the return type SafeHtml is preserved and reference
marked.parse and transform to locate the change.
| transform(value: string): SafeHtml { | ||
| const renderer = new Renderer(); | ||
| renderer.link = ({ href, title, text }: { href: string; title?: string | null; text: string }) => { | ||
| const titleAttr = title ? ` title="${title}"` : ''; | ||
| return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`; | ||
| }; | ||
| const html = marked.parse(value, { renderer }) as string; | ||
| return this.sanitizer.bypassSecurityTrustHtml(html); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
How to safely sanitize HTML with DOMPurify in Angular applications?
💡 Result:
To safely sanitize HTML with DOMPurify in Angular applications, follow these steps for XSS protection when rendering user-generated content via [innerHTML]: 1. Install DOMPurify: npm install dompurify @types/dompurify 2. For basic manual sanitization in a component (recommended for control): import { Component } from '@angular/core'; import DOMPurify from 'dompurify'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; @Component({ template: <div [innerHTML]="safeHtml"> }) export class MyComponent { rawHtml = Safe content; safeHtml: SafeHtml; constructor(private sanitizer: DomSanitizer) { const clean = DOMPurify.sanitize(this.rawHtml); this.safeHtml = this.sanitizer.bypassSecurityTrustHtml(clean); } } DOMPurify removes malicious attributes/scripts; DomSanitizer then marks it trusted for Angular. 3. For easier integration, use
@taiga-ui/dompurify (latest v5.0.1 as of 2025, Angular 19+ compatible): npm install @taiga-ui/dompurify - As pipe (simplest): import { NgDompurifyModule } from '@taiga-ui/dompurify'; @NgModule({ imports: [NgDompurifyModule] }) export class AppModule {} <div [innerHtml]="rawHtml | dompurify"> - As service: constructor(private sanitizer: NgDompurifySanitizer) {} safeHtml = this.sanitizer.sanitize(SecurityContext.HTML, rawHtml); - Global replacement: providers: [ { provide: Sanitizer, useClass: NgDompurifySanitizer } ] 4. Configure DOMPurify (e.g., restrict tags): providers: [ { provide: DOMPURIFY_CONFIG, useValue: { ALLOWED_TAGS: ['b', 'i', 'p'] } } ] Best practices: - Always sanitize untrusted input at injection point. - Use restrictive whitelists for tags/attributes. - DOMPurify > Angular's basic sanitizer for complex HTML. - Test with XSS payloads; prefer pipes/services over manual for consistency. This prevents XSS while allowing safe HTML rendering.
Citations:
- 1: https://medium.com/@vkrntteotia/safe-html-sanitization-dompurify-2c1cbdd8714f
- 2: https://www.npmjs.com/package/@tinkoff/ng-dompurify/v/2.0.0
- 3: https://registry.npmjs.org/%40taiga-ui%2Fdompurify
- 4: https://github.com/taiga-family/ng-dompurify
- 5: https://github.com/cure53/DOMPurify/blob/main/README.md
🏁 Script executed:
# Check if marked library is installed and what version
cd /tmp && npm list marked 2>/dev/null || cat package.json | grep -A2 '"marked"' || find . -name "package.json" -type f -exec grep -l "marked" {} \;Repository: berta-cms/berta
Length of output: 138
🏁 Script executed:
# Find the markdown.pipe.ts file to verify it exists and matches the snippet
fd -t f "markdown.pipe.ts"Repository: berta-cms/berta
Length of output: 96
🏁 Script executed:
# Check if DOMPurify is already installed or referenced
cd /tmp && npm list dompurify 2>/dev/null || find . -name "package.json" -type f -exec grep -l "dompurify\|DOMPurify" {} \;Repository: berta-cms/berta
Length of output: 76
🏁 Script executed:
# Search for marked library usage and configuration in the codebase
rg "marked\|Renderer" --type ts --type js -A 3 -B 1Repository: berta-cms/berta
Length of output: 41
🏁 Script executed:
# Read the markdown.pipe.ts file
cat editor/src/app/pipes/markdown.pipe.tsRepository: berta-cms/berta
Length of output: 844
🏁 Script executed:
# Find package.json to check dependencies
find . -maxdepth 2 -name "package.json" -type f | head -5Repository: berta-cms/berta
Length of output: 119
🏁 Script executed:
# Look for marked or sanitizer usage in the entire codebase
rg "marked|DomSanitizer|sanitize" --type ts --type js | head -30Repository: berta-cms/berta
Length of output: 3184
🏁 Script executed:
# Check the main package.json for marked version
cat package.json | grep -A 2 -B 2 "marked"Repository: berta-cms/berta
Length of output: 41
🏁 Script executed:
# Check editor package.json
cat editor/package.json | grep -A 2 -B 2 "marked"Repository: berta-cms/berta
Length of output: 209
🏁 Script executed:
# Search for how this markdown pipe is being used in the codebase
rg "markdown\s*['\"]|MarkdownPipe" --type ts --type html -B 2 -A 2Repository: berta-cms/berta
Length of output: 1529
🏁 Script executed:
# Check the marked v17 documentation and behavior regarding HTML escaping
# First, let's see if there's any escape/sanitization happening
rg "escape|sanitize|xss|Security" editor/src/app/pipes/ --type tsRepository: berta-cms/berta
Length of output: 990
🏁 Script executed:
# Check the ai-assistant component to understand context better
cat editor/src/app/ai-assistant/ai-assistant.component.ts | head -100Repository: berta-cms/berta
Length of output: 2964
🏁 Script executed:
# Check what marked v17 does regarding HTML sanitization by default
# Let's look at the marked package documentation or check if there are any config options
cd editor && npm ls marked 2>/dev/null | head -10Repository: berta-cms/berta
Length of output: 109
🏁 Script executed:
# Create a test to understand marked v17 behavior with HTML input
python3 << 'EOF'
import subprocess
import json
# Check if we can inspect the marked module or its documentation
result = subprocess.run(
["npm", "info", "marked@17.0.4", "--json"],
cwd="editor",
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0:
try:
info = json.loads(result.stdout)
# Look for security-related info
if "description" in info:
print("Description:", info["description"][:500])
if "repository" in info:
print("Repository:", info.get("repository", {}).get("url", "N/A"))
except:
print(result.stdout[:1000])
else:
print("Error:", result.stderr[:500])
EOFRepository: berta-cms/berta
Length of output: 154
🏁 Script executed:
# Verify marked v17 behavior with HTML by checking its actual code or docs
# Let's look for any existing security considerations or issues
python3 << 'EOF'
# Test to understand if marked v17 sanitizes by default
# marked v17 should NOT sanitize HTML by default - this is documented behavior
# The library intentionally does not sanitize to allow users to choose their sanitizer
import subprocess
# Get information about marked's current behavior
result = subprocess.run(
["npm", "view", "marked@17.0.4", "readme"],
cwd="editor",
capture_output=True,
text=True,
timeout=10
)
# Print a snippet to verify
if result.returncode == 0 and result.stdout:
lines = result.stdout.split('\n')
# Look for security or sanitization mentions
for i, line in enumerate(lines):
if any(keyword in line.lower() for keyword in ['sanitize', 'xss', 'security', 'escape', 'html']):
start = max(0, i - 2)
end = min(len(lines), i + 3)
print('\n'.join(lines[start:end]))
print("---")
else:
print("Could not fetch readme")
EOFRepository: berta-cms/berta
Length of output: 1484
🏁 Script executed:
# Let's verify the vulnerability by checking if href/title values could contain XSS payloads
# In marked, are href/title/text values escaped or raw?
python3 << 'EOF'
# Check documentation claim: marked v17 does NOT escape HTML by default
# This is intentional - users must handle sanitization themselves
# Key facts about marked v17:
# 1. Does NOT sanitize HTML by default
# 2. Passes through raw content
# 3. Does NOT escape attribute values
# 4. Users are responsible for sanitization
print("marked v17 key facts:")
print("1. HTML sanitization: DISABLED by default")
print("2. Attribute escaping: NOT APPLIED")
print("3. Responsibility: User must sanitize")
print("\nXSS Attack Vectors in current code:")
print("- href='javascript:void(0)' -> executable")
print("- title='x\" onclick=\"alert()\"' -> attribute breakout")
print("- text with <script> tags -> raw HTML injection")
EOFRepository: berta-cms/berta
Length of output: 375
🏁 Script executed:
# Check if there's a better way to handle the security attributes in marked v17
# Look for any marked configuration options for link attributes
python3 << 'EOF'
# In marked v17, after sanitization with DOMPurify,
# we lose the target="_blank" and rel="noopener noreferrer" if we remove the renderer
# Options:
# 1. Keep custom renderer but escape the values
# 2. Use marked's renderer + DOMPurify config to allow target attribute
# 3. Post-process DOMPurify output to add attributes
# The correct approach would be to ESCAPE the values, not remove the renderer
print("Correct fix approaches:")
print("\n1. ESCAPE attribute values (simpler):")
print(" Use escapeHtml() on href, title, text before interpolation")
print("\n2. SANITIZE full output (proposed diff):")
print(" Use DOMPurify on full marked output")
print(" Then handle target/rel attributes via config or post-processing")
print("\nThe review's proposed diff is incomplete:")
print("- Removes renderer entirely (loses security attributes)")
print("- Doesn't show how to re-add target/rel")
print("- DOMPurify config shown doesn't handle target attribute setup")
EOFRepository: berta-cms/berta
Length of output: 533
Critical: XSS vulnerability – marked does not sanitize HTML by default.
The marked library (v17.0.4) explicitly does not sanitize HTML and documents this as the user's responsibility. The current code has three critical security issues:
-
No HTML sanitization:
marked.parse()passes through raw HTML, allowing<script>tags and event handlers from AI responses. -
Unescaped attribute values (lines 12–13):
href,title, andtextare interpolated directly into the HTML string without escaping, enabling:href="javascript:alert('xss')"titlebreaking out of the attribute:" onclick="malicious()"textcontaining raw HTML tags
-
bypassSecurityTrustHtmlcompletely disables Angular's built-in XSS protection.
Since the pipe renders untrusted AI assistant responses, this is exploitable.
Fix: Use DOMPurify to sanitize the marked output before bypassing security:
import DOMPurify from 'dompurify';
const html = marked.parse(value, { renderer }) as string;
const sanitized = DOMPurify.sanitize(html, {
ADD_ATTR: ['target', 'rel'],
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li', 'code', 'pre'],
});
return this.sanitizer.bypassSecurityTrustHtml(sanitized);Install DOMPurify: npm install dompurify @types/dompurify``
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@editor/src/app/pipes/markdown.pipe.ts` around lines 9 - 17, The transform
method in markdown.pipe.ts uses marked.parse with a custom Renderer and then
calls this.sanitizer.bypassSecurityTrustHtml without sanitizing or escaping
attributes, which allows raw HTML, script tags and attribute injection (href,
title, text) — fix by installing and importing DOMPurify, run DOMPurify.sanitize
on the string returned by marked.parse (pass a safe ALLOWED_TAGS/ALLOWED_ATTRS
config and allow target/rel if needed), stop interpolating raw attribute values
by ensuring marked's renderer produces safe attributes (or escape
href/title/text before constructing the <a>), and only call
this.sanitizer.bypassSecurityTrustHtml on the sanitized output; keep references
to transform, Renderer, marked.parse and this.sanitizer.bypassSecurityTrustHtml
when making the change.
…aths Four AI tool config files contained an absolute machine-specific path. They are now gitignored and replaced with .example counterparts using /path/to/berta/_api_app. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary by CodeRabbit
Release Notes
New Features
Documentation
Updates