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

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions anime-watch-hub/app/api/check-platform/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,7 @@ If you encounter a geo-restriction or region block, mention that in the message.

try {
for await (const chunk of minoResponse.body as any) {
controller.enqueue(
encoder.encode(decoder.decode(chunk, { stream: true }))
)
controller.enqueue(encoder.encode(decoder.decode(chunk, { stream: true })))
}
} catch (error) {
console.error('Error streaming Mino response:', error)
Expand Down
2 changes: 1 addition & 1 deletion anime-watch-hub/components/platform-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function PlatformCard({ agent }: PlatformCardProps) {
src={agent.streamingUrl}
className="h-full w-full"
title={`${agent.platformName} live view`}
sandbox="allow-same-origin"
sandbox="allow-same-origin allow-scripts allow-forms"
/>
Comment on lines 69 to 73
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash set -euo pipefail # Verify unvalidated streaming_url -> streamingUrl data flow rg -nP --type=ts --type=tsx '\bstreaming_url\b|\bstreamingUrl\b' -C2 # Verify iframe sandbox usage and current permissions rg -nP --type=tsx '<iframe|sandbox=' -C2

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 100


🏁 Script executed:

# List repository structure to understand the codebase git ls-files | head -30

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 1292


🏁 Script executed:

# Search for platform-card.tsx regardless of reported type fd "platform-card" -t f

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 117


🏁 Script executed:

# Also search for anime-watch-hub or similar directory patterns fd -t d "anime" 2>/dev/null || true

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 89


🏁 Script executed:

# Read the platform-card.tsx file to verify the actual code cat -n anime-watch-hub/components/platform-card.tsx

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 5057


🏁 Script executed:

# Check file sizes to decide on full vs partial read wc -l anime-watch-hub/hooks/use-anime-search.ts anime-watch-hub/lib/types.ts 2>/dev/null || true

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 164


🏁 Script executed:

# Read use-anime-search.ts around lines 56-66 to verify data flow sed -n '45,100p' anime-watch-hub/hooks/use-anime-search.ts

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 2016


🏁 Script executed:

# Read types.ts to check MinoAgentState definition and streamingUrl type cat -n anime-watch-hub/lib/types.ts

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 1761


🏁 Script executed:

# Check the start of use-anime-search.ts to see where the SSE endpoint comes from head -50 anime-watch-hub/hooks/use-anime-search.ts

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 1619


Harden iframe sandbox + validate streaming URL before render

Line 72 enables scripts and forms while line 69 renders agent.streamingUrl directly. The streamingUrl originates from external SSE payloads via /api/check-platform (anime-watch-hub/hooks/use-anime-search.ts, line 64) with no origin validation or format checking before assignment to state. This creates a high-risk trust boundary: an attacker controlling the backend or intercepting the SSE stream could inject arbitrary URLs that execute scripts inside your app frame. Gate the URL with a strict HTTPS origin allowlist and remove allow-same-origin unless absolutely required.

🔒 Suggested fix (least-privilege + allowlist)
+const TRUSTED_STREAM_ORIGINS = new Set([ + 'https://www.crunchyroll.com', + 'https://www.netflix.com', + 'https://www.amazon.com/Prime-Video', + 'https://www.hulu.com', + 'https://www.funimation.com', + 'https://www.hidive.com', + 'https://www.disneyplus.com', + 'https://www.max.com', +]) + +function getTrustedStreamingUrl(raw?: string): string | null { + if (!raw) return null + try { + const u = new URL(raw) + if (u.protocol !== 'https:') return null + return TRUSTED_STREAM_ORIGINS.has(u.origin) ? u.toString() : null + } catch { + return null + } +} + export function PlatformCard({ agent }: PlatformCardProps) { + const trustedStreamingUrl = getTrustedStreamingUrl(agent.streamingUrl) ... - {agent.streamingUrl && (agent.status === 'connecting' || agent.status === 'browsing') && ( + {trustedStreamingUrl && (agent.status === 'connecting' || agent.status === 'browsing') && ( <div className="relative aspect-video w-full overflow-hidden rounded-md border bg-muted"> <iframe - src={agent.streamingUrl} + src={trustedStreamingUrl} className="h-full w-full" title={`${agent.platformName} live view`} - sandbox="allow-same-origin allow-scripts allow-forms" + sandbox="allow-scripts allow-forms" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@anime-watch-hub/components/platform-card.tsx` around lines 69 - 73, The iframe is rendering agent.streamingUrl directly and uses a permissive sandbox ("allow-same-origin allow-scripts allow-forms"); validate and gate the URL before rendering by enforcing a strict HTTPS origin allowlist and a safe URL format (e.g., hostname check + https scheme) where the SSE assigns state (the useAnimeSearch hook / the code that sets agent.streamingUrl), and remove dangerous sandbox flags (drop allow-same-origin and allow-scripts/allow-forms unless explicitly needed); only render the <iframe> in PlatformCard when the streamingUrl has passed the allowlist/validation and use a minimal sandbox (no same-origin/scripts/forms) to follow least privilege. 
<div className="absolute bottom-2 left-2">
<Badge variant="secondary" className="text-xs">
Expand Down
41 changes: 17 additions & 24 deletions anime-watch-hub/hooks/use-anime-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,40 +57,33 @@ export function useAnimeSearch() {
try {
const data = JSON.parse(line.slice(6))

if (data.streamingUrl) {
if (data.type === 'STREAMING_URL' && data.streaming_url) {
updateAgent(platform.id, {
status: 'browsing',
streamingUrl: data.streamingUrl,
streamingUrl: data.streaming_url,
statusMessage: 'Browsing platform...',
})
}

if (data.type === 'STATUS' && data.message) {
updateAgent(platform.id, { statusMessage: data.message })
if (data.type === 'PROGRESS' && data.purpose) {
updateAgent(platform.id, { statusMessage: data.purpose })
}

if (data.type === 'COMPLETE') {
let result = {
available: false,
message: 'Check completed',
if (data.status === 'failed') {
updateAgent(platform.id, {
status: 'error',
statusMessage: data.error?.message ?? 'Automation failed',
streamingUrl: undefined,
})
} else {
updateAgent(platform.id, {
status: 'complete',
result: data.result,
statusMessage: undefined,
Comment on lines +60 to +83
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash set -euo pipefail echo "== Documented Mino SSE fields ==" rg -n -C2 'streamingUrl|type":"STATUS"|resultJson' anime-watch-hub/docs/mino-api-integration.md echo echo "== Current client parser ==" sed -n '56,85p' anime-watch-hub/hooks/use-anime-search.ts echo echo "== Server proxy behavior ==" sed -n '88,97p' anime-watch-hub/app/api/check-platform/route.ts echo echo "== Downstream UI dependence on agent.result.available ==" rg -n -C2 'result\?\.available' anime-watch-hub/components/platform-card.tsx

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 4228


Fix schema mismatch between Mino SSE and client parser.

The documented Mino SSE schema (lines 256–265 in anime-watch-hub/docs/mino-api-integration.md and concrete examples at lines 395–407) sends:

  • streamingUrl as a direct field (not under type: 'STREAMING_URL')
  • type: 'STATUS' with message for progress updates
  • resultJson (stringified) in COMPLETE events

The hook parser (lines 60–83 in anime-watch-hub/hooks/use-anime-search.ts) currently looks for:

  • type: 'STREAMING_URL' with streaming_url (field structure mismatch)
  • type: 'PROGRESS' with purpose (type mismatch)
  • result field (schema has resultJson)

Since /api/check-platform/route.ts proxies the upstream stream unchanged, the parser drops all streaming URL events and progress updates, and sets result: undefined on completion. This causes anime-watch-hub/components/platform-card.tsx (lines 22, 42) to render "Not Found" regardless of the actual search result.

🔧 Proposed compatibility fix
- if (data.type === 'STREAMING_URL' && data.streaming_url) { + const streamingUrl = data.streamingUrl ?? data.streaming_url + if (streamingUrl) { updateAgent(platform.id, { status: 'browsing', - streamingUrl: data.streaming_url, + streamingUrl, statusMessage: 'Browsing platform...', }) } - if (data.type === 'PROGRESS' && data.purpose) { - updateAgent(platform.id, { statusMessage: data.purpose }) + const progressMessage = + data.type === 'STATUS' + ? data.message + : data.type === 'PROGRESS' + ? data.purpose + : undefined + if (progressMessage) { + updateAgent(platform.id, { statusMessage: progressMessage }) } if (data.type === 'COMPLETE') { if (data.status === 'failed') { updateAgent(platform.id, { @@ } else { + const result = + data.result ?? + (typeof data.resultJson === 'string' + ? JSON.parse(data.resultJson) + : data.resultJson) + updateAgent(platform.id, { status: 'complete', - result: data.result, + result, statusMessage: undefined, streamingUrl: undefined, }) } }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@anime-watch-hub/hooks/use-anime-search.ts` around lines 60 - 83, The SSE parser in use-anime-search.ts is using the wrong event shape; update the handling in the event dispatching (the code that currently checks for data.type === 'STREAMING_URL' / 'PROGRESS' / 'COMPLETE') to match the documented Mino schema: treat streamingUrl as a top-level field on events (if data.streamingUrl exists, call updateAgent(platform.id, { status: 'browsing', streamingUrl: data.streamingUrl, statusMessage: 'Browsing platform...' })), treat progress updates as type === 'STATUS' and use data.message for statusMessage (updateAgent(platform.id, { statusMessage: data.message })), and for COMPLETE events read data.resultJson (string) and JSON.parse it into result before calling updateAgent(platform.id, { status: 'complete', result: parsedResult, statusMessage: undefined }); also preserve the error branch for COMPLETE when data.status === 'failed' but read error message from data.error?.message as before. Ensure you reference these symbols: the SSE parsing block in use-anime-search.ts and the updateAgent(platform.id, ...) calls. 
streamingUrl: undefined,
})
}

if (data.resultJson) {
try {
result = typeof data.resultJson === 'string'
? JSON.parse(data.resultJson)
: data.resultJson
} catch {
// Use default result if parsing fails
}
}

updateAgent(platform.id, {
status: 'complete',
result,
statusMessage: undefined,
streamingUrl: undefined,
})
}

if (data.type === 'ERROR') {
Expand Down