mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 07:29:42 +02:00
Require the hook-event secret unconditionally, not only under a managed tunnel
COD-54 gated the /api/hook-event + /api/status-telemetry localhost bypass behind the shared X-Codeman-Hook-Secret only WHILE a managed tunnel was running, keeping a plain localhost bypass otherwise. But Codeman can't detect a user's OWN loopback reverse proxy (their own `cloudflared --url`, `tailscale serve`, nginx -> 127.0.0.1), which proxies internet traffic into the loopback origin with req.ip === 127.0.0.1 — so that setup kept the unsafe plain bypass. Require the secret on the loopback bypass unconditionally. Managed-session hooks already always present it (X-Codeman-Hook-Secret from $CODEMAN_HOOK_SECRET_FILE, generated for every instance), so the legitimate hook channel is unaffected; only the previously-unguarded own-proxy path is now rejected. Drops the now-unused getTunnelRunning param from registerAuthMiddleware. Tests: cod54-hook-event-auth (tunnel-down now also requires the secret, plus a good-secret positive case); auth-security (hook tests present the secret to reach schema validation).
This commit is contained in:
@@ -14,6 +14,7 @@ import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi }
|
||||
import { WebServer } from '../src/web/server.js';
|
||||
import { TmuxManager } from '../src/tmux-manager.js';
|
||||
import { SettingsUpdateSchema } from '../src/web/schemas.js';
|
||||
import { getHookSecret, HOOK_SECRET_HEADER } from '../src/config/hook-secret.js';
|
||||
|
||||
const AUTH_PORT = 3160;
|
||||
const NOAUTH_PORT = 3161;
|
||||
@@ -250,28 +251,28 @@ describe('Auth Security', () => {
|
||||
});
|
||||
|
||||
describe('Hook Event Endpoint', () => {
|
||||
it('should allow hook events from localhost without auth', async () => {
|
||||
it('should allow hook events from localhost with the hook secret (no Basic auth)', async () => {
|
||||
const res = await fetch(`${baseUrl}/api/hook-event`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', [HOOK_SECRET_HEADER]: getHookSecret() },
|
||||
body: JSON.stringify({
|
||||
event: 'stop',
|
||||
sessionId: 'nonexistent-session',
|
||||
data: {},
|
||||
}),
|
||||
});
|
||||
// Should pass auth (localhost bypass) but may 404 on session — that's fine
|
||||
// The key assertion is it does NOT return 401
|
||||
// Should pass auth (localhost bypass + hook secret) but may 404 on session — that's fine.
|
||||
// The key assertion is it does NOT return 401 (COD-91: secret required even with no tunnel).
|
||||
expect(res.status).not.toBe(401);
|
||||
});
|
||||
|
||||
it('should reject hook events with invalid schema', async () => {
|
||||
const res = await fetch(`${baseUrl}/api/hook-event`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', [HOOK_SECRET_HEADER]: getHookSecret() },
|
||||
body: JSON.stringify({ invalid: 'data' }),
|
||||
});
|
||||
// Schema validation should catch this
|
||||
// Past the auth gate (valid secret) → schema validation should catch this (not a 401).
|
||||
expect(res.status).not.toBe(401); // Not an auth error
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user