Skip to content

[Bug] Greenfield data plane returns HTTP 500 instead of 401 for missing/invalid caller JWT (ext_proc reject path) #3

Description

@xulis

Summary

On the greenfield data plane (agentgateway :13000), a governed write with a missing or invalid caller JWT returns HTTP 500 (ext_proc failed: no more response messages) instead of 401. FinGuard's logs show it intends to reject with "authentication credentials" semantics (401), but the agentgateway ext_proc client fails to deliver that rejection and surfaces a 500. The request never reaches the upstream backend.

This breaks the documented validation contract (docs/identity-iam.md §7: "Wrong iss, wrong aud, or expired JWT → 401"). Clients cannot distinguish "not authenticated" from "service failure", which also breaks client retry semantics (500 typically triggers retries; 401 does not).

Environment

  • FinGuard image: ghcr.io/finogeeks/finguard:0.1.0 (signed digest sha256:63206295f5724a814892129ff8129f97a5ec26f4145e6450c80d5f14dce7f7a5)
  • Deployment: Compose lab (Docker Desktop, macOS arm64), finguard serve + pinned agentgateway
  • IdP: Keycloak 26.1, RS256 + JWKS; OIDC enabled (FINGUARD_OIDC_ISSUER/AUDIENCE/JWKS_URL set)
  • Backend: mock HTTP service (POST /writes) that never rejects (always 2xx)

Steps to Reproduce

# 1. Baseline backend counter
curl -s http://127.0.0.1:18081/writes          # -> {"writes":5,"attempts":5}

# 2. Write WITHOUT any caller JWT
curl -s -w '\nHTTP:%{http_code}\n' -X POST http://127.0.0.1:13000/writes \
  -H 'content-type: application/json' \
  -H "Idempotency-Key: nojwt-$(date +%s)" \
  -d '{"batch":"allow","run":"nojwt"}'
# -> ext_proc failed: no more response messages / HTTP:500

# 3. Write with a FORGED JWT
curl -s -w '\nHTTP:%{http_code}\n' -X POST http://127.0.0.1:13000/writes \
  -H 'content-type: application/json' \
  -H 'x-finguard-id-token: eyJhbGciOiJSUzI1NiJ9.fake.fake' \
  -H "Idempotency-Key: fakejwt-$(date +%s)" \
  -d '{"batch":"allow","run":"fakejwt"}'
# -> ext_proc failed: no more response messages / HTTP:500

# 4. Confirm the request never reached the backend (counter unchanged)
curl -s http://127.0.0.1:18081/writes          # -> {"writes":5,"attempts":5}  (unchanged)

Expected Behavior

HTTP 401 for both cases (missing / invalid caller JWT), per docs/identity-iam.md §7 ("Wrong iss, wrong aud, or expired JWT → 401") and the semantics of the FinGuard rejection logged below. The management plane already does this correctly (e.g. GET /v1/journal without token → 401).

Actual Behavior

  • HTTP 500 with body ext_proc failed: no more response messages for both cases.
  • FinGuard's own logs show the intended rejection (401 semantics) and that delivering it failed:
WARN finguard_extproc::proc: ext_proc respond failed
  status=code: 'The request does not have valid authentication credentials',
  message: "oidc required but no bearer or x-finguard-id-token present"

WARN finguard_extproc::proc: ext_proc respond failed
  status=code: 'The request does not have valid authentication credentials',
  message: "invalid oidc token: InvalidSignature"
  • The backend counter is unchanged (attempts stays 5), so the request never reached upstream — the 500 is produced by the gateway, not the backend (direct calls to the backend without any auth return 2xx, confirming the backend is not the source).

Impact

Medium. Authentication failures are misreported as server errors:

  1. Clients/agents cannot distinguish "not authenticated" from "gateway/service down" — an agent framework will typically retry a 500, and every retry also fails with 500 (no 401 to stop the loop).
  2. Monitoring/alerting cannot key on 401 for auth-failure dashboards; auth failures pollute the 5xx error budget.
  3. Inconsistent with the management plane (401) and with the documented contract (§7).

Possible Cause / Suggested Direction

  • The agentgateway ext_proc client fails to process FinGuard's rejection response (no more response messages — likely the reject/denied response framing is not handled, or an error occurs while streaming the response back to the caller).
  • Suggest: on FinGuard ext_proc rejection with Unauthenticated semantics, the gateway should surface HTTP 401 (and 403 for authorization denials), and log the rejection reason without crashing the response stream. Add a test that asserts 401 for missing/invalid JWT on the greenfield path.

Additional Context

  • OIDC is enabled in this environment (real JWKS verification, not stub identity). The same 500 behavior was observed before Vault/credential-injection was added, so it is specific to the authentication-rejection path, not custody.
  • The management-plane Authorization check (/v1/journal) returns a clean 401 for missing/invalid tokens — only the data-plane ext_proc path is affected.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions