Skip to content

feat: RFC 9421 HTTP Message Signatures for gateway responses (PE-9049)#671

Open
vilenarios wants to merge 5 commits intodevelopfrom
PE-9049-httpsig
Open

feat: RFC 9421 HTTP Message Signatures for gateway responses (PE-9049)#671
vilenarios wants to merge 5 commits intodevelopfrom
PE-9049-httpsig

Conversation

@vilenarios
Copy link
Copy Markdown
Contributor

Summary

  • Add opt-in RFC 9421 HTTP Message Signature support (HTTPSIG_ENABLED=true)
  • Every qualifying response gets Signature + Signature-Input headers signed by an Ed25519 key (~33us per request)
  • Observer wallet RSA attestation links Ed25519 key to on-chain gateway identity, uploaded permanently to Arweave
  • Self-contained keyId embeds full public key — no lookup needed for verification
  • Solana address derived from Ed25519 key for future multi-chain identity
  • Signs trust headers, ArNS resolution, data item tags, Content-Digest, chunk provenance
  • Auto-skips admin/GraphQL/health endpoints, strips upstream signatures on gateway-to-gateway forwarding
  • Prometheus metrics, OTEL span attributes, docs/envs.md documented

Test plan

  • 69 unit + integration tests passing (signing, attestation, info builder, middleware)
  • Full test suite: 1617/1622 pass (1 pre-existing failure in DataVerificationWorker, 4 skipped)
  • TypeScript compiles clean, lint clean
  • Live verified on gateway: response signatures valid, RSA attestation valid, attestation TX on Arweave
  • End-to-end verification script confirms full trust chain
  • Performance: ~40us/request, negligible at 3,000 req/s
  • HTTPSIG_ENABLED=false (default) has zero impact on existing behavior

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds RFC 9421 HTTP response signing (HTTPSIG) and attestation: new env/config flags, Ed25519 key management, middleware to sign responses, attestation creation/upload to Arweave with Turbo fallback, metrics, OpenAPI/docs updates, docker-compose mounts, tests, and a runtime bs58 dependency.

Changes

Cohort / File(s) Summary
Compose / Docs
docker-compose.yaml, docs/envs.md
Added host mounts for keys/wallets; documented HTTPSIG env vars and attestation/upload behavior.
Dependency
package.json
Added bs58 dependency.
Config / Startup
src/config.ts, src/app.ts
New HTTPSIG env exports; key/attestation load, cache, and startup attestation upload; conditional registration of HTTPSIG middleware.
Core libraries
src/lib/httpsig.ts, src/lib/httpsig-upload.ts
New utilities: Ed25519 key load/generation, RFC9421 signature input/base formatting, wallet/JWK handling, attestation creation, and Arweave upload with Turbo + L1 fallback.
Middleware / Metrics
src/middleware/httpsig.ts, src/metrics.ts
Added Express response-signing middleware and Prometheus metrics for signing counts, durations, and errors.
Routes / API schemas
src/routes/ar-io.ts, src/routes/ar-io-info-builder.ts, docs/openapi.yaml
Expose HTTPSIG info in /ar-io/info and add Signature/Signature-Input response headers in OpenAPI for relevant endpoints.
Tests
src/lib/httpsig.test.ts, src/lib/httpsig-upload.test.ts, src/middleware/httpsig.test.ts, src/routes/ar-io.test.ts
New tests for key loading, signature formatting/verification, attestation building/upload, middleware behavior, and route output.
Constants
src/constants.ts
Added exported header names Signature and Signature-Input.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Server
  participant Middleware as HTTPSIG Middleware
  participant Disk
  Client->>Server: GET /{txId} or /ar-io/info
  Server->>Middleware: prepare response (headers/status)
  Middleware->>Disk: read private key / keyId
  Middleware-->>Middleware: build signature base (status, headers, optional req)
  Middleware->>Middleware: sign with Ed25519 private key
  Middleware->>Server: set Signature-Input / Signature headers
  Server->>Client: send signed response
Loading
sequenceDiagram
  autonumber
  participant Server
  participant Config as Startup/Config
  participant Disk
  participant Observer as Observer Wallet (RSA JWK)
  participant Arweave
  Server->>Config: start (HTTPSIG_ENABLED)
  Config->>Disk: load/generate Ed25519 key & attestation cache
  alt observer configured and no cached txId
    Config->>Observer: load observer JWK
    Config->>Config: create attestation payload & RSA signature
    Config->>Arweave: tryTurboUpload / fallback L1 upload
    Arweave-->>Config: return txId
    Config->>Disk: save attestation + txId
  end
  Server->>Server: register HTTPSIG middleware with private key/keyId
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • dtfiedler
  • djwhitt
  • hlolli
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main feature being added: RFC 9421 HTTP Message Signatures for gateway responses, which is the primary objective across all modified files.
Description check ✅ Passed The description comprehensively relates to the changeset, detailing the feature's implementation (Ed25519 signing, RSA attestation, environment variables, middleware), performance metrics, and testing scope.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch PE-9049-httpsig
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch PE-9049-httpsig

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
src/routes/ar-io.ts (1)

131-169: Refresh the /ar-io/info TSDoc for the new httpsig field.

The handler comment and example still describe only the pre-HTTPSIG response shape, so they now drift from what this endpoint returns.

As per coding guidelines, **/*.{ts,tsx,js}: Add or improve TSDoc comments when modifying code to enhance documentation.

Also applies to: 170-219

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/ar-io.ts` around lines 131 - 169, The TSDoc for the /ar-io/info
handler is out of date because the response now includes a new "httpsig" field;
update the comment block above the handler in src/routes/ar-io.ts (the
/ar-io/info handler comment around the existing example JSON) to document the
httpsig property (its shape and when it's present) and add it to the example
response JSON so the docs match the actual response; keep the rest of the
existing descriptions (wallet, processId, bundlers, rateLimiter, x402) unchanged
and follow the existing formatting/style used in the comment.
src/lib/httpsig-upload.ts (1)

134-142: Consider adding a timeout for the L1 transaction post.

The arweave.transactions.post call has no timeout. While this is only called at startup (not on request path), L1 transactions can be slow under network congestion. A timeout would prevent indefinite startup hangs.

♻️ Suggested improvement
+  // L1 POST can be slow; use AbortController for timeout
+  const controller = new AbortController();
+  const timeout = setTimeout(() => controller.abort(), 60000);
+
-  const response = await arweave.transactions.post(tx);
+  let response;
+  try {
+    response = await arweave.transactions.post(tx);
+  } finally {
+    clearTimeout(timeout);
+  }

Note: This depends on whether arweave.transactions.post supports AbortSignal. If not, consider wrapping with Promise.race and a timeout promise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/httpsig-upload.ts` around lines 134 - 142, The
arweave.transactions.post call (after arweave.transactions.sign using tx and
jwkAny) currently has no timeout and can hang startup; add a timeout around the
POST: if arweave.transactions.post supports AbortSignal, create an
AbortController, pass controller.signal and call controller.abort after the
timeout; otherwise wrap the post call in a Promise.race with a timeout-rejecting
promise, ensure you cleanly throw a descriptive error like "Timed out posting
attestation L1 transaction" on timeout and preserve the existing non-200/202
error handling.
src/routes/ar-io-info-builder.ts (1)

89-107: Consider adding TSDoc comments to new interfaces.

The existing interfaces in this file have TSDoc comments explaining their purpose. Adding similar documentation to HttpsigAttestationInfo and HttpsigInfo would maintain consistency and help consumers understand the attestation trust model.

📝 Suggested documentation
+/**
+ * RSA-signed attestation linking an Ed25519 signing key to an Arweave
+ * observer wallet identity. Enables verifiers to establish trust chain
+ * from the HTTP signature back to the on-chain gateway registration.
+ */
 export interface HttpsigAttestationInfo {
+  /** Arweave transaction ID where attestation is permanently stored */
   txId?: string;
+  /** Base64url Arweave address of the observer wallet that signed the attestation */
   observerAddress: string;
+  /** Canonical JSON attestation payload */
   payload: string;
+  /** RSA-PSS-SHA256 signature over the payload (base64url) */
   signature: string;
+  /** RSA public key in SPKI DER format (base64url) */
   rsaPublicKey: string;
 }

+/**
+ * HTTPSIG response signing configuration exposed in the info endpoint.
+ * When enabled, qualifying responses include RFC 9421 signatures.
+ */
 export interface HttpsigInfo {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/ar-io-info-builder.ts` around lines 89 - 107, Add TSDoc comments
for the two new interfaces HttpsigAttestationInfo and HttpsigInfo similar to the
rest of the file: describe the purpose of HttpsigAttestationInfo (fields txId,
observerAddress, payload, signature, rsaPublicKey and how they represent an
HTTPSIG attestation/verification record) and describe HttpsigInfo (enabled,
algorithm, publicKey, keyId, optional solanaAddress and optional attestation)
including the trust model and when attestation is present; attach the comments
immediately above each interface declaration.
src/middleware/httpsig.ts (2)

47-49: Consider using constants for header names.

The header names 'Signature' and 'Signature-Input' are hardcoded here, but constants are already defined in src/constants.ts (lines 78-81). Using the constants would improve maintainability and ensure consistency across the codebase.

♻️ Suggested refactor
+import { headerNames } from '../constants.js';
+
 // ...
 
 res.writeHead = function (this: Response, ...args: any[]) {
   // Strip upstream signature headers (gateway-to-gateway forwarding)
-  this.removeHeader('Signature');
-  this.removeHeader('Signature-Input');
+  this.removeHeader(headerNames.signature);
+  this.removeHeader(headerNames.signatureInput);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/middleware/httpsig.ts` around lines 47 - 49, Replace the hardcoded header
strings in the middleware's signature-stripping block with the shared constants:
import and use SIGNATURE_HEADER and SIGNATURE_INPUT_HEADER (the constants
defined in constants.ts) instead of literal 'Signature' and 'Signature-Input';
update the import statement in src/middleware/httpsig.ts to include those
constants and change the two calls this.removeHeader('Signature') and
this.removeHeader('Signature-Input') to this.removeHeader(SIGNATURE_HEADER) and
this.removeHeader(SIGNATURE_INPUT_HEADER) so the code uses the centralized
header names.

109-112: Consider logging signing errors for observability.

The empty catch block silently swallows all signing errors—only the error counter is incremented. While the design choice to not break responses is correct, adding a debug/warn log would help diagnose signing failures in production without impacting request handling.

♻️ Suggested improvement
-    } catch {
+    } catch (err) {
       // Signing failure must not break the response — fall through unsigned.
       metrics.httpSigErrorsTotal.inc();
+      // Log at debug level to aid diagnosis without flooding logs
+      // Consider: log.debug('HTTPSIG signing failed', { error: err });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/middleware/httpsig.ts` around lines 109 - 112, The catch block that
currently only calls metrics.httpSigErrorsTotal.inc() silently swallows signing
errors; update that catch to also log the caught error (without rethrowing)
using the module's logger (e.g., processLogger.warn or logger.debug) so we keep
the non-failing behavior but add observability; target the catch in
src/middleware/httpsig.ts where signing is attempted (the block calling
metrics.httpSigErrorsTotal.inc()) and include the error object and contextual
text in the log message.
src/lib/httpsig.ts (1)

385-391: Consider atomic write for attestation cache.

The cache file write isn't atomic—a crash during write could leave a partially written file. While the code handles corrupt cache gracefully (deletes and recreates), using write-to-temp-then-rename would prevent this edge case entirely.

♻️ Suggested atomic write pattern
+import { renameSync } from 'node:fs';
+
 // Cache to disk
 const cacheData: CachedAttestation = {
   ...attestation,
   ed25519PublicKey: currentPubKey,
 };
 mkdirSync(keysDir, { recursive: true });
-writeFileSync(cachePath, JSON.stringify(cacheData, null, 2));
+const tmpPath = `${cachePath}.tmp`;
+writeFileSync(tmpPath, JSON.stringify(cacheData, null, 2));
+renameSync(tmpPath, cachePath);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/httpsig.ts` around lines 385 - 391, The current cache write (built
from cacheData and using mkdirSync(keysDir...) then
writeFileSync(cachePath,...)) is not atomic and can leave a partial file; change
it to an atomic write-to-temp-and-rename pattern: ensure keysDir exists, write
the JSON to a temporary file in the same directory (e.g., cachePath + '.tmp' or
a unique temp name), fsync the file (or use writeFileSync then fsync via the
file descriptor) to flush data, close it, then rename (fs.renameSync) the temp
file to cachePath so the replace is atomic; reference cacheData, keysDir,
cachePath, mkdirSync, writeFileSync and perform the rename step to guarantee
atomicity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docker-compose.yaml`:
- Line 63: The WALLETS_PATH env is being reused for both the host bind source
and the container lookup which breaks in-container wallet paths; update the
docker-compose service definition so the volume uses the host override (keep the
current ${WALLETS_PATH:-./wallets} as the left side of the volume mapping) but
set the container-side WALLETS_PATH environment value to the fixed in-container
path /app/wallets (i.e., ensure the container ENV WALLETS_PATH points to
/app/wallets while the volume mapping remains
${WALLETS_PATH:-./wallets}:/app/wallets) so code expecting
/app/wallets/<wallet>.json inside the container still works.

In `@docs/envs.md`:
- Around line 303-316: Add documentation for the KEYS_DATA_PATH environment
variable in the HTTPSIG section of docs/envs.md: describe it as a String
(default "data"), explain it maps to the container path /app/data/keys and is
used to persist or relocate the generated HTTPSIG keys (which are referenced by
HTTPSIG_KEY_FILE), note that docker-compose.yaml uses KEYS_DATA_PATH to mount
the host directory so operators can persist keys across restarts, and mention
any interaction with WALLETS_PATH if relevant.

In `@src/app.ts`:
- Around line 49-74: The upload logic must not treat HTTPSIG_ATTESTATION_CACHED
as proof the Arweave upload succeeded; change the condition to run upload when
there is no persisted txId (introduce or use a persistent field like
HTTPSIG_ATTESTATION_TXID or similar) instead of HTTPSIG_ATTESTATION_CACHED, and
ensure setHttpSigAttestationTxId persists the txId to durable storage (not just
in-memory). Update the upload block that calls uploadAttestation(...) and the
catch path accordingly so a transient failure won't prevent future attempts; if
config currently lacks a persistent setter/getter (e.g.,
setHttpSigAttestationTxId, HTTPSIG_ATTESTATION_CACHED), implement a persistent
setter/getter to save the txId to disk/config storage and read it on startup,
and only skip the upload when that persisted txId is present.

In `@src/config.ts`:
- Around line 987-989: The current keysDir calculation uses string slicing with
'/' which fails on Windows; replace that expression to use Node's
path.dirname(HTTPSIG_KEY_FILE) and keep the same fallback to 'data/keys' (i.e.,
const keysDir = path.dirname(HTTPSIG_KEY_FILE) || 'data/keys'); also ensure the
file imports the path module (import path from 'path') at the top if not already
present so path.dirname is available.

---

Nitpick comments:
In `@src/lib/httpsig-upload.ts`:
- Around line 134-142: The arweave.transactions.post call (after
arweave.transactions.sign using tx and jwkAny) currently has no timeout and can
hang startup; add a timeout around the POST: if arweave.transactions.post
supports AbortSignal, create an AbortController, pass controller.signal and call
controller.abort after the timeout; otherwise wrap the post call in a
Promise.race with a timeout-rejecting promise, ensure you cleanly throw a
descriptive error like "Timed out posting attestation L1 transaction" on timeout
and preserve the existing non-200/202 error handling.

In `@src/lib/httpsig.ts`:
- Around line 385-391: The current cache write (built from cacheData and using
mkdirSync(keysDir...) then writeFileSync(cachePath,...)) is not atomic and can
leave a partial file; change it to an atomic write-to-temp-and-rename pattern:
ensure keysDir exists, write the JSON to a temporary file in the same directory
(e.g., cachePath + '.tmp' or a unique temp name), fsync the file (or use
writeFileSync then fsync via the file descriptor) to flush data, close it, then
rename (fs.renameSync) the temp file to cachePath so the replace is atomic;
reference cacheData, keysDir, cachePath, mkdirSync, writeFileSync and perform
the rename step to guarantee atomicity.

In `@src/middleware/httpsig.ts`:
- Around line 47-49: Replace the hardcoded header strings in the middleware's
signature-stripping block with the shared constants: import and use
SIGNATURE_HEADER and SIGNATURE_INPUT_HEADER (the constants defined in
constants.ts) instead of literal 'Signature' and 'Signature-Input'; update the
import statement in src/middleware/httpsig.ts to include those constants and
change the two calls this.removeHeader('Signature') and
this.removeHeader('Signature-Input') to this.removeHeader(SIGNATURE_HEADER) and
this.removeHeader(SIGNATURE_INPUT_HEADER) so the code uses the centralized
header names.
- Around line 109-112: The catch block that currently only calls
metrics.httpSigErrorsTotal.inc() silently swallows signing errors; update that
catch to also log the caught error (without rethrowing) using the module's
logger (e.g., processLogger.warn or logger.debug) so we keep the non-failing
behavior but add observability; target the catch in src/middleware/httpsig.ts
where signing is attempted (the block calling metrics.httpSigErrorsTotal.inc())
and include the error object and contextual text in the log message.

In `@src/routes/ar-io-info-builder.ts`:
- Around line 89-107: Add TSDoc comments for the two new interfaces
HttpsigAttestationInfo and HttpsigInfo similar to the rest of the file: describe
the purpose of HttpsigAttestationInfo (fields txId, observerAddress, payload,
signature, rsaPublicKey and how they represent an HTTPSIG
attestation/verification record) and describe HttpsigInfo (enabled, algorithm,
publicKey, keyId, optional solanaAddress and optional attestation) including the
trust model and when attestation is present; attach the comments immediately
above each interface declaration.

In `@src/routes/ar-io.ts`:
- Around line 131-169: The TSDoc for the /ar-io/info handler is out of date
because the response now includes a new "httpsig" field; update the comment
block above the handler in src/routes/ar-io.ts (the /ar-io/info handler comment
around the existing example JSON) to document the httpsig property (its shape
and when it's present) and add it to the example response JSON so the docs match
the actual response; keep the rest of the existing descriptions (wallet,
processId, bundlers, rateLimiter, x402) unchanged and follow the existing
formatting/style used in the comment.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 857dab8c-61d7-4527-bab5-168a31042737

📥 Commits

Reviewing files that changed from the base of the PR and between 6e023ad and f433df3.

📒 Files selected for processing (16)
  • docker-compose.yaml
  • docs/envs.md
  • package.json
  • src/app.ts
  • src/config.ts
  • src/constants.ts
  • src/lib/httpsig-upload.test.ts
  • src/lib/httpsig-upload.ts
  • src/lib/httpsig.test.ts
  • src/lib/httpsig.ts
  • src/metrics.ts
  • src/middleware/httpsig.test.ts
  • src/middleware/httpsig.ts
  • src/routes/ar-io-info-builder.ts
  • src/routes/ar-io.test.ts
  • src/routes/ar-io.ts

- ${ETL_STAGING_PATH:-./data/etl/staging}:/app/data/etl/staging
- ${CDB64_ROOT_TX_INDEX_DATA_PATH:-./data/cdb64-root-tx-index}:/app/data/cdb64-root-tx-index
- ${KEYS_DATA_PATH:-./data/keys}:/app/data/keys
- ${WALLETS_PATH:-./wallets}:/app/wallets
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Set WALLETS_PATH to the in-container mount path.

Right now the same variable is used for both the host bind-mount source and the app's in-container lookup path. Any non-default override makes core look for ${WALLETS_PATH}/<wallet>.json on the container filesystem instead of /app/wallets, so observer wallet loading breaks. Keep the host override only in the volume spec, and point WALLETS_PATH at /app/wallets inside the container.

🛠️ Proposed fix
-      - ${WALLETS_PATH:-./wallets}:/app/wallets
+      - ${WALLETS_PATH:-./wallets}:/app/wallets:ro
...
-      - WALLETS_PATH=${WALLETS_PATH:-}
+      - WALLETS_PATH=/app/wallets

Also applies to: 327-327

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yaml` at line 63, The WALLETS_PATH env is being reused for
both the host bind source and the container lookup which breaks in-container
wallet paths; update the docker-compose service definition so the volume uses
the host override (keep the current ${WALLETS_PATH:-./wallets} as the left side
of the volume mapping) but set the container-side WALLETS_PATH environment value
to the fixed in-container path /app/wallets (i.e., ensure the container ENV
WALLETS_PATH points to /app/wallets while the volume mapping remains
${WALLETS_PATH:-./wallets}:/app/wallets) so code expecting
/app/wallets/<wallet>.json inside the container still works.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 8, 2026

Codecov Report

❌ Patch coverage is 79.25134% with 194 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.32%. Comparing base (125bafd) to head (dc51cf5).
⚠️ Report is 30 commits behind head on develop.

Files with missing lines Patch % Lines
src/lib/httpsig-upload.ts 40.81% 87 Missing ⚠️
src/config.ts 51.69% 56 Missing and 1 partial ⚠️
src/lib/httpsig.ts 91.54% 39 Missing ⚠️
src/middleware/httpsig.ts 91.12% 11 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #671      +/-   ##
===========================================
+ Coverage    77.27%   77.32%   +0.04%     
===========================================
  Files          111      114       +3     
  Lines        37599    38534     +935     
  Branches      2803     2843      +40     
===========================================
+ Hits         29055    29796     +741     
- Misses        8508     8701     +193     
- Partials        36       37       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
docker-compose.yaml (1)

62-63: Volume mounts look correct; consider read-only for wallets.

The keys and wallets volume mounts are properly configured. The WALLETS_PATH issue from the previous review is now correctly addressed at line 327.

For defense-in-depth, consider mounting wallets read-only like the secrets volume on line 64, since wallet files should only be read at runtime.

🔒 Suggested hardening
       - ${KEYS_DATA_PATH:-./data/keys}:/app/data/keys
-      - ${WALLETS_PATH:-./wallets}:/app/wallets
+      - ${WALLETS_PATH:-./wallets}:/app/wallets:ro
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yaml` around lines 62 - 63, Make the wallets mount read-only
for defense-in-depth: update the docker-compose.yaml service volume that uses
${WALLETS_PATH:-./wallets}:/app/wallets so it mounts read-only (like the secrets
volume), leaving the KEYS_DATA_PATH
(${KEYS_DATA_PATH:-./data/keys}:/app/data/keys) mount unchanged; ensure the
container still has expected read access to /app/wallets and update any startup
scripts if they attempt to write there.
src/lib/httpsig.ts (1)

264-285: Add TSDoc for the exported attestation contracts.

AttestationPayload, Attestation, and CachedAttestation are part of the module surface, but unlike the surrounding exports they are undocumented. A short TSDoc block clarifying semantics—especially when gatewayAddress is absent and what rsaPublicKey/txId represent—would make downstream use safer.

As per coding guidelines, **/*.{ts,tsx,js}: Add or improve TSDoc comments when modifying code to enhance documentation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/httpsig.ts` around lines 264 - 285, Add concise TSDoc comments for
the exported interfaces AttestationPayload, Attestation, and CachedAttestation:
document each field’s semantics (e.g., for AttestationPayload explain that
gatewayAddress may be undefined when the gateway is not yet assigned or when
attestation is observer-only, clarify issuedAt is ISO8601 timestamp, purpose and
type fixed strings), for Attestation describe that payload is the
serialized/encoded AttestationPayload, signature is the signer’s signature over
payload, and rsaPublicKey is the PEM/DER-encoded RSA public key used for
verification; for CachedAttestation note that ed25519PublicKey is the gateway’s
ed25519 key and txId is an optional blockchain transaction identifier when the
attestation was recorded on-chain. Include brief examples or allowed formats
only if helpful and keep comments above the interface declarations
AttestationPayload, Attestation, and CachedAttestation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/httpsig.ts`:
- Around line 73-101: The current loadOrGenerateKey can leave a partially
written PEM because it creates the target file before writing; fix by writing
the PEM to a temporary file (e.g., keyFile + '.tmp.' + process.pid or a random
suffix) with mode 0o600, fsync the temp file and its directory, then atomically
rename the temp file into place with renameSync; keep the existing EEXIST
fallback by catching rename errors and loading the existing key via
crypto.createPrivateKey(readFileSync(keyFile,'utf8')) if another process already
installed the file. Ensure these changes are applied inside loadOrGenerateKey
and use the same symbols: crypto.createPrivateKey, writeFileSync (for temp),
fsyncSync, renameSync, and readFileSync.
- Around line 237-248: The containment check in resolveWalletPath is unsafe
because normalize(resolved).startsWith(normalizedBase) can be fooled by sibling
paths; instead compute the path relative to normalizedBase (using path.relative)
and reject if that relative path begins with '..' or is an absolute path,
ensuring resolved is strictly inside walletsPath; update the logic around
resolved/normalizedBase in resolveWalletPath to use this relative-path check and
throw the same error when the check fails.
- Around line 356-369: The cache-load block currently only compares
cached.ed25519PublicKey to currentPubKey and can wrongly reuse an attestation
when the observer wallet or gatewayAddress changed; update the validation in the
cached-attestation loading path (the code that reads cachePath and inspects
CachedAttestation) to also compare the cached gatewayAddress and observer wallet
identifier against the current runtime values (e.g., gatewayAddress and
observerWallet/observerAddress) and only treat it as a hit if all three match;
also extend the CachedAttestation type to store gatewayAddress and observer
wallet identifier so these fields are written when creating/updating the cache
and can be validated on load.

---

Nitpick comments:
In `@docker-compose.yaml`:
- Around line 62-63: Make the wallets mount read-only for defense-in-depth:
update the docker-compose.yaml service volume that uses
${WALLETS_PATH:-./wallets}:/app/wallets so it mounts read-only (like the secrets
volume), leaving the KEYS_DATA_PATH
(${KEYS_DATA_PATH:-./data/keys}:/app/data/keys) mount unchanged; ensure the
container still has expected read access to /app/wallets and update any startup
scripts if they attempt to write there.

In `@src/lib/httpsig.ts`:
- Around line 264-285: Add concise TSDoc comments for the exported interfaces
AttestationPayload, Attestation, and CachedAttestation: document each field’s
semantics (e.g., for AttestationPayload explain that gatewayAddress may be
undefined when the gateway is not yet assigned or when attestation is
observer-only, clarify issuedAt is ISO8601 timestamp, purpose and type fixed
strings), for Attestation describe that payload is the serialized/encoded
AttestationPayload, signature is the signer’s signature over payload, and
rsaPublicKey is the PEM/DER-encoded RSA public key used for verification; for
CachedAttestation note that ed25519PublicKey is the gateway’s ed25519 key and
txId is an optional blockchain transaction identifier when the attestation was
recorded on-chain. Include brief examples or allowed formats only if helpful and
keep comments above the interface declarations AttestationPayload, Attestation,
and CachedAttestation.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 854d9ea3-bd94-48ec-8f2f-464e27cca557

📥 Commits

Reviewing files that changed from the base of the PR and between f433df3 and c77d6f0.

📒 Files selected for processing (8)
  • docker-compose.yaml
  • docs/envs.md
  • src/app.ts
  • src/config.ts
  • src/lib/httpsig.test.ts
  • src/lib/httpsig.ts
  • src/middleware/httpsig.ts
  • src/routes/ar-io-info-builder.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/envs.md
  • src/lib/httpsig.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/middleware/httpsig.ts
  • src/routes/ar-io-info-builder.ts
  • src/config.ts
  • src/app.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/openapi.yaml`:
- Around line 1092-1101: The 206 response schema is missing the RFC 9421
signature headers even though the httpsig middleware (src/middleware/httpsig.ts)
signs any response with trust-relevant headers; update the 206 response in the
OpenAPI spec to include the same Signature and Signature-Input header
definitions (with type string, description, and examples) as used for the 200
response so partial content responses show the documented Signature and
Signature-Input headers.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a523ffac-d7cf-4c17-9471-49dd658ed8de

📥 Commits

Reviewing files that changed from the base of the PR and between c77d6f0 and b48cdfb.

📒 Files selected for processing (1)
  • docs/openapi.yaml

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/httpsig.ts`:
- Around line 83-99: The current atomic write uses renameSync(tmpFile, keyFile)
which can overwrite an existing destination; change this to linkSync(tmpFile,
keyFile) inside the same try block (around the openSync/writeFileSync/closeSync
sequence for tmpFile) so the operation fails with EEXIST when another process
already installed the key, and update the surrounding catch handler that
currently catches err to specifically handle EEXIST by loading the
already-installed keyFile (instead of treating it as a generic write error);
keep the tmp file cleanup logic as-is so unsuccessful writers remove their
tmpFile.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d1d888f8-0328-417a-828f-03c54ef419c8

📥 Commits

Reviewing files that changed from the base of the PR and between b48cdfb and f9c095b.

📒 Files selected for processing (3)
  • docker-compose.yaml
  • docs/openapi.yaml
  • src/lib/httpsig.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/openapi.yaml

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/httpsig.ts`:
- Around line 427-440: The temp file path used for atomic caching (tmpPath) can
collide across processes; update the code in the attestation cache routine
(where cacheData is built and written using writeFileSync and renameSync, and
keysDir/cachePath/tmpPath are used) to create a unique temp filename per
invocation (e.g., append process.pid plus a high-entropy suffix like Date.now()
or a UUID) instead of a fixed `${cachePath}.tmp`; write to that unique tmp file
and then renameSync it into cachePath, optionally catching and ignoring ENOENT
on rename to avoid startup crashes from benign races.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 266b47e4-5d92-4aa6-92f3-fedfa2a76d1a

📥 Commits

Reviewing files that changed from the base of the PR and between f9c095b and dc51cf5.

📒 Files selected for processing (1)
  • src/lib/httpsig.ts

Comment on lines +427 to +440
// Cache to disk atomically (write to temp, then rename)
const cacheData: CachedAttestation = {
...attestation,
ed25519PublicKey: currentPubKey,
observerAddress: currentObserverAddr,
gatewayAddress,
};
mkdirSync(keysDir, { recursive: true });
const tmpPath = `${cachePath}.tmp`;
writeFileSync(tmpPath, JSON.stringify(cacheData, null, 2));
renameSync(tmpPath, cachePath);

return { ...attestation, cached: false };
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add unique suffix to temp file path to prevent startup race failures.

The shared tmpPath (line 435) can cause ENOENT if two processes race during attestation creation: Process A writes, Process B writes and renames, Process A's rename fails because the temp file no longer exists.

While both attestations would be valid (differing only in issuedAt), the losing process throws an unhandled error that fails startup.

🛠️ Suggested fix
   mkdirSync(keysDir, { recursive: true });
-  const tmpPath = `${cachePath}.tmp`;
+  const tmpPath = `${cachePath}.tmp.${process.pid}`;
   writeFileSync(tmpPath, JSON.stringify(cacheData, null, 2));
-  renameSync(tmpPath, cachePath);
+  try {
+    renameSync(tmpPath, cachePath);
+  } finally {
+    try {
+      unlinkSync(tmpPath);
+    } catch {
+      // Ignore - temp file may have been renamed or already cleaned
+    }
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/httpsig.ts` around lines 427 - 440, The temp file path used for
atomic caching (tmpPath) can collide across processes; update the code in the
attestation cache routine (where cacheData is built and written using
writeFileSync and renameSync, and keysDir/cachePath/tmpPath are used) to create
a unique temp filename per invocation (e.g., append process.pid plus a
high-entropy suffix like Date.now() or a UUID) instead of a fixed
`${cachePath}.tmp`; write to that unique tmp file and then renameSync it into
cachePath, optionally catching and ignoring ENOENT on rename to avoid startup
crashes from benign races.

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