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>
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { useNavigate, useLocation } from 'react-router-dom';
|
|
import { setupApi } from '@/lib/api';
|
|
|
|
interface SetupCompletionCheckerProps {
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export const SetupCompletionChecker: React.FC<SetupCompletionCheckerProps> = ({ children }) => {
|
|
const [isSetupMode, setIsSetupMode] = useState<boolean | null>(null);
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
|
|
useEffect(() => {
|
|
let wasInSetup = false; // Local variable instead of state
|
|
|
|
const checkSetupStatus = async () => {
|
|
try {
|
|
const data = await setupApi.checkHealth();
|
|
|
|
const currentSetupMode = data.status === 'waiting for configuration';
|
|
|
|
// Track if we were previously in setup mode
|
|
if (currentSetupMode) {
|
|
wasInSetup = true;
|
|
}
|
|
|
|
// If we were in setup mode and now we're not, redirect to login
|
|
if (wasInSetup && !currentSetupMode && location.pathname === '/setup') {
|
|
console.log('Setup completed - redirecting to login');
|
|
navigate('/login', { replace: true });
|
|
return; // Prevent further state updates
|
|
}
|
|
|
|
setIsSetupMode(currentSetupMode);
|
|
} catch (error) {
|
|
// If we can't reach the health endpoint, assume normal mode
|
|
if (wasInSetup && location.pathname === '/setup') {
|
|
console.log('Setup completed (endpoint unreachable) - redirecting to login');
|
|
navigate('/login', { replace: true });
|
|
return;
|
|
}
|
|
setIsSetupMode(false);
|
|
}
|
|
};
|
|
|
|
checkSetupStatus();
|
|
|
|
// Check periodically for configuration changes
|
|
const interval = setInterval(checkSetupStatus, 3000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, [location.pathname, navigate]); // Removed wasInSetupMode from dependencies
|
|
|
|
// Always render children - this component only handles redirects
|
|
return <>{children}</>;
|
|
}; |