Skip to content

cross-node auth tokens#1404

Draft
giurgiur99 wants to merge 1 commit into
mainfrom
feature/cross-node-authtoken
Draft

cross-node auth tokens#1404
giurgiur99 wants to merge 1 commit into
mainfrom
feature/cross-node-authtoken

Conversation

@giurgiur99

@giurgiur99 giurgiur99 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Changes proposed in this PR:

  • Enable cross-node auth tokens
  • Ask another node for confirmation that this token is legit + recover the address of the user that signed it to prove ownership

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7005d5c0-8bd3-46b2-b77a-688024f7dd76

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/cross-node-authtoken

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@giurgiur99

Copy link
Copy Markdown
Contributor Author

/run-security-scan

@alexcos20 alexcos20 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI automated code review (Gemini 3).

Overall risk: high

Summary:
The PR introduces cross-node token verification but contains a critical security flaw. The implementation uses jwt.decode() to parse remote tokens, bypassing cryptographic signature verification. Since the user's embedded signature only authenticates the address, nonce, and command, an attacker can forge the JWT payload to alter or remove the validUntil field, creating tokens that never expire. Additionally, there are bypass vulnerabilities in how validUntil and createdAt are validated.

Comments:
• [ERROR][security] Using jwt.decode() evaluates the payload without verifying its cryptographic signature (CWE-345). Because the embedded user signature (signatureMatchesAddress) only covers address, nonce, and command, the validUntil and createdAt fields in the JWT payload are completely unauthenticated.

An attacker can take a legitimate token, extract the address, nonce, and signature, and generate a forged JWT payload with an altered or missing validUntil field. The validateRemoteToken method will incorrectly accept this forged token.

Recommendations for architectural fix:

  1. Cryptographic Trust: If nodes share trust, verify the JWT using jwt.verify() with a shared secret or the issuing node's public key (JWKS).
  2. Stateless Verification (Client-Signed Expiration): If validation must be stateless and trustless, the user's original signature MUST include the expiration time (e.g., signing address + nonce + validUntil + command).
  3. Nonce Freshness: If the nonce is guaranteed to be a timestamp, the server must enforce a strict maximum time-to-live against it (e.g., rejecting if Date.now() - Number(nonce) > MAX_TTL), and completely ignore the unauthenticated validUntil field in the payload.
    • [ERROR][security] The expiration check is flawed and can be bypassed. An attacker can omit validUntil from a forged JWT payload (making it null or undefined), or pass a non-numeric string (e.g., "infinity") which causes Number(validUntil) to evaluate to NaN. Because Date.now() >= NaN evaluates to false, the token will be considered valid indefinitely.

Require validUntil to be a valid number and reject tokens that fail this check.

-    if (validUntil != null && Date.now() >= Number(validUntil)) {
+    const expTime = Number(validUntil)
+    if (validUntil == null || Number.isNaN(expTime) || Date.now() >= expTime) {
       return null
     }

• [WARNING][bug] Similar to validUntil, the createdAt value is pulled from an unverified payload. If it is omitted or a non-numeric string is provided, Number(createdAt) will be NaN, resulting in an invalid Date object being returned inside the AuthToken. This could lead to downstream crashes if other components expect a valid Date object.

Ensure createdAt is validated before constructing the Date object.

+    const createdTime = Number(createdAt)
+    if (Number.isNaN(createdTime)) {
+      return null
+    }
     return {
       token,
       address,
-      created: new Date(Number(createdAt)),
+      created: new Date(createdTime),
       validUntil: validUntil != null ? new Date(Number(validUntil)) : null,
       isValid: true
     }

• [INFO][style] Good use of falling back to verify both the hex string and the raw byte array with toBeArray. This ensures compatibility with different client-side signing implementations (e.g., clients that sign the raw bytes vs. clients that sign the literal hex representation).

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.

2 participants