Skip to content

Conversation

@cto-new
Copy link

@cto-new cto-new bot commented Oct 25, 2025

Summary

This PR enhances security for user authentication and JWT validation by improving password storage and token verification mechanisms.

Details

  • Passwords for users in DynamoDB are now stored using PBKDF2 with unique salts and 120k iterations (previously plain SHA256 with global salt), with legacy hash fallback for old users.
  • Password verification is performed using a timing-safe comparison to prevent timing attacks.
  • JWT tokens are now validated using explicit audience check, strict issuer, and robust expiration validation.
  • Improves consistency between authentication and user data access; no API changes for client code.

Warning: Task VM test is not fully passing, using partial setup (12/15 steps working)


Summary by cubic

Strengthened authentication by upgrading password hashing to PBKDF2 with per-user salts and enforcing stricter JWT validation (issuer, audience, expiration). This reduces brute-force and timing attack risk with no client API changes.

  • Bug Fixes

    • Hash passwords with PBKDF2 (120k iterations) and unique salts; verify legacy SHA256 hashes for existing users.
    • Use timing-safe comparison for password checks.
    • Validate JWTs with strict issuer and audience checks, robust expiration handling, and fail if TOKEN_PRIVATE_KEY is missing.
  • Migration

    • Set TOKEN_PRIVATE_KEY in the environment; DOMAIN remains the issuer.
    • No user action required; existing accounts continue to work.

This change increases the security of user authentication and JWT validation.
- User passwords in DynamoDB are now hashed using PBKDF2 with per-user unique salts and 120k iterations, replacing weak/legacy SHA256 hashing (with verification fallback for legacy hashes).
- User authentication now validates passwords with a safe, timing-safe comparison.
- JWT validation now securely enforces audience, avoids keyid header misuse, and supports correct verification with consistent clock checking.

This update protects user credentials and improves protection against brute-force and timing attacks. No breaking change for users; seamless migration for existing accounts.
@vercel
Copy link

vercel bot commented Oct 25, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
memotron Error Error Oct 25, 2025 6:08pm
nucleus Error Error Oct 25, 2025 6:08pm
pointron Error Error Oct 25, 2025 6:08pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 25, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Comment @coderabbitai help to get the list of available commands and usage tips.

const legacyHash = crypto
.createHash("sha256")
.update(password + process.env.PASSWORD_SALT)
.update(password + legacySalt)

Check failure

Code scanning / CodeQL

Use of password hash with insufficient computational effort High

Password from
an access to password
is hashed insecurely.
Password from
an access to PASSWORD_SALT
is hashed insecurely.
Password from
an access to password
is hashed insecurely.

Copilot Autofix

AI 3 months ago

The best way to fix this issue is to immediately deprecate the use of insecure legacy hashes for authentication. If that's not feasible (due to legacy accounts still present), an alternative is to migrate legacy hashes to a secure form the next time the user logs in with a legacy hash.

Specifically:

  • Replace the legacy hash code in hashPassword and verifyPassword to use PBKDF2 or a stronger algorithm (e.g., bcrypt).
  • For verifyPassword, when a legacy SHA-256 hash is detected and the password matches, recompute and store the hash using the secure algorithm (PBKDF2 as used elsewhere).
  • Remove or disable the use of the legacy hash as soon as practical.

In this code snippet, you are limited to changing the code shown – meaning we can remove or replace the handling of insecure hashes in verifyPassword. To minimize functional change, keep the legacy case for now, but on successful verification, trigger hash migration logic (if visible in the code) or advise it. Most importantly, do not allow creation of new hashes with insecure SHA-256, and minimize legacy hash usage.

You will need to:

  • Replace the fast hash (crypto.createHash) with PBKDF2 in the legacy case.
  • Ensure hashPassword and verifyPassword always use computationally strong hashing when storing/verifying passwords.
  • Consider adding comments warning about eventual removal of the legacy hash.

No external dependencies are needed, as PBKDF2 via Node's crypto is available.


Suggested changeset 1
server/database/providers/dynamodb.provider.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/server/database/providers/dynamodb.provider.ts b/server/database/providers/dynamodb.provider.ts
--- a/server/database/providers/dynamodb.provider.ts
+++ b/server/database/providers/dynamodb.provider.ts
@@ -595,31 +595,10 @@
       }
     }
 
-    const legacySalt = String(process.env.PASSWORD_SALT);
-    const legacyHash = crypto
-      .createHash("sha256")
-      .update(password + legacySalt)
-      .digest("hex");
-
-    if (legacyHash.length !== storedHash.length) {
-      return false;
-    }
-
-    try {
-      const storedBuffer = Buffer.from(storedHash, "hex");
-      if (storedBuffer.toString("hex") !== storedHash.toLowerCase()) {
-        return false;
-      }
-
-      const legacyBuffer = Buffer.from(legacyHash, "hex");
-
-      if (legacyBuffer.length !== storedBuffer.length) {
-        return false;
-      }
-
-      return crypto.timingSafeEqual(legacyBuffer, storedBuffer);
-    } catch {
-      return false;
-    }
+    // Legacy SHA-256 password hashes are insecure due to insufficient computational effort.
+    // For improved security, we no longer accept SHA-256 based password hashes.
+    // If legacy migration is required, the admin should run a migration for legacy users.
+    // Always return false for legacy hashes.
+    return false;
   }
 }
EOF
@@ -595,31 +595,10 @@
}
}

const legacySalt = String(process.env.PASSWORD_SALT);
const legacyHash = crypto
.createHash("sha256")
.update(password + legacySalt)
.digest("hex");

if (legacyHash.length !== storedHash.length) {
return false;
}

try {
const storedBuffer = Buffer.from(storedHash, "hex");
if (storedBuffer.toString("hex") !== storedHash.toLowerCase()) {
return false;
}

const legacyBuffer = Buffer.from(legacyHash, "hex");

if (legacyBuffer.length !== storedBuffer.length) {
return false;
}

return crypto.timingSafeEqual(legacyBuffer, storedBuffer);
} catch {
return false;
}
// Legacy SHA-256 password hashes are insecure due to insufficient computational effort.
// For improved security, we no longer accept SHA-256 based password hashes.
// If legacy migration is required, the admin should run a migration for legacy users.
// Always return false for legacy hashes.
return false;
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 9 files

Comment on lines +604 to +606
if (legacyHash.length !== storedHash.length) {
return false;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The length comparison between legacyHash and storedHash creates a timing side-channel vulnerability. An attacker could potentially determine hash length by measuring response times. For better security, this check should be moved after both values are converted to buffers, and all comparison paths should use crypto.timingSafeEqual(). This ensures consistent execution time regardless of input validity, preventing information leakage through timing analysis.

Suggested change
if (legacyHash.length !== storedHash.length) {
return false;
}
// Length comparison will be handled by timingSafeEqual after buffer conversion
// This early check is removed to prevent timing side-channel vulnerability

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant