Skip to content

feat: add zod validation to internal and external endpoints - #612

Open
ECWireless wants to merge 8 commits into
mainfrom
feat/add-zod-validation-to-internal-and-external-endpoints
Open

ECWireless wants to merge 8 commits into
mainfrom
feat/add-zod-validation-to-internal-and-external-endpoints

Conversation

@ECWireless

Copy link
Copy Markdown
Collaborator

See #521 for all details. Original PR created by @Roaring30s .

Copilot AI review requested due to automatic review settings March 31, 2026 02:30
@vercel

vercel Bot commented Mar 31, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
explorer-arbitrum-one Ready Ready Preview Oct 6, 2026 10:55pm UTC

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces centralized Zod schemas and validation helpers, then applies them across multiple Next.js API routes to harden user-input handling and upstream-response parsing.

Changes:

  • Added reusable Zod schema modules under lib/api/schemas/* plus shared validation helpers in lib/api/errors.ts.
  • Updated a broad set of API routes to validate request inputs, external API responses, and/or endpoint outputs before returning JSON.
  • Added a new /api/totalTokenSupply endpoint backed by a subgraph query with response validation.

Reviewed changes

Copilot reviewed 37 out of 38 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
pages/api/usage.tsx Uses centralized usage schemas and validates output; adds guardrails for insufficient data.
pages/api/upload-ipfs.tsx Validates POST body and Pinata response; validates output shape.
pages/api/treasury/votes/[address]/registered.tsx Validates address input and response output for registered-to-vote.
pages/api/treasury/votes/[address]/index.tsx Validates address input and voting power output.
pages/api/treasury/proposal/[proposalId]/votes/[address].tsx Validates proposalId/address inputs and voting-power output.
pages/api/treasury/proposal/[proposalId]/state.tsx Validates proposalId input and proposal-state output.
pages/api/totalTokenSupply.tsx New endpoint querying subgraph totalSupply with schema validation.
pages/api/score/index.tsx Validates query params and upstream responses; adds output validation.
pages/api/score/[address].tsx Validates address input, upstream responses, and performance-metrics output.
pages/api/regions/index.ts Validates upstream regions payloads and validates merged output.
pages/api/pipelines/index.tsx Validates region query param and upstream pipelines payload.
pages/api/pending-stake/[address].tsx Validates address input and pending-stake output.
pages/api/l1-delegator/[address].tsx Validates address input and L1 delegator output.
pages/api/generateProof.tsx Validates POST body and proof output.
pages/api/ens-data/index.tsx Validates subgraph response + addresses and validates ENS identities output.
pages/api/ens-data/image/[name].tsx Validates ENS name and avatar result; adds URL validation before fetch.
pages/api/ens-data/[address].tsx Validates address input (including blacklist) and ENS identity output.
pages/api/current-round.tsx Validates subgraph result and current-round output.
pages/api/contracts.tsx Validates contracts-info output shape.
pages/api/changefeed.tsx Validates GraphQL envelope and changefeed response output.
pages/api/account-balance/[address].tsx Validates address input and account-balance output.
lib/api/schemas/usage.ts New Zod schemas for /api/usage input/output structures.
lib/api/schemas/upload-ipfs.ts New Zod schemas for IPFS upload input + Pinata response + output.
lib/api/schemas/treasury.ts New Zod schemas for treasury proposal/voting responses.
lib/api/schemas/total-token-supply.ts New Zod schemas for total supply subgraph response and output.
lib/api/schemas/subgraph.ts New Zod schemas for common subgraph envelopes and specific queries.
lib/api/schemas/staking.ts New Zod schemas for staking-related endpoint outputs.
lib/api/schemas/performance.ts New Zod schemas for metrics/score-related endpoints.
lib/api/schemas/index.ts Central barrel export for the new schema modules.
lib/api/schemas/generate-proof.ts New Zod schemas for generateProof input/output.
lib/api/schemas/ens.ts New Zod schemas for ENS identity/name/address and provider responses.
lib/api/schemas/current-round.ts New Zod schema for current-round endpoint output.
lib/api/schemas/contracts.ts New Zod schema for contracts endpoint output.
lib/api/schemas/common.ts New shared primitives (Address, URL, handles, regions, etc.).
lib/api/schemas/changefeed.ts New Zod schemas for changefeed GraphQL/envelope response validation.
lib/api/errors.ts Adds validateInput, validateOutput, validateExternalResponse helpers.
lib/api/ens.ts Validates ENS resolver/provider results with graceful fallbacks.
.gitignore Ignores *.tsbuildinfo.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pages/api/pipelines/index.tsx Outdated
Comment thread pages/api/score/index.tsx Outdated
Comment thread pages/api/score/index.tsx Outdated
Comment thread pages/api/score/[address].tsx Outdated
Comment thread pages/api/score/[address].tsx Outdated
Comment thread pages/api/ens-data/image/[name].tsx Outdated
Comment thread lib/api/schemas/common.ts
Comment thread lib/api/schemas/common.ts
Comment thread lib/api/schemas/treasury.ts Outdated
Comment thread pages/api/score/index.tsx
Comment thread pages/api/score/[address].tsx Outdated
Comment thread lib/api/schemas/treasury.ts Outdated
ECWireless and others added 6 commits May 6, 2026 12:59
… (#521)

* feat: add account-balance zod validation

* feat: add ens name validation

* feat: add score and pending stake validation

* feat: add ens-data image validation

* feat: add ens-data index validation

* feat: add pipelines validation

* feat: add regions validation

* feat: add treasury proposal state validation

* feat: add proposal vote by address validation

* feat: add treasury votes validation

* feat: add score validation

* feat: add changefeed validation

* feat: add contract validation

* feat: add current-round validation

* feat: add generateProof validation

* feat: add totalTokenSupply validation

* feat: add upload ipfs validation

* refactor: refactor usage validation

* feat: add external validation for ens image

* refactor: refactor ens schema

* feat: add external checks for score endpoint

* fix: add guard for projectBySlugs

* refactor: refactor schemas

* feat: add defense checks to usage endpoint

* feat: add getEnsForAddress validation

---------

Co-authored-by: ECWireless <40322776+ECWireless@users.noreply.github.com>
@rickstaa
rickstaa force-pushed the feat/add-zod-validation-to-internal-and-external-endpoints branch from b1af7db to eb365e9 Compare May 6, 2026 10:59
@rickstaa rickstaa linked an issue Jun 16, 2026 that may be closed by this pull request
Copilot AI balanced review requested due to automatic review settings October 6, 2026 22:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Production output validation is bypassed, and several input schemas still permit malformed or injectable values.

Review effort: Balanced
Findings: 2 High severity · 3 Medium severity

Open (5)

Comment thread pages/api/pipelines/index.tsx Outdated
Comment thread pages/api/score/index.tsx Outdated
Comment thread lib/api/errors.ts Outdated
Comment thread lib/api/schemas/performance.ts
Comment thread lib/api/schemas/upload-ipfs.ts Outdated
Copilot AI balanced review requested due to automatic review settings October 6, 2026 22:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Production output validation is ineffective, and several input and upstream-validation edge cases remain unresolved.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
Resolved since last review (3)
Previously missed (5)

In code that hasn't changed since last review

Medium severity ENS schema accepts malformed names before normalization

lib/​api/​schemas/​ens.ts:56

This schema accepts any non-empty string, including malformed ENS names such as values containing spaces. Those inputs pass validateInput, then normalize() throws and the route returns 404 instead of the intended 400. Incorporate ENS normalization into the schema (and apply the blacklist to the normalized name) so malformed names are rejected before the fetch path.

Medium severity Proposal ID schema allows values beyond uint256

lib/​api/​schemas/​treasury.ts:14

The numeric regex accepts values larger than uint256, but both callers immediately ABI-encode the result as a proposal ID. A value of 2^256 or greater therefore passes input validation and fails later as a 500 instead of a 400. Add the ABI range check to this shared schema.

Medium severity Payload limit counts UTF-16 units instead of UTF-8 bytes

lib/​api/​schemas/​upload-ipfs.ts:11

z.string().max(500000) limits UTF-16 code units, not payload bytes, so this does not enforce the documented 500KB abuse limit. For example, 200,000 emoji pass this check at 400,000 code units but occupy 800,000 UTF-8 bytes. Enforce the UTF-8 byte length (and use a body-parser limit if the limit is intended for the entire JSON request).

Medium severity Unvalidated upstream project data causes incorrect responses

pages/​api/​changefeed.tsx:80

projectBySlugs is still data supplied by changefeed.app, but it is passed to validateOutput. Consequently a malformed upstream payload becomes a 500 in development and is returned with 200 in production. Validate it with validateExternalResponse and return externalApiError (502) on failure, consistent with the other external-service endpoints.

Medium severity Case-sensitive address comparison reports zero price

pages/​api/​score/​index.tsx:177

The metrics record key may be a valid checksummed address, but only the pricing address is lowercased here. In that case the same address does not match and this endpoint reports pricePerPixel: 0. Compare both sides case-insensitively, as the single-address endpoint already does.

@@ -0,0 +1,31 @@
import { z } from "zod";

const ChangeSchema = z.object({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ECWireless changefeed has been broken for a long time now https://explorer.livepeer.org/whats-new. I think its best to remove it and ask @mehrdadmms on what the best place to link to is. I expect https://livepeer.org/changelog. I asked @mehrdadmms to review #838. This would mean this file can go from this pr.

Comment thread pages/api/usage.tsx
data.dayData = data.dayData.slice(0, -1);
}

if (validateOutput(UsageOutputSchema.safeParse(data), res, "usage")) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ECWireless this file can be removed since its no longer used in https://github.com/livepeer/explorer/pull/825/changes. We are not planing to bring it back.

currentL2Block: Number(currentL2Block),
};

const validationResult = CurrentRoundInfoSchema.safeParse(roundInfo);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ECWireless This output is built by us from typed values, so validating it can't fail and can be removed.

Comment thread pages/api/contracts.tsx
Comment on lines +132 to +135
const validationResult = ContractInfoSchema.safeParse(contractsInfo);
if (validateOutput(validationResult, res, "contracts")) {
return;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ECWireless This output is built by us from typed values, so validating it can't fail and can be removed.

@rickstaa rickstaa left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ECWireless Thanks for this. It's a big PR, so I'd trim it to where zod actually protects something.

Keep zod where data crosses a trust boundary:

  • user input (address and id params, generateProof, upload-ipfs body)
  • third-party responses (Pinata, subgraph, metrics and pricing servers, ENS records)

Remove output validation on responses we build ourselves from typed values (current-round, contracts, account-balance, l1-delegator, pending-stake, treasury routes, generateProof, upload-ipfs, usage). It can't fail there, and validateOutput only errors in development anyway.

Also:

  • Drop changefeed (removed in #838) and these unused schemas: OptionalStringSchema, EnsAvatarResultSchema, EnsAvatarProviderSchema, SubgraphResponseSchema, CurrentRoundSubgraphResultSchema, TreasuryProposalStateSchema, TreasuryVoteSchema.
  • usage can go too unless the chart is coming back soon (hidden in #825).
  • Once trimmed, consider one schema file per route (pending-stake.ts, generate-proof.ts, ...) with common.ts for shared primitives only, all exported from index.ts and imported only from @lib/api/schemas.

errorText,
`URL: ${url}`
);
return externalApiError(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes sense, is this change from {pipelines: []} handled correctly by the front-end.

Comment thread pages/api/score/index.tsx
}

const transcodersWithPrice = validateExternalResponse(
PriceResponseSchema.safeParse(await response.json()),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ECWireless right now it requires every row, we could filter invalid rows instead like in regions.

Comment thread lib/utils.tsx
import { DEFAULT_CHAIN_ID, NETWORK_RPC_URLS } from "lib/chains";
import numbro from "numbro";

export { sanitizeExternalUrl } from "./sanitizeExternalUrl";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ECWireless why was this moved outside into its own file?

This branch was successfully deployed

1 active deployment
Preview — ea9088dd Deployed Oct 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects
Status: Triage

Development

Successfully merging this pull request may close these issues.

Add zod validation in more places

4 participants