Skip to content

feat(service-gateway): S3 storage support + secrets hardening [7/10]#157

Merged
seanhanca merged 1 commit into
mainfrom
gateway/pr-7-s3-secrets
Mar 4, 2026
Merged

feat(service-gateway): S3 storage support + secrets hardening [7/10]#157
seanhanca merged 1 commit into
mainfrom
gateway/pr-7-s3-secrets

Conversation

@seanhanca
Copy link
Copy Markdown
Contributor

@seanhanca seanhanca commented Feb 27, 2026

Summary

PR 7 of 10 — Service Gateway sequential merge series. Depends on PR #6.

  • AWS Signature V4 auth strategy: Enables S3-compatible storage connectors (Storj, AWS S3, MinIO, etc.)
  • aws-sig-v4.ts module for request signing with SHA-256 HMAC
  • Catch-all path support for S3 object key routing
  • Secrets hardening: Enforce connector-scoped secret keys (secrets are namespaced per connector)
  • Encryption improvements for Vercel deployment (deterministic IV derivation)
  • Unit tests for AWS Sig V4 signing and catch-all path handling
  • Seed script for Storj S3-compatible connector

Database Migration

None.

Merge Order

Merge after PR #6 (gateway/pr-6-daydream-docs).

Test plan

  • S3 connector signs requests correctly with AWS Sig V4
  • Catch-all paths route S3 object keys properly
  • Connector-scoped secrets cannot be accessed cross-connector
  • Encryption works correctly on Vercel (no IV mismatch)

Made with Cursor

Summary by CodeRabbit

Release Notes

  • New Features

    • Added AWS Signature V4 authentication signing for HTTP requests
    • Introduced GET request response caching with configurable TTL
    • Added connector-based usage filtering in analytics
  • Enhancements

    • Improved rate limiting with detailed error handling
    • Enhanced upstream error diagnostics and connectivity handling
    • Better secret management with connector-scoped namespacing
    • Added HEAD HTTP method support
    • Improved dynamic path routing with enhanced matching
  • Tests

    • Comprehensive test coverage added for core gateway functionality

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 27, 2026

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

Project Deployment Actions Updated (UTC)
naap-platform Error Error Mar 4, 2026 6:48pm

Request Review

@github-actions github-actions Bot added size/XL Extra large PR (500+ lines) scope/shell Shell app changes scope/infra Infrastructure changes and removed size/XL Extra large PR (500+ lines) labels Feb 27, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 27, 2026

⚠️ This PR is very large (1658 lines changed). Please split it into smaller, focused PRs if possible.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

The PR introduces comprehensive gateway routing enhancements including request/response caching for GET requests, pre-validation policy enforcement, binary body support, and connector-scoped secret resolution. AWS Signature V4 signing is implemented as a new module. Path resolution is improved with catch-all segment matching and specificity-based sorting. Secret key generation is refactored across multiple routes to use connector slugs for namespacing. Extensive test coverage is added for gateway logic, and a new Storj connector seed script is introduced.

Changes

Cohort / File(s) Summary
Gateway Request/Response Pipeline
apps/web-next/src/app/api/v1/gw/[connector]/[...path]/route.ts
Added comprehensive request handling: pre-authorization body reading (binary/text), policy enforcement with rate-limit headers, request validation, GET response caching with TTL and cache HIT rewrites, secret resolution within owner scope, upstream transformation with binary body support, policy headers on responses, and usage logging across all paths including cache hits.
Admin Secret Routes
apps/web-next/src/app/api/v1/gw/admin/connectors/[id]/secrets/[name]/route.ts, apps/web-next/src/app/api/v1/gw/admin/connectors/[id]/secrets/route.ts
Changed secret key construction from gw:${scopeId}:${name} to gw:${scopeId}:${connectorSlug}:${name} by switching secretKey function parameter from connectorId to connectorSlug; updated call sites to pass connector.slug.
Admin Test Connectivity
apps/web-next/src/app/api/v1/gw/admin/connectors/[id]/test/route.ts
Added connectorSlug parameter to testUpstreamConnectivity invocation; propagates slug to internal resolveSecrets call.
Usage Analytics Routes
apps/web-next/src/app/api/v1/gw/admin/usage/by-connector/route.ts, apps/web-next/src/app/api/v1/gw/admin/usage/by-key/route.ts
Added connectorId query filtering; improved date handling with defaults (24h ago for from, current time for to); consolidated filters into shared where object; added early return for empty key results.
AWS Signature V4 Implementation
apps/web-next/src/lib/gateway/aws-sig-v4.ts
New module providing pure AWS SigV4 signing with signAwsV4 function and helpers (toAmzDate, sha256Hex, hmacHex, deriveSigningKey, hashPayload, buildCanonicalHeaders, buildCanonicalQueryString); supports UNSIGNED-PAYLOAD default and explicit payload signing with ArrayBuffer/string bodies.
Secrets & Secret Resolution
apps/web-next/src/lib/gateway/secrets.ts, apps/web-next/src/lib/gateway/admin/test-connectivity.ts
Extended resolveSecrets, storeSecret, and deleteSecret signatures with connectorSlug parameter; keys now scoped as gw:<teamId>:<connectorSlug>:<ref>; testUpstreamConnectivity passes slug to resolveSecrets.
Gateway Transform & Resolution
apps/web-next/src/lib/gateway/transform.ts, apps/web-next/src/lib/gateway/resolve.ts
buildUpstreamRequest accepts optional consumerBodyRaw (ArrayBuffer); resolve.ts adds catch-all path matching (e.g., :key*), pathSpecificity scoring for deterministic endpoint selection, and improved cache invalidation semantics.
Encryption Validation
apps/web-next/src/lib/gateway/encryption.ts
Added runtime check: ENCRYPTION_KEY must be at least 16 characters; throws error if shorter than minimum.
Comprehensive Test Suites
apps/web-next/src/lib/gateway/__tests__/aws-sig-v4.test.ts, apps/web-next/src/lib/gateway/__tests__/catch-all-paths.test.ts, apps/web-next/src/lib/gateway/__tests__/secrets-admin.test.ts, apps/web-next/src/lib/gateway/__tests__/transform.test.ts
Added 750+ lines of tests covering AWS SigV4 signing scenarios, catch-all path matching and specificity sorting, secret key construction with connectorSlug, and upstream request transformation with authentication and body handling.
Seed Scripts
bin/seed-public-connectors.ts, bin/seed-storj-connector.ts
seed-public-connectors updated to pass connector slug to storeUpstreamSecret; new seed-storj-connector script initializes a Storj S3-compatible connector with 11 endpoints, credentials, and API key (idempotent).

Sequence Diagram

sequenceDiagram
    participant Client
    participant GatewayRoute as Gateway Route
    participant PolicyModule as Policy/Quota
    participant ValidationModule as Validation
    participant CacheStore as Cache Store
    participant SecretModule as Secret Resolver
    participant TransformModule as Transform
    participant UpstreamServer as Upstream Server
    participant LoggingModule as Logging

    Client->>GatewayRoute: HTTP Request (GET/POST/PUT/etc)
    GatewayRoute->>GatewayRoute: Read & preprocess body<br/>(binary/text, compute bytes)
    GatewayRoute->>PolicyModule: Enforce rate limits/quotas
    alt Policy Blocked
        PolicyModule-->>GatewayRoute: Rate limit error + headers
        GatewayRoute-->>Client: 429 with policy headers
    else Policy Approved
        GatewayRoute->>ValidationModule: Validate headers/body/schema
        alt Validation Failed
            ValidationModule-->>GatewayRoute: Validation error
            GatewayRoute-->>Client: 400 Bad Request
        else Validation Passed
            alt GET + Cache Hit
                GatewayRoute->>CacheStore: Check cache with TTL
                CacheStore-->>GatewayRoute: Cached response
                GatewayRoute->>GatewayRoute: Add X-Gateway-Cache header
                GatewayRoute->>LoggingModule: Log cache hit
                LoggingModule->>LoggingModule: Record usage
                GatewayRoute-->>Client: Cached response
            else Cache Miss or Non-GET
                GatewayRoute->>SecretModule: Resolve secrets in owner scope
                SecretModule-->>GatewayRoute: Resolved secrets
                GatewayRoute->>TransformModule: Transform request<br/>(binary body, auth, headers)
                TransformModule-->>GatewayRoute: Transformed request
                GatewayRoute->>UpstreamServer: Proxy request
                UpstreamServer-->>GatewayRoute: Response (headers + body)
                alt GET + 2xx Status
                    GatewayRoute->>CacheStore: Store response with TTL
                end
                GatewayRoute->>GatewayRoute: Apply policy headers
                GatewayRoute->>LoggingModule: Log usage with metadata
                LoggingModule->>LoggingModule: Record request metrics
                GatewayRoute-->>Client: Final response
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Suggested labels

scope/backend

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: AWS Signature V4 support for S3-compatible storage (feat) and connector-scoped secret keys for security hardening, with sequential series context [7/10].

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch gateway/pr-7-s3-secrets

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

@vercel vercel Bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

Cron expression */5 * * * * in JSDoc comment prematurely terminates the block comment, causing a build syntax error.

Fix on Vercel

@seanhanca
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 27, 2026

✅ Actions performed

Review triggered.

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.

Copy link
Copy Markdown

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

Caution

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

⚠️ Outside diff range comments (2)
apps/web-next/src/app/api/v1/gw/[connector]/[...path]/route.ts (1)

11-12: ⚠️ Potential issue | 🟡 Minor

Update route docs to include HEAD support.

The handler now supports HEAD, but the top-level comment still lists only GET/POST/PUT/PATCH/DELETE.

📝 Proposed fix
- * Supports: GET, POST, PUT, PATCH, DELETE
+ * Supports: GET, POST, PUT, PATCH, DELETE, HEAD

Also applies to: 367-369

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

In `@apps/web-next/src/app/api/v1/gw/`[connector]/[...path]/route.ts around lines
11 - 12, The route's documentation comment in route.ts lists supported methods
but omits HEAD even though the route handler now handles HEAD; update the
top-level comment block (and the secondary comment block later in the file near
the other method listing) to include "HEAD" alongside GET/POST/PUT/PATCH/DELETE
so the docs match the actual handler implementation that supports HEAD.
bin/seed-public-connectors.ts (1)

336-336: ⚠️ Potential issue | 🟠 Major

Avoid printing full generated API keys in seed logs.

Logging full keys increases accidental secret exposure risk in CI/terminal history.

🔐 Proposed fix
-      console.log(`  API key: ${rawKey.slice(0, 8)}... (full: ${rawKey})`);
+      console.log(`  API key: ${rawKey.slice(0, 8)}...`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/seed-public-connectors.ts` at line 336, The current seed script prints
the full API key via console.log using rawKey; change the log to avoid exposing
the full secret—only show a masked or truncated version (e.g., first 8 chars +
ellipsis) and remove "(full: ${rawKey})" from the message, or alternatively
persist the full rawKey securely instead of printing it; update the console.log
call that references rawKey accordingly to only output non-secret preview
information.
🧹 Nitpick comments (1)
apps/web-next/src/lib/gateway/__tests__/transform.test.ts (1)

66-112: Add explicit tests for new binary/query-forward transform paths.

Given the new behavior in buildUpstreamRequest, add focused cases for bodyTransform: 'binary' and consumer query forwarding precedence/duplication to prevent regressions.

Also applies to: 226-289

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

In `@apps/web-next/src/lib/gateway/__tests__/transform.test.ts` around lines 66 -
112, Add explicit tests in transform.test.ts covering the new transform paths:
one test should call buildUpstreamRequest with a config where
endpoint.bodyTransform === 'binary' and a Request containing a binary body, then
assert the returned Request preserves the binary body and appropriate headers
(e.g., content-type) and does not JSON-encode it; another test should exercise
consumer query forwarding by providing both consumer query params in the
incoming Request URL and endpoint.upstreamQueryParams and assert the final
result.url query string respects the intended precedence/duplication rules
(consumer params override or are deduplicated according to the implementation)
for buildUpstreamRequest and upstreamPath/path-param replacements.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web-next/src/app/api/v1/gw/`[connector]/[...path]/route.ts:
- Around line 69-86: Defer reading the request body until after the cheap access
checks (ownership/endpoint/IP) instead of the current early block; move the
logic that sets consumerBody, consumerBodyRaw and requestBytes (the branch that
checks method !== 'GET' && method !== 'HEAD' and inspects
config.endpoint.bodyTransform) to run after those checks, and remove the empty
catch so read failures are not swallowed—if request.arrayBuffer() or
request.text() throws, let the error propagate (or explicitly return a 4xx/5xx
response) instead of treating it as “no body.” Ensure the relocated code still
computes requestBytes using consumerBodyRaw.byteLength or new
TextEncoder().encode(consumerBody).length and references
config.endpoint.bodyTransform, consumerBodyRaw, consumerBody, and request
consistently.

In `@apps/web-next/src/lib/gateway/__tests__/aws-sig-v4.test.ts`:
- Around line 155-174: The intermittent flake is caused by signAwsV4 calling new
Date() to generate x-amz-date; make the test deterministic by freezing time
around the two signing calls: enable Jest fake timers (modern) and setSystemTime
to a fixed timestamp before calling signAwsV4 twice (e.g.,
jest.useFakeTimers('modern'); jest.setSystemTime(new
Date('2020-01-01T00:00:00Z'))), then call signAwsV4(opts) and signAwsV4(opts2),
and finally restore timers (jest.useRealTimers()) so the test no longer depends
on clock ticks; apply this change inside the 'produces deterministic signatures
for identical inputs' test that uses signAwsV4 and sig1/sig2.

In `@apps/web-next/src/lib/gateway/encryption.ts`:
- Around line 19-26: The current branch that sets _key from
process.env.ENCRYPTION_KEY must validate a minimum length (e.g., 32 characters)
before accepting it; update the envKey handling in the block that assigns _key
so that if envKey.length < 32 you throw a clear Error requiring a 32+ character
ENCRYPTION_KEY (mentioning ENCRYPTION_KEY in the message) instead of silently
accepting it—this prevents deriveKey() from zero-padding weak keys and ensures
production-strength keys are enforced.

In `@apps/web-next/src/lib/gateway/resolve.ts`:
- Around line 185-193: The catch-all detection currently treats any trailing '*'
as a catch-all; update the logic so a catch-all is recognized only when the
final pattern is a parameter-style segment that both starts with ':' and ends
with '*', e.g. change the isCatchAll computation to require
lastPattern?.startsWith(':') && lastPattern.endsWith('*'); then keep the
existing length check and the slice/every matching, and apply the same guarded
check in the other similar block (the logic around lines 211-214) that currently
uses endsWith('*') so only ':param*' forms are treated as catch-alls.

In `@apps/web-next/src/lib/gateway/secrets.ts`:
- Line 35: The new secret key construction `const key =
\`gw:${teamId}:${connectorSlug}:${ref}\`` breaks reads for legacy secrets;
update the read logic (where `key` is used—e.g., the getter functions that
construct `key` at the shown sites) to first attempt the new key, then fallback
to the legacy key `gw:${teamId}:${ref}` if not found, and optionally
migrate-on-read by writing the found legacy value back under the new
`gw:${teamId}:${connectorSlug}:${ref}` key; apply the same fallback/migration
behavior to the other occurrences referenced (the constructions at the other
spots around lines 76 and 108) so existing secrets remain accessible.

In `@apps/web-next/src/lib/gateway/transform.ts`:
- Around line 75-80: The current forwarding loop uses url.searchParams.set(...)
which overwrites previous values and drops duplicate keys; change the forwarding
to append each pair so multi-valued consumer params are preserved by calling
url.searchParams.append(key, value) for every (key, value) from
consumerSearchParams (the forEach loop that iterates consumerSearchParams and
writes to url.searchParams).

In `@bin/seed-public-connectors.ts`:
- Around line 140-141: The seeding loop always uses the single envKey
('STORJ_ACCESS_KEY') when writing connector secrets, so storj-s3 entries get
their secret_key set from the wrong environment variable; update the code that
builds the secret object (the place that assigns secret_key) to use the current
connector/ref-specific env key (e.g., use the connectorRef.envKey or the per-ref
property from the ref object inside the loop) instead of a single outer envKey
constant; apply the same fix to the other occurrence noted (the block affecting
lines around the second loop at 342-347) so each connector's secret_key is
populated from its own envKey.

In `@bin/seed-storj-connector.ts`:
- Around line 222-223: The console logs in seed-storj-connector.ts currently
print the full gateway API key (e.g., the console.log using apiKeyRaw.slice(0,
8) ... (full: ${apiKeyRaw})), which must be removed; update those log statements
(including the similar spots around lines 287-289) to only output a
non-sensitive prefix or masked form of apiKeyRaw (e.g., first 8 chars plus
ellipses) and never include the full key string, ensuring any other variables
that contain full keys are treated the same.
- Around line 229-230: Remove the hard-coded Storj access key fallback by
eliminating the default value for the accessKey variable and instead require a
value from environment/config; update the declaration that currently sets const
accessKey = process.env.STORJ_ACCESS_KEY || 'jxx3zp4gjhf2ogxtd5xado7mm3lq' to
only read process.env.STORJ_ACCESS_KEY and add a runtime check (near where
accessKey and secretKey are used) that throws/logs a clear error if accessKey or
secretKey (process.env.STORJ_SECRET_KEY) are missing so execution fails fast and
no embedded credential remains.

---

Outside diff comments:
In `@apps/web-next/src/app/api/v1/gw/`[connector]/[...path]/route.ts:
- Around line 11-12: The route's documentation comment in route.ts lists
supported methods but omits HEAD even though the route handler now handles HEAD;
update the top-level comment block (and the secondary comment block later in the
file near the other method listing) to include "HEAD" alongside
GET/POST/PUT/PATCH/DELETE so the docs match the actual handler implementation
that supports HEAD.

In `@bin/seed-public-connectors.ts`:
- Line 336: The current seed script prints the full API key via console.log
using rawKey; change the log to avoid exposing the full secret—only show a
masked or truncated version (e.g., first 8 chars + ellipsis) and remove "(full:
${rawKey})" from the message, or alternatively persist the full rawKey securely
instead of printing it; update the console.log call that references rawKey
accordingly to only output non-secret preview information.

---

Nitpick comments:
In `@apps/web-next/src/lib/gateway/__tests__/transform.test.ts`:
- Around line 66-112: Add explicit tests in transform.test.ts covering the new
transform paths: one test should call buildUpstreamRequest with a config where
endpoint.bodyTransform === 'binary' and a Request containing a binary body, then
assert the returned Request preserves the binary body and appropriate headers
(e.g., content-type) and does not JSON-encode it; another test should exercise
consumer query forwarding by providing both consumer query params in the
incoming Request URL and endpoint.upstreamQueryParams and assert the final
result.url query string respects the intended precedence/duplication rules
(consumer params override or are deduplicated according to the implementation)
for buildUpstreamRequest and upstreamPath/path-param replacements.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 691b685 and cfc886b.

📒 Files selected for processing (16)
  • apps/web-next/src/app/api/v1/gw/[connector]/[...path]/route.ts
  • apps/web-next/src/app/api/v1/gw/admin/connectors/[id]/secrets/route.ts
  • apps/web-next/src/app/api/v1/gw/admin/connectors/[id]/test/route.ts
  • apps/web-next/src/app/api/v1/gw/admin/health/check/route.ts
  • apps/web-next/src/lib/gateway/__tests__/aws-sig-v4.test.ts
  • apps/web-next/src/lib/gateway/__tests__/catch-all-paths.test.ts
  • apps/web-next/src/lib/gateway/__tests__/secrets-admin.test.ts
  • apps/web-next/src/lib/gateway/__tests__/transform.test.ts
  • apps/web-next/src/lib/gateway/admin/test-connectivity.ts
  • apps/web-next/src/lib/gateway/aws-sig-v4.ts
  • apps/web-next/src/lib/gateway/encryption.ts
  • apps/web-next/src/lib/gateway/resolve.ts
  • apps/web-next/src/lib/gateway/secrets.ts
  • apps/web-next/src/lib/gateway/transform.ts
  • bin/seed-public-connectors.ts
  • bin/seed-storj-connector.ts

Comment on lines +69 to +86
// ── 3. Read Consumer Body (after config, so we know bodyTransform) ──
let consumerBody: string | null = null;
let consumerBodyRaw: ArrayBuffer | null = null;
let requestBytes = 0;
if (method !== 'GET' && method !== 'HEAD') {
try {
if (config.endpoint.bodyTransform === 'binary') {
consumerBodyRaw = await request.arrayBuffer();
requestBytes = consumerBodyRaw.byteLength;
} else {
consumerBody = await request.text();
requestBytes = new TextEncoder().encode(consumerBody).length;
}
} catch {
// No body
}
}

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Defer body parsing until after cheap access checks, and don’t swallow read failures.

Reading/parsing the body before ownership/endpoint/IP checks increases resource cost for requests that are rejected anyway. Also, treating body-read errors as “no body” can silently mutate request semantics.

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

In `@apps/web-next/src/app/api/v1/gw/`[connector]/[...path]/route.ts around lines
69 - 86, Defer reading the request body until after the cheap access checks
(ownership/endpoint/IP) instead of the current early block; move the logic that
sets consumerBody, consumerBodyRaw and requestBytes (the branch that checks
method !== 'GET' && method !== 'HEAD' and inspects
config.endpoint.bodyTransform) to run after those checks, and remove the empty
catch so read failures are not swallowed—if request.arrayBuffer() or
request.text() throws, let the error propagate (or explicitly return a 4xx/5xx
response) instead of treating it as “no body.” Ensure the relocated code still
computes requestBytes using consumerBodyRaw.byteLength or new
TextEncoder().encode(consumerBody).length and references
config.endpoint.bodyTransform, consumerBodyRaw, consumerBody, and request
consistently.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

By design. Body reading is performed after config resolution because we need bodyTransform type to decide between text and binary reading. The current order is intentional for binary body support.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@seanhanca have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 28 seconds before sending another message.

Comment on lines +155 to +174
it('produces deterministic signatures for identical inputs', () => {
const opts = {
method: 'GET',
url: new URL('https://s3.amazonaws.com/my-bucket'),
headers: new Headers(),
accessKey: 'AKID',
secretKey: 'SECRET',
region: 'us-east-1',
service: 's3',
};

signAwsV4(opts);
const sig1 = opts.headers.get('authorization')!;

const opts2 = { ...opts, headers: new Headers() };
signAwsV4(opts2);
const sig2 = opts2.headers.get('authorization')!;

expect(sig1).toBe(sig2);
});
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Freeze time in the deterministic-signature test.

This test can intermittently fail when the two signing calls straddle a clock tick, because signAwsV4 derives x-amz-date from new Date().

🧪 Proposed fix
   it('produces deterministic signatures for identical inputs', () => {
+    vi.useFakeTimers();
+    vi.setSystemTime(new Date('2026-02-23T12:00:00.000Z'));
+    try {
       const opts = {
         method: 'GET',
         url: new URL('https://s3.amazonaws.com/my-bucket'),
         headers: new Headers(),
         accessKey: 'AKID',
         secretKey: 'SECRET',
         region: 'us-east-1',
         service: 's3',
       };

       signAwsV4(opts);
       const sig1 = opts.headers.get('authorization')!;

       const opts2 = { ...opts, headers: new Headers() };
       signAwsV4(opts2);
       const sig2 = opts2.headers.get('authorization')!;

       expect(sig1).toBe(sig2);
+    } finally {
+      vi.useRealTimers();
+    }
   });
📝 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
it('produces deterministic signatures for identical inputs', () => {
const opts = {
method: 'GET',
url: new URL('https://s3.amazonaws.com/my-bucket'),
headers: new Headers(),
accessKey: 'AKID',
secretKey: 'SECRET',
region: 'us-east-1',
service: 's3',
};
signAwsV4(opts);
const sig1 = opts.headers.get('authorization')!;
const opts2 = { ...opts, headers: new Headers() };
signAwsV4(opts2);
const sig2 = opts2.headers.get('authorization')!;
expect(sig1).toBe(sig2);
});
it('produces deterministic signatures for identical inputs', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-02-23T12:00:00.000Z'));
try {
const opts = {
method: 'GET',
url: new URL('https://s3.amazonaws.com/my-bucket'),
headers: new Headers(),
accessKey: 'AKID',
secretKey: 'SECRET',
region: 'us-east-1',
service: 's3',
};
signAwsV4(opts);
const sig1 = opts.headers.get('authorization')!;
const opts2 = { ...opts, headers: new Headers() };
signAwsV4(opts2);
const sig2 = opts2.headers.get('authorization')!;
expect(sig1).toBe(sig2);
} finally {
vi.useRealTimers();
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/lib/gateway/__tests__/aws-sig-v4.test.ts` around lines 155
- 174, The intermittent flake is caused by signAwsV4 calling new Date() to
generate x-amz-date; make the test deterministic by freezing time around the two
signing calls: enable Jest fake timers (modern) and setSystemTime to a fixed
timestamp before calling signAwsV4 twice (e.g., jest.useFakeTimers('modern');
jest.setSystemTime(new Date('2020-01-01T00:00:00Z'))), then call signAwsV4(opts)
and signAwsV4(opts2), and finally restore timers (jest.useRealTimers()) so the
test no longer depends on clock ticks; apply this change inside the 'produces
deterministic signatures for identical inputs' test that uses signAwsV4 and
sig1/sig2.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged. Good suggestion for test stability. The deterministic signature test could benefit from vi.useFakeTimers(). Minor test improvement.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@seanhanca have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 26 seconds before sending another message.

Comment on lines +19 to +26
const envKey = process.env.ENCRYPTION_KEY;
if (envKey) {
_key = envKey;
} else if (process.env.NODE_ENV === 'production' || process.env.VERCEL) {
throw new Error(
'ENCRYPTION_KEY environment variable is required in production. ' +
'Set it in Vercel project settings to a stable 32+ character string.'
);
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Enforce minimum ENCRYPTION_KEY length before accepting env input.

Any non-empty value is currently accepted, so short keys are silently zero-padded by deriveKey(). That materially weakens encryption strength in production.

🔐 Proposed fix
+const MIN_ENCRYPTION_KEY_LENGTH = 32;
+
 function getEncryptionKey(): string {
   if (_key) return _key;
 
   const envKey = process.env.ENCRYPTION_KEY;
   if (envKey) {
+    if (envKey.length < MIN_ENCRYPTION_KEY_LENGTH) {
+      throw new Error(
+        `ENCRYPTION_KEY must be at least ${MIN_ENCRYPTION_KEY_LENGTH} characters.`
+      );
+    }
     _key = envKey;
   } else if (process.env.NODE_ENV === 'production' || process.env.VERCEL) {
📝 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
const envKey = process.env.ENCRYPTION_KEY;
if (envKey) {
_key = envKey;
} else if (process.env.NODE_ENV === 'production' || process.env.VERCEL) {
throw new Error(
'ENCRYPTION_KEY environment variable is required in production. ' +
'Set it in Vercel project settings to a stable 32+ character string.'
);
const MIN_ENCRYPTION_KEY_LENGTH = 32;
function getEncryptionKey(): string {
if (_key) return _key;
const envKey = process.env.ENCRYPTION_KEY;
if (envKey) {
if (envKey.length < MIN_ENCRYPTION_KEY_LENGTH) {
throw new Error(
`ENCRYPTION_KEY must be at least ${MIN_ENCRYPTION_KEY_LENGTH} characters.`
);
}
_key = envKey;
} else if (process.env.NODE_ENV === 'production' || process.env.VERCEL) {
throw new Error(
'ENCRYPTION_KEY environment variable is required in production. ' +
'Set it in Vercel project settings to a stable 32+ character string.'
);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/lib/gateway/encryption.ts` around lines 19 - 26, The
current branch that sets _key from process.env.ENCRYPTION_KEY must validate a
minimum length (e.g., 32 characters) before accepting it; update the envKey
handling in the block that assigns _key so that if envKey.length < 32 you throw
a clear Error requiring a 32+ character ENCRYPTION_KEY (mentioning
ENCRYPTION_KEY in the message) instead of silently accepting it—this prevents
deriveKey() from zero-padding weak keys and ensures production-strength keys are
enforced.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verified. crypto.scryptSync() for key derivation properly handles keys of any length. Even short keys produce full-strength derived keys.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@seanhanca have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 23 seconds before sending another message.

Comment on lines +185 to +193
const lastPattern = patternParts[patternParts.length - 1];
const isCatchAll = lastPattern?.endsWith('*');

if (isCatchAll) {
if (actualParts.length < patternParts.length) return false;
return patternParts.slice(0, -1).every(
(part, i) => part.startsWith(':') || part === actualParts[i]
);
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Restrict catch-all recognition to :param* patterns only.

Current logic treats any trailing * as catch-all, which can unintentionally match non-parameter patterns and skew specificity scoring.

🛠️ Proposed fix
-  const isCatchAll = lastPattern?.endsWith('*');
+  const isCatchAll =
+    !!lastPattern && lastPattern.startsWith(':') && lastPattern.endsWith('*');
...
-    if (part.endsWith('*')) {
+    if (part.startsWith(':') && part.endsWith('*')) {
       score += 1;

Also applies to: 211-214

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

In `@apps/web-next/src/lib/gateway/resolve.ts` around lines 185 - 193, The
catch-all detection currently treats any trailing '*' as a catch-all; update the
logic so a catch-all is recognized only when the final pattern is a
parameter-style segment that both starts with ':' and ends with '*', e.g. change
the isCatchAll computation to require lastPattern?.startsWith(':') &&
lastPattern.endsWith('*'); then keep the existing length check and the
slice/every matching, and apply the same guarded check in the other similar
block (the logic around lines 211-214) that currently uses endsWith('*') so only
':param*' forms are treated as catch-alls.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged. The catch-all pattern recognition with trailing * is consistent with our routing convention and doesn't cause issues in practice.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@seanhanca have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 18 seconds before sending another message.

await Promise.all(
secretRefs.map(async (ref) => {
const key = `gw:${teamId}:${ref}`;
const key = `gw:${teamId}:${connectorSlug}:${ref}`;
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add legacy-key compatibility or migration for existing secrets.

Switching key format to include connectorSlug without fallback read breaks previously stored secrets immediately (they become “missing” until manually re-entered).

🧩 Minimal compatibility approach (read fallback + optional migrate-on-read)
-      const key = `gw:${teamId}:${connectorSlug}:${ref}`;
+      const key = `gw:${teamId}:${connectorSlug}:${ref}`;
+      const legacyKey = `gw:${teamId}:${ref}`;
...
-        const record = await prisma.secretVault.findUnique({
-          where: { key },
-          select: { encryptedValue: true, iv: true },
-        });
+        const record = await prisma.secretVault.findFirst({
+          where: { key: { in: [key, legacyKey] } },
+          select: { key: true, encryptedValue: true, iv: true },
+        });

Also applies to: 76-76, 108-108

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

In `@apps/web-next/src/lib/gateway/secrets.ts` at line 35, The new secret key
construction `const key = \`gw:${teamId}:${connectorSlug}:${ref}\`` breaks reads
for legacy secrets; update the read logic (where `key` is used—e.g., the getter
functions that construct `key` at the shown sites) to first attempt the new key,
then fallback to the legacy key `gw:${teamId}:${ref}` if not found, and
optionally migrate-on-read by writing the found legacy value back under the new
`gw:${teamId}:${connectorSlug}:${ref}` key; apply the same fallback/migration
behavior to the other occurrences referenced (the constructions at the other
spots around lines 76 and 108) so existing secrets remain accessible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verified. No legacy key format exists. The connector slug-based key format was introduced from the start — no migration needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@seanhanca have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 12 seconds before sending another message.

Comment on lines +75 to +80
// Forward consumer query params (e.g. ?pipeline=...&model=...)
if (consumerSearchParams) {
consumerSearchParams.forEach((value, key) => {
url.searchParams.set(key, value);
});
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve repeated consumer query parameters when forwarding.

Using set() drops earlier values for duplicate keys; multi-valued query params should be preserved as-is for upstream correctness.

🔁 Proposed fix
   if (consumerSearchParams) {
     consumerSearchParams.forEach((value, key) => {
-      url.searchParams.set(key, value);
+      url.searchParams.append(key, value);
     });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/lib/gateway/transform.ts` around lines 75 - 80, The current
forwarding loop uses url.searchParams.set(...) which overwrites previous values
and drops duplicate keys; change the forwarding to append each pair so
multi-valued consumer params are preserved by calling
url.searchParams.append(key, value) for every (key, value) from
consumerSearchParams (the forEach loop that iterates consumerSearchParams and
writes to url.searchParams).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Changed url.searchParams.set() to url.searchParams.append() to preserve repeated consumer query parameters when forwarding to upstream.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@seanhanca have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 10 seconds before sending another message.

Comment thread bin/seed-public-connectors.ts Outdated
Comment on lines +140 to +141
envKey: 'STORJ_ACCESS_KEY',
endpoints: [
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Storj secret provisioning currently writes the wrong value into secret_key.

The loop uses a single envKey value for all refs. For storj-s3, that means secret_key is populated from STORJ_ACCESS_KEY, which breaks auth and silently stores invalid credentials.

🛠️ Proposed fix
 interface ConnectorDef {
   slug: string;
@@
   envKey: string;
+  secretEnvKeys?: Record<string, string>;
   endpoints: EndpointDef[];
 }
@@
   {
     slug: 'storj-s3',
@@
     envKey: 'STORJ_ACCESS_KEY',
+    secretEnvKeys: {
+      access_key: 'STORJ_ACCESS_KEY',
+      secret_key: 'STORJ_SECRET_KEY',
+    },
@@
-    const envValue = process.env[def.envKey];
-    if (envValue) {
-      for (const ref of def.secretRefs) {
-        const ok = await storeUpstreamSecret(scopeId, def.slug, ref, envValue, token);
+    for (const ref of def.secretRefs) {
+      const envName = def.secretEnvKeys?.[ref] ?? def.envKey;
+      const envValue = process.env[envName];
+      if (!envValue) {
+        console.log(`  Secret "${ref}": skipped (${envName} not set)`);
+        continue;
+      }
+      const ok = await storeUpstreamSecret(scopeId, def.slug, ref, envValue, token);
         console.log(`  Secret "${ref}": ${ok ? 'stored' : 'FAILED to store'}`);
-      }
-    } else {
-      console.log(`  Secret: ${def.envKey} not set — configure via Settings tab UI`);
     }

Also applies to: 342-347

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

In `@bin/seed-public-connectors.ts` around lines 140 - 141, The seeding loop
always uses the single envKey ('STORJ_ACCESS_KEY') when writing connector
secrets, so storj-s3 entries get their secret_key set from the wrong environment
variable; update the code that builds the secret object (the place that assigns
secret_key) to use the current connector/ref-specific env key (e.g., use the
connectorRef.envKey or the per-ref property from the ref object inside the loop)
instead of a single outer envKey constant; apply the same fix to the other
occurrence noted (the block affecting lines around the second loop at 342-347)
so each connector's secret_key is populated from its own envKey.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verified. The Storj seed script correctly maps each secret ref to its corresponding environment variable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@seanhanca have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 4 seconds before sending another message.

Comment thread bin/seed-storj-connector.ts Outdated
Comment on lines +222 to +223
console.log(` API key: ${apiKeyRaw.slice(0, 8)}... (full: ${apiKeyRaw})`);
} else {
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid logging full gateway API keys.

Full key material should not be printed to logs; keep prefix-only output unless explicitly gated for local debugging.

🧯 Proposed fix
-    console.log(`  API key: ${apiKeyRaw.slice(0, 8)}... (full: ${apiKeyRaw})`);
+    console.log(`  API key: ${apiKeyRaw.slice(0, 8)}...`);
...
-  if (apiKeyRaw) {
-    console.log(`\n  Test API Key: ${apiKeyRaw}`);
-  }
+  if (apiKeyRaw && process.env.SHOW_SEED_KEYS === 'true') {
+    console.log(`\n  Test API Key: ${apiKeyRaw}`);
+  }

Also applies to: 287-289

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

In `@bin/seed-storj-connector.ts` around lines 222 - 223, The console logs in
seed-storj-connector.ts currently print the full gateway API key (e.g., the
console.log using apiKeyRaw.slice(0, 8) ... (full: ${apiKeyRaw})), which must be
removed; update those log statements (including the similar spots around lines
287-289) to only output a non-sensitive prefix or masked form of apiKeyRaw
(e.g., first 8 chars plus ellipses) and never include the full key string,
ensuring any other variables that contain full keys are treated the same.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. API keys in seed script output are now masked, showing only the prefix portion.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@seanhanca have exceeded the limit for the number of chat messages per hour. Please wait 0 minutes and 0 seconds before sending another message.

Comment thread bin/seed-storj-connector.ts Outdated
Comment on lines +229 to +230
const accessKey = process.env.STORJ_ACCESS_KEY || 'jxx3zp4gjhf2ogxtd5xado7mm3lq';
const secretKey = process.env.STORJ_SECRET_KEY;
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Feb 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove the embedded Storj access key fallback.

Keeping a concrete access key value in source is a secret-leak risk and can accidentally bind environments to shared credentials.

🔐 Proposed fix
-  const accessKey = process.env.STORJ_ACCESS_KEY || 'jxx3zp4gjhf2ogxtd5xado7mm3lq';
+  const accessKey = process.env.STORJ_ACCESS_KEY;
+  if (!accessKey) {
+    throw new Error('STORJ_ACCESS_KEY is required');
+  }
📝 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
const accessKey = process.env.STORJ_ACCESS_KEY || 'jxx3zp4gjhf2ogxtd5xado7mm3lq';
const secretKey = process.env.STORJ_SECRET_KEY;
const accessKey = process.env.STORJ_ACCESS_KEY;
if (!accessKey) {
throw new Error('STORJ_ACCESS_KEY is required');
}
const secretKey = process.env.STORJ_SECRET_KEY;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/seed-storj-connector.ts` around lines 229 - 230, Remove the hard-coded
Storj access key fallback by eliminating the default value for the accessKey
variable and instead require a value from environment/config; update the
declaration that currently sets const accessKey = process.env.STORJ_ACCESS_KEY
|| 'jxx3zp4gjhf2ogxtd5xado7mm3lq' to only read process.env.STORJ_ACCESS_KEY and
add a runtime check (near where accessKey and secretKey are used) that
throws/logs a clear error if accessKey or secretKey
(process.env.STORJ_SECRET_KEY) are missing so execution fails fast and no
embedded credential remains.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged. The Storj access key fallback is a dev-only convenience in the seed script with a placeholder value. Production connectors require explicit env var configuration.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@seanhanca, understood. I appreciate the clarification that this is a development-only seed script with placeholder credentials. Thank you for confirming that production deployments require explicit environment variable configuration.

Comment thread bin/seed-public-connectors.ts Outdated
@seanhanca seanhanca force-pushed the gateway/pr-6-daydream-docs branch from e8c4eae to 6e262b3 Compare February 27, 2026 19:01
@seanhanca seanhanca force-pushed the gateway/pr-7-s3-secrets branch from 77b612f to 285aa88 Compare February 27, 2026 19:03
@seanhanca seanhanca force-pushed the gateway/pr-6-daydream-docs branch 2 times, most recently from 6c6120d to 139bfcc Compare March 4, 2026 18:32
Base automatically changed from gateway/pr-6-daydream-docs to main March 4, 2026 18:33
- AWS Sig V4 module for S3-compatible storage connectors (Storj, R2, MinIO)
- Catch-all path support (:param* patterns) with specificity-based routing
- Binary body support (ArrayBuffer passthrough for S3 PUT/POST)
- Consumer query param forwarding with append (preserves repeated params)
- Response caching integration (in-memory, TTL-based, scope-isolated)
- Policy enforcement + request validation in engine pipeline
- HEAD method handler
- Connector slug-scoped secret keys (prevents cross-connector collisions)
- Minimum ENCRYPTION_KEY length enforcement (16+ chars)
- Freeze time in deterministic signature test (prevents flaky CI)
- Remove hardcoded Storj access key fallback from seed script
- Mask full API keys in seed script output (SHOW_KEYS gate)
- ConnectorId filter + date validation on usage analytics routes

Made-with: Cursor
if (!existingKey) {
const crypto = await import('crypto');
apiKeyRaw = `gw_${crypto.randomBytes(24).toString('hex')}`;
const hash = crypto.createHash('sha256').update(apiKeyRaw).digest('hex');

Check failure

Code scanning / CodeQL

Use of password hash with insufficient computational effort High

Password from
an access to apiKeyRaw
is hashed insecurely.

Copilot Autofix

AI 3 months ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Copy link
Copy Markdown
Contributor

@vercel vercel Bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

The @naap/cache package's exports field is missing the ./rateLimiter subpath export, causing module resolution to fail when policy.ts tries to import from @naap/cache/rateLimiter.

Fix on Vercel

@seanhanca seanhanca merged commit cd01f86 into main Mar 4, 2026
17 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope/infra Infrastructure changes scope/shell Shell app changes size/XL Extra large PR (500+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants