F-2026-0016·latent-sql-injection

`$queryRawUnsafe` with Table Name String Interpolation in Watchdog Service

Fixedpentestbackendapigithub.com/bloom-art/api
TL;DR

Watchdog query used Prisma's `$queryRawUnsafe` with a table name interpolated directly into the SQL string. Currently mitigated by a feedType whitelist, but creates a latent SQL injection risk that future code changes could trigger.

Severity
LOW
Impact
LOW
Likelihood
LOW
Method
MManual review
CAT.
Complexity
HIGH
Exploitability
LOW
02Section · Description

Description

The getLastComputedFeedPublishedDate method uses Prisma's $queryRawUnsafe, the explicitly unsafe variant that does not parameterize inputs, with a tableName variable interpolated directly into the SQL string:

typescript
const tableName = feedType;
const result = await this.prismaClient.$queryRawUnsafe<{ publishedAt: Date }[]>(
`SELECT t."publishedAt" FROM "Market" m INNER JOIN "${tableName}" t ON t."marketId" = m.id ...`,
marketId,
);

A whitelist check (knownFeedTypes.includes(feedType)) runs immediately before this call and currently limits feedType to three known table names. However:

The function uses $queryRawUnsafe rather than the safe $queryRaw with Prisma.raw. This means if the whitelist check is ever weakened, removed, or bypassed, the raw string interpolation directly enables SQL injection.

feedType originates from a raw database query result (row.feedType from $queryRaw), which is typically safe but creates a trust chain that is difficult to audit: if the upstream raw query ever returns a manipulated value, it flows directly into an unsafe SQL string.

03Section · Impact

Impact

Currently mitigated by the whitelist, but the use of $queryRawUnsafe with string interpolation creates a latent SQL injection risk that could be triggered by future code changes.

04Section · Recommendation

Recommendation

Replace $queryRawUnsafe with the safe $queryRaw using Prisma.raw for the table name component:

typescript
const result = await this.prismaClient.$queryRaw<{ publishedAt: Date }[]>(
Prisma.sql`SELECT t."publishedAt" FROM "Market" m INNER JOIN ${Prisma.raw(`"${tableName}"`)} t ON t."marketId" = m.id WHERE m."marketId" = ${marketId} ORDER BY t."publishedAt" DESC LIMIT 1`,
);

Retain the whitelist as defense-in-depth.

05Section · Resolution

Resolution

Fixed in PR #3745, $queryRawUnsafe was replaced with $queryRaw + Prisma.sql, and now appears in zero production call sites.

Status
Fixed
F-2026-0016

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx