Skip to content
Open
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
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 /<verb>/v1.1.0` for the verbs enabled by `ENABLED_VERBS`.

The default enabled verbs are:
Expand All @@ -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:

Expand All @@ -59,8 +59,6 @@ They are available only when both of these are set:

Requests must present the token either as `Authorization: Bearer <token>` or `X-Debug-Token: <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`.
Expand Down Expand Up @@ -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: "<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.

Expand Down
9 changes: 5 additions & 4 deletions src/middleware/rateLimit.mjs
Original file line number Diff line number Diff line change
@@ -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.

/**
Expand All @@ -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() {
Expand Down
7 changes: 4 additions & 3 deletions tests/smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Loading