masterplan-lock / check (push) Successful in 3s
masterplan-lock / check (pull_request) Successful in 10s
Security Scan (SIAX Cloud) / security (push) Successful in 14s
CI (SIAX Cloud) / security (push) Successful in 17s
CI (SIAX Cloud) / security (pull_request) Successful in 17s
CI (SIAX Cloud) / integration (push) Successful in 25s
CI (SIAX Cloud) / quality (push) Successful in 35s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 40s
CI (SIAX Cloud) / integration (pull_request) Successful in 49s
CI (SIAX Cloud) / quality (pull_request) Successful in 56s
- X-Internal-Token skickades av edgen men verifierades ALDRIG — gate som raw-ASGI-middleware: INTERNAL_TOKEN satt => alla routes utom /health kräver exakt token, 401 (constant-time) annars. Ospecificerad token = inert (dev). - Dockerfile: ARG GIT_SHA + OCI-label + /health.reporting revision - compose var redan skriven för INTERNAL_TOKEN — motorn hade bara aldrig deployats med den - tester: tests/test_internal_token_gate.py (3: off/public/401-pass) — hela services/api-sviten 62 passed, 2 skipped (dokumenterade known-gaps), 0 fail
71 lines
2.4 KiB
Python
71 lines
2.4 KiB
Python
"""The engine must not accept anonymous traffic when INTERNAL_TOKEN is set.
|
|
|
|
Background (2026-09-15): lib/engine.ts on the edge has always SENT
|
|
`X-Internal-Token`, but the engine never verified it — a deployed engine would
|
|
have accepted anyone who could reach the port. The gate is fail-closed:
|
|
|
|
* INTERNAL_TOKEN set -> every route except /health requires the EXACT
|
|
token; wrong/missing = 401.
|
|
* INTERNAL_TOKEN unset -> gate inert (local dev); production compose always
|
|
sets it.
|
|
|
|
`/health` stays public on purpose: it is the liveness probe that
|
|
`lib/health-checks.ts` calls from the edge, and it leaks nothing but booleans.
|
|
|
|
The token is read at module import (the same value the running process serves
|
|
with), so each test reloads the module with the env it wants — and the
|
|
teardown reloads it one more time with the env CLEARED so later test files in
|
|
the same session never inherit a baked-in token.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import os
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture()
|
|
def engine(monkeypatch):
|
|
monkeypatch.setenv("INTERNAL_TOKEN", "s3cret")
|
|
import orchestrator.main as main
|
|
|
|
importlib.reload(main)
|
|
yield TestClient(main.app)
|
|
os.environ.pop("INTERNAL_TOKEN", None)
|
|
importlib.reload(main)
|
|
|
|
|
|
@pytest.fixture()
|
|
def engine_no_token(monkeypatch):
|
|
monkeypatch.delenv("INTERNAL_TOKEN", raising=False)
|
|
import orchestrator.main as main
|
|
|
|
importlib.reload(main)
|
|
yield TestClient(main.app)
|
|
os.environ.pop("INTERNAL_TOKEN", None)
|
|
importlib.reload(main)
|
|
|
|
|
|
def test_gate_off_when_internal_token_unset(engine_no_token):
|
|
assert engine_no_token.get("/health").status_code == 200
|
|
assert engine_no_token.get("/sources").status_code == 200
|
|
|
|
|
|
def test_health_stays_public_but_sources_require_token(engine):
|
|
# liveness probe is public by design
|
|
assert engine.get("/health").status_code == 200
|
|
# anonymous call to a data route -> 401 (was 200 before 2026-09-15)
|
|
r = engine.get("/sources")
|
|
assert r.status_code == 401
|
|
assert r.json()["error"]["code"] == "unauthorized"
|
|
|
|
|
|
def test_wrong_token_is_401_and_correct_token_passes(engine):
|
|
wrong = engine.get("/estimates?markets=SE", headers={"X-Internal-Token": "nope"})
|
|
assert wrong.status_code == 401
|
|
right = engine.get("/sources", headers={"X-Internal-Token": "s3cret"})
|
|
assert right.status_code == 200
|