fix(proxy): guard telemetry and TOIN endpoints

Harden telemetry and TOIN routes and detail payloads (#2927).
This commit is contained in:
Tejas Chopra 2026-08-11 16:18:53 -07:00 committed by GitHub
parent 8cd138039e
commit cde1513c91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 65 additions and 11 deletions

View file

@ -4578,7 +4578,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
}
# Telemetry endpoints (Data Flywheel)
@app.get("/v1/telemetry")
@app.get("/v1/telemetry", dependencies=[Depends(_require_loopback)])
async def telemetry_stats():
"""Get telemetry statistics for the data flywheel.
@ -4601,7 +4601,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
telemetry = get_telemetry_collector()
return telemetry.get_stats()
@app.get("/v1/telemetry/export")
@app.get("/v1/telemetry/export", dependencies=[Depends(_require_loopback)])
async def telemetry_export():
"""Export full telemetry data for aggregation.
@ -4617,7 +4617,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
telemetry = get_telemetry_collector()
return telemetry.export_stats()
@app.post("/v1/telemetry/import")
@app.post("/v1/telemetry/import", dependencies=[Depends(_require_loopback)])
async def telemetry_import(request: Request):
"""Import telemetry data from another source.
@ -4631,7 +4631,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
telemetry.import_stats(data)
return {"status": "imported", "current_stats": telemetry.get_stats()}
@app.get("/v1/telemetry/tools")
@app.get("/v1/telemetry/tools", dependencies=[Depends(_require_loopback)])
async def telemetry_tools():
"""Get telemetry statistics for all tracked tool signatures.
@ -4647,7 +4647,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
"tools": {sig_hash: stats.to_dict() for sig_hash, stats in all_stats.items()},
}
@app.get("/v1/telemetry/tools/{signature_hash}")
@app.get("/v1/telemetry/tools/{signature_hash}", dependencies=[Depends(_require_loopback)])
async def telemetry_tool_detail(signature_hash: str):
"""Get detailed telemetry for a specific tool signature.
@ -4669,7 +4669,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
}
# TOIN (Tool Output Intelligence Network) endpoints
@app.get("/v1/toin/stats")
@app.get("/v1/toin/stats", dependencies=[Depends(_require_loopback)])
async def toin_stats():
"""Get overall TOIN statistics.
@ -4687,7 +4687,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
toin = get_toin()
return toin.get_stats()
@app.get("/v1/toin/patterns")
@app.get("/v1/toin/patterns", dependencies=[Depends(_require_loopback)])
async def toin_patterns(limit: int = 20):
"""List TOIN patterns with most samples.
@ -4742,7 +4742,7 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
return patterns_list[:limit]
@app.get("/v1/toin/pattern/{hash_prefix}")
@app.get("/v1/toin/pattern/{hash_prefix}", dependencies=[Depends(_require_loopback)])
async def toin_pattern_detail(hash_prefix: str):
"""Get detailed TOIN pattern info by hash prefix.
@ -4761,7 +4761,17 @@ def create_app(config: ProxyConfig | None = None) -> FastAPI:
# Search for pattern with matching hash prefix
for sig_hash, pattern_dict in patterns_data.items():
if sig_hash.startswith(hash_prefix):
return pattern_dict
# Keep this response aligned with /v1/toin/patterns while
# excluding query text, field semantics, and other internal
# learning state from the detail endpoint.
return {
"compressions": pattern_dict.get("total_compressions", 0),
"retrievals": pattern_dict.get("total_retrievals", 0),
"retrieval_rate": pattern_dict.get("retrieval_rate", 0.0),
"confidence": pattern_dict.get("confidence", 0.0),
"skip_recommended": pattern_dict.get("skip_compression_recommended", False),
"optimal_max_items": pattern_dict.get("optimal_max_items", 20),
}
raise HTTPException(
status_code=404, detail=f"No TOIN pattern found with hash starting with: {hash_prefix}"

View file

@ -22,6 +22,14 @@ from headroom.proxy.server import ProxyConfig, create_app
GATED = [
("get", "/transformations/feed"),
("post", "/cache/clear"),
("get", "/v1/telemetry"),
("get", "/v1/telemetry/export"),
("post", "/v1/telemetry/import"),
("get", "/v1/telemetry/tools"),
("get", "/v1/telemetry/tools/example"),
("get", "/v1/toin/stats"),
("get", "/v1/toin/patterns"),
("get", "/v1/toin/pattern/example"),
]
@ -71,8 +79,44 @@ def test_non_loopback_caller_gets_404(method: str, path: str) -> None:
@pytest.mark.parametrize("method,path", GATED)
def test_loopback_caller_allowed(method: str, path: str) -> None:
client = _loopback_client()
resp = client.request(method, path)
assert resp.status_code == 200, resp.text
resp = client.request(method, path, json={} if method == "post" else None)
# Detail routes legitimately return 404 when their test key is absent;
# the companion non-loopback test proves the guard itself.
assert resp.status_code in {200, 404, 422}, resp.text
def test_toin_pattern_detail_whitelists_learned_payload(monkeypatch: pytest.MonkeyPatch) -> None:
class FakeTOIN:
def export_patterns(self):
return {
"patterns": {
"unknown|unknown|abc123": {
"sample_size": 10,
"total_compressions": 8,
"total_retrievals": 2,
"retrieval_rate": 0.25,
"confidence": 0.4,
"skip_compression_recommended": False,
"optimal_max_items": 20,
"query_pattern_frequency": {"secret prompt": 1},
"common_query_patterns": ["secret prompt"],
"field_semantics": {"secret": "value"},
}
}
}
monkeypatch.setattr("headroom.proxy.server.get_toin", lambda: FakeTOIN())
response = _loopback_client().get("/v1/toin/pattern/unknown")
assert response.status_code == 200
assert response.json() == {
"compressions": 8,
"retrievals": 2,
"retrieval_rate": 0.25,
"confidence": 0.4,
"skip_recommended": False,
"optimal_max_items": 20,
}
# CCR data endpoints — cached session content, gated to 404 off-loopback (#1227).