Skip to content

feat: auth hard fail without ENGRAM_API_TOKEN (ADR-0001)#11

Merged
thebtf merged 2 commits into
mainfrom
feat/auth-hardening
Mar 17, 2026
Merged

feat: auth hard fail without ENGRAM_API_TOKEN (ADR-0001)#11
thebtf merged 2 commits into
mainfrom
feat/auth-hardening

Conversation

@thebtf
Copy link
Copy Markdown
Owner

@thebtf thebtf commented Mar 17, 2026

Summary

  • Server exits with log.Fatal if ENGRAM_API_TOKEN is not set and ENGRAM_AUTH_DISABLED is not true
  • Periodic 60s warning goroutine when auth is explicitly disabled
  • /api/version added to exempt paths (needed for plugin version detection)
  • Old non-blocking warning removed (superseded by startup gate)

ADR

ADR-0001: Mandatory Authentication with Explicit Opt-Out

Test plan

  • docker run engram without ENGRAM_API_TOKEN → exit 1 with clear message
  • docker run -e ENGRAM_AUTH_DISABLED=true engram → starts with warning
  • docker run -e ENGRAM_API_TOKEN=test engram → starts normally
  • /health, /api/version, /api/ready accessible without token

Summary by CodeRabbit

Примечания к выпуску

  • Новые возможности

    • Добавлена переменная окружения для управления требованием аутентификации (включение/отключение).
    • Эндпоинт /api/version теперь доступен без аутентификации.
    • Реализована проверка наличия токена при запуске сервиса; без токена запуск прерывается, если аутентификация не отключена.
  • Улучшения

    • Обновлены и уточнены предупреждения о состоянии аутентификации; при отключённой аутентификации периодически выводится уведомление.

Server now refuses to start without ENGRAM_API_TOKEN unless
ENGRAM_AUTH_DISABLED=true is explicitly set. When auth is disabled,
a periodic warning logs every 60 seconds. Added /api/version to
exempt paths for unauthenticated plugin version detection.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d9bdfbf-6bad-49fc-b260-7b308ff81d12

📥 Commits

Reviewing files that changed from the base of the PR and between 76a52b4 and 5f9f33b.

📒 Files selected for processing (2)
  • internal/worker/middleware.go
  • internal/worker/service.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/worker/service.go

Walkthrough

Добавлена поддержка ENGRAM_AUTH_DISABLED для отключения обязательной аутентификации, проверка наличия токена при старте сервиса с фатальным завершением при отсутствии токена, расширен список exempt-путей, и добавлен периодический ворнинг при отключённой аутентификации.

Changes

Cohort / File(s) Summary
Middleware Authentication
internal/worker/middleware.go
Добавлена обработка переменной окружения ENGRAM_AUTH_DISABLED (case-insensitive "true"), обновлён TokenAuth.enabled чтобы учитывать флаг, расширен ExemptPaths путём "/api/version", предупреждение при отсутствии токена выводится только если аутентификация не отключена.
Service Startup Authentication
internal/worker/service.go
При инициализации и старте сервиса читается токен и ENGRAM_AUTH_DISABLED; если токен пуст и аутентификация не отключена — запуск завершается фатально. Если аутентификация отключена — запускается периодический ворнинг каждые 60 секунд. Удалено старое предупреждение о биндинге на не-loopback. Добавлён импорт strings.

Sequence Diagram(s)

sequenceDiagram
  participant Env as Env (OS)
  participant Service as WorkerService
  participant Logger as Logger
  participant Middleware as AuthMiddleware
  participant Client as Client

  Env->>Service: read GetWorkerToken, ENGRAM_AUTH_DISABLED
  alt token present OR auth enabled with token
    Service->>Logger: start server
  else no token AND auth not disabled
    Service->>Logger: log fatal -> exit
  else auth disabled
    Service->>Logger: start server
    Service->>Logger: spawn goroutine (60s warning)
  end

  Client->>Service: HTTP request
  Service->>Middleware: check token / exempt paths
  alt path is exempt (e.g., /api/version)
    Middleware->>Service: allow request
  else token valid or auth disabled
    Middleware->>Service: allow request
  else missing/invalid token
    Middleware->>Client: 401 Unauthorized (or warning only if auth disabled)
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 В среде перемен — флаг и токен,

Слушаю я шелест кода ночной.
Версия свободна, путь открыт снизум,
Ворнинг шепчет: "Будь осторожен, герой!" ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing mandatory authentication with hard failure at startup when ENGRAM_API_TOKEN is not set, which aligns with the changeset's primary objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/auth-hardening
📝 Coding Plan
  • Generate coding plan for human review comments

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.3)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security posture of the application by making authentication mandatory by default. It introduces a startup gate that prevents the server from running without an API token, unless authentication is explicitly disabled. This change aligns with the new Architectural Decision Record (ADR-0001) for mandatory authentication with an explicit opt-out, ensuring that instances are not inadvertently exposed without security.

Highlights

  • Mandatory Authentication: The server now enforces authentication by default. It will exit with a fatal error if 'ENGRAM_API_TOKEN' is not set, unless 'ENGRAM_AUTH_DISABLED' is explicitly set to 'true'.
  • Explicit Opt-Out Warning: When authentication is explicitly disabled via 'ENGRAM_AUTH_DISABLED=true', a periodic warning message will be logged every 60 seconds to remind users of the unauthenticated state.
  • Exempted API Path: The '/api/version' endpoint has been added to the list of paths that do not require authentication, which is necessary for plugin version detection.
  • Removed Old Warning: The previous non-blocking warning about running an unauthenticated server on a non-loopback address has been removed, as it is superseded by the new startup gate.
Changelog
  • internal/worker/middleware.go
    • Imported the 'os' package to access environment variables.
    • Updated the 'NewTokenAuth' function to check for the 'ENGRAM_AUTH_DISABLED' environment variable.
    • Added '/api/version' to the list of authentication-exempt paths.
    • Modified the warning message when 'ENGRAM_API_TOKEN' is not set to reflect the new startup enforcement.
  • internal/worker/service.go
    • Imported the 'strings' package for environment variable processing.
    • Implemented a new authentication startup gate in the 'Start' method.
    • Added logic to fatally exit if 'ENGRAM_API_TOKEN' is missing and 'ENGRAM_AUTH_DISABLED' is not true.
    • Introduced a goroutine to log periodic warnings every 60 seconds when authentication is explicitly disabled.
    • Removed the old non-blocking warning about unauthenticated servers on non-loopback addresses.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request strengthens the authentication mechanism by making it mandatory by default. The server will now exit if ENGRAM_API_TOKEN is not set, unless ENGRAM_AUTH_DISABLED is explicitly set to true. When auth is disabled, a periodic warning is logged. My review focuses on code duplication and ensuring graceful shutdown of the new background task. I've identified duplicated logic for checking the auth-disabled flag and a missing WaitGroup tracking for the new goroutine, which could affect shutdown reliability. I've provided suggestions to address these points.

Comment thread internal/worker/service.go
Comment thread internal/worker/middleware.go
Comment thread internal/worker/service.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/worker/middleware.go`:
- Around line 112-113: The middleware currently sets enabled := token != "" so a
non-empty token overrides the ENGRAM_AUTH_DISABLED flag; change the logic that
constructs the auth middleware (the struct fields enabled and token in
internal/worker/middleware.go, likely in the NewAuthMiddleware/constructor) to
honor ENGRAM_AUTH_DISABLED explicitly: compute enabled as false when
ENGRAM_AUTH_DISABLED is set to "true" (or the same flag source used in
internal/worker/service.go), otherwise enabled = token != ""; update the place
that reads the env var so the middleware never requires auth if
ENGRAM_AUTH_DISABLED=true even when token is non-empty.

In `@internal/worker/service.go`:
- Around line 1958-1964: The auth token check (GetWorkerToken +
ENGRAM_AUTH_DISABLED) is happening after heavy async initialization starts; move
this validation earlier so NewService performs the "hard-fail at startup" before
calling initializeAsync. Specifically, in NewService locate the call to
initializeAsync and insert the token validation logic (using
config.GetWorkerToken() and checking ENGRAM_AUTH_DISABLED via
strings.EqualFold(strings.TrimSpace(os.Getenv("ENGRAM_AUTH_DISABLED")), "true"))
before initializeAsync is invoked; if token is missing and auth is not disabled,
call log.Fatal().Msg with the existing message to abort startup immediately.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9171ff35-91b4-48d0-9f41-b66ec01ebb7e

📥 Commits

Reviewing files that changed from the base of the PR and between d78640d and 76a52b4.

📒 Files selected for processing (2)
  • internal/worker/middleware.go
  • internal/worker/service.go

Comment thread internal/worker/middleware.go Outdated
Comment thread internal/worker/service.go
@thebtf
Copy link
Copy Markdown
Owner Author

thebtf commented Mar 17, 2026

@codex review

…ED in middleware

- Move auth startup gate into NewService() before go svc.initializeAsync() so
  the process fails fast without wasting resources on heavy initialization when
  no token is configured and ENGRAM_AUTH_DISABLED is not set. The Start() check
  remains as a secondary layer for belt-and-suspenders safety.
- Fix TokenAuth constructor: enabled was computed as token != "", which ignored
  ENGRAM_AUTH_DISABLED=true when a token was present. Now computed as
  token != "" && !authDisabled so an explicit opt-out always wins.
- Track the periodic auth-disabled warning goroutine in s.wg so graceful
  shutdown waits for it to exit cleanly instead of potentially racing.
@thebtf thebtf merged commit 49501c7 into main Mar 17, 2026
2 checks passed
@thebtf thebtf deleted the feat/auth-hardening branch March 17, 2026 22:59
thebtf added a commit that referenced this pull request Apr 26, 2026
First emulation playbook for engram, scoped to v6.0.0 auth-two-tier-tokens
release surfaces (server, plugin install, CLI proxy, dashboard tokens).

Resolves the MISSING verdict from the emulation-playbook skill before
v6.0.0 release. Future releases extend the scenario list per
/emulation-playbook --add.

Customer-mode walkthrough on this commit produced verdict
PASS_WITH_DEFERRED:
- Build, FR-4 fail-fast (server side), FR-4 fail-fast (client side) all PASS
- Two UX surprises noted (no --help handler, hardcoded keycard URL in FATAL
  message) — queued as low-effort follow-ups, not in scope of v6.0.0
- Three scenarios (live-stand happy path, dashboard /tokens, plugin install)
  deferred to user-driven walkthrough before tag — they do not exercise the
  v6 auth contract; they exercise unchanged infrastructure surfaces

Run report: .agent/reports/emulation-playbook-run-2026-04-26.md (gitignored,
working state).
thebtf added a commit that referenced this pull request Apr 26, 2026
BREAKING CHANGES — v6.0.0 auth model.

* Two-tier token model. Daemon and plugin no longer accept the operator
  key on workstations. Each workstation reads `ENGRAM_TOKEN` — a
  per-workstation API token (worker keycard) issued via the dashboard
  `/tokens` page. Operator key (`ENGRAM_AUTH_ADMIN_TOKEN`) lives ONLY
  on the server host.
* Plugin `.mcp.json` env rename: `ENGRAM_AUTH_ADMIN_TOKEN` → `ENGRAM_TOKEN`.
  No legacy fallback chain.
* Daemon fail-fast on missing token (FR-4): clean exit 1 instead of silent
  graceful-degrade to `loom_*-only` (PR #203 regression class).
* Issuance hardening: `POST/GET/DELETE /api/auth/tokens` and
  `/api/auth/tokens/{id}/stats` require an admin browser session cookie;
  bearer-token authentication (operator key OR worker keycard) → 403.
* Single `auth.Validator` shared by HTTP middleware + gRPC interceptor —
  the symmetric validation contract that PR #203's regression broke.
* Strict token shape gate: `engram_` + exactly 32 hex chars; fails closed
  before any DB / bcrypt work.
* Whitelisted scope mapping: only `read-write` / `read-only` are accepted
  from the api_tokens.scope column; anything else is treated as data
  corruption.
* Connection pool keys credential AND TLS CA: rotating ENGRAM_TLS_CA or
  switching keycards cannot ride a stale pooled connection.
* Bootstrap: 1 critical test (`tests/critical/auth_two_tier_test.go`)
  proves gRPC + HTTP both accept the same valid keycard via the same
  validator chain. Resolves the project-wide MISSING_SUITE gate.
* Production playbook (`docs/PRODUCTION-TESTING-PLAYBOOK.md`) bootstraps
  the customer-mode walkthrough required by rule #11. Three scenarios
  (S2.full, S3, S4) require live-stand validation before v6.0.0 tag.

17/17 review threads resolved. Critical suite verdict: PASS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant