F-2026-0001·broken-http-semantics

State-Changing Operation via GET: Unauthenticated Referral Code Auto-Creation

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

An unauthenticated GET endpoint silently created database records for any wallet address supplied in the URL, violating HTTP GET safety and enabling bulk pre-seeding of referral codes for arbitrary addresses.

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

Description

The GET /lending/referral-code/:walletAddress endpoint violates a fundamental HTTP safety requirement: GET requests must be safe and idempotent, they must not modify server state. This endpoint silently breaks that contract by writing a new database record every time it is called for a wallet address that does not yet have a referral code.

The logic inside ReferralCodeService.getReferralCode() first attempts a database lookup, and if the record is not found it immediately creates one rather than returning a 404:

typescript
// referral-code.service.ts
public async getReferralCode(walletAddress: string): Promise<ReferralCode> {
try {
return await PrismaHelpers.execute(() =>
this.prismaClient.referralCode.findFirstOrThrow({
where: { walletAddress },
}),
);
} catch (error) {
if (error instanceof NotFoundException) {
return this.createReferralCode(walletAddress); // ← DB write on GET
}
throw error;
}
}
private async createReferralCode(walletAddress: string): Promise<ReferralCode> {
const codeLength = 16;
return PrismaHelpers.execute(() =>
this.prismaClient.referralCode.create({
data: {
referralCodeId: ...,
code: generateRandomString(codeLength),
walletAddress, // taken from caller-supplied path param
...
},
}),
);
}

This compounds into a more serious issue because the controller has no authentication guard applied at either the class or method level. The auto-creation path is therefore reachable by any HTTP client on the internet, including web crawlers, search engine indexers, browser link prefetchers, security scanners, and monitoring probes, all of which routinely issue GET requests without credentials. None of these clients need any knowledge of the platform or its authentication model to trigger a database write.

Vulnerable scenario:

  1. A security scanner or web crawler discovers the endpoint URL pattern through Swagger exposure (the internal Swagger is publicly accessible on sandbox, see corresponding finding), JavaScript bundle analysis, or network traffic interception.
  2. The scanner begins issuing GET /lending/referral-code/:address requests against a list of wallet addresses sourced from public blockchain data.
  3. For every wallet address in the list that has no existing referral code, the backend silently creates a new ReferralCode record in PostgreSQL and returns the generated code in the response.
  4. The attacker now holds the referral codes for wallets they do not own, assigned before the legitimate users have ever interacted with the lending platform.
  5. These pre-seeded codes may interfere with the legitimate onboarding flow if the platform uses a code's existence as a signal that a user has already been onboarded, or if the code is reused across systems.
  6. At sufficient scale, this creates unbounded database growth as new ReferralCode rows are written for every enumerated address with no mechanism to clean them up.
03Section · Impact

Impact

Any unauthenticated HTTP client, including automated tools that would never reach an authenticated endpoint, can force database record creation for arbitrary wallet addresses by issuing a standard GET request. This enables bulk pre-seeding of referral codes across the entire addressable wallet population, interferes with legitimate onboarding flows, creates unbounded storage growth through automated enumeration, and exposes generated referral codes for wallets before their owners have interacted with the platform.

04Section · Recommendation

Recommendation

Separate the read and write operations. The GET handler should perform a pure lookup and return 404 Not Found if no referral code exists for the target wallet. Record creation must happen through an authenticated POST endpoint that verifies the caller owns the wallet.

Apply CustomerJwtGuard to the creation endpoint and derive the wallet address from the JWT's embedded walletAddress claim rather than accepting it as a URL parameter, this ensures a referral code can only be created by the authenticated wallet owner, not by any arbitrary caller supplying an address.

05Section · Resolution

Resolution

Accepted by team. The endpoint is fully disabled in every environment via DeprecatedEndpointGuard plus the controllerBlocked module flag (PR #3721).

F-2026-0001

oog
zealynx

Smart Contract Security Digest

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

© 2026 Zealynx