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
- Replace
jwt.decode() with jwt.verify() and explicitly restrict the accepted algorithm.
- Do not grant service privileges solely because the caller-controlled JWT contains
sub: "server@server.com".
- Inject service authorization through an internal server-only channel that cannot be supplied by ordinary local requests.
- Add a regression test proving that incorrectly signed and unsigned service-role tokens are rejected.
- 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
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 devaccepts 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 devlocal backendReviewed commit:
3059fbafb8c71555ad42507bea4fe2c2d9453ec0Root cause
The repository contains the hardcoded JWT secret:
The value is used to mint local JWTs:
Source:
tokens.ts#L1-L15However, knowledge of this secret is not required for exploitation. The authentication layer calls
jwt.decode()instead ofjwt.verify():Source:
current-user.ts#L40-L75When the decoded subject equals
server@server.com, the application returns a hardcoded service user:Source:
current-user.ts#L20-L26The RLS implementation grants unconditional access to this user:
Source:
rls.ts#L145-L161Impact
A local attacker can forge the JWT payload and obtain the service-role identity.
This allows unauthorized local access to entity operations, including:
create: falseThe 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-L443Proof of concept
The following code creates a token with an invalid signature. The hardcoded secret is intentionally not used:
Against a running local development server:
For an entity configured with
create: falseandread: 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:
Source:
main.ts#L185-L201base44 dev --remotedoes not start this local backend. It serves the frontend against the published production application:Source:
dev.ts#L91-L157Therefore, 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
jwt.decode()withjwt.verify()and explicitly restrict the accepted algorithm.sub: "server@server.com".LOCAL_DEV_SECRETwith 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
Error Logs
Additional Context
No response