Skip to content

cross-node auth tokens#1404

Draft
giurgiur99 wants to merge 3 commits into
mainfrom
feature/cross-node-authtoken
Draft

cross-node auth tokens#1404
giurgiur99 wants to merge 3 commits 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

Summary by CodeRabbit

  • New Features

    • Added support for delegated token validation across peers, including returning the resolved account address on success.
    • Expanded token payloads to carry additional authentication details for cross-node verification.
  • Bug Fixes

    • Improved authentication fallback so valid local tokens are used first, with remote validation as a backup.
    • Fixed address resolution for storage actions by using the authenticated identity instead of the raw token.
  • Tests

    • Added coverage for cross-node token validation, including success, rejection, timeout, and loop-prevention scenarios.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

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: 2b4d7680-a4d2-4219-bbd0-de39926e96b9

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
📝 Walkthrough

Walkthrough

Adds cross-node delegated JWT token validation via a new P2P VALIDATE_AUTH_TOKEN command. Auth now derives fallback verification remotely through configurable-timeout P2P calls, signature verification is refactored into a reusable helper, and consumerAddress is propagated from auth results into persistent storage handlers instead of decoding tokens directly.

Changes

Delegated auth token validation

Layer / File(s) Summary
Protocol command definitions
src/utils/constants.ts
Adds VALIDATE_AUTH_TOKEN to PROTOCOL_COMMANDS and SUPPORTED_PROTOCOL_COMMANDS.
Signature verification refactor
src/components/core/utils/nonceHandler.ts
Extracts verifyConsumerSignature supporting EOA and ERC-1271 checks returning booleans; validateNonceAndSignature delegates to it.
Auth class remote validation flow
src/components/Auth/index.ts
Extends constructor with getP2PNode, extends getJWTToken claims, adds getLocalToken and private validateRemoteToken for P2P-based verdict retrieval, and returns address from validateAuthenticationOrToken.
P2P configurable timeout
src/components/P2P/index.ts
Adds timeoutMs parameter to sendTo used for dial/stream abort timeout.
ValidateAuthTokenHandler and registry wiring
src/components/core/handler/authHandler.ts, src/components/core/handler/coreHandlersRegistry.ts
Adds ValidateAuthTokenCommand/ValidateAuthTokenHandler, updates CreateAuthTokenHandler to include issuerPeerId/chainId, and registers the new handler replacing invalidate-token registration.
consumerAddress propagation
src/components/core/handler/handler.ts, src/components/core/handler/persistentStorage.ts
validateTokenOrSignature returns consumerAddress, removes getAddressFromToken, and storage handlers use isAuthRequestValid.consumerAddress as ownerNormalized fallback.
Delegated validation tests
src/test/unit/auth/token.test.ts
Adds a test suite mocking P2P delegation covering valid/invalid verdicts, unreachable issuer, missing signature/issuerPeerId, and loop-prevention cases.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant LocalNode as Auth (local node)
    participant P2PNode as OceanP2P
    participant IssuerNode as Issuer node
    participant ValidateHandler as ValidateAuthTokenHandler

    Client->>LocalNode: validateToken(token)
    LocalNode->>LocalNode: check local AuthTokenDatabase
    alt token found locally
        LocalNode-->>Client: AuthToken (local)
    else not found locally
        LocalNode->>LocalNode: validateRemoteToken(token)
        LocalNode->>LocalNode: decode JWT, verifyConsumerSignature
        LocalNode->>LocalNode: parse issuerPeerId, ensure not self
        LocalNode->>P2PNode: sendTo(issuerPeerId, VALIDATE_AUTH_TOKEN, timeoutMs)
        P2PNode->>IssuerNode: dial + stream request
        IssuerNode->>ValidateHandler: handle(ValidateAuthTokenCommand)
        ValidateHandler->>ValidateHandler: verify JWT + fetch local token
        ValidateHandler-->>P2PNode: { valid, validUntil }
        P2PNode-->>LocalNode: streamed verdict
        alt verdict.valid
            LocalNode-->>Client: AuthToken (created, validUntil)
        else invalid
            LocalNode-->>Client: null / rejected
        end
    end
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: enabling auth tokens to work across nodes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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).

@giurgiur99

Copy link
Copy Markdown
Contributor Author

/run-security-scan

@giurgiur99

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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:
This PR implements cross-node authentication token validation. While the feature is well-structured, there are critical security vulnerabilities that need to be addressed. The primary issue is an authentication bypass in the remote token validation logic due to trusting the issuerPeerId from an unverified JWT. Additionally, there is an IDOR/privilege escalation flaw in the Persistent Storage handlers, as well as potential DDoS vectors via unbounded stream reading and long network timeouts.

Comments:
• [ERROR][security] Critical Authentication Bypass: jwt.decode is used without cryptographic verification of the issuing node. An attacker who intercepts or observes a victim's valid CREATE_AUTH_TOKEN signature (which is node-agnostic) can craft a forged JWT containing the victim's address and signature, but setting issuerPeerId to the attacker's own malicious node. Node B will successfully verify the victim's consumer signature locally, then query the attacker's node. The attacker's node will reply {"valid": true}, causing Node B to fully authenticate the attacker as the victim. To resolve this, the issuing node MUST sign the JWT asymmetrically (e.g., using its libp2p private key), and Node B MUST verify the token's signature against the issuerPeerId's public key before making any P2P calls.
• [ERROR][security] Privilege Escalation / IDOR: By using task.consumerAddress ? getAddress(task.consumerAddress) : ..., you allow an authenticated user to act on behalf of another user. An attacker can supply their own valid token in task.authorization (which sets isAuthRequestValid.consumerAddress to the attacker's address) but pass a victim's address in task.consumerAddress. Since task.consumerAddress is truthy, the system will use it and grant access to the victim's storage. You should strictly use the authenticated identity:

-        ownerNormalized = task.consumerAddress
-          ? getAddress(task.consumerAddress)
-          : getAddress(isAuthRequestValid.consumerAddress)
+        ownerNormalized = getAddress(isAuthRequestValid.consumerAddress)

Note: This issue is also present in other PersistentStorage handlers (e.g., lines 163, 271, 315, 373, 423). Please update all occurrences.
• [WARNING][security] Unbounded Stream Reading: streamToString consumes the entire stream into memory. A malicious peer could exploit this by sending an endless stream of data in response to VALIDATE_AUTH_TOKEN, causing an Out-Of-Memory (OOM) crash on the node. You should enforce a maximum byte limit (e.g., 1024 bytes) when reading this JSON response.
• [WARNING][performance] A 30-second timeout for remote token validation (VALIDATE_TOKEN_DIAL_TIMEOUT_MS = 30_000) is very long. Since this validation blocks incoming API requests, it can lead to resource exhaustion if an attacker supplies a slow or unresponsive issuerPeerId. Consider reducing this to a much shorter timeout (e.g., 5000ms).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/core/handler/persistentStorage.ts (1)

106-110: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the derived consumer address before the access-list check.

For token-only delegated auth, task.consumerAddress can be absent while isAuthRequestValid.consumerAddress is valid. The create-bucket access-list check still uses task.consumerAddress, so valid requests can be denied before Line 110’s fallback is applied.

🔧 Proposed fix
+      let ownerNormalized: string
+      try {
+        const effectiveConsumerAddress =
+          task.consumerAddress || isAuthRequestValid.consumerAddress
+        ownerNormalized = getAddress(effectiveConsumerAddress)
+      } catch {
+        return {
+          stream: null,
+          status: { httpStatus: 400, error: 'Invalid parameter: "consumerAddress"' }
+        }
+      }
+
       // if we have access lists,check them.
       if (
         config.persistentStorage?.accessLists &&
         config.persistentStorage?.accessLists.length > 0
       ) {
         const isAllowedCreate = await checkAddressOnAccessList(
-          task.consumerAddress,
+          ownerNormalized,
           config.persistentStorage?.accessLists,
           node
         )
@@
-      let ownerNormalized: string
-      try {
-        ownerNormalized = task.consumerAddress
-          ? getAddress(task.consumerAddress)
-          : getAddress(isAuthRequestValid.consumerAddress)
-      } catch {
-        return {
-          stream: null,
-          status: { httpStatus: 400, error: 'Invalid parameter: "consumerAddress"' }
-        }
-      }
-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/handler/persistentStorage.ts` around lines 106 - 110, The
access-list check is still using task.consumerAddress even when the derived
consumer address should come from isAuthRequestValid.consumerAddress for
token-only delegated auth. Update the create-bucket authorization path in
persistentStorage.ts to resolve the consumer address once into ownerNormalized
(or a similarly named local) and use that derived value for the access-list
check instead of the possibly undefined task.consumerAddress.
🧹 Nitpick comments (3)
src/test/unit/auth/token.test.ts (3)

106-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hard-coded command string risks silent drift from PROTOCOL_COMMANDS.CREATE_AUTH_TOKEN.

mintRemoteToken signs with the literal string 'createAuthToken' instead of importing PROTOCOL_COMMANDS.CREATE_AUTH_TOKEN, which is what Auth.validateRemoteToken actually passes to verifyConsumerSignature. If that constant's value ever changes, these tests will keep passing while production code silently breaks (or vice versa) since there is no compile-time link between the two.

♻️ Proposed fix
+import { PROTOCOL_COMMANDS } from '../../../utils/constants.js'
...
   const signature = await safeSign(
     signWith,
-    createHashForSignature(claimAddress, nonce, 'createAuthToken')
+    createHashForSignature(claimAddress, nonce, PROTOCOL_COMMANDS.CREATE_AUTH_TOKEN)
   )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/auth/token.test.ts` around lines 106 - 124, Update
mintRemoteToken in token.test.ts to use PROTOCOL_COMMANDS.CREATE_AUTH_TOKEN
instead of the hard-coded 'createAuthToken' string when calling
createHashForSignature, so the test stays aligned with Auth.validateRemoteToken
and verifyConsumerSignature. Import or reference the shared command constant
directly in the test helper to avoid drift if the protocol command value
changes.

81-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

sendTo mock never asserts the outgoing message payload.

The mock tracks call count but the tests never verify _msg contains the expected { command: PROTOCOL_COMMANDS.VALIDATE_AUTH_TOKEN, token } shape. This leaves the actual wire contract between Auth.validateRemoteToken and the P2P layer unverified — a regression that mangles the request body would go undetected as long as call counts match.

Consider capturing _msg on the tracker and asserting its shape in at least the "accepts" test.

Also applies to: 133-244

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/auth/token.test.ts` around lines 81 - 104, The `makeFakeP2P`
test double in `token.test.ts` only counts `sendTo` calls and does not verify
the request body sent by `Auth.validateRemoteToken`. Update the mock to capture
the `_msg` payload (alongside `tracker.calls`) and add assertions in the
remote-token tests, especially the “accepts” case, that the outgoing message
contains `{ command: PROTOCOL_COMMANDS.VALIDATE_AUTH_TOKEN, token }`. This keeps
the wire contract between `validateRemoteToken` and `OceanP2P.sendTo` covered.

133-244: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Test cases correctly mirror Auth.validateRemoteToken control flow.

Verified each scenario (local hit, valid/invalid verdict, unreachable issuer, missing signature, missing issuerPeerId, self-issuer, loop prevention via getLocalToken) against src/components/Auth/index.ts's validateRemoteToken/getLocalToken implementation — the assertions on tracker.calls and return values line up with the guard order (signature check → issuerPeerId presence → self-peer check → P2P dial → verdict parsing).

One gap: no case exercises a malformed issuerPeerId that fails peerIdFromString. Optional to add for completeness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit/auth/token.test.ts` around lines 133 - 244, Add a unit test in
the Auth token suite to cover a malformed issuerPeerId that causes
peerIdFromString to fail in validateRemoteToken. Use the existing Auth,
makeFakeP2P, and mintRemoteToken helpers as the reference points, and assert
that validateToken returns null without delegating to the issuer (tracker.calls
stays 0). Keep the new case aligned with the current control-flow assertions
around validateRemoteToken/getLocalToken so it fits the existing guard-order
coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Auth/index.ts`:
- Around line 145-150: The P2P auth validation path in `Auth` is sending a raw
JWT through `p2p.sendTo`, and `OceanP2P.sendTo()` logs the full message payload.
Update `sendTo` to redact or omit token-bearing fields before logging, or stop
logging the raw `message` for `VALIDATE_AUTH_TOKEN` requests so `token` never
appears in logs.
- Around line 155-157: The auth verdict parsing in Auth/index.ts currently reads
the entire remote response with streamToString() before JSON.parse, which can
hang indefinitely or consume too much memory. Update the verdict-fetch path
around the verdict assignment to enforce a maximum byte limit and a read timeout
on response.stream before parsing. Use the existing auth validation flow and
streamToString-related handling to ensure oversized or slow responses are
rejected cleanly before JSON.parse runs.

In `@src/components/core/handler/authHandler.ts`:
- Around line 140-142: The new ValidateAuthTokenCommand path can leak bearer
tokens because validateCommandParameters() logs the raw command payload. Update
authHandler’s validate() flow to redact the token before any shared command
logging, using a VALIDATE_AUTH_TOKEN redaction path in the same
validation/logging path so the token is never emitted. Keep the fix localized
around validateCommandParameters and the ValidateAuthTokenCommand handling.

In `@src/components/core/handler/handler.ts`:
- Around line 208-209: The success response in the handler’s P2PCommandResponse
is missing the error field, causing the response shape to drift from the rest of
the handler path. Update the success return in handler.ts to keep the full
P2PCommandResponse contract by including error: null alongside status and
consumerAddress, matching the other response branches in this handler.

---

Outside diff comments:
In `@src/components/core/handler/persistentStorage.ts`:
- Around line 106-110: The access-list check is still using task.consumerAddress
even when the derived consumer address should come from
isAuthRequestValid.consumerAddress for token-only delegated auth. Update the
create-bucket authorization path in persistentStorage.ts to resolve the consumer
address once into ownerNormalized (or a similarly named local) and use that
derived value for the access-list check instead of the possibly undefined
task.consumerAddress.

---

Nitpick comments:
In `@src/test/unit/auth/token.test.ts`:
- Around line 106-124: Update mintRemoteToken in token.test.ts to use
PROTOCOL_COMMANDS.CREATE_AUTH_TOKEN instead of the hard-coded 'createAuthToken'
string when calling createHashForSignature, so the test stays aligned with
Auth.validateRemoteToken and verifyConsumerSignature. Import or reference the
shared command constant directly in the test helper to avoid drift if the
protocol command value changes.
- Around line 81-104: The `makeFakeP2P` test double in `token.test.ts` only
counts `sendTo` calls and does not verify the request body sent by
`Auth.validateRemoteToken`. Update the mock to capture the `_msg` payload
(alongside `tracker.calls`) and add assertions in the remote-token tests,
especially the “accepts” case, that the outgoing message contains `{ command:
PROTOCOL_COMMANDS.VALIDATE_AUTH_TOKEN, token }`. This keeps the wire contract
between `validateRemoteToken` and `OceanP2P.sendTo` covered.
- Around line 133-244: Add a unit test in the Auth token suite to cover a
malformed issuerPeerId that causes peerIdFromString to fail in
validateRemoteToken. Use the existing Auth, makeFakeP2P, and mintRemoteToken
helpers as the reference points, and assert that validateToken returns null
without delegating to the issuer (tracker.calls stays 0). Keep the new case
aligned with the current control-flow assertions around
validateRemoteToken/getLocalToken so it fits the existing guard-order coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bf6185ff-2be9-4790-b4d4-5199d074ca3b

📥 Commits

Reviewing files that changed from the base of the PR and between f274568 and e419182.

📒 Files selected for processing (9)
  • src/components/Auth/index.ts
  • src/components/P2P/index.ts
  • src/components/core/handler/authHandler.ts
  • src/components/core/handler/coreHandlersRegistry.ts
  • src/components/core/handler/handler.ts
  • src/components/core/handler/persistentStorage.ts
  • src/components/core/utils/nonceHandler.ts
  • src/test/unit/auth/token.test.ts
  • src/utils/constants.ts

Comment thread src/components/Auth/index.ts Outdated
Comment on lines +145 to +150
const response = await p2p.sendTo(
issuerPeerId,
JSON.stringify({ command: PROTOCOL_COMMANDS.VALIDATE_AUTH_TOKEN, token }),
undefined,
undefined,
VALIDATE_TOKEN_DIAL_TIMEOUT_MS

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact auth tokens before they hit P2P logs.

This sends the raw JWT in the P2P message, and OceanP2P.sendTo() logs the full message string. That leaks reusable auth tokens into logs; add redaction in sendTo or avoid logging token-bearing payloads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Auth/index.ts` around lines 145 - 150, The P2P auth validation
path in `Auth` is sending a raw JWT through `p2p.sendTo`, and
`OceanP2P.sendTo()` logs the full message payload. Update `sendTo` to redact or
omit token-bearing fields before logging, or stop logging the raw `message` for
`VALIDATE_AUTH_TOKEN` requests so `token` never appears in logs.

Comment thread src/components/Auth/index.ts Outdated
Comment on lines +155 to +157
let verdict: { valid?: boolean; validUntil?: number | string | null }
try {
verdict = JSON.parse(await streamToString(response.stream as Readable))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the remote verdict stream read.

streamToString() buffers until EOF, so a remote peer can keep auth validation hanging or stream an oversized body. Cap bytes and apply a response-read timeout before JSON.parse.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Auth/index.ts` around lines 155 - 157, The auth verdict
parsing in Auth/index.ts currently reads the entire remote response with
streamToString() before JSON.parse, which can hang indefinitely or consume too
much memory. Update the verdict-fetch path around the verdict assignment to
enforce a maximum byte limit and a read timeout on response.stream before
parsing. Use the existing auth validation flow and streamToString-related
handling to ensure oversized or slow responses are rejected cleanly before
JSON.parse runs.

Comment on lines +140 to +142
validate(command: ValidateAuthTokenCommand): ValidateParams {
return validateCommandParameters(command, ['token'])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Redact validation tokens before shared command logging.

validateCommandParameters() logs the command payload, so this new command can write bearer tokens to logs. Add a VALIDATE_AUTH_TOKEN redaction path before logging.

🛡️ Proposed redaction
+  } else if (commandStr === PROTOCOL_COMMANDS.VALIDATE_AUTH_TOKEN && commandData.token) {
+    logCommandData.token = '[REDACTED]'
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/handler/authHandler.ts` around lines 140 - 142, The new
ValidateAuthTokenCommand path can leak bearer tokens because
validateCommandParameters() logs the raw command payload. Update authHandler’s
validate() flow to redact the token before any shared command logging, using a
VALIDATE_AUTH_TOKEN redaction path in the same validation/logging path so the
token is never emitted. Keep the fix localized around validateCommandParameters
and the ValidateAuthTokenCommand handling.

Comment on lines +208 to +209
status: { httpStatus: 200 },
consumerAddress: isAuthRequestValid.address

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Keep the success response contract complete.

Line 208 drops error: null, unlike the other P2PCommandResponse statuses in this handler path. Add it back to avoid response-shape drift. As per coding guidelines, “All handler responses must use P2PCommandResponse format with httpStatus, body, stream, and error properties”.

🔧 Proposed fix
-      status: { httpStatus: 200 },
+      status: { httpStatus: 200, error: null },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
status: { httpStatus: 200 },
consumerAddress: isAuthRequestValid.address
status: { httpStatus: 200, error: null },
consumerAddress: isAuthRequestValid.address
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/core/handler/handler.ts` around lines 208 - 209, The success
response in the handler’s P2PCommandResponse is missing the error field, causing
the response shape to drift from the rest of the handler path. Update the
success return in handler.ts to keep the full P2PCommandResponse contract by
including error: null alongside status and consumerAddress, matching the other
response branches in this handler.

Source: Coding guidelines

@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: medium

Summary:
This PR implements cross-node verifiable JWT authentication by embedding the original authentication signature and a validUntil timestamp into the token payload. The architecture allows nodes to validate tokens statelessly while maintaining strong cryptographic guarantees. The refactoring to reuse consumerAddress in storage handlers is also excellent. A few security hardening suggestions are provided, specifically around enforcing a max token TTL during stateless validation to prevent permanently valid tokens, and adding explicit type checking for the decoded JWT payload.

Comments:
• [WARNING][security] Cross-node tokens bypass the MAX_AUTH_TOKEN_TTL_MS limit that is normally enforced during token creation. A malicious dApp could prompt a user to sign a token valid for many years, construct the JWT locally, and use it indefinitely across nodes since this stateless validation lacks TTL bounds (and stateless tokens cannot be easily revoked by a single node). Consider exporting MAX_AUTH_TOKEN_TTL_MS from authHandler.ts and enforcing it here as well:

-    if (!Number.isFinite(validUntilNum) || Date.now() >= validUntilNum) {
+    if (
+      !Number.isFinite(validUntilNum) || 
+      Date.now() >= validUntilNum || 
+      validUntilNum - Date.now() > MAX_AUTH_TOKEN_TTL_MS
+    ) {
       return null
     }

• [INFO][security] The decoded JWT payload is cast to an object without strict type checking. Although downstream functions (like verifyConsumerSignature) handle non-string values safely by throwing catchable errors in ethers, it is best practice to explicitly validate types to prevent prototype pollution or edge-case coercion bugs.

     const { address, nonce, signature, createdAt, validUntil, chainId } = decoded as {
       address?: string
       nonce?: string
       signature?: string
       createdAt?: number
       validUntil?: number | null
       chainId?: string | null
     }
-    if (!address || !nonce || !signature || validUntil == null) {
+    if (
+      typeof address !== 'string' || 
+      typeof nonce !== 'string' || 
+      typeof signature !== 'string' || 
+      typeof validUntil !== 'number'
+    ) {
       return null
     }

• [INFO][security] Hardcoding PROTOCOL_COMMANDS.CREATE_AUTH_TOKEN during signature verification is an excellent security measure. It ensures that signatures intended for other operational commands (e.g., UPDATE_BUCKET_LABEL) cannot be maliciously repurposed to mint cross-node authentication tokens.
• [INFO][performance] Excellent refactoring here! Reusing the consumerAddress returned from validateTokenOrSignature completely avoids a redundant token database lookup (getAddressFromToken), improving overall performance and keeping the code DRY.

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