cross-node auth tokens#1404
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
/run-security-scan |
alexcos20
left a comment
There was a problem hiding this comment.
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:
- Cryptographic Trust: If nodes share trust, verify the JWT using
jwt.verify()with a shared secret or the issuing node's public key (JWKS). - 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). - Nonce Freshness: If the
nonceis guaranteed to be a timestamp, the server must enforce a strict maximum time-to-live against it (e.g., rejecting ifDate.now() - Number(nonce) > MAX_TTL), and completely ignore the unauthenticatedvalidUntilfield in the payload.
• [ERROR][security] The expiration check is flawed and can be bypassed. An attacker can omitvalidUntilfrom a forged JWT payload (making itnullorundefined), or pass a non-numeric string (e.g.,"infinity") which causesNumber(validUntil)to evaluate toNaN. BecauseDate.now() >= NaNevaluates tofalse, 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).
Changes proposed in this PR: