fix: redact Surplus validation errors safely
This commit is contained in:
@@ -153,7 +153,7 @@ export class SurplusClient {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "content-type": "application/json" },
|
headers: { "content-type": "application/json" },
|
||||||
body: JSON.stringify({ api_key: apiKey, base_url: baseUrl }),
|
body: JSON.stringify({ api_key: apiKey, base_url: baseUrl }),
|
||||||
});
|
}, true, [apiKey]);
|
||||||
if (response.text.trim() === "") {
|
if (response.text.trim() === "") {
|
||||||
throw new Error("Surplus discovery returned an empty body");
|
throw new Error("Surplus discovery returned an empty body");
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ export class SurplusClient {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "content-type": "application/json" },
|
headers: { "content-type": "application/json" },
|
||||||
body: JSON.stringify({ api_key: apiKey, base_url: baseUrl, model }),
|
body: JSON.stringify({ api_key: apiKey, base_url: baseUrl, model }),
|
||||||
});
|
}, true, [apiKey]);
|
||||||
const root = record(body, "Surplus connection response");
|
const root = record(body, "Surplus connection response");
|
||||||
if (root.ok !== true) throw new Error("Surplus provider connection test failed");
|
if (root.ok !== true) throw new Error("Surplus provider connection test failed");
|
||||||
return nonNegativeNumber(root.latency_ms, "Surplus connection latency_ms");
|
return nonNegativeNumber(root.latency_ms, "Surplus connection latency_ms");
|
||||||
@@ -229,7 +229,7 @@ export class SurplusClient {
|
|||||||
cap_daily_usd: input.dailyCapUsd,
|
cap_daily_usd: input.dailyCapUsd,
|
||||||
payout_address: input.payoutAddress,
|
payout_address: input.payoutAddress,
|
||||||
}),
|
}),
|
||||||
});
|
}, true, [input.apiKey]);
|
||||||
const root = record(body, "Surplus create offer response");
|
const root = record(body, "Surplus create offer response");
|
||||||
return offerId(root, "Surplus create offer response");
|
return offerId(root, "Surplus create offer response");
|
||||||
}
|
}
|
||||||
@@ -282,8 +282,14 @@ export class SurplusClient {
|
|||||||
path: string,
|
path: string,
|
||||||
init: RequestInit = {},
|
init: RequestInit = {},
|
||||||
authenticated = true,
|
authenticated = true,
|
||||||
|
additionalSecrets: readonly string[] = [],
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
const response = await this.requestText(path, init, authenticated);
|
const response = await this.requestText(
|
||||||
|
path,
|
||||||
|
init,
|
||||||
|
authenticated,
|
||||||
|
additionalSecrets,
|
||||||
|
);
|
||||||
if (response.text === "") return {};
|
if (response.text === "") return {};
|
||||||
try {
|
try {
|
||||||
return JSON.parse(response.text);
|
return JSON.parse(response.text);
|
||||||
@@ -296,6 +302,7 @@ export class SurplusClient {
|
|||||||
path: string,
|
path: string,
|
||||||
init: RequestInit = {},
|
init: RequestInit = {},
|
||||||
authenticated = true,
|
authenticated = true,
|
||||||
|
additionalSecrets: readonly string[] = [],
|
||||||
): Promise<{ status: number; text: string }> {
|
): Promise<{ status: number; text: string }> {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
||||||
@@ -311,7 +318,14 @@ export class SurplusClient {
|
|||||||
const text = await readBoundedText(response, controller);
|
const text = await readBoundedText(response, controller);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new SurplusHttpError(
|
throw new SurplusHttpError(
|
||||||
`Surplus ${path} returned HTTP ${response.status}`,
|
`Surplus ${path} returned HTTP ${response.status}${
|
||||||
|
text
|
||||||
|
? `: ${sanitizedErrorSnippet(
|
||||||
|
text,
|
||||||
|
[this.sellerApiKey, ...additionalSecrets],
|
||||||
|
)}`
|
||||||
|
: ""
|
||||||
|
}`,
|
||||||
response.status,
|
response.status,
|
||||||
response.headers.get("retry-after"),
|
response.headers.get("retry-after"),
|
||||||
);
|
);
|
||||||
@@ -502,6 +516,20 @@ function optionalNonNegativeNumber(value: unknown, field: string): number | null
|
|||||||
return nonNegativeNumber(value, field);
|
return nonNegativeNumber(value, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizedErrorSnippet(
|
||||||
|
value: string,
|
||||||
|
secrets: readonly string[],
|
||||||
|
): string {
|
||||||
|
let sanitized = value;
|
||||||
|
for (const secret of secrets) {
|
||||||
|
if (secret !== "") sanitized = sanitized.replaceAll(secret, "[redacted]");
|
||||||
|
}
|
||||||
|
return sanitized
|
||||||
|
.replace(/(?:si_seller_|sk-or-v1-)[A-Za-z0-9_-]+/g, "[redacted]")
|
||||||
|
.replace(/[\r\n]+/g, " ")
|
||||||
|
.slice(0, 300);
|
||||||
|
}
|
||||||
|
|
||||||
function positiveSafeInteger(value: unknown, field: string): number {
|
function positiveSafeInteger(value: unknown, field: string): number {
|
||||||
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
||||||
throw new Error(`${field} must be a positive safe integer`);
|
throw new Error(`${field} must be a positive safe integer`);
|
||||||
|
|||||||
Reference in New Issue
Block a user