Get this on every PR automatically. Webhook fires, AI reviews, you approve.
Install on stripe/link-cli →
// reviewed by pulllight
PullLight found 5 issues
3 high
2 medium
// convert to your repo
PullLight caught 5 3 high, 2 medium on this PR.
Install on stripe/link-cli to get this on every PR automatically — 60 seconds.
Findings
5 issues
high
race-condition
`nextRequestId` is a module-level mutable variable (`let nextRequestId = 1`). When multiple `UcpResource` instances are used concurrently (e.g., in a server handling parallel requests), they all share and mutate this counter, which can produce duplicate JSON-RPC request IDs within the same session or across sessions. This is a shared-state mutation bug. Move `nextRequestId` to be an instance field on `UcpResource` instead.
// packages/sdk/src/resources/ucp.ts:97
high
injection
The `normalizeBusinessUrl` method prepends `https://` to URLs that lack a protocol, then constructs `${normalized}/.well-known/ucp`. If a caller passes a value like `evil.com/path?x=` or a URL with embedded path segments, the resulting well-known URL could point to an unintended host or path. The normalized URL is also used as a cache key and as the base for REST/MCP endpoint construction. There is no validation that the normalized URL is actually an `https://` or `http://` URL pointing to a valid host — a value like `javascript:alert(1)` or `file:///etc/passwd` would pass through. Add URL parsing validation (e.g., using `new URL(...)` and asserting `protocol` is `http:` or `https:`) before using the normalized value.
// packages/sdk/src/resources/ucp.ts:148
medium
resource-leak
The `discoveryCache` and `negotiatedCache` Maps on `UcpResource` instances grow without bound — there is no eviction, TTL, or size cap. A long-lived resource instance that discovers many distinct business URLs will accumulate entries indefinitely. This is a memory leak for server-side usage.
// packages/sdk/src/resources/ucp.ts:113
medium
error-handling
`JSON.parse(c.options.input)` and `JSON.parse(c.options.lineItems)` in `cartCreate`, `cartUpdate`, `checkoutCreate`, and `checkoutUpdate` run handlers are not wrapped in try/catch. A malformed JSON string passed via `--input` or `--line-items` will throw an unhandled exception that propagates as an unformatted crash rather than a user-friendly CLI error message.
// packages/cli/src/commands/ucp/index.tsx:120
high
auth
The `clientSecret` is included verbatim in a Basic auth header constructed as `btoa(clientId + ':' + clientSecret)`. If `clientId` contains a colon character (`:`), the credential boundary is ambiguous and the server may misparse the credentials. RFC 7617 requires that the client-id must not contain a colon. There is no validation that `clientId` is colon-free before constructing the header, which could allow credential confusion or bypass if an attacker can influence the `clientId` value.
// packages/sdk/src/resources/ucp.ts:130