Complete RedFlag codebase with two major security audit implementations.
== A-1: Ed25519 Key Rotation Support ==
Server:
- SignCommand sets SignedAt timestamp and KeyID on every signature
- signing_keys database table (migration 020) for multi-key rotation
- InitializePrimaryKey registers active key at startup
- /api/v1/public-keys endpoint for rotation-aware agents
- SigningKeyQueries for key lifecycle management
Agent:
- Key-ID-aware verification via CheckKeyRotation
- FetchAndCacheAllActiveKeys for rotation pre-caching
- Cache metadata with TTL and staleness fallback
- SecurityLogger events for key rotation and command signing
== A-2: Replay Attack Fixes (F-1 through F-7) ==
F-5 CRITICAL - RetryCommand now signs via signAndCreateCommand
F-1 HIGH - v3 format: "{agent_id}:{cmd_id}:{type}:{hash}:{ts}"
F-7 HIGH - Migration 026: expires_at column with partial index
F-6 HIGH - GetPendingCommands/GetStuckCommands filter by expires_at
F-2 HIGH - Agent-side executedIDs dedup map with cleanup
F-4 HIGH - commandMaxAge reduced from 24h to 4h
F-3 CRITICAL - Old-format commands rejected after 48h via CreatedAt
Verification fixes: migration idempotency (ETHOS #4), log format
compliance (ETHOS #1), stale comments updated.
All 24 tests passing. Docker --no-cache build verified.
See docs/ for full audit reports and deviation log (DEV-001 to DEV-019).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import React, { useState } from 'react';
|
|
import {
|
|
History,
|
|
Search,
|
|
RefreshCw,
|
|
} from 'lucide-react';
|
|
import ChatTimeline from '@/components/ChatTimeline';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
import { logApi } from '@/lib/api';
|
|
import toast from 'react-hot-toast';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
const HistoryPage: React.FC = () => {
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');
|
|
|
|
// Debounce search query
|
|
React.useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
setDebouncedSearchQuery(searchQuery);
|
|
}, 300);
|
|
|
|
return () => {
|
|
clearTimeout(timer);
|
|
};
|
|
}, [searchQuery]);
|
|
|
|
const { data: historyData, isLoading, refetch, isFetching } = useQuery({
|
|
queryKey: ['history', { search: debouncedSearchQuery }],
|
|
queryFn: async () => {
|
|
try {
|
|
const params: any = {
|
|
page: 1,
|
|
page_size: 50,
|
|
};
|
|
|
|
if (debouncedSearchQuery) {
|
|
params.search = debouncedSearchQuery;
|
|
}
|
|
|
|
const response = await logApi.getAllLogs(params);
|
|
return response;
|
|
} catch (error) {
|
|
console.error('Failed to fetch history:', error);
|
|
toast.error('Failed to fetch history');
|
|
return { logs: [], total: 0, page: 1, page_size: 50 };
|
|
}
|
|
},
|
|
refetchInterval: 30000,
|
|
});
|
|
|
|
return (
|
|
<div className="px-4 sm:px-6 lg:px-8">
|
|
{/* Header */}
|
|
<div className="mb-6">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<div className="flex items-center space-x-3">
|
|
<History className="h-8 w-8 text-indigo-600" />
|
|
<h1 className="text-2xl font-bold text-gray-900">History & Audit Log</h1>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<div className="relative">
|
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder="Search events..."
|
|
className="pl-10 pr-4 py-2 w-64 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
|
|
/>
|
|
</div>
|
|
<button
|
|
onClick={() => refetch()}
|
|
disabled={isFetching}
|
|
className="flex items-center space-x-2 px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 text-sm font-medium transition-colors disabled:opacity-50"
|
|
>
|
|
<RefreshCw className={cn("h-4 w-4", isFetching && "animate-spin")} />
|
|
<span>Refresh</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<p className="text-gray-600">
|
|
Complete chronological timeline of all system activities across all agents
|
|
</p>
|
|
</div>
|
|
|
|
{/* Timeline */}
|
|
<ChatTimeline isScopedView={false} externalSearch={debouncedSearchQuery} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default HistoryPage; |