Skip to content
This repository was archived by the owner on Jul 3, 2026. It is now read-only.
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ jobs:
with:
node-version: "22"

- name: Run Cloudflare proxy tests
working-directory: cloudflare/codex-audit-proxy
- name: Run Cloudflare Worker tests
run: |
set -euo pipefail
node --experimental-default-type=module --test tests/index.test.mjs
node --experimental-default-type=module --test cloudflare/codex-audit-proxy/tests/index.test.mjs
node --experimental-default-type=module --test cloudflare/ai-gateway-dash/tests/index.test.mjs
37 changes: 32 additions & 5 deletions cloudflare/ai-gateway-dash/src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ const COOKIE_NAME = "dash_session";
const STATE_COOKIE_NAME = "dash_oauth_state";
const COOKIE_MAX_AGE = 86400; // 24h
const SESSION_SECRET_LENGTH = 32;
const DASHBOARD_API_ROUTES = new Set([
"/v1/ai/health",
"/v1/ai/quota",
"/v1/ai/changes",
"/v1/ai/changes/effectiveness",
"/v1/ai/feedback/shadow",
]);

// ── HTML templates ─────────────────────────────────────────────────────

Expand Down Expand Up @@ -258,11 +265,31 @@ function getSession(token) {

// ── API proxy ──────────────────────────────────────────────────────────

async function proxyAPI(path, env) {
const origin = (env.AI_GATEWAY_ORIGIN_URL || "").trim();
if (!origin) throw new Error("AI_GATEWAY_ORIGIN_URL not configured");
function withoutTrailingSlash(pathname) {
return pathname.replace(/\/+$/, "");
}

function allowedDashboardApiPath(pathname) {
const clean = withoutTrailingSlash(pathname);
return DASHBOARD_API_ROUTES.has(clean) ? clean : "";
}

export function buildDashboardApiUrl(rawOrigin, pathname, search = "") {
if (!rawOrigin || !rawOrigin.trim()) throw new Error("AI_GATEWAY_ORIGIN_URL not configured");
const clean = allowedDashboardApiPath(pathname);
if (!clean) throw new Error("dashboard API route is not allowed");
const origin = new URL(rawOrigin.trim());
if (origin.protocol !== "https:") throw new Error("AI_GATEWAY_ORIGIN_URL must use HTTPS");
const basePath = withoutTrailingSlash(origin.pathname);
origin.pathname = !basePath || basePath === "/" ? clean : basePath + clean;
origin.search = search;
origin.hash = "";
return origin.toString();
}

async function proxyAPI(path, search, env) {
const token = (env.DASHBOARD_API_TOKEN || "").trim();
const url = origin.replace(/\/+$/, "") + path;
const url = buildDashboardApiUrl(env.AI_GATEWAY_ORIGIN_URL || "", path, search);
const resp = await fetch(url, {
headers: { Authorization: token ? "Bearer " + token : "", Accept: "application/json", "User-Agent": "AiGatewayDashboard/1.0" },
});
Expand Down Expand Up @@ -373,7 +400,7 @@ export default {

const apiPath = path.slice(4);
try {
return await proxyAPI(apiPath, env);
return await proxyAPI(apiPath, url.search, env);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route effectiveness queries correctly

When the dashboard loads it calls /api/v1/ai/changes/effectiveness?days=90; forwarding url.search makes the origin receive /v1/ai/changes/effectiveness?days=90. The current VPS handler in service/ai_gateway_service.py:503 checks exact self.path == "/v1/ai/changes/effectiveness" before the generic /v1/ai/changes/ branch, so with a query it falls into change-detail lookup and returns 404. Because this call is part of the dashboard's Promise.all, that one 404 prevents all dashboard cards from rendering; please fix the origin router first or avoid forwarding the query for this route.

Useful? React with 👍 / 👎.

} catch (e) {
return json({ status: "error", error: e.message }, 502);
}
Expand Down
32 changes: 32 additions & 0 deletions cloudflare/ai-gateway-dash/tests/index.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import assert from "node:assert/strict";
import test from "node:test";

import { buildDashboardApiUrl } from "../src/index.mjs";

test("buildDashboardApiUrl preserves query strings for allowed read routes", () => {
assert.equal(
buildDashboardApiUrl("https://origin.example/base", "/v1/ai/changes", "?days=7"),
"https://origin.example/base/v1/ai/changes?days=7",
);
});

test("buildDashboardApiUrl allows effectiveness route with query", () => {
assert.equal(
buildDashboardApiUrl("https://origin.example", "/v1/ai/changes/effectiveness", "?days=90"),
"https://origin.example/v1/ai/changes/effectiveness?days=90",
);
});

test("buildDashboardApiUrl rejects unsupported origin paths", () => {
assert.throws(
() => buildDashboardApiUrl("https://origin.example", "/v1/ai/execute/jobs", ""),
/not allowed/,
);
});

test("buildDashboardApiUrl rejects insecure origins", () => {
assert.throws(
() => buildDashboardApiUrl("http://origin.example", "/v1/ai/health", ""),
/HTTPS/,
);
});
90 changes: 70 additions & 20 deletions scripts/codex_audit_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,21 +599,68 @@ def _find_existing_job(dedupe_key: str) -> tuple[str, dict[str, Any]] | None:
return None


def _service_git_token() -> str:
for env_name in ("CROSS_REPO_GIT_TOKEN", "GH_TOKEN"):
raw = os.environ.get(env_name, "").strip()
if raw:
return raw
return ""


def _git_auth_env(token: str) -> dict[str, str] | None:
if not token:
return None
encoded = base64.b64encode(f"x-access-token:{token}".encode("utf-8")).decode("ascii")
env = dict(os.environ)
env.update(
{
"GIT_CONFIG_COUNT": "1",
"GIT_CONFIG_KEY_0": "http.https://github.com/.extraheader",
"GIT_CONFIG_VALUE_0": f"AUTHORIZATION: basic {encoded}",
}
)
return env


def _redact_clone_error(text: str, token: str) -> str:
redacted = text
if token:
redacted = redacted.replace(token, "[REDACTED]")
redacted = redacted.replace(
base64.b64encode(f"x-access-token:{token}".encode("utf-8")).decode("ascii"),
"[REDACTED]",
)
redacted = re.sub(
r"https://x-access-token:[^\s@]+@github\.com/",
"https://x-access-token:[REDACTED]@github.com/",
redacted,
)
return redacted


def _prepare_repo(repo: str, ref: str, tmp: Path) -> Path:
"""Clone a repository and return the checkout path."""
repo_dir = tmp / "repo"
token = ""
for env_name in ("CROSS_REPO_GITHUB_APP_PRIVATE_KEY", "CROSS_REPO_GIT_TOKEN", "GH_TOKEN"):
raw = os.environ.get(env_name, "").strip()
if raw:
token = raw
break
url = f"https://x-access-token:{token}@github.com/{repo}" if token else f"https://github.com/{repo}"
token = _service_git_token()
url = f"https://github.com/{repo}.git"
shutil.rmtree(repo_dir, ignore_errors=True)
subprocess.run(
["git", "clone", "--depth=1", "--branch", ref, url, str(repo_dir)],
capture_output=True, text=True, check=True, timeout=120,
)
try:
subprocess.run(
["git", "clone", "--depth=1", "--branch", ref, url, str(repo_dir)],
capture_output=True,
text=True,
check=True,
timeout=120,
env=_git_auth_env(token),
)
except subprocess.CalledProcessError as exc:
output = "\n".join(part.strip() for part in (exc.stdout, exc.stderr) if part and part.strip())
if len(output) > 1200:
output = f"...\n{output[-1200:]}"
detail = f"git clone failed for {repo}@{ref} with exit code {exc.returncode}"
if output:
detail = f"{detail}:\n{_redact_clone_error(output, token)}"
raise RuntimeError(detail) from exc
return repo_dir


Expand Down Expand Up @@ -718,15 +765,18 @@ def _run_job_background(job_id: str, payload: dict[str, Any]) -> None:
"""Background thread for execute tasks (repo clone + codex exec)."""
_update_job(job_id, "running")
try:
with tempfile.TemporaryDirectory(prefix="ai-gateway-") as tmp:
repo_dir = _prepare_repo(
payload["source_repository"],
payload.get("source_ref", "main"),
Path(tmp),
)
output = _run_task(payload, repo_dir=repo_dir)
_update_job(job_id, "succeeded", output=output)
except (RuntimeError, subprocess.TimeoutExpired, OSError) as exc:
if os.environ.get("CODEX_AUDIT_SERVICE_FAKE_OUTPUT") is not None:
output = _run_task(payload, repo_dir=None)
else:
with tempfile.TemporaryDirectory(prefix="ai-gateway-") as tmp:
repo_dir = _prepare_repo(
payload["source_repository"],
payload.get("source_ref", "main"),
Path(tmp),
)
output = _run_task(payload, repo_dir=repo_dir)
_update_job(job_id, "succeeded", output=output)
except (RuntimeError, subprocess.CalledProcessError, subprocess.TimeoutExpired, OSError) as exc:
_update_job(job_id, "failed", error=str(exc))


Expand Down
50 changes: 50 additions & 0 deletions tests/test_run_monthly_codex_audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import json
import os
import subprocess
import tempfile
import threading
import time
Expand Down Expand Up @@ -579,6 +580,55 @@ def test_codex_audit_service_async_job_lifecycle(self) -> None:
server.shutdown()
server.server_close()

def test_codex_audit_service_background_job_marks_clone_failure_failed_without_secret(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
token = "test-token-value"
captured: dict[str, object] = {}
payload = {
"source_repository": "QuantStrategyLab/CryptoLivePoolPipelines",
"source_ref": "main",
"task": "monthly_snapshot_audit",
"mode": "review_only",
"prompt": "Review this snapshot.",
"timeout_seconds": 60,
}

def fail_clone(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
captured["command"] = command
captured["env"] = kwargs.get("env")
raise subprocess.CalledProcessError(
128,
command,
stderr=f"fatal: authentication failed for {token}",
)

env = {
"CODEX_AUDIT_SERVICE_JOB_DIR": tmp,
"CROSS_REPO_GIT_TOKEN": token,
}
with (
patch.dict(os.environ, env, clear=False),
patch.object(codex_audit_service.subprocess, "run", side_effect=fail_clone),
):
job_id = "a" * 24
codex_audit_service._write_job(job_id, payload, "dedupe-key")
codex_audit_service._run_job_background(job_id, payload)
job = codex_audit_service._read_job(job_id)

self.assertIsNotNone(job)
self.assertEqual(job["status"], "failed")
self.assertIn("git clone failed", job["error"])
self.assertNotIn(token, job["error"])
self.assertIn("[REDACTED]", job["error"])
command = captured.get("command")
self.assertIsInstance(command, list)
command_text = " ".join(command)
self.assertNotIn(token, command_text)
self.assertIn("https://github.com/QuantStrategyLab/CryptoLivePoolPipelines.git", command_text)
env_value = captured["env"]
self.assertIsInstance(env_value, dict)
self.assertEqual(env_value.get("GIT_CONFIG_KEY_0"), "http.https://github.com/.extraheader")

def test_codex_audit_service_deduplicates_active_audit_jobs(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
env = {
Expand Down
Loading