Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion node/sophia_governor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from __future__ import annotations

import hmac
import json
import logging
import os
Expand Down Expand Up @@ -938,7 +939,7 @@ def _is_admin(req) -> bool:
if not required:
return False
provided = (req.headers.get("X-Admin-Key") or req.headers.get("X-API-Key") or "").strip()
return bool(provided and provided == required)
return bool(provided and hmac.compare_digest(provided, required))

@app.route("/sophia/governor/status", methods=["GET"])
def sophia_governor_status():
Expand Down
48 changes: 47 additions & 1 deletion node/tests/test_sophia_governor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import gc
import os
import sqlite3
import tempfile
import time
import types

import pytest
Expand All @@ -10,6 +12,7 @@

sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

import sophia_governor
from sophia_governor import (
ROUTE_IMMEDIATE_PHONE_HOME,
ROUTE_LOCAL_ONLY,
Expand Down Expand Up @@ -37,7 +40,13 @@ def tmp_db():
db_path = handle.name
init_sophia_governor_schema(db_path)
yield db_path
os.unlink(db_path)
for _ in range(5):
try:
os.unlink(db_path)
break
except PermissionError:
gc.collect()
time.sleep(0.05)


@pytest.fixture
Expand Down Expand Up @@ -177,6 +186,43 @@ def test_governor_endpoints_require_admin_for_manual_review(client):
assert response.status_code == 401


def test_governor_admin_auth_uses_constant_time_compare(client, monkeypatch):
"""Admin-gated governor endpoints compare configured keys with hmac.compare_digest."""
calls = []

def spy_compare_digest(provided, expected):
calls.append((provided, expected))
return provided == expected

monkeypatch.setattr(sophia_governor.hmac, "compare_digest", spy_compare_digest)

denied = client.post(
"/sophia/governor/review",
headers={"X-Admin-Key": "wrong-admin"},
json={
"event_type": "pending_transfer",
"payload": {"amount_rtc": 50},
},
)
assert denied.status_code == 401

accepted = client.post(
"/sophia/governor/review",
headers={"X-API-Key": "test-admin"},
json={
"event_type": "pending_transfer",
"source": "pytest.manual",
"payload": {"amount_rtc": 50},
},
)
assert accepted.status_code == 200

assert calls == [
("wrong-admin", "test-admin"),
("test-admin", "test-admin"),
]


def test_governor_endpoints_report_status_and_recent(client):
review = client.post(
"/sophia/governor/review",
Expand Down
Loading