mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat(security): implement path validation to prevent traversal attacks in session handling
This commit is contained in:
@@ -187,6 +187,8 @@ function ToolCallPanel({ toolCall, index }: ToolCallPanelProps) {
|
||||
type="button"
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="w-full flex items-center justify-between px-3 py-2.5 text-sm hover:bg-muted/50 transition-colors"
|
||||
aria-expanded={isExpanded}
|
||||
aria-controls={`toolcall-panel-${toolCall.name}-${index}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{isExpanded ? (
|
||||
@@ -194,7 +196,9 @@ function ToolCallPanel({ toolCall, index }: ToolCallPanelProps) {
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
{getToolStatusIcon(toolCall.output ? 'completed' : undefined)}
|
||||
<span aria-label={toolCall.output ? 'Tool completed' : 'Tool status unknown'}>
|
||||
{getToolStatusIcon(toolCall.output ? 'completed' : undefined)}
|
||||
</span>
|
||||
<span className="font-mono font-medium">{toolCall.name}</span>
|
||||
<span className="text-muted-foreground text-xs">#{index + 1}</span>
|
||||
</div>
|
||||
@@ -205,7 +209,10 @@ function ToolCallPanel({ toolCall, index }: ToolCallPanelProps) {
|
||||
|
||||
{/* Collapsible content */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-border/50 divide-y divide-border/50">
|
||||
<div
|
||||
id={`toolcall-panel-${toolCall.name}-${index}`}
|
||||
className="border-t border-border/50 divide-y divide-border/50"
|
||||
>
|
||||
{toolCall.arguments && (
|
||||
<div className="p-3">
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1.5">
|
||||
@@ -331,7 +338,7 @@ function TurnNode({ turn, isLatest, isLast }: TurnNodeProps) {
|
||||
</summary>
|
||||
<ul className="px-4 pb-3 space-y-1 text-sm text-muted-foreground list-disc list-inside">
|
||||
{turn.thoughts.map((thought, i) => (
|
||||
<li key={i} className="leading-relaxed pl-2">{thought}</li>
|
||||
<li key={`thought-${turn.turnNumber}-${i}`} className="leading-relaxed pl-2">{thought}</li>
|
||||
))}
|
||||
</ul>
|
||||
</details>
|
||||
@@ -348,7 +355,7 @@ function TurnNode({ turn, isLatest, isLast }: TurnNodeProps) {
|
||||
<span className="text-xs">({turn.toolCalls.length})</span>
|
||||
</div>
|
||||
{turn.toolCalls.map((tc, i) => (
|
||||
<ToolCallPanel key={i} toolCall={tc} index={i} />
|
||||
<ToolCallPanel key={`toolcall-${turn.turnNumber}-${tc.name}-${i}`} toolCall={tc} index={i} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useEffect, useRef, useCallback } from 'react';
|
||||
import { useNotificationStore } from '@/stores';
|
||||
import { useExecutionStore } from '@/stores/executionStore';
|
||||
import { useFlowStore } from '@/stores';
|
||||
import { useCliStreamStore } from '@/stores/cliStreamStore';
|
||||
import { useCliSessionStore } from '@/stores/cliSessionStore';
|
||||
import {
|
||||
handleSessionLockedMessage,
|
||||
@@ -36,7 +35,6 @@ function getStoreState() {
|
||||
const notification = useNotificationStore.getState();
|
||||
const execution = useExecutionStore.getState();
|
||||
const flow = useFlowStore.getState();
|
||||
const cliStream = useCliStreamStore.getState();
|
||||
const cliSessions = useCliSessionStore.getState();
|
||||
return {
|
||||
// Notification store
|
||||
@@ -64,8 +62,6 @@ function getStoreState() {
|
||||
addNodeOutput: execution.addNodeOutput,
|
||||
// Flow store
|
||||
updateNode: flow.updateNode,
|
||||
// CLI stream store
|
||||
addOutput: cliStream.addOutput,
|
||||
|
||||
// CLI session store (PTY-backed terminal)
|
||||
upsertCliSession: cliSessions.upsertSession,
|
||||
@@ -167,18 +163,6 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
||||
// Handle CLI messages
|
||||
if (data.type?.startsWith('CLI_')) {
|
||||
switch (data.type) {
|
||||
case 'CLI_STARTED': {
|
||||
const { executionId, tool, mode, timestamp } = data.payload;
|
||||
|
||||
// Add system message for CLI start
|
||||
stores.addOutput(executionId, {
|
||||
type: 'system',
|
||||
content: `[${new Date(timestamp).toLocaleTimeString()}] CLI execution started: ${tool} (${mode || 'default'} mode)`,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// ========== PTY CLI Sessions ==========
|
||||
case 'CLI_SESSION_CREATED': {
|
||||
const session = data.payload?.session;
|
||||
@@ -245,7 +229,7 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
||||
}
|
||||
|
||||
case 'CLI_OUTPUT': {
|
||||
const { executionId, chunkType, data: outputData, unit } = data.payload;
|
||||
const { chunkType, data: outputData, unit } = data.payload;
|
||||
|
||||
// Handle structured output
|
||||
const unitContent = unit?.content || outputData;
|
||||
@@ -301,33 +285,6 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Legacy CLI Stream Output ==========
|
||||
// Split by lines and add each line to cliStreamStore
|
||||
const lines = content.split('\n');
|
||||
lines.forEach((line: string) => {
|
||||
// Add non-empty lines, or single line if that's all we have
|
||||
if (line.trim() || lines.length === 1) {
|
||||
stores.addOutput(executionId, {
|
||||
type: unitType as any,
|
||||
content: line,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'CLI_COMPLETED': {
|
||||
const { executionId, success, duration } = data.payload;
|
||||
|
||||
const statusText = success ? 'completed successfully' : 'failed';
|
||||
const durationText = duration ? ` (${duration}ms)` : '';
|
||||
|
||||
stores.addOutput(executionId, {
|
||||
type: 'system',
|
||||
content: `[${new Date().toLocaleTimeString()}] CLI execution ${statusText}${durationText}`,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user