mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 00:19:42 +02:00
feat(tunnel,ui): purple tunnel button + opt-in unauthenticated tunnel with warning (v1.1.9)
- Daylight Blue: Cloudflare Tunnel welcome button is now purple (was orange), keeping Claude blue / Tunnel purple / OpenCode green distinct. - Allow enabling the Cloudflare tunnel with no CODEMAN_PASSWORD via the UI: the toggle now pops a security confirm dialog and, on confirm, sends an explicit per-request acknowledgeUnauthTunnel:true (new action field, never persisted). Server logs a loud warning whenever a passwordless public tunnel starts. curl/API/CLI stay refused unless password/env/flag — no accidental exposure. Tests: extend test/routes/system-routes-tunnel-guard.test.ts (ack allows + not persisted; ack:false still refuses). Verified e2e on an isolated instance (purple button, confirm dialog, retry carries the flag, no real tunnel opened). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1176,6 +1176,48 @@ Object.assign(CodemanApp.prototype, {
|
||||
} catch {
|
||||
/* non-JSON body — use the default message */
|
||||
}
|
||||
// 403 = the no-password safety refusal (COD-55). Warn loudly and let the
|
||||
// operator acknowledge the risk; on confirm, retry with explicit acknowledgment.
|
||||
if (res.status === 403) {
|
||||
const confirmed = confirm(
|
||||
'⚠️ SECURITY WARNING — no password set\n\n' +
|
||||
'Enabling the Cloudflare tunnel will publish THIS machine to a public URL with ' +
|
||||
'NO login. Anyone who gets the URL has full terminal control — effectively remote ' +
|
||||
'code execution on your computer.\n\n' +
|
||||
'Strongly recommended: set CODEMAN_PASSWORD instead.\n\n' +
|
||||
'Enable the unauthenticated public tunnel anyway?'
|
||||
);
|
||||
if (!confirmed) {
|
||||
this._dismissTunnelConnecting?.();
|
||||
this.showToast('Tunnel not enabled', 'info');
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const retry = await fetch('/api/settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ tunnelEnabled: true, acknowledgeUnauthTunnel: true }),
|
||||
});
|
||||
if (retry.ok) {
|
||||
this.showToast('Public tunnel enabling — no password set ⚠️', 'warning');
|
||||
return false; // proceed with the caller's success/connecting path
|
||||
}
|
||||
let m = 'Failed to enable tunnel.';
|
||||
try {
|
||||
const b = await retry.json();
|
||||
if (b && b.error) m = b.error;
|
||||
} catch {
|
||||
/* non-JSON */
|
||||
}
|
||||
this._dismissTunnelConnecting?.();
|
||||
this.showToast(m, 'error');
|
||||
return true;
|
||||
} catch {
|
||||
this._dismissTunnelConnecting?.();
|
||||
this.showToast('Failed to enable tunnel', 'error');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this._dismissTunnelConnecting?.();
|
||||
this.showToast(message, 'error');
|
||||
return true;
|
||||
|
||||
+15
-15
@@ -10201,10 +10201,10 @@ html:not([data-skin="og"]) {
|
||||
}
|
||||
|
||||
/* ---- Daylight Blue: distinct identity per welcome action button ----
|
||||
Run Claude Code keeps the blue accent; Cloudflare Tunnel takes Cloudflare's
|
||||
brand orange; Run OpenCode takes an emerald green — so the three are clearly
|
||||
different colors instead of all reading blue. Scoped to daylight-blue only;
|
||||
placed after the shared daylight block to win on equal specificity. ---- */
|
||||
Run Claude Code keeps the blue accent; Cloudflare Tunnel is purple; Run
|
||||
OpenCode is emerald green — so the three are clearly different colors instead
|
||||
of all reading blue. Scoped to daylight-blue only; placed after the shared
|
||||
daylight block to win on equal specificity. ---- */
|
||||
html[data-skin="daylight-blue"] .welcome-btn-opencode {
|
||||
background: linear-gradient(135deg, #0d9f6e, #2fbf85);
|
||||
border-color: rgba(16, 185, 129, 0.5);
|
||||
@@ -10216,21 +10216,21 @@ html[data-skin="daylight-blue"] .welcome-btn-opencode:hover {
|
||||
box-shadow: 0 0 28px -4px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
html[data-skin="daylight-blue"] .welcome-btn-tunnel {
|
||||
background: linear-gradient(135deg, #ef7611, #fbad41);
|
||||
border-color: rgba(246, 130, 31, 0.5);
|
||||
color: #2e1503;
|
||||
background: linear-gradient(135deg, #7c3aed, #a855f7);
|
||||
border-color: rgba(168, 85, 247, 0.5);
|
||||
color: #f5f3ff;
|
||||
}
|
||||
html[data-skin="daylight-blue"] .welcome-btn-tunnel:hover {
|
||||
background: linear-gradient(135deg, #fb8b2a, #ffc05a);
|
||||
border-color: rgba(251, 173, 65, 0.6);
|
||||
box-shadow: 0 0 28px -4px rgba(246, 130, 31, 0.45);
|
||||
background: linear-gradient(135deg, #8b5cf6, #c084fc);
|
||||
border-color: rgba(192, 132, 252, 0.6);
|
||||
box-shadow: 0 0 28px -4px rgba(124, 58, 237, 0.45);
|
||||
}
|
||||
html[data-skin="daylight-blue"] .welcome-btn-tunnel.active {
|
||||
background: linear-gradient(135deg, #d9650b, #f6821f);
|
||||
border-color: rgba(246, 130, 31, 0.6);
|
||||
color: #fff7ed;
|
||||
background: linear-gradient(135deg, #6d28d9, #7c3aed);
|
||||
border-color: rgba(124, 58, 237, 0.6);
|
||||
color: #f5f3ff;
|
||||
}
|
||||
html[data-skin="daylight-blue"] .welcome-btn-tunnel.active:hover {
|
||||
background: linear-gradient(135deg, #ef7611, #fb8b2a);
|
||||
box-shadow: 0 0 28px -4px rgba(246, 130, 31, 0.5);
|
||||
background: linear-gradient(135deg, #7c3aed, #8b5cf6);
|
||||
box-shadow: 0 0 28px -4px rgba(124, 58, 237, 0.5);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user