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
7 changes: 6 additions & 1 deletion services/appraisal-api/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { x402HTTPClient } from "@x402/core/client";
import { Keypair } from "@stellar/stellar-sdk";
import { describe, test } from "node:test";

import { createPaidFetch, X402PaymentError, AppraisalResponseParseError } from "./client.js";
import { createPaidFetch, X402PaymentError, AppraisalResponseParseError, MAX_PAYMENT_ERROR_DIAGNOSTIC_LENGTH, sanitizePaymentErrorDiagnostic } from "./client.js";

const TEST_SECRET = Keypair.random().secret();
const VALID_402_BODY = {
Expand Down Expand Up @@ -33,6 +33,11 @@ function buildResponse(status: number, body: string | undefined, headers?: Recor
}

describe("createPaidFetch response parsing", () => {
test("bounds diagnostics and redacts credential fields", () => {
const diagnostic = sanitizePaymentErrorDiagnostic(JSON.stringify({ token: "secret", detail: "x".repeat(1000) }));
assert.ok(diagnostic.length <= MAX_PAYMENT_ERROR_DIAGNOSTIC_LENGTH);
assert.doesNotMatch(diagnostic, /secret/);
});
test("throws a typed parse error for an unpaid empty response body", async () => {
const paidFetch = createPaidFetch({ secret: TEST_SECRET });
const originalFetch = globalThis.fetch;
Expand Down
8 changes: 8 additions & 0 deletions services/appraisal-api/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ export interface PaidResult<T = unknown> {
settlement?: SettleResponse;
}

export const MAX_PAYMENT_ERROR_DIAGNOSTIC_LENGTH = 512;
const SENSITIVE_FIELD = /("?(?:secret|token|password|authorization|privateKey|private_key|apiKey|api_key)"?\s*:\s*)"?[^,}\s]+/gi;

/** Bound provider diagnostics and redact common credential fields before display/logging. */
export function sanitizePaymentErrorDiagnostic(body: string): string {
return body.slice(0, MAX_PAYMENT_ERROR_DIAGNOSTIC_LENGTH).replace(SENSITIVE_FIELD, '$1[REDACTED]');
}

export class AppraisalResponseParseError extends Error {
readonly name = "AppraisalResponseParseError";
readonly status: number;
Expand Down