Skip to content

Conversation

@haroon0x
Copy link
Owner

@haroon0x haroon0x commented Dec 12, 2025

#19

Summary by CodeRabbit

  • New Features

    • Security scan reports now display in a multi-tab interface with real-time data population during execution.
    • Added JSON and Markdown download options for scan reports.
    • Real-time output streaming from task execution to the dashboard.
  • UI/UX Improvements

    • Added download icon to the icon library.
    • Updated execution status labels for improved clarity.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
red-loop Ready Ready Preview Comment Dec 12, 2025 5:52am

@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Walkthrough

The changes introduce output streaming and reporting capabilities across the stack. The backend webhook now includes task output data, the frontend WebSocket handler captures it, a new ScanReport component displays multi-tab security reports with export functionality, and orchestration flows transmit actual task outputs to webhooks for display.

Changes

Cohort / File(s) Summary
Backend webhook updates
backend/app/api/webhooks.py
Added "output" field to task_update broadcast payload in receive_task_update, including data.get("output") alongside existing fields.
Frontend build configuration
frontend/Dockerfile.dev
Extended COPY step to include .npmrc* files alongside package.json and package-lock.json* files into the container.
Frontend API and type definitions
frontend/app/api.ts, frontend/app/types.ts
Added optional output field to WebSocketMessage interface; enhanced WebSocket onmessage handler to ignore 'pong' messages, parse typed objects, and add logging; updated IconName type to include 'download' variant.
Frontend UI components
frontend/app/components/Icon.tsx, frontend/app/dashboard/components/ExecutionStatus.tsx
Added "download" icon SVG path to Icon component; added optional onOutputReceived callback prop to ExecutionStatus, wired output handling in WebSocket message handler, and synchronized callback via ref; updated UI labels (Clone → Clone Repository, Generate Fixes → Security Fixes, Live Execution → Execution Status).
ScanReport component
frontend/app/dashboard/components/ScanReport.tsx
New client-side component rendering multi-tab security report UI with tabbed interface (adversary, summary, fixes, report), conditional tab enablement, JSON/Markdown export downloads, formatted output display, and animated transitions via framer-motion.
Dashboard page integration
frontend/app/dashboard/page.tsx
Added ScanReport component with loading skeleton, new scanOutputs state to store per-task outputs, handleOutputReceived callback to update outputs, and integrated output passing to ExecutionStatus.
Orchestration flow updates
kestra/flows/redloop_orchestrator_v1.yaml
Removed section header comments; updated all task completion webhook bodies to include "output" field populated from downstream task outputs (adversary_kestra, summarizer_agent, defender_agent, final_report); revised ADVERSARY AI AGENT prompt to stricter vulnerability audit rules with fixed JSON format.

Sequence Diagram(s)

sequenceDiagram
    participant Kestra as Kestra Orchestration
    participant Backend as Backend API
    participant WS as WebSocket Server
    participant Frontend as Frontend Handler
    participant State as React State
    participant UI as ScanReport UI

    Kestra->>Kestra: Execute task (adversary, summarizer, etc.)
    Kestra->>Backend: POST webhook with task output
    note over Backend: Includes output field in payload
    
    Backend->>WS: Broadcast task_update with output
    note over WS: Payload contains output data
    
    WS->>Frontend: WebSocket message (task_update)
    Frontend->>Frontend: Parse WebSocketMessage<br/>Extract output field
    Frontend->>Frontend: console.log received output
    
    Frontend->>State: Call onOutputReceived(taskId, output)
    State->>State: Update scanOutputs[taskId] = output
    
    State->>UI: Re-render with updated outputs
    UI->>UI: Display in active tab<br/>Enable corresponding tab
    UI->>UI: Render formatted output or placeholder
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–25 minutes

  • ScanReport component: Most complex change; review logic for tab state management, output formatting, JSON/Markdown export generation, and error handling for missing data.
  • Output data flow: Trace the path from Kestra webhook → backend payload → WebSocket → frontend state → UI rendering to ensure type consistency and proper callback wiring.
  • Kestra orchestration changes: Verify that all webhook bodies correctly reference downstream task outputs and that the new ADVERSARY AI AGENT prompt aligns with downstream processing expectations.
  • ExecutionStatus integration: Confirm onOutputReceived callback is properly passed through and synchronizes with ref usage.

Poem

🐰 A bounty of outputs now flows,
Through webhooks and sockets it goes,
With tabs and with tabs, a report takes its shape,
Downloads and charts—no more to escape,
Security scans wear a fresh, friendly face! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of integrating Kestra with frontend and backend components across multiple files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/kestra

Comment @coderabbitai help to get the list of available commands and usage tips.

@haroon0x haroon0x merged commit b71260e into main Dec 12, 2025
4 of 5 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/api/webhooks.py (1)

43-51: Consider validating output size before broadcasting.

The output field is broadcasted without size validation. Large outputs from AI agents could overwhelm WebSocket clients or cause memory issues.

Apply this diff to add size validation:

         await manager.broadcast_update(execution_id, {
             "type": "task_update",
             "execution_id": execution_id,
             "task_id": task_id,
             "status": status,
             "message": message,
-            "output": data.get("output"),
+            "output": data.get("output") if (output := data.get("output")) and len(str(output)) < 1_000_000 else None,
             "data": data.get("data")
         })

Alternatively, log the output size for monitoring:

+        output = data.get("output")
+        if output:
+            print(f"📊 Output size: {len(str(output))} bytes")
+        
         await manager.broadcast_update(execution_id, {
             "type": "task_update",
             "execution_id": execution_id,
             "task_id": task_id,
             "status": status,
             "message": message,
-            "output": data.get("output"),
+            "output": output,
             "data": data.get("data")
         })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 091d847 and 6743c9d.

📒 Files selected for processing (9)
  • backend/app/api/webhooks.py (1 hunks)
  • frontend/Dockerfile.dev (1 hunks)
  • frontend/app/api.ts (2 hunks)
  • frontend/app/components/Icon.tsx (1 hunks)
  • frontend/app/dashboard/components/ExecutionStatus.tsx (6 hunks)
  • frontend/app/dashboard/components/ScanReport.tsx (1 hunks)
  • frontend/app/dashboard/page.tsx (4 hunks)
  • frontend/app/types.ts (1 hunks)
  • kestra/flows/redloop_orchestrator_v1.yaml (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/app/dashboard/components/ScanReport.tsx (2)
frontend/app/components/UI.tsx (2)
  • Card (200-230)
  • Button (241-299)
frontend/app/components/Icon.tsx (1)
  • Icon (12-74)
frontend/app/dashboard/page.tsx (2)
frontend/app/dashboard/components/ScanReport.tsx (1)
  • ScanReport (32-233)
frontend/app/dashboard/components/ExecutionStatus.tsx (1)
  • ExecutionStatus (22-179)
🪛 Biome (2.1.2)
frontend/app/dashboard/components/ScanReport.tsx

[error] 160-172: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (15)
frontend/app/components/Icon.tsx (1)

52-55: LGTM!

The download icon addition follows the existing pattern and the SVG path looks correct.

frontend/app/types.ts (1)

14-15: LGTM!

The type extension correctly aligns with the new download icon added to Icon.tsx.

frontend/app/api.ts (1)

48-48: LGTM!

The optional output field addition aligns with the backend changes and enables per-task output propagation.

frontend/app/dashboard/page.tsx (3)

27-30: LGTM!

The dynamic import of ScanReport follows the existing pattern for lazy-loaded components.


223-238: LGTM!

The state management for scan outputs is well-structured. The handleOutputReceived callback is properly memoized and the state initialization in handleScanStart ensures a clean slate for each scan.


295-316: LGTM!

The integration of ScanReport with ExecutionStatus is well-structured. The onOutputReceived callback properly propagates per-task outputs, and the conditional rendering ensures ScanReport only appears when there's an active execution.

kestra/flows/redloop_orchestrator_v1.yaml (1)

100-141: Excellent prompt improvements to reduce false positives.

The updated adversary prompt enforces strict validation rules requiring exact code references and file paths. This significantly reduces the risk of hallucinated vulnerabilities. The structured JSON output format ensures consistent parsing downstream.

Note: The strict requirements may reduce recall—real vulnerabilities might be missed if the AI cannot provide exact code quotes. Consider monitoring for cases where legitimate issues are not reported due to the constraints.

frontend/app/dashboard/components/ExecutionStatus.tsx (3)

19-19: LGTM!

The onOutputReceived callback prop is correctly implemented with ref-based handling to avoid stale closures. The ref is properly synchronized in the effect.

Also applies to: 22-22, 31-31, 36-36


56-63: LGTM!

The output handling logic properly validates the output (string type, non-empty) before invoking the callback. The line count summary log provides helpful feedback without exposing sensitive data.


91-95: LGTM!

The UI text improvements make the workflow stages clearer and more user-friendly.

Also applies to: 115-115

frontend/app/dashboard/components/ScanReport.tsx (5)

35-55: LGTM!

The JSON download function correctly constructs the report, creates a blob, and cleans up the object URL. The approach is standard for client-side file downloads.


57-103: LGTM!

The Markdown export provides a well-formatted security assessment document. Fallback text for missing sections ensures the export always succeeds.


109-125: LGTM!

The loading state provides clear feedback during scanning with an animated spinner and descriptive message.


183-211: LGTM!

The content area with AnimatePresence provides smooth tab transitions. The conditional rendering handles both empty states appropriately with helpful messages.


214-230: LGTM!

The footer provides useful execution tracking and visual indicators for completion status of each analysis stage.

Comment on lines +102 to +108
// Ignore ping/pong keep-alive messages
if (event.data === 'pong') return;
try {
onMessage(JSON.parse(event.data) as WebSocketMessage);
const parsed = JSON.parse(event.data) as WebSocketMessage;
console.log('📡 WebSocket message received:', parsed);
console.log('📡 Output field:', parsed.output ? `${parsed.output.substring(0, 100)}...` : 'NONE');
onMessage(parsed);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Consider security implications of logging output data.

The console logs output data which could contain sensitive information (API keys, credentials, PII) found during vulnerability scans. This could expose sensitive data in browser developer tools.

Apply this diff to sanitize logs or make them conditional:

         try {
             const parsed = JSON.parse(event.data) as WebSocketMessage;
-            console.log('📡 WebSocket message received:', parsed);
-            console.log('📡 Output field:', parsed.output ? `${parsed.output.substring(0, 100)}...` : 'NONE');
+            if (process.env.NODE_ENV === 'development') {
+                console.log('📡 WebSocket message received:', { ...parsed, output: parsed.output ? '[REDACTED]' : undefined });
+            }
             onMessage(parsed);
🤖 Prompt for AI Agents
In frontend/app/api.ts around lines 102 to 108, the code logs parsed WebSocket
payloads including parsed.output which may contain sensitive data; replace these
direct console logs with safe logging: stop logging the full parsed message and
the raw output, and instead log only non-sensitive metadata (e.g., message type,
length) and at most the first N characters with redaction of likely secrets
(tokens, keys, emails) or a fixed placeholder; make verbose output conditional
on a dev-only flag (NODE_ENV !== 'production' or a dedicated feature flag) so
production builds never emit raw payloads.

Comment on lines +159 to +172
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
disabled={!hasOutput}
className={`flex-shrink-0 px-5 py-3 flex items-center gap-2 text-sm font-medium transition-all border-b-2 ${isActive
? 'border-current bg-white/5'
: 'border-transparent hover:bg-white/5'
} ${hasOutput
? 'text-white cursor-pointer'
: 'text-[#404050] cursor-not-allowed'
}`}
style={{ color: isActive ? tab.color : undefined }}
>
<span>{tab.icon}</span>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add explicit type="button" to tab buttons.

The tab buttons lack an explicit type attribute. While these buttons are not inside a form, adding type="button" is a best practice to prevent unintended form submissions if the component is ever used within a form context.

Apply this diff:

                         <button
                             key={tab.id}
+                            type="button"
                             onClick={() => setActiveTab(tab.id)}
                             disabled={!hasOutput}
                             className={`flex-shrink-0 px-5 py-3 flex items-center gap-2 text-sm font-medium transition-all border-b-2 ${isActive

Based on learnings, static analysis hints about button types should be addressed.

📝 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.

Suggested change
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
disabled={!hasOutput}
className={`flex-shrink-0 px-5 py-3 flex items-center gap-2 text-sm font-medium transition-all border-b-2 ${isActive
? 'border-current bg-white/5'
: 'border-transparent hover:bg-white/5'
} ${hasOutput
? 'text-white cursor-pointer'
: 'text-[#404050] cursor-not-allowed'
}`}
style={{ color: isActive ? tab.color : undefined }}
>
<span>{tab.icon}</span>
<button
key={tab.id}
type="button"
onClick={() => setActiveTab(tab.id)}
disabled={!hasOutput}
className={`flex-shrink-0 px-5 py-3 flex items-center gap-2 text-sm font-medium transition-all border-b-2 ${isActive
? 'border-current bg-white/5'
: 'border-transparent hover:bg-white/5'
} ${hasOutput
? 'text-white cursor-pointer'
: 'text-[#404050] cursor-not-allowed'
}`}
style={{ color: isActive ? tab.color : undefined }}
>
<span>{tab.icon}</span>
🧰 Tools
🪛 Biome (2.1.2)

[error] 160-172: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🤖 Prompt for AI Agents
In frontend/app/dashboard/components/ScanReport.tsx around lines 159 to 172, the
tab <button> elements are missing an explicit type attribute; add type="button"
to the button JSX so the tabs cannot accidentally submit a surrounding form if
this component is ever used inside a form context, and ensure the attribute is
applied alongside the existing props (key, onClick, disabled, className, style).

WORKDIR /app

COPY package.json package-lock.json* ./
COPY package.json package-lock.json* .npmrc* ./
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Avoid copying .npmrc into the image layer if it can contain auth tokens/secrets.

COPY ... .npmrc* ... can bake private registry credentials into a Docker layer (and leak via image sharing/layer caching). Prefer BuildKit secrets for .npmrc, or ensure the repo .npmrc is tokenless (env-var substitution only) and that secrets are injected at build/run time.

Suggested approach (BuildKit secret):

-COPY package.json package-lock.json* .npmrc* ./
+COPY package.json package-lock.json* ./
 
 RUN --mount=type=cache,target=/root/.npm \
-    npm ci
+    --mount=type=secret,id=npmrc,target=/root/.npmrc \
+    npm ci

(Then build with --secret id=npmrc,src=$HOME/.npmrc or equivalent.)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/Dockerfile.dev around line 5, the Dockerfile currently copies .npmrc
into the image which may bake auth tokens into a layer; remove .npmrc (and any
glob that matches it) from the COPY line so it is not added to the image, and
instead switch to using BuildKit secrets (pass your local .npmrc with --secret
id=npmrc,src=~/.npmrc at build time) or ensure the repository .npmrc contains no
secrets and uses env-var substitution at runtime; update the Dockerfile to read
the secret at build time via the BuildKit secret mechanism or otherwise inject
credentials at container run time rather than copying the file into an image
layer.

contentType: application/json
body: |
{"execution_id": "{{ execution.id }}", "task_id": "adversary_kestra", "status": "SUCCESS", "message": "🔴 Adversary scan complete - found vulnerabilities"}
{"execution_id": "{{ execution.id }}", "task_id": "adversary_kestra", "status": "SUCCESS", "message": "🔴 Adversary scan complete", "output": {{ outputs.adversary_kestra.textOutput | default('"No output"') | json }}}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Kestra json filter escaping nested quotes special characters

💡 Result:

Short answer: use Kestra/Pebble's toJson or jq(@JSON) to produce properly escaped JSON strings, and use bracket notation for keys with special characters.

Examples:

  • Escape a string value in a template: {{ myString | toJson }} — toJson wraps in quotes and escapes " and . (kestra.io) [1]
  • Produce a JSON-escaped string with jq: {{ myString | jq('@JSON') | first }} — jq's @JSON emits a JSON-quoted/escaped representation (use first because jq returns an array in expressions). (kestra.io) [2][1]
  • Access keys that contain dots, hyphens or quotes using bracket notation: {{ outputs.task['weird-key'] }} or {{ obj['key.with.dots'] }}. (kestra.io) [1]

Notes on nested quoting inside HTTP bodies:

  • Build the body using toJson or jq to avoid manual escaping. Example:
    message: "{{ { text: myString } | toJson }}"
    or
    body: "{{ { text: (myString | jq('@JSON') | first) } | toJson }}" — both ensure nested quotes and control chars are escaped for JSON. (kestra.io) [1][2]

If you want, paste a specific failing example and I’ll show the exact template with escaping.

Citations:


Use toJson or jq('@json') instead of | json for proper JSON escaping.

The webhook bodies should use Kestra's standard JSON filters to handle nested quotes and special characters. Replace {{ outputs.X.textOutput | default('"No output"') | json }} with {{ outputs.X.textOutput | default('No output') | toJson }} (or {{ outputs.X.textOutput | default('No output') | jq('@json') | first }}). The | json filter is not a documented Kestra filter; use toJson to properly escape strings within JSON context. Also remove literal quotes from the default value—use plain strings and let toJson handle the escaping.

🤖 Prompt for AI Agents
In kestra/flows/redloop_orchestrator_v1.yaml around line 150, the template uses
the nonstandard "| json" filter and includes literal quotes in the default value
which can produce incorrect escaping; replace the expression to use Kestra's
toJson (or jq('@json') | first) and remove the extra literal quotes in the
default string so it becomes default('No output') piped into toJson to ensure
proper JSON escaping of nested quotes/special characters.

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.

2 participants