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
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,11 @@ This repository's GitHub Actions workflows currently run:

`npm test` runs Node unit tests plus `tests/smoke.mjs`.

The Python SDK test file present in this repo is a placeholder and is not part of the npm-based CI path.

## Legacy verification code

This repository still contains `runtime/src/receipt-verification.js` and related tests under `runtime/tests/` and `sdk/typescript-sdk/tests/`.

That code exercises an older verification model and compatibility helpers for test material in this repository. It is not the production verification path used by `server.mjs`, which imports signing and verification from `@commandlayer/runtime-core`.
That file is explicitly legacy compatibility code for older fixture-based verification coverage in this repository. It is not the production verification path used by `server.mjs`, which imports signing and verification from `@commandlayer/runtime-core`.

## Configuration and operations

Expand Down
2 changes: 1 addition & 1 deletion docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,4 @@ Blocked localhost, private-network, or disallowed-host requests fail before the

The checked-in GitHub workflows currently run Node-based checks and a scheduled/manual ENS smoke script.

This repo also contains `sdk/python-sdk/tests/test_verification.py`, but the file is a placeholder and is not part of the npm-based CI workflow defined in `.github/workflows/ci.yml`.
There is no repo-local Python verification test in the npm-based CI workflow defined in `.github/workflows/ci.yml`.
9 changes: 9 additions & 0 deletions runtime/src/receipt-verification.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import crypto from "node:crypto";

/**
* Legacy compatibility verifier used only by repo-local fixtures and SDK parity tests.
*
* Production runtime verification flows through @commandlayer/runtime-core via server.mjs.
* Keep this file only for older compatibility material in this repository; do not treat it
* as the primary verification path for the runtime service.
*/

const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");

export function stableStringify(value) {
Expand Down Expand Up @@ -64,6 +72,7 @@ function verifySignature(receiptHash, signatureB64, pubkeyBytes) {
return crypto.verify(null, Buffer.from(receiptHash, "utf8"), key, Buffer.from(signatureB64, "base64"));
}

/** @deprecated Legacy compatibility helper; production verification uses @commandlayer/runtime-core. */
export async function verifyReceipt(receipt, { resolver, expectedIssuer } = {}) {
if (!resolver) throw new Error("Resolver required");
if (expectedIssuer && receipt.issuer !== expectedIssuer) throw new Error("Issuer mismatch");
Expand Down
4 changes: 2 additions & 2 deletions runtime/tests/ens-resolution.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import assert from "node:assert/strict";
import { resolveSigner } from "../src/receipt-verification.js";
import { buildResolver } from "./helpers.mjs";

test("ENS Resolution: resolves cl.receipt.signer correctly", async () => {
test("Legacy verification helper: resolves cl.receipt.signer correctly", async () => {
const signer = await resolveSigner("parseagent.eth", buildResolver());
assert.equal(signer, "runtime.commandlayer.eth");
});

test("ENS Resolution: fails if cl.receipt.signer missing", async () => {
test("Legacy verification helper: fails if cl.receipt.signer missing", async () => {
await assert.rejects(() => resolveSigner("invalidagent.eth", buildResolver()), /Missing cl\.receipt\.signer/);
});
6 changes: 3 additions & 3 deletions runtime/tests/key-resolution.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import assert from "node:assert/strict";
import { resolveSignatureKey } from "../src/receipt-verification.js";
import { buildResolver } from "./helpers.mjs";

test("Signer Key Resolution: resolves cl.sig.pub and cl.sig.kid", async () => {
test("Legacy verification helper: resolves cl.sig.pub and cl.sig.kid", async () => {
const key = await resolveSignatureKey("runtime.commandlayer.eth", buildResolver());
assert.equal(key.kid, "v1");
assert.equal(key.pubkeyBytes.length, 32);
});

test("Signer Key Resolution: fails if cl.sig.pub missing", async () => {
test("Legacy verification helper: fails if cl.sig.pub missing", async () => {
await assert.rejects(() => resolveSignatureKey("bad-signer.eth", buildResolver()), /Missing cl\.sig\.pub/);
});

test("Signer Key Resolution: fails if pubkey malformed", async () => {
test("Legacy verification helper: fails if pubkey malformed", async () => {
await assert.rejects(() => resolveSignatureKey("malformed.eth", buildResolver()), /Invalid ed25519 format/);
});
2 changes: 1 addition & 1 deletion runtime/tests/key-rotation.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from "node:assert/strict";
import { verifyReceipt } from "../src/receipt-verification.js";
import { buildResolver, loadFixture } from "./helpers.mjs";

test("Key Rotation: v1 receipt still verifies after v2 key added", async () => {
test("Legacy receipt verification compatibility: v1 receipt still verifies after v2 key added", async () => {
const baseResolver = buildResolver();
const v1Pub = await baseResolver.getText("runtime.commandlayer.eth", "cl.sig.pub");
const resolver = buildResolver({
Expand Down
6 changes: 3 additions & 3 deletions runtime/tests/receipt-verification.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import assert from "node:assert/strict";
import { verifyReceipt } from "../src/receipt-verification.js";
import { buildResolver, loadFixture } from "./helpers.mjs";

test("Receipt Verification: valid receipt verifies", async () => {
test("Legacy receipt verification compatibility: valid receipt verifies", async () => {
const result = await verifyReceipt(loadFixture("receipt_valid.json"), { resolver: buildResolver() });
assert.equal(result.valid, true);
});

test("Receipt Verification: invalid signature fails", async () => {
test("Legacy receipt verification compatibility: invalid signature fails", async () => {
const result = await verifyReceipt(loadFixture("receipt_invalid_sig.json"), { resolver: buildResolver() });
assert.equal(result.valid, false);
assert.match(result.error, /Signature verification failed/);
});

test("Receipt Verification: wrong kid fails", async () => {
test("Legacy receipt verification compatibility: wrong kid fails", async () => {
const result = await verifyReceipt(loadFixture("receipt_wrong_kid.json"), { resolver: buildResolver() });
assert.equal(result.valid, false);
assert.match(result.error, /Unknown key id/);
Expand Down
16 changes: 0 additions & 16 deletions sdk/python-sdk/tests/test_verification.py

This file was deleted.

2 changes: 1 addition & 1 deletion sdk/typescript-sdk/tests/canonicalization.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from "node:assert/strict";
import { computeReceiptHash } from "../../../runtime/src/receipt-verification.js";
import { loadFixture } from "../../../runtime/tests/helpers.mjs";

test("Canonicalization: stable JSON produces deterministic hash", () => {
test("Legacy verification compatibility: stable JSON produces deterministic hash", () => {
const receipt = loadFixture("receipt_valid.json");
const hash1 = computeReceiptHash(receipt);
const hash2 = computeReceiptHash(JSON.parse(JSON.stringify(receipt)));
Expand Down
2 changes: 1 addition & 1 deletion sdk/typescript-sdk/tests/ens-delegation.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from "node:assert/strict";
import { resolveSignerKey } from "../../../runtime/src/receipt-verification.js";
import { buildResolver } from "../../../runtime/tests/helpers.mjs";

test("ENS Delegation Flow: agent delegates to runtime signer", async () => {
test("Legacy verification compatibility: agent delegates to runtime signer", async () => {
const { algorithm, kid, rawPublicKeyBytes } = await resolveSignerKey("parseagent.eth", buildResolver());
assert.equal(algorithm, "ed25519");
assert.equal(kid, "v1");
Expand Down
4 changes: 2 additions & 2 deletions sdk/typescript-sdk/tests/security-cases.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import assert from "node:assert/strict";
import { verifyReceipt } from "../../../runtime/src/receipt-verification.js";
import { buildResolver, loadFixture } from "../../../runtime/tests/helpers.mjs";

test("Security Edge Cases: fails if receipt.issuer mismatches ENS name", async () => {
test("Legacy verification compatibility: fails if receipt.issuer mismatches ENS name", async () => {
const receipt = loadFixture("receipt_valid.json");
receipt.issuer = "evil.eth";
await assert.rejects(() => verifyReceipt(receipt, { resolver: buildResolver(), expectedIssuer: "parseagent.eth" }), /Issuer mismatch/);
});

test("Security Edge Cases: fails on tampered payload_hash", async () => {
test("Legacy verification compatibility: fails on tampered payload_hash", async () => {
const receipt = loadFixture("receipt_valid.json");
receipt.payload_hash = "fakehash";
const result = await verifyReceipt(receipt, { resolver: buildResolver() });
Expand Down
60 changes: 29 additions & 31 deletions server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1423,39 +1423,37 @@ app.get("/", (req, res) => {
);
});

app.get("/health", (req, res) => {
function buildHealthPayload() {
return {
ok: true,
service: SERVICE_NAME,
version: SERVICE_VERSION,
api_version: API_VERSION,
base: CANONICAL_BASE,
node: process.version,
host: HOST,
port: PORT,
enabled_verbs: ENABLED_VERBS,
signer_id: runtimeConfig.signerId,
signer_ok: signerBootState.ok,
verifier_ok: !!getActivePublicPem() || hasRpc(),
signer_source: activeSigner.source,
kid: runtimeConfig.kid,
canonical_id: runtimeConfig.canonicalId,
public_key_fingerprint: activeSigner.publicKeyFingerprint,
signer_errors: signerBootState.errors,
time: nowIso(),
...instancePayload(),
};
}

function sendHealth(res) {
res.setHeader("Content-Type", "application/json; charset=utf-8");
return res.status(200).end(
JSON.stringify({
ok: true,
service: SERVICE_NAME,
version: SERVICE_VERSION,
api_version: API_VERSION,
base: CANONICAL_BASE,
node: process.version,
host: HOST,
port: PORT,
enabled_verbs: ENABLED_VERBS,
signer_id: runtimeConfig.signerId,
signer_ok: signerBootState.ok,
verifier_ok: !!getActivePublicPem() || hasRpc(),
signer_source: activeSigner.source,
kid: runtimeConfig.kid,
canonical_id: runtimeConfig.canonicalId,
public_key_fingerprint: activeSigner.publicKeyFingerprint,
signer_errors: signerBootState.errors,
time: nowIso(),
...instancePayload(),
})
);
});
return res.status(200).end(JSON.stringify(buildHealthPayload()));
}

app.get("/healthz", (req, res) => {
// keep behavior: alias to /health
req.url = "/health";
req.originalUrl = "/health";
return app._router.handle(req, res, () => {});
});
app.get("/health", (req, res) => sendHealth(res));
app.get("/healthz", (req, res) => sendHealth(res));

// -----------------------
// debug (gated)
Expand Down
Loading