Skip to content

fix: security and correctness hardening across credentials, API, storage, and tooling#45

Merged
Patel230 merged 6 commits into
mainfrom
fix/security-and-correctness-review
Jun 18, 2026
Merged

fix: security and correctness hardening across credentials, API, storage, and tooling#45
Patel230 merged 6 commits into
mainfrom
fix/security-and-correctness-review

Conversation

@Patel230

Copy link
Copy Markdown
Contributor

Summary

Comprehensive security and correctness hardening across eyrie, covering 4 commits and 21 files.

Critical fixes

  • audit.go: File perms 0o644 → 0o600 (metadata exposure)
  • api/server.go: Store and call bgCancel in Shutdown (context leak); add validateAuthConfig loopback auth gate on ListenAndServe
  • guardrails.go: Add AddRuleSafe/NewGuardrailsSafe (error-returning variants for untrusted rule sources); fix ApplyRedactions to use FindAllStringIndex positions so the correct match instance is redacted
  • cache/backend.go: Bound RESP bulk-string allocation at 64 MB to prevent memory-exhaustion DoS
  • budgets.go: chmod budget DB to 0o600 (stores plaintext API keys)

High fixes

  • conversation/engine.go: Defer-close the final StreamResult on all exit paths; nil-provider guard
  • client/stream.go: Sort OpenAI tool calls by Index before emitting (was non-deterministic map iteration); nil args fallback
  • credentials/keyring_platform.go: Only map not-found errors to ErrNotFound; real backend errors pass through
  • openai_proxy.go: Surface streaming errors as SSE data event; raise body limit to 10 MiB
  • ratelimit.go + adaptive_ratelimit.go: time.After → time.NewTimer+Stop (timer leak fix)
  • router/retry.go: Same timer leak fix
  • internal/api/server.go: Security headers middleware

Medium fixes

  • provider_registry.go: 10s timeout in resolveEnvSecret
  • retry.go: time.After → time.NewTimer+Stop
  • sqlite.go: Escape backslash in GetNodeByPrefix LIKE query
  • config/provider_env.go: ValidateBaseURL uses url.Parse; dir perms 0o700

Refactor

  • internal/httputil: New centralized package with ConstantTimeEqual, DecodeJSONBody, WriteJSON, SecurityHeaders, IsLoopbackHost, ValidateAuthConfig, ExtractBearerToken + tests
  • config/runtime.go + runtime/runtime.go: Bounded keyring lookups with 5s timeouts (was unbounded context.Background)

Verification

  • go build ./...
  • go test -race -count=1 ./... — all pass
  • go vet ./...
  • go fmt clean
  • ✅ govulncheck: no vulnerabilities

Patel230 added 6 commits June 18, 2026 23:33
…rage

Critical:
- audit.go: tighten JSONLFileSink file perms 0o644 -> 0o600 (metadata)
- api/server.go: store and call bgCancel in Shutdown (context leak);
  add validateAuthConfig loopback auth gate on ListenAndServe
- guardrails.go: add AddRuleSafe/NewGuardrailsSafe (error-returning
  variants) for untrusted rule sources; fix ApplyRedactions to use
  FindAllStringIndex positions so the correct match instance is
  redacted instead of the first occurrence via strings.Index
- cache/backend.go: bound RESP bulk-string allocation at 64 MB to
  prevent memory-exhaustion DoS from a malicious Redis
- budgets.go: chmod budget DB to 0o600 (stores plaintext API keys)

High:
- conversation/engine.go: defer-close the final StreamResult on all
  exit paths; add nil-provider guard in streamAndSave
- client/stream.go: sort OpenAI tool calls by Index before emitting
  (was non-deterministic map iteration); nil args -> {_raw} fallback
- credentials/keyring_platform.go: only map not-found errors to
  ErrNotFound; real backend errors now pass through so LookupSecret
  logs them at Warn instead of silently classifying as 'no secret'
- openai_proxy.go: surface streaming errors as SSE data event before
  [DONE]; raise body limit to 10 MiB for multi-turn conversations

Medium:
- provider_registry.go: bound resolveEnvSecret with 10s timeout ctx
- retry.go: time.After -> time.NewTimer + Stop (avoid timer leak)
- sqlite.go: escape backslash in GetNodeByPrefix LIKE query
- config/provider_env.go: ValidateBaseURL uses url.Parse instead of
  os.Stat; config dir perms 0o755 -> 0o700
…server

- ratelimit.go: replace time.After with time.NewTimer+Stop in both
  token-bucket wait paths (minInterval enforcement and token-deficit
  wait) to prevent runtime timer leaks on ctx cancellation
- adaptive_ratelimit.go: same fix in RPM and TPM throttle paths
- internal/api/server.go: wrap handler chain with securityHeaders
  middleware (X-Content-Type-Options, X-Frame-Options, Cache-Control)
  matching the hawk daemon's security posture
- router/retry.go: replace afterFunc = time.After with newTimer =
  time.NewTimer to avoid leaking the timer in the runtime when ctx
  is cancelled before the delay elapses
- router/router.go: update call site to use newTimer + timer.Stop()
- internal/httputil: new package with canonical ConstantTimeEqual,
  DecodeJSONBody, WriteJSON, SecurityHeaders, IsLoopbackHost,
  ValidateAuthConfig, ExtractBearerToken — eliminates duplication
  across internal/api/server.go (previously had local copies)
- internal/api/server.go: delegate to httputil; remove duplicated
  constantTimeEqual, decodeJSONBody, writeJSON, securityHeaders,
  validateAuthConfig, isLoopbackHost
- internal/api/auth_test.go: update to use httputil.ConstantTimeEqual
- config/runtime.go: bound envValue keyring lookup with 5s timeout
  (was unbounded context.Background())
- runtime/runtime.go: bound CredentialTargets keyring probes with 5s
  timeout each (was unbounded context.Background())
Brings in 4 commits covering critical/high/medium fixes and the
centralized internal/httputil package. See branch history for details.
- budgets.go: chmod WAL/SHM sidecar files to 0o600 in addition to the
  main DB file (WAL holds uncheckpointed pages including plaintext keys)
- httputil.go: make ExtractBearerToken case-insensitive per RFC 7235
  and adopt it in Server.auth (was dead code with duplicated logic)
- server.go: use httputil.ExtractBearerToken instead of inline duplicate
@Patel230

Copy link
Copy Markdown
Contributor Author

Code Review — Findings & Fixes

Reviewed all 23 changed files. Found 2 issues (1 P1, 1 P3), both fixed in commit 138b60d.

[P1] storage/budgets.go: chmod 0o600 misses WAL/SHM sidecar files

os.Chmod(path, 0o600) only tightened the main DB file, but WAL mode creates <path>-wal and <path>-shm at umask (0o644). The WAL holds uncheckpointed pages including plaintext API keys from virtual_key_secrets, which persist at 0o644 if the process crashes. Fix: also os.Chmod the -wal and -shm sidecar files after migrate().

[P3] internal/httputil/httputil.go: ExtractBearerToken was dead code

ExtractBearerToken had zero production callers — Server.auth duplicated the same Authorization/X-API-Key extraction inline. Also used case-sensitive strings.TrimPrefix(token, "Bearer "), deviating from RFC 7235. Fix: made the Bearer scheme match case-insensitively via strings.EqualFold, and adopted ExtractBearerToken in Server.auth (removing the duplicate and the now-unused strings import).

Verified clean

ConstantTimeEqual (crypto/subtle), IsLoopbackHost (IPv4/IPv6/mapped), ValidateAuthConfig gate, bgCancel lifecycle in Shutdown, guardrails FindAllStringIndex redaction (sorted desc, right-to-left), LIKE backslash escaping, cache RESP 64MB bound, keyring error mapping, bounded keyring lookups (no goroutine leak), conversation/engine StreamResult closed on all paths, OpenAI tool call sort by Index, timer.Stop() on all paths.

@Patel230 Patel230 merged commit 3ff69b5 into main Jun 18, 2026
15 checks passed
@Patel230 Patel230 deleted the fix/security-and-correctness-review branch June 18, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant