Task: Verify session tokens at the gateway with IAM's key alone

Table of Contents

This page documents a task in the Clean ores.http to the component clean standard story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

The gateway verifies session tokens with IAM's published RS256 key alone, holds no signing credential of its own, and refuses requests when it has no verifier; the security architecture page states how the edge verifies.

2. Status

Field Value
State DONE
Parent story Clean ores.http to the component clean standard
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-09-27

3. Acceptance

  • The gateway verifies with one verifier: IAM's RS256 public key, fetched from its JWKS at startup.
  • The HS256 path is gone: no option, no parser requirement, and no environment variable.
  • A session with no verifier refuses every request, so a failed key fetch cannot fall back to anything weaker.
  • The HTTP edge's verification is documented on the security architecture page.

4. Plan

Remove the second verifier rather than reconcile it. The gateway preferred IAM's RS256 key and fell back to a symmetric HS256 secret, and the fallback was load-bearing for nothing: the component never mints, so the secret only widened what an attacker who learned it could do, and it turned a failed key fetch into a quiet weakening instead of a loud outage.

That makes the change a subtraction in four places, and a documentation addition in one:

  1. http_server and http_session hold one verifier instead of two. The accessor that returned the old symmetric one is dead and goes with it.
  2. http_server_options loses the four JWT fields, and parser.cpp loses the three options and the requirement that made an operator generate a secret nothing needed.
  3. env_init.py stops generating ORES_HTTP_SERVER_JWT_SECRET. Removing the variable matters as much as removing the option: CLI11 maps it onto the option name, so leaving it in /.env fails the parse at startup.
  4. The unreachable public-key-file branch goes, and so do the includes that only it used.
  5. The security architecture page gains the "how" beside its existing "must": the edge holds no signing key, the key arrives through the framework, and there is deliberately no fallback.

The failure mode to respect is that this is a security change to a running service, so it is verified by starting the gateway with no secret in its environment and replaying recipes that need a session, not by the build.

5. Notes

The defect this removes was introduced by the fix for the same symptom one branch earlier, which added the RS256 verifier without noticing the symmetric one already there was now redundant. The lesson is the usual one: a new credential path is a change to the trust surface, so the old path has to be accounted for in the same piece of work.

6. Test Scenarios

Manual QA scenarios (scaffolded via compass add test_scenario) that verify this task. Link new ones here as they're created; the scenario doc itself links back via its "Verifies task" field.

Scenario State Notes
     

7. PRs

PR Title
#2258 [http] Verify session tokens with IAM's key alone

8. Review

# Comment summary File Decision Notes
1 `<fstream>` and `<sstream>` were left behind by the public-key-file branch this removes. http_server.cpp Taken Both were unused after the removal; `grep` confirmed nothing else in the file read a stream.
2 The accessor returning the new verifier has no callers, as the one it replaced had none. http_server.hpp Taken Deleted rather than renamed. An uncalled public accessor is a liability, and the diagram is regenerated without it.
3 The task's Status and Plan still read as unstarted. this task Taken Written up and closed with the work.

9. Result

The gateway has one way of being believed. It verifies session tokens with the RS256 key IAM publishes, fetched from IAM's JWKS by the service framework at startup, and it holds no signing credential of its own.

The symmetric path is gone in every place it appeared: the option fields, the parser's three options and its requirement, the environment variable that feeding it implied, the unreachable public-key-file branch, the includes only that branch used, and the accessor nothing called. A session with no verifier refuses every request, so a failed key fetch is an outage rather than a quiet fallback.

The security architecture page states how the edge verifies, beside its existing statement that it must: the gateway holds no key, the key arrives through the framework, and there is deliberately no second, symmetric verifier, because the edge has nothing to sign.

Verified by running it, not by building it: the whole tree builds (COMPASS_BUILD_EXIT=0), ctest -R ores.http is 4 of 4, the gateway starts and serves with no ORES_HTTP_SERVER_JWT_SECRET in its environment and no HS256 line in its log, and the four read recipes still answer 200 — so the RS256 path is what authenticates sessions. The component-drift, validate_docs, recipe-inventory and backlog-index gates are all green.

The six body-carrying recipes still fail with 400 because the generated recipes send no request body. That is a separate defect in the recipe facet and is not addressed here.

Emacs 29.3 (Org mode 9.6.15)