feat(web): harden server for public exposure

- hard per-traversal deadline (EXPLOREDNS_JOB_TIMEOUT, default 5m) so
  every job reaches a terminal state; timed-out jobs report error with
  any partial results instead of masquerading as complete
- cap concurrent traversals (EXPLOREDNS_MAX_JOBS, default 8) returning
  429 when saturated
- CORS off by default (the embedded SPA is same-origin); opt in via
  EXPLOREDNS_CORS_ORIGIN

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Gary Hansen
2026-07-07 21:50:29 +10:00
co-authored by Claude Fable 5
parent d71c7fbef2
commit 111b8bf48e
4 changed files with 178 additions and 9 deletions
+20 -2
View File
@@ -50,7 +50,25 @@ func TestHealth(t *testing.T) {
}
}
func TestCORSPreflight(t *testing.T) {
func TestCORSDisabledByDefault(t *testing.T) {
srv := newTestServer(t)
defer srv.Shutdown(5 * time.Second) //nolint:errcheck
req, _ := http.NewRequest(http.MethodGet, "http://"+srv.Addr()+"/api/health", nil)
req.Header.Set("Origin", "http://localhost:3000")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "" {
t.Fatalf("CORS should be off by default, got origin header %q", got)
}
}
func TestCORSPreflightWithConfiguredOrigin(t *testing.T) {
t.Setenv("EXPLOREDNS_CORS_ORIGIN", "http://localhost:3000")
srv := newTestServer(t)
defer srv.Shutdown(5 * time.Second) //nolint:errcheck
@@ -65,7 +83,7 @@ func TestCORSPreflight(t *testing.T) {
if resp.StatusCode != http.StatusNoContent {
t.Fatalf("want 204, got %d", resp.StatusCode)
}
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "*" {
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "http://localhost:3000" {
t.Fatalf("CORS origin header: %q", got)
}
}