diff --git a/packages/cli/src/lib/api/issues.ts b/packages/cli/src/lib/api/issues.ts index ec2ac970a..a136007af 100644 --- a/packages/cli/src/lib/api/issues.ts +++ b/packages/cli/src/lib/api/issues.ts @@ -17,6 +17,7 @@ import { } from "../custom-ca.js"; import { applyCustomHeaders } from "../custom-headers.js"; import { ApiError, ValidationError } from "../errors.js"; +import { logger } from "../logger.js"; import { resolveOrgRegion } from "../region.js"; import { invalidateCachedResponsesMatching } from "../response-cache.js"; import { getApiBaseUrl } from "../sentry-client.js"; @@ -31,6 +32,8 @@ import { unwrapPaginatedResult, } from "./infrastructure.js"; +const log = logger.withTag("api.issues"); + const TRAILING_SLASH_RE = /\/$/; /** @@ -745,5 +748,28 @@ export async function getSharedIssue( ); } - return (await response.json()) as { groupID: string }; + let json: unknown; + try { + json = await response.json(); + } catch (err) { + log.debug("Failed to parse shared issue JSON", err); + throw new ApiError( + "Share link returned invalid JSON", + response.status, + undefined, + `shared/issues/${shareId}` + ); + } + + const result = json as Record; + if (typeof result?.groupID !== "string" || !result.groupID) { + throw new ApiError( + "Share link response missing groupID", + response.status, + "The share link returned an unexpected response shape.", + `shared/issues/${shareId}` + ); + } + + return { groupID: result.groupID }; } diff --git a/packages/cli/src/lib/ghcr.ts b/packages/cli/src/lib/ghcr.ts index 43d6deb7e..dabb7e35e 100644 --- a/packages/cli/src/lib/ghcr.ts +++ b/packages/cli/src/lib/ghcr.ts @@ -20,6 +20,9 @@ import { getUserAgent } from "./constants.js"; import { customFetch } from "./custom-ca.js"; import { UpgradeError } from "./errors.js"; +import { logger } from "./logger.js"; + +const log = logger.withTag("ghcr"); /** Default timeout for GHCR HTTP requests (10 seconds) */ const GHCR_REQUEST_TIMEOUT = 10_000; @@ -250,7 +253,26 @@ export async function fetchManifest( ); } - return (await response.json()) as OciManifest; + let json: unknown; + try { + json = await response.json(); + } catch (err) { + log.debug("Failed to parse manifest JSON", err); + throw new UpgradeError( + "network_error", + `Manifest for tag "${tag}" returned invalid JSON` + ); + } + + const manifest = json as OciManifest; + if (!Array.isArray(manifest?.layers)) { + throw new UpgradeError( + "network_error", + `Manifest for tag "${tag}" has no layers array` + ); + } + + return manifest; } /** diff --git a/packages/cli/src/lib/hex-id-recovery.ts b/packages/cli/src/lib/hex-id-recovery.ts index 91304533f..95dc38eeb 100644 --- a/packages/cli/src/lib/hex-id-recovery.ts +++ b/packages/cli/src/lib/hex-id-recovery.ts @@ -457,6 +457,10 @@ const eventAdapter: FuzzyLookupAdapter = async (ctx) => { statsPeriod: ctx.period ?? SCAN_PERIODS.event, sort: "date", }); + if (!Array.isArray(data)) { + log.debug("listTransactions returned non-array data", typeof data); + return []; + } return (data as TransactionListItem[]).map((t) => t.id); }; @@ -469,6 +473,10 @@ const traceAdapter: FuzzyLookupAdapter = async (ctx) => { statsPeriod: ctx.period ?? SCAN_PERIODS.trace, sort: "date", }); + if (!Array.isArray(data)) { + log.debug("listSpans (trace) returned non-array data", typeof data); + return []; + } return (data as SpanListItem[]).map((s) => s.trace); }; @@ -494,6 +502,10 @@ const spanAdapter: FuzzyLookupAdapter = async (ctx) => { statsPeriod: ctx.period ?? SCAN_PERIODS.span, sort: "date", }); + if (!Array.isArray(data)) { + log.debug("listSpans (span) returned non-array data", typeof data); + return []; + } return (data as SpanListItem[]).map((s) => s.id); };