groove-dev 0.27.42 → 0.27.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/default/groovedev-beta-auth-endpoint.md +166 -0
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +619 -0
- package/node_modules/@groove-dev/daemon/src/firstrun.js +11 -0
- package/node_modules/@groove-dev/daemon/src/index.js +28 -0
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +1 -1
- package/node_modules/@groove-dev/daemon/src/providers/groove-network.js +114 -0
- package/node_modules/@groove-dev/daemon/src/providers/index.js +2 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BoIbnaqa.js +8607 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CyVj0fHl.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.jsx +3 -0
- package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +5 -0
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +7 -3
- package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +12 -0
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +25 -7
- package/node_modules/@groove-dev/gui/src/components/network/network-status.jsx +164 -0
- package/node_modules/@groove-dev/gui/src/components/network/node-details.jsx +66 -0
- package/node_modules/@groove-dev/gui/src/components/network/node-toggle.jsx +172 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +191 -0
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/views/network.jsx +227 -0
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +88 -1
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +619 -0
- package/packages/daemon/src/firstrun.js +11 -0
- package/packages/daemon/src/index.js +28 -0
- package/packages/daemon/src/providers/claude-code.js +1 -1
- package/packages/daemon/src/providers/groove-network.js +114 -0
- package/packages/daemon/src/providers/index.js +2 -0
- package/packages/gui/dist/assets/index-BoIbnaqa.js +8607 -0
- package/packages/gui/dist/assets/index-CyVj0fHl.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.jsx +3 -0
- package/packages/gui/src/components/editor/terminal.jsx +5 -0
- package/packages/gui/src/components/layout/activity-bar.jsx +7 -3
- package/packages/gui/src/components/layout/status-bar.jsx +12 -0
- package/packages/gui/src/components/layout/terminal-panel.jsx +25 -7
- package/packages/gui/src/components/network/network-status.jsx +164 -0
- package/packages/gui/src/components/network/node-details.jsx +66 -0
- package/packages/gui/src/components/network/node-toggle.jsx +172 -0
- package/packages/gui/src/stores/groove.js +191 -0
- package/packages/gui/src/views/agents.jsx +1 -1
- package/packages/gui/src/views/network.jsx +227 -0
- package/packages/gui/src/views/settings.jsx +88 -1
- package/analyist/groove-security-audit.md +0 -323
- package/node_modules/@groove-dev/gui/dist/assets/index-C1C2biHU.js +0 -8607
- package/node_modules/@groove-dev/gui/dist/assets/index-Dx7i-7_K.css +0 -1
- package/packages/gui/dist/assets/index-C1C2biHU.js +0 -8607
- package/packages/gui/dist/assets/index-Dx7i-7_K.css +0 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { useGrooveStore } from '../stores/groove';
|
|
4
|
+
import { ScrollArea } from '../components/ui/scroll-area';
|
|
5
|
+
import { StatusDot } from '../components/ui/status-dot';
|
|
6
|
+
import { Badge } from '../components/ui/badge';
|
|
7
|
+
import { Button } from '../components/ui/button';
|
|
8
|
+
import { Dialog, DialogContent, DialogTrigger } from '../components/ui/dialog';
|
|
9
|
+
import { NodeToggle } from '../components/network/node-toggle';
|
|
10
|
+
import { NodeDetails } from '../components/network/node-details';
|
|
11
|
+
import { NetworkStatus } from '../components/network/network-status';
|
|
12
|
+
import { Globe, Download, Check, AlertCircle, Loader2, Trash2 } from 'lucide-react';
|
|
13
|
+
|
|
14
|
+
const REQUIREMENTS = [
|
|
15
|
+
'Python 3.10 or higher',
|
|
16
|
+
'~2 GB disk space for model shards',
|
|
17
|
+
'8 GB+ RAM recommended',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function InstallProgress({ progress }) {
|
|
21
|
+
const percent = Math.max(0, Math.min(100, Number.isFinite(progress.percent) ? progress.percent : 0));
|
|
22
|
+
return (
|
|
23
|
+
<div className="w-full flex flex-col gap-3">
|
|
24
|
+
<div className="h-2 w-full rounded-full bg-surface-3 overflow-hidden">
|
|
25
|
+
<div
|
|
26
|
+
className="h-full rounded-full bg-accent transition-all duration-500 ease-out"
|
|
27
|
+
style={{ width: `${percent}%` }}
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="flex items-center justify-between text-2xs font-mono text-text-3 tabular-nums">
|
|
31
|
+
<div className="flex items-center gap-2 text-text-2 font-sans">
|
|
32
|
+
<Loader2 size={12} className="animate-spin text-accent" />
|
|
33
|
+
<span className="truncate">{progress.message || 'Installing…'}</span>
|
|
34
|
+
</div>
|
|
35
|
+
<span>{percent}%</span>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function InstallError({ message, onRetry }) {
|
|
42
|
+
return (
|
|
43
|
+
<div className="w-full flex flex-col gap-3">
|
|
44
|
+
<div className="rounded-md border border-danger/40 bg-danger/10 px-4 py-3 flex items-start gap-2.5 text-left">
|
|
45
|
+
<AlertCircle size={14} className="text-danger flex-shrink-0 mt-0.5" />
|
|
46
|
+
<div className="flex-1 min-w-0">
|
|
47
|
+
<div className="text-xs font-semibold text-danger font-sans mb-0.5">Install failed</div>
|
|
48
|
+
<div className="text-xs text-text-1 font-sans break-words">{message}</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<Button variant="primary" size="lg" onClick={onRetry} className="w-full">
|
|
52
|
+
<Download size={14} />
|
|
53
|
+
Retry Install
|
|
54
|
+
</Button>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function InstallGate() {
|
|
60
|
+
const installNetworkPackage = useGrooveStore((s) => s.installNetworkPackage);
|
|
61
|
+
const progress = useGrooveStore((s) => s.networkInstallProgress);
|
|
62
|
+
const { installing, error } = progress;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="flex flex-col items-center justify-center min-h-full px-6 py-12">
|
|
66
|
+
<div className="w-full max-w-md flex flex-col items-center text-center">
|
|
67
|
+
<div className="mb-5 rounded-full bg-surface-2 border border-border-subtle p-5">
|
|
68
|
+
<Globe size={48} className="text-text-3" strokeWidth={1.25} />
|
|
69
|
+
</div>
|
|
70
|
+
<h3 className="text-base font-semibold text-text-0 font-sans mb-2">
|
|
71
|
+
Install Groove Network
|
|
72
|
+
</h3>
|
|
73
|
+
<p className="text-sm text-text-2 font-sans leading-relaxed mb-6">
|
|
74
|
+
The network package enables decentralized LLM inference. Contribute your compute power or run models across the Groove network.
|
|
75
|
+
</p>
|
|
76
|
+
|
|
77
|
+
<div className="w-full rounded-md border border-border-subtle bg-surface-1 px-4 py-3 mb-6">
|
|
78
|
+
<div className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-2 text-left">
|
|
79
|
+
Requirements
|
|
80
|
+
</div>
|
|
81
|
+
<ul className="flex flex-col gap-1.5">
|
|
82
|
+
{REQUIREMENTS.map((req) => (
|
|
83
|
+
<li key={req} className="flex items-center gap-2 text-xs font-sans text-text-1 text-left">
|
|
84
|
+
<Check size={12} className="text-accent flex-shrink-0" />
|
|
85
|
+
<span>{req}</span>
|
|
86
|
+
</li>
|
|
87
|
+
))}
|
|
88
|
+
</ul>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{installing ? (
|
|
92
|
+
<InstallProgress progress={progress} />
|
|
93
|
+
) : error ? (
|
|
94
|
+
<InstallError message={error} onRetry={() => installNetworkPackage()} />
|
|
95
|
+
) : (
|
|
96
|
+
<>
|
|
97
|
+
<Button
|
|
98
|
+
variant="primary"
|
|
99
|
+
size="lg"
|
|
100
|
+
onClick={() => installNetworkPackage()}
|
|
101
|
+
className="w-full"
|
|
102
|
+
>
|
|
103
|
+
<Download size={14} />
|
|
104
|
+
Install Network Package
|
|
105
|
+
</Button>
|
|
106
|
+
<p className="text-2xs font-sans text-text-3 mt-3">
|
|
107
|
+
This will download and set up the Groove Network runtime (~500 MB)
|
|
108
|
+
</p>
|
|
109
|
+
</>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function UninstallButton() {
|
|
117
|
+
const [open, setOpen] = useState(false);
|
|
118
|
+
const uninstallNetworkPackage = useGrooveStore((s) => s.uninstallNetworkPackage);
|
|
119
|
+
const [busy, setBusy] = useState(false);
|
|
120
|
+
|
|
121
|
+
const confirm = async () => {
|
|
122
|
+
setBusy(true);
|
|
123
|
+
try {
|
|
124
|
+
await uninstallNetworkPackage();
|
|
125
|
+
setOpen(false);
|
|
126
|
+
} catch { /* toast already shown */ }
|
|
127
|
+
finally { setBusy(false); }
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
132
|
+
<DialogTrigger asChild>
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
className="inline-flex items-center gap-1.5 text-2xs font-sans text-text-3 hover:text-danger transition-colors"
|
|
136
|
+
>
|
|
137
|
+
<Trash2 size={11} />
|
|
138
|
+
Uninstall Network Package
|
|
139
|
+
</button>
|
|
140
|
+
</DialogTrigger>
|
|
141
|
+
<DialogContent title="Uninstall Network Package" description="Confirm uninstall">
|
|
142
|
+
<div className="px-5 py-4 flex flex-col gap-3">
|
|
143
|
+
<p className="text-sm text-text-1 font-sans leading-relaxed">
|
|
144
|
+
This will stop your node and remove the network package from <span className="font-mono text-text-2">~/.groove/network</span>.
|
|
145
|
+
</p>
|
|
146
|
+
<p className="text-xs text-text-3 font-sans leading-relaxed">
|
|
147
|
+
Your identity (<span className="font-mono">~/.groove/node_key.json</span>) will be preserved — you can reinstall later without losing your wallet.
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
<div className="flex items-center justify-end gap-2 px-5 py-3 border-t border-border-subtle bg-surface-0">
|
|
151
|
+
<Button variant="ghost" size="sm" onClick={() => setOpen(false)} disabled={busy}>Cancel</Button>
|
|
152
|
+
<Button variant="danger" size="sm" onClick={confirm} disabled={busy}>
|
|
153
|
+
{busy ? <Loader2 size={12} className="animate-spin" /> : <Trash2 size={12} />}
|
|
154
|
+
Uninstall
|
|
155
|
+
</Button>
|
|
156
|
+
</div>
|
|
157
|
+
</DialogContent>
|
|
158
|
+
</Dialog>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default function NetworkView() {
|
|
163
|
+
const fetchNetworkNodeStatus = useGrooveStore((s) => s.fetchNetworkNodeStatus);
|
|
164
|
+
const fetchNetworkStatus = useGrooveStore((s) => s.fetchNetworkStatus);
|
|
165
|
+
const node = useGrooveStore((s) => s.networkNode);
|
|
166
|
+
const installed = useGrooveStore((s) => s.networkInstalled);
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
fetchNetworkNodeStatus();
|
|
170
|
+
if (installed) {
|
|
171
|
+
fetchNetworkStatus();
|
|
172
|
+
const interval = setInterval(() => { fetchNetworkStatus(); }, 10000);
|
|
173
|
+
return () => clearInterval(interval);
|
|
174
|
+
}
|
|
175
|
+
}, [fetchNetworkNodeStatus, fetchNetworkStatus, installed]);
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div className="flex flex-col h-full">
|
|
179
|
+
{/* Header */}
|
|
180
|
+
<div className="flex items-center gap-3 px-4 py-2.5 bg-surface-1 border-b border-border flex-shrink-0">
|
|
181
|
+
<Globe size={14} className="text-accent" />
|
|
182
|
+
<h2 className="text-sm font-semibold text-text-0 font-sans">Groove Network</h2>
|
|
183
|
+
<Badge variant="purple">Early Access</Badge>
|
|
184
|
+
<div className="flex-1" />
|
|
185
|
+
{installed && (
|
|
186
|
+
<>
|
|
187
|
+
<UninstallButton />
|
|
188
|
+
<div className="flex items-center gap-1.5 text-2xs font-sans text-text-3">
|
|
189
|
+
<StatusDot status={node.active ? 'running' : 'crashed'} size="sm" />
|
|
190
|
+
{node.active ? 'Contributing' : 'Idle'}
|
|
191
|
+
</div>
|
|
192
|
+
</>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
{/* Body */}
|
|
197
|
+
<ScrollArea className="flex-1">
|
|
198
|
+
{!installed ? (
|
|
199
|
+
<InstallGate />
|
|
200
|
+
) : (
|
|
201
|
+
<div className="p-4 grid grid-cols-1 xl:grid-cols-2 gap-4">
|
|
202
|
+
{/* Left column — node operator */}
|
|
203
|
+
<div className="flex flex-col gap-3 min-w-0">
|
|
204
|
+
<div>
|
|
205
|
+
<div className="flex items-center gap-2 mb-2 px-0.5">
|
|
206
|
+
<span className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider">Node Operator</span>
|
|
207
|
+
<div className="flex-1 h-px bg-border-subtle" />
|
|
208
|
+
</div>
|
|
209
|
+
<NodeToggle />
|
|
210
|
+
</div>
|
|
211
|
+
<NodeDetails />
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Right column — network status */}
|
|
215
|
+
<div className="flex flex-col gap-3 min-w-0">
|
|
216
|
+
<div className="flex items-center gap-2 px-0.5">
|
|
217
|
+
<span className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider">Network Status</span>
|
|
218
|
+
<div className="flex-1 h-px bg-border-subtle" />
|
|
219
|
+
</div>
|
|
220
|
+
<NetworkStatus />
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
)}
|
|
224
|
+
</ScrollArea>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
@@ -15,7 +15,7 @@ import { fmtUptime } from '../lib/format';
|
|
|
15
15
|
import {
|
|
16
16
|
Key, Eye, EyeOff, Check, Cpu,
|
|
17
17
|
FolderOpen, FolderSearch, Users, Gauge,
|
|
18
|
-
ShieldCheck, Settings,
|
|
18
|
+
ShieldCheck, Settings, Lock,
|
|
19
19
|
Newspaper, Radio, Send, MessageSquare, MessageCircle,
|
|
20
20
|
Plus, Trash2, Plug, PlugZap, TestTube, X, HelpCircle, ExternalLink,
|
|
21
21
|
} from 'lucide-react';
|
|
@@ -940,6 +940,91 @@ function AddGatewayCard({ existingTypes, onAdd }) {
|
|
|
940
940
|
);
|
|
941
941
|
}
|
|
942
942
|
|
|
943
|
+
/* ── Early Access Section ─────────────────────────────────── */
|
|
944
|
+
|
|
945
|
+
function EarlyAccessSection() {
|
|
946
|
+
const networkUnlocked = useGrooveStore((s) => s.networkUnlocked);
|
|
947
|
+
const activateBeta = useGrooveStore((s) => s.activateBeta);
|
|
948
|
+
const deactivateBeta = useGrooveStore((s) => s.deactivateBeta);
|
|
949
|
+
const [code, setCode] = useState('');
|
|
950
|
+
const [submitting, setSubmitting] = useState(false);
|
|
951
|
+
const [error, setError] = useState('');
|
|
952
|
+
|
|
953
|
+
useEffect(() => {
|
|
954
|
+
if (!error) return;
|
|
955
|
+
const t = setTimeout(() => setError(''), 3000);
|
|
956
|
+
return () => clearTimeout(t);
|
|
957
|
+
}, [error]);
|
|
958
|
+
|
|
959
|
+
async function handleSubmit() {
|
|
960
|
+
const trimmed = code.trim();
|
|
961
|
+
if (!trimmed || submitting) return;
|
|
962
|
+
setSubmitting(true);
|
|
963
|
+
setError('');
|
|
964
|
+
try {
|
|
965
|
+
await activateBeta(trimmed);
|
|
966
|
+
setCode('');
|
|
967
|
+
} catch (err) {
|
|
968
|
+
setError(err.message || 'Invalid code');
|
|
969
|
+
} finally {
|
|
970
|
+
setSubmitting(false);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
async function handleDeactivate() {
|
|
975
|
+
try { await deactivateBeta(); } catch { /* toast handled in store */ }
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return (
|
|
979
|
+
<div>
|
|
980
|
+
<div className="flex items-center gap-2 mb-2.5 px-0.5">
|
|
981
|
+
<span className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider">Early Access</span>
|
|
982
|
+
<div className="flex-1 h-px bg-border-subtle" />
|
|
983
|
+
</div>
|
|
984
|
+
<div className="rounded-lg border border-border-subtle bg-surface-1 px-4 py-3.5 max-w-md">
|
|
985
|
+
{networkUnlocked ? (
|
|
986
|
+
<div className="flex items-center gap-2.5">
|
|
987
|
+
<div className="w-6 h-6 rounded-full bg-success/10 flex items-center justify-center flex-shrink-0">
|
|
988
|
+
<Check size={12} className="text-success" />
|
|
989
|
+
</div>
|
|
990
|
+
<div className="flex-1 text-xs font-sans text-text-1">Early access enabled</div>
|
|
991
|
+
<button
|
|
992
|
+
onClick={handleDeactivate}
|
|
993
|
+
className="text-2xs text-text-4 hover:text-danger cursor-pointer font-sans"
|
|
994
|
+
>
|
|
995
|
+
Deactivate
|
|
996
|
+
</button>
|
|
997
|
+
</div>
|
|
998
|
+
) : (
|
|
999
|
+
<div className="flex items-center gap-2">
|
|
1000
|
+
<Lock size={12} className="text-text-4 flex-shrink-0" />
|
|
1001
|
+
<input
|
|
1002
|
+
value={code}
|
|
1003
|
+
onChange={(e) => setCode(e.target.value)}
|
|
1004
|
+
onKeyDown={(e) => e.key === 'Enter' && handleSubmit()}
|
|
1005
|
+
type="text"
|
|
1006
|
+
placeholder="Enter invite code"
|
|
1007
|
+
className="flex-1 h-8 px-2.5 text-xs bg-surface-0 border border-border rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
1008
|
+
/>
|
|
1009
|
+
<Button
|
|
1010
|
+
variant="primary"
|
|
1011
|
+
size="sm"
|
|
1012
|
+
onClick={handleSubmit}
|
|
1013
|
+
disabled={!code.trim() || submitting}
|
|
1014
|
+
className="h-8 text-xs px-3"
|
|
1015
|
+
>
|
|
1016
|
+
{submitting ? '...' : 'Submit'}
|
|
1017
|
+
</Button>
|
|
1018
|
+
</div>
|
|
1019
|
+
)}
|
|
1020
|
+
{error && !networkUnlocked && (
|
|
1021
|
+
<div className="mt-2 text-2xs text-danger font-sans">{error}</div>
|
|
1022
|
+
)}
|
|
1023
|
+
</div>
|
|
1024
|
+
</div>
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
943
1028
|
/* ── Main Settings View ────────────────────────────────────── */
|
|
944
1029
|
|
|
945
1030
|
export default function SettingsView() {
|
|
@@ -1200,6 +1285,8 @@ export default function SettingsView() {
|
|
|
1200
1285
|
</div>
|
|
1201
1286
|
)}
|
|
1202
1287
|
|
|
1288
|
+
{/* ═══════ EARLY ACCESS ═══════ */}
|
|
1289
|
+
<EarlyAccessSection />
|
|
1203
1290
|
|
|
1204
1291
|
</div>
|
|
1205
1292
|
</ScrollArea>
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
GROOVE SECURITY AUDIT & ANALYSIS
|
|
2
|
-
=================================
|
|
3
|
-
|
|
4
|
-
Date: April 17, 2026
|
|
5
|
-
Auditor: GROOVE Security Agent (internal, automated)
|
|
6
|
-
Version: v0.27.41
|
|
7
|
-
Scope: Full daemon, GUI, CLI, and provider architecture review
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
EXECUTIVE SUMMARY
|
|
11
|
-
-----------------
|
|
12
|
-
|
|
13
|
-
Groove is a localhost-only process manager for AI coding agents. Its security model is
|
|
14
|
-
fundamentally different from tools that bind to open ports or proxy credentials through
|
|
15
|
-
a server. The daemon hard-blocks network exposure, encrypts stored credentials with
|
|
16
|
-
machine-bound keys, validates all inputs against strict schemas, and isolates agents
|
|
17
|
-
through scope-based file locks enforced at runtime.
|
|
18
|
-
|
|
19
|
-
This report covers 12 security areas across the full codebase.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
================================================================================
|
|
23
|
-
1. NETWORK BINDING -- LOCALHOST ONLY (ENFORCED)
|
|
24
|
-
================================================================================
|
|
25
|
-
|
|
26
|
-
The daemon binds to 127.0.0.1:31415. This is not just a default -- it is a hard
|
|
27
|
-
security policy. If anyone attempts to bind to 0.0.0.0 or ::, the daemon prints an
|
|
28
|
-
error and exits immediately. The process refuses to start.
|
|
29
|
-
|
|
30
|
-
Remote access is only available through two channels, both encrypted and authenticated:
|
|
31
|
-
|
|
32
|
-
- SSH tunnel (groove connect)
|
|
33
|
-
- Tailscale private mesh network (--host tailscale)
|
|
34
|
-
|
|
35
|
-
There is no configuration path that exposes Groove to the open internet.
|
|
36
|
-
|
|
37
|
-
Result: PASS
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
================================================================================
|
|
41
|
-
2. CORS -- RESTRICTIVE ORIGIN VALIDATION
|
|
42
|
-
================================================================================
|
|
43
|
-
|
|
44
|
-
Every HTTP request is checked against a strict origin whitelist. Only requests from
|
|
45
|
-
localhost, 127.0.0.1, or the explicitly bound Tailscale interface are allowed. Any
|
|
46
|
-
other origin is silently rejected -- the browser never receives a permissive CORS
|
|
47
|
-
header, so cross-origin requests from external websites are blocked automatically.
|
|
48
|
-
|
|
49
|
-
Result: PASS
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
================================================================================
|
|
53
|
-
3. WEBSOCKET SECURITY -- ORIGIN VERIFICATION + FEDERATION SIGNING
|
|
54
|
-
================================================================================
|
|
55
|
-
|
|
56
|
-
WebSocket upgrade requests go through the same origin check as HTTP. Non-whitelisted
|
|
57
|
-
origins have their sockets destroyed immediately -- the connection is terminated before
|
|
58
|
-
any data is exchanged.
|
|
59
|
-
|
|
60
|
-
Federation connections (daemon-to-daemon) require signed headers with a daemon ID and
|
|
61
|
-
cryptographic signature. Missing or invalid headers result in immediate socket
|
|
62
|
-
destruction.
|
|
63
|
-
|
|
64
|
-
Result: PASS
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
================================================================================
|
|
68
|
-
4. INPUT VALIDATION -- STRICT SCHEMA ENFORCEMENT
|
|
69
|
-
================================================================================
|
|
70
|
-
|
|
71
|
-
All API inputs are validated against strict patterns and size limits before processing:
|
|
72
|
-
|
|
73
|
-
Role: Alphanumeric plus dash/underscore, max 50 characters
|
|
74
|
-
Name: Alphanumeric plus dash/underscore, max 64 characters
|
|
75
|
-
Scope patterns: Max 20 patterns, each max 200 characters
|
|
76
|
-
Scope paths: No ".." (path traversal), no "/" prefix (absolute paths), no null bytes
|
|
77
|
-
Prompt: Max 50,000 characters
|
|
78
|
-
Permission: Must be "auto" or "full" (whitelist)
|
|
79
|
-
Unknown fields: Silently stripped -- only safe fields are accepted
|
|
80
|
-
|
|
81
|
-
Path traversal, absolute path injection, and null byte injection are all explicitly
|
|
82
|
-
rejected at the validation layer.
|
|
83
|
-
|
|
84
|
-
Result: PASS
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
================================================================================
|
|
88
|
-
5. CREDENTIAL ENCRYPTION -- AES-256-GCM WITH MACHINE-BOUND KEYS
|
|
89
|
-
================================================================================
|
|
90
|
-
|
|
91
|
-
Stored API keys are encrypted using AES-256-GCM, which is authenticated encryption --
|
|
92
|
-
it provides both confidentiality (nobody can read the key) and integrity (nobody can
|
|
93
|
-
tamper with the ciphertext without detection).
|
|
94
|
-
|
|
95
|
-
The encryption key is derived via scrypt from a machine-specific seed that incorporates
|
|
96
|
-
the machine's hostname, home directory path, and a 256-bit random seed. Each encryption
|
|
97
|
-
operation uses a fresh random 128-bit initialization vector.
|
|
98
|
-
|
|
99
|
-
Key properties:
|
|
100
|
-
|
|
101
|
-
- Credentials copied to another machine are unrecoverable
|
|
102
|
-
- The GCM authentication tag detects any tampering
|
|
103
|
-
- All credential files are set to owner-only read/write permissions (0o600)
|
|
104
|
-
- The random seed file itself is also owner-only (0o600)
|
|
105
|
-
|
|
106
|
-
Result: PASS
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
================================================================================
|
|
110
|
-
6. PROCESS SPAWNING -- NO SHELL INJECTION
|
|
111
|
-
================================================================================
|
|
112
|
-
|
|
113
|
-
Agent processes are spawned using Node.js spawn() with an arguments array, never
|
|
114
|
-
through a shell. This is a critical distinction -- arguments are passed as discrete
|
|
115
|
-
values, not concatenated into a string that gets interpreted by bash or zsh.
|
|
116
|
-
|
|
117
|
-
User-controlled values like model names, prompts, and agent roles are passed as literal
|
|
118
|
-
arguments. The "shell: true" option is never set anywhere in the codebase. No string
|
|
119
|
-
interpolation occurs in command construction.
|
|
120
|
-
|
|
121
|
-
Result: PASS
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
================================================================================
|
|
125
|
-
7. SCOPE-BASED AGENT ISOLATION (KNOCK PROTOCOL)
|
|
126
|
-
================================================================================
|
|
127
|
-
|
|
128
|
-
Groove implements a scope-based file isolation system that operates at two stages:
|
|
129
|
-
|
|
130
|
-
Spawn-Time Collision Check:
|
|
131
|
-
When an agent is spawned, its declared file scope patterns are checked against all
|
|
132
|
-
running agents. If two agents' scopes overlap, the second spawn fails. Two agents
|
|
133
|
-
cannot be assigned to the same files simultaneously.
|
|
134
|
-
|
|
135
|
-
Runtime Knock Protocol:
|
|
136
|
-
Every file operation an agent attempts triggers a validation check before it executes.
|
|
137
|
-
The lock manager verifies the target file path against the agent's registered scope
|
|
138
|
-
patterns. If an agent tries to write outside its scope, the operation is denied and
|
|
139
|
-
the attempt is logged to the audit trail.
|
|
140
|
-
|
|
141
|
-
Working Directory Containment:
|
|
142
|
-
Agent working directories must reside within the project directory. Paths outside the
|
|
143
|
-
project boundary are rejected at spawn time.
|
|
144
|
-
|
|
145
|
-
Important caveat: This is orchestration-level isolation, not OS-level sandboxing. Agents
|
|
146
|
-
run as the same Unix user as the daemon. The knock protocol prevents agents from
|
|
147
|
-
conflicting with each other through the tool layer, but does not provide kernel-level
|
|
148
|
-
containment like containers or seccomp. The isolation is enforced at the coordination
|
|
149
|
-
layer, which is effective for its purpose but distinct from a full sandbox.
|
|
150
|
-
|
|
151
|
-
Result: PASS
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
================================================================================
|
|
155
|
-
8. PROTOTYPE POLLUTION PROTECTION
|
|
156
|
-
================================================================================
|
|
157
|
-
|
|
158
|
-
The agent registry only accepts updates to a defined whitelist of safe fields. Any
|
|
159
|
-
attempt to set unknown fields -- including __proto__ or constructor -- is silently
|
|
160
|
-
dropped.
|
|
161
|
-
|
|
162
|
-
WebSocket message parsing explicitly checks for and rejects messages containing
|
|
163
|
-
__proto__ or constructor keys. This prevents attackers from manipulating the JavaScript
|
|
164
|
-
prototype chain through crafted payloads.
|
|
165
|
-
|
|
166
|
-
Result: PASS
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
================================================================================
|
|
170
|
-
9. FILE PERMISSIONS -- OWNER-ONLY ACROSS ALL SENSITIVE FILES
|
|
171
|
-
================================================================================
|
|
172
|
-
|
|
173
|
-
All sensitive files are created with 0o600 permissions (owner read/write only, no
|
|
174
|
-
group or world access):
|
|
175
|
-
|
|
176
|
-
Agent logs
|
|
177
|
-
Credential seed file
|
|
178
|
-
Credential store
|
|
179
|
-
Audit log
|
|
180
|
-
Federation private keys
|
|
181
|
-
Integration credential files
|
|
182
|
-
|
|
183
|
-
The only exception is the federation public key, which is intentionally set to 0o644
|
|
184
|
-
(world-readable) since it is designed to be shared.
|
|
185
|
-
|
|
186
|
-
Result: PASS
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
================================================================================
|
|
190
|
-
10. WHAT GROOVE DOES NOT DO
|
|
191
|
-
================================================================================
|
|
192
|
-
|
|
193
|
-
This is architecturally significant and differentiates Groove from tools that proxy
|
|
194
|
-
credentials:
|
|
195
|
-
|
|
196
|
-
Proxy API calls to Claude/OpenAI/Gemini? No.
|
|
197
|
-
Touch OAuth tokens? No.
|
|
198
|
-
Impersonate AI providers? No.
|
|
199
|
-
Store user subscription credentials? No.
|
|
200
|
-
Forward agent credentials through the daemon? No.
|
|
201
|
-
|
|
202
|
-
Each agent process (Claude Code, Codex, Gemini CLI) runs as a standalone executable
|
|
203
|
-
with its own authentication. The daemon never relays API calls or handles provider
|
|
204
|
-
credentials on behalf of agents.
|
|
205
|
-
|
|
206
|
-
The one exception: Groove's journalist feature makes direct Anthropic API calls using
|
|
207
|
-
a locally-stored API key for internal project synthesis. This is the daemon's own
|
|
208
|
-
feature -- it does not proxy on behalf of any agent.
|
|
209
|
-
|
|
210
|
-
Result: PASS
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
================================================================================
|
|
214
|
-
11. AUDIT TRAIL
|
|
215
|
-
================================================================================
|
|
216
|
-
|
|
217
|
-
All state-changing operations are logged to an append-only audit file:
|
|
218
|
-
|
|
219
|
-
Knock protocol decisions (allowed and denied) with agent ID, tool, and targets
|
|
220
|
-
Agent lifecycle events (spawn, kill, rotate)
|
|
221
|
-
Configuration changes
|
|
222
|
-
Credential operations
|
|
223
|
-
|
|
224
|
-
Audit logs use owner-only permissions and are append-only -- no API endpoint can
|
|
225
|
-
truncate or delete the log.
|
|
226
|
-
|
|
227
|
-
Result: PASS
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
================================================================================
|
|
231
|
-
12. PORT EXPOSURE
|
|
232
|
-
================================================================================
|
|
233
|
-
|
|
234
|
-
Port 31415: Bound to 127.0.0.1. Not network-accessible.
|
|
235
|
-
Ports 31416-25: Fallback range if 31415 is in use. Also bound to 127.0.0.1.
|
|
236
|
-
|
|
237
|
-
No other ports are opened by the daemon. Remote access requires explicit SSH tunnel
|
|
238
|
-
or Tailscale configuration, both of which are encrypted and authenticated.
|
|
239
|
-
|
|
240
|
-
Result: PASS
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
================================================================================
|
|
244
|
-
GROOVE vs. TOOLS THAT LEAVE PORTS OPEN
|
|
245
|
-
================================================================================
|
|
246
|
-
|
|
247
|
-
Network binding:
|
|
248
|
-
Groove binds to 127.0.0.1 and enforces it -- you cannot override to 0.0.0.0.
|
|
249
|
-
Many open-source agent tools default to 0.0.0.0, making them accessible to any
|
|
250
|
-
device on the network.
|
|
251
|
-
|
|
252
|
-
Credential handling:
|
|
253
|
-
Groove uses machine-bound AES-256-GCM encryption and never proxies credentials.
|
|
254
|
-
Tools with open ports may proxy API keys through the server, exposing them to
|
|
255
|
-
network-level interception.
|
|
256
|
-
|
|
257
|
-
Agent isolation:
|
|
258
|
-
Groove enforces scope locks and a knock protocol on every file operation.
|
|
259
|
-
Most comparable tools have no agent-to-agent isolation at all.
|
|
260
|
-
|
|
261
|
-
Input validation:
|
|
262
|
-
Groove validates all inputs against strict schemas and blocks path traversal.
|
|
263
|
-
Input validation quality varies widely across open-source agent tools.
|
|
264
|
-
|
|
265
|
-
Remote access:
|
|
266
|
-
Groove requires SSH tunnel or Tailscale -- both encrypted and authenticated.
|
|
267
|
-
Tools with open ports allow direct, often unauthenticated network access.
|
|
268
|
-
|
|
269
|
-
CORS policy:
|
|
270
|
-
Groove validates origins and only allows localhost.
|
|
271
|
-
Many tools use permissive CORS (wildcard *) or skip CORS entirely.
|
|
272
|
-
|
|
273
|
-
Process spawning:
|
|
274
|
-
Groove uses argument arrays, never shell interpolation.
|
|
275
|
-
Some tools concatenate user input into shell commands.
|
|
276
|
-
|
|
277
|
-
Prototype pollution:
|
|
278
|
-
Groove explicitly whitelists fields and blocks __proto__/constructor.
|
|
279
|
-
This is rarely addressed in comparable tools.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
================================================================================
|
|
283
|
-
SHOULD GROOVE BE INSTALLED ON A SEPARATE MACHINE?
|
|
284
|
-
================================================================================
|
|
285
|
-
|
|
286
|
-
For standard development use: No. A separate machine is not required.
|
|
287
|
-
|
|
288
|
-
The daemon is not network-accessible. No provider credentials are proxied or exposed.
|
|
289
|
-
Stored keys are encrypted and machine-bound. All sensitive files have restrictive
|
|
290
|
-
permissions. The architecture is designed for safe use on a daily development machine.
|
|
291
|
-
|
|
292
|
-
The residual risk is the AI agents themselves, not Groove's architecture. Claude Code,
|
|
293
|
-
Codex, and Gemini CLI run with your filesystem permissions. If an agent were to execute
|
|
294
|
-
a destructive command, that would be the agent's behavior within its own permission
|
|
295
|
-
model -- not a Groove vulnerability. Groove mitigates this through scope locks and the
|
|
296
|
-
knock protocol, but does not provide kernel-level containment.
|
|
297
|
-
|
|
298
|
-
For high-security environments where production credentials, banking sessions, or
|
|
299
|
-
sensitive SSH keys are present on the same machine, using a dedicated development
|
|
300
|
-
machine or VM is a reasonable precaution. However, this applies to any tool that gives
|
|
301
|
-
AI agents shell access, not to Groove specifically.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
================================================================================
|
|
305
|
-
CONCLUSION
|
|
306
|
-
================================================================================
|
|
307
|
-
|
|
308
|
-
Groove's security architecture is defense-in-depth at the orchestration layer:
|
|
309
|
-
localhost binding (enforced, not just default), no credential proxying, authenticated
|
|
310
|
-
encryption, scope-based agent isolation, strict input validation, prototype pollution
|
|
311
|
-
protection, and a full audit trail.
|
|
312
|
-
|
|
313
|
-
It significantly reduces the attack surface compared to tools that bind to open ports
|
|
314
|
-
or tunnel credentials through a server. The architecture makes network exposure,
|
|
315
|
-
credential theft, command injection, and prototype pollution structurally impossible
|
|
316
|
-
through the daemon's interfaces.
|
|
317
|
-
|
|
318
|
-
The remaining attack surface -- AI agents acting within their own process permissions --
|
|
319
|
-
is an industry-wide challenge that no orchestration tool fully solves today. Groove
|
|
320
|
-
addresses it better than most through scope locks and the knock protocol, while being
|
|
321
|
-
transparent about the boundary of its guarantees.
|
|
322
|
-
|
|
323
|
-
All 12 areas audited: PASS.
|