diff --git a/README.md b/README.md index 07af026..a38ba3f 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ The runtime currently exposes: - `GET /health` — health and signer/verifier readiness. - `GET /healthz` — alias for `/health`. - `POST /verify` — runtime verification API for receipt hash/signature checks, with optional ENS lookup and optional schema validation. +- `POST /execute` — unified execution endpoint; dispatches to the handler for `execution.verb` or `verb`. - `POST //v1.1.0` for the verbs enabled by `ENABLED_VERBS`. The default enabled verbs are: @@ -45,12 +46,11 @@ The default enabled verbs are: ## Debug routes -The runtime also implements these gated debug routes: +The runtime implements these gated debug routes: -- `GET /debug/env` -- `GET /debug/enskey` -- `GET /debug/validators` -- `POST /debug/prewarm` +- `GET /debug/signer` — active signer state, kid, fingerprint, and boot errors. +- `GET /debug/ens` — ENS resolution result for the configured signer name (accepts `?signer=` and `?refresh=1`). +- `GET /debug/schema/:verb` — schema compilation status for a given verb. They are available only when both of these are set: @@ -59,8 +59,6 @@ They are available only when both of these are set: Requests must present the token either as `Authorization: Bearer ` or `X-Debug-Token: `. When debug access is not enabled or the token is missing or wrong, these routes return `404`. -There is no `/debug/schemafetch` route in the current implementation. - ## Receipt model Verb routes return a JSON object with a signed `receipt` and optional unsigned `runtime_metadata`. @@ -126,7 +124,7 @@ When `schema=1`, schema validation uses the receipt verb to compute a `v1.1.0` r When a commons verb request omits `execution`, the runtime fabricates receipt execution defaults from the live route version: `entry: "https://runtime.commandlayer.org/execute"`, `verb: ""`, `version: "1.1.0"`, and `class: "commons"`. Commercial/payment-aware behavior belongs in the separate commercial runtime and is intentionally out of scope here. -When `VERIFY_SCHEMA_CACHED_ONLY=1` (the default), `/verify?schema=1` returns HTTP `202` with `validator_not_warmed_yet` if the validator for that verb has not been compiled yet. `POST /debug/prewarm` can queue validator warmup, and `GET /debug/validators` shows cache state. +When `VERIFY_SCHEMA_CACHED_ONLY=1` (the default), `/verify?schema=1` returns HTTP `202` with `validator_not_warmed_yet` if the validator for that verb has not been compiled yet. `GET /debug/schema/:verb` can report validator cache state and trigger compilation. If `/verify` exceeds `VERIFY_MAX_MS`, the runtime returns HTTP `502` with `failure_type: "availability"`, `retryable: true`, and the message `Verification service did not respond. Receipt may still be valid; retry recommended.` Clients should treat that as transient service unavailability, not a cryptographic proof failure. diff --git a/src/middleware/rateLimit.mjs b/src/middleware/rateLimit.mjs index 8c1d063..fe160f5 100644 --- a/src/middleware/rateLimit.mjs +++ b/src/middleware/rateLimit.mjs @@ -1,4 +1,4 @@ -// Simple in-memory rate limiter — no external deps, respects X-Forwarded-For. +// Simple in-memory rate limiter — no external deps, respects Express trust proxy. // Replace with express-rate-limit + Redis for multi-instance deployments. /** @@ -8,10 +8,11 @@ export function createRateLimiter({ windowMs = 60_000, max = 120, skipPaths = [] // ip -> { count, resetAt } const store = new Map(); + // Use req.ip (resolved by Express via trust proxy setting) rather than + // reading X-Forwarded-For directly. Reading the raw header allows any caller + // to inject an arbitrary IP string and bypass per-IP rate limiting. function getIp(req) { - const xff = req.headers["x-forwarded-for"]; - if (xff) return String(xff).split(",")[0].trim(); - return req.socket?.remoteAddress || "unknown"; + return req.ip || req.socket?.remoteAddress || "unknown"; } function cleanup() { diff --git a/tests/smoke.mjs b/tests/smoke.mjs index 21f0ed2..f6a8100 100644 --- a/tests/smoke.mjs +++ b/tests/smoke.mjs @@ -178,15 +178,16 @@ async function main() { const health = await waitForHealthy(baseUrl, proc); assert.equal(health.status, 200); if (health.json.signer_ok !== true) { - const dbg = await httpJson(`${baseUrl}/debug/env`, { + // /debug/signer is the implemented debug endpoint (not /debug/env) + const dbg = await httpJson(`${baseUrl}/debug/signer`, { method: "GET", headers: { "X-Debug-Token": "smoke" }, }); console.error("health:", JSON.stringify(health.json, null, 2)); - console.error("debug/env:", JSON.stringify(dbg.json, null, 2)); + console.error("debug/signer:", JSON.stringify(dbg.json, null, 2)); - throw new Error("signer_ok must be true (see debug/env above)"); + throw new Error("signer_ok must be true (see debug/signer above)"); } const describeBody = {