# How a Profile Update Feature Could Drain a Company's Bank Account

I was looking at one of the most normal features you can find in almost any application:

**Update Profile.**

Nothing exciting.

A user changes their name, profile picture, maybe email, and the backend updates it.

That was what I expected.

The endpoint looked something like this:

```plaintext
PUT /api/users/{userId}
```

But when I looked at the response, a few fields immediately caught my attention.

Along with normal user information, I could see things like:

```text
credits
planTokens
topUpTokens
currentPlanId
userType
userId
status
createdAt
updatedAt
```

And that made me curious.

Not because all these fields were inside one JSON object. A backend can combine data from multiple tables, collections, or services and return it as one response.

The real question was:

**Why are billing, subscription, authorization, identity, and server-managed fields sitting so close to a simple profile update operation?**

So I started testing.

## One Field Changed Everything

The platform I was testing provides different AI agents.

Users purchase credits, and those credits can be spent on different agents.

Some agents generate scripts.

Some generate storyboards.

Some generate high-quality images.

Others generate realistic AI videos using expensive third-party AI models.

So the credit balance is not just some number displayed on the UI.

It represents something that costs the company real money.

I wanted to understand whether fields like `credits` were only returned by the API or whether the profile endpoint would actually accept them during an update.

So I tested it carefully on my own account.

And the server accepted the credit value.

At first, even that wasn't enough to call it critical.

Changing a number in the database doesn't automatically mean anything.

Maybe the real billing system uses another ledger.

Maybe the value is only used by the frontend.

Maybe the agent backend checks payments separately.

So the important question became:

**Does this modified balance actually allow access to the AI agents?**

It did.

That was the point where a normal profile update bug turned into something much more serious.

## This Wasn't Just “Free Credits”

The attack path was basically:

```text
Normal user
    ↓
Profile update endpoint
    ↓
Credit balance modified
    ↓
Premium AI agents become available
    ↓
Images / Videos / Other AI generations
    ↓
Third-party AI providers
    ↓
Company pays the usage cost
```

Think about what that means.

An attacker wouldn't need to steal the company's API keys.

They wouldn't need to compromise the AI providers.

They wouldn't even necessarily need an admin account.

They could potentially use the company's own legitimate infrastructure against it.

Imagine someone automating content generation.

Scripts.

Storyboards.

Images.

4K AI videos.

Again and again.

From the attacker's side, the cost could effectively be zero.

But somewhere behind the platform, every generation may be creating a real bill for the company.

That's why I don't see this simply as a **“get free credits” vulnerability**.

It is a financial trust-boundary problem.

At enough scale, and without proper rate limits or provider-side spending controls, a bug like this could potentially create serious financial damage.

## And Credits Weren't the Only Interesting Field

Once I understood what was happening, other fields became much more concerning.

For example:

```text
userType
userId
currentPlanId
planTokens
topUpTokens
status
createdAt
updatedAt
```

If `userType` can be changed from a normal user to an administrator, and the authorization system trusts that value, that could become privilege escalation.

If `userId` is writable, it creates another set of identity and account-integrity questions.

If subscription fields are writable, users may potentially manipulate their own plan or entitlement.

Even fields like `createdAt` and `updatedAt` were interesting.

They weren't the main vulnerability.

But they were a clue.

These are normally values the **server should own**, not the client.

And that pointed to the bigger problem.

## The Backend Didn't Clearly Separate Trust

A dangerous backend implementation can sometimes look conceptually like this:

```javascript
Object.assign(user, req.body);
await user.save();
```

Or something equivalent to:

```javascript
$set: req.body
```

The problem is simple.

The backend receives a request body and trusts too much of it.

A user should probably be able to update things like:

```text
userName
profileImage
preferences
```

But not:

```text
credits
role
subscription
userId
account status
timestamps
```

This type of issue is commonly known as **Mass Assignment** or **Over-Posting**.

But I think there is an even simpler way to understand it:

**The backend forgot to clearly decide which fields belong to the user and which fields belong to the system.**

## This Is Where DTOs Actually Matter

As a developer, this is also a good example of why request DTOs and explicit validation matter.

But just “using a DTO” doesn't automatically solve the problem.

You can still create a bad DTO:

```ts
class UpdateUserDto {
  userName: string;
  credits: number;
  userType: string;
  currentPlanId: number;
}
```

That's still dangerous.

A profile update DTO should contain only the fields a user is actually allowed to modify.

Something closer to:

```ts
class UpdateProfileDto {
  userName?: string;
  profileImage?: string;
}
```

Billing should have its own logic.

Authorization should have its own logic.

Subscriptions should have their own logic.

A profile endpoint should not quietly become the place where all of them can be modified.

## One Bug Shouldn't Be Able to Create an Unlimited Bill

There is another important lesson here.

Even if one endpoint has a vulnerability, a company handling paid AI services should have more layers of protection.

Things like:

```text
Per-user rate limits
Daily generation limits
Maximum concurrent jobs
Credit sanity checks
Usage anomaly detection
Provider spending limits
Alerts for unusual consumption
Emergency kill switches
```

Security shouldn't depend on one API endpoint being perfectly implemented.

Especially when every successful request can cost real money.

## The Main Lesson I Took From This

What started as a boring profile update feature ended up crossing multiple security boundaries:

**Profile data.**

**Billing.**

**Authorization.**

**Subscription state.**

**Identity.**

And eventually, real infrastructure cost.

The dangerous part wasn't the `PUT` request itself.

The dangerous part was that the backend didn't clearly understand where the user's control should stop.

A username can belong to the user.

A profile picture can belong to the user.

But their authorization role doesn't.

Their subscription doesn't.

And their credit balance definitely shouldn't.

**Never trust the client with fields that represent money, identity, authorization, or server-controlled state.**

Sometimes the most expensive security vulnerability isn't hidden inside some complex cryptographic system.

Sometimes it is sitting inside something as ordinary as an **Update Profile** button.
