fix(http): raw request bytes for webhook signature verification + blueprint stripe webhook - #244
Conversation
Routes could not receive the raw, unparsed request bytes, making HMAC/webhook signature verification (Stripe, GitHub, Slack, ...) impossible without bypassing the framework entirely. - express contentParse builds every body-parser with a verify hook that stashes the exact request bytes on req._rawBody, while req.body stays the parsed value; user-supplied verify callbacks still run - per-route contentType now applies to the dispatched parser (a body declared as text with contentType application/json parses JSON-typed requests as raw text) — overrides only when it differs from the parser's default so default matching semantics are preserved - parser options are resolved lazily from req._globalOptions(), so application-level json/text/raw/urlencoded/busboy options reach router-level parsing (previously parsers were built at construction time, before addRouterOptions merged app options into routers) - hyper-express captures req._rawBody via req.buffer() before parsing (multipart excluded); the cached body is reused by json()/text() - core parse middleware no longer clobbers a parser-captured _rawBody (now _rawBody ??= body), and createHmacToken signs Buffer/string bodies verbatim so raw-bytes verification matches object-signing clients byte-for-byte Published as @forklaunch/[email protected], @forklaunch/[email protected], @forklaunch/[email protected]. Co-Authored-By: Claude Fable 5 <[email protected]>
- webhook route declares body as text with contentType application/json (new framework support), so req.body is the exact payload string that stripe.webhooks.constructEvent verifies — previously the text parser never matched Stripe's application/json posts and requests failed validation before the handler ran - bump @forklaunch/core to 1.5.11 and express/hyper-express to 1.2.38 - fix pre-existing typecheck breaks surfaced by the dep refresh (present on main with prior versions too): spread EraseResult/ ExportResult into res.json literals (interfaces lack index signatures) and annotate surfacePermissions/surfaceRoles payload params where inference collapsed to never Co-Authored-By: Claude Fable 5 <[email protected]>
📝 WalkthroughWalkthroughThe PR preserves raw webhook payloads during request parsing and HMAC signing. It updates Express and Hyper-Express middleware, webhook schema configuration, compliance responses, IAM callback typing, package versions, and CLI documentation. ChangesWebhook and compliance integration
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Stripe
participant contentParse
participant WebhookController
participant createHmacToken
Stripe->>contentParse: send signed JSON payload
contentParse->>contentParse: capture exact raw bytes
contentParse->>WebhookController: provide parsed body and _rawBody
WebhookController->>createHmacToken: pass raw payload
createHmacToken->>WebhookController: construct UTF-8 HMAC payload
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
framework/express/__test__/rawBody.webhook.test.ts (1)
1-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse the required test filename and import layers.
Rename this file to
rawBodyWebhook.test.ts. Move thehttpimport before package imports.As per coding guidelines,
**/*.test.tsmust use camelCase<resource>.test.tsnames, and TypeScript imports must place Node built-ins before external dependencies.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@framework/express/__test__/rawBody.webhook.test.ts` around lines 1 - 5, Rename the test file to rawBodyWebhook.test.ts and reorder its imports so the Node built-in http import appears before all external package imports, while preserving the existing forklaunch and validator imports.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@framework/core/src/http/createHmacToken.ts`:
- Around line 35-45: Update the body canonicalization in createHmacToken to keep
Buffer inputs binary-safe by passing the original Buffer bytes to hmac.update
rather than decoding them as UTF-8, while retaining the newline delimiter for
every body type. Preserve existing string and object serialization behavior, and
add a regression test using non-UTF-8 bytes to verify the generated HMAC matches
the raw payload.
In `@framework/express/__test__/rawBody.webhook.test.ts`:
- Around line 10-11: Update the test server setup around PORT and HOST to listen
on port 0 instead of reserving fixed port 6480, then construct the host URL from
the actual address returned by server.address() after the server is listening.
Ensure all webhook requests use this dynamically resolved host.
---
Nitpick comments:
In `@framework/express/__test__/rawBody.webhook.test.ts`:
- Around line 1-5: Rename the test file to rawBodyWebhook.test.ts and reorder
its imports so the Node built-in http import appears before all external package
imports, while preserving the existing forklaunch and validator imports.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0e15928a-91bf-43cf-a661-6a41f13c3813
⛔ Files ignored due to path filters (1)
blueprint/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (34)
blueprint/billing-base/api/controllers/compliance.controller.tsblueprint/billing-base/package.jsonblueprint/billing-stripe/api/controllers/compliance.controller.tsblueprint/billing-stripe/api/controllers/webhook.controller.tsblueprint/billing-stripe/package.jsonblueprint/client-sdk/package.jsonblueprint/core/package.jsonblueprint/ecommerce-stripe/package.jsonblueprint/iam-base/api/controllers/compliance.controller.tsblueprint/iam-base/package.jsonblueprint/iam-base/server.tsblueprint/iam-better-auth/api/controllers/compliance.controller.tsblueprint/iam-better-auth/package.jsonblueprint/iam-better-auth/registrations.tsblueprint/implementations/billing/base/package.jsonblueprint/implementations/billing/stripe/package.jsonblueprint/implementations/ecommerce/base/package.jsonblueprint/implementations/iam/base/package.jsonblueprint/implementations/worker/bullmq/package.jsonblueprint/implementations/worker/database/package.jsonblueprint/implementations/worker/kafka/package.jsonblueprint/implementations/worker/redis/package.jsonblueprint/monitoring/package.jsonblueprint/package.jsonblueprint/pnpm-workspace.yamlblueprint/sample-worker/package.jsonframework/core/package.jsonframework/core/src/http/createHmacToken.tsframework/core/src/http/middleware/request/parse.middleware.tsframework/express/__test__/rawBody.webhook.test.tsframework/express/package.jsonframework/express/src/middleware/content.parse.middleware.tsframework/hyper-express/package.jsonframework/hyper-express/src/middleware/contentParse.middleware.ts
| const bodyString = body | ||
| ? `${ | ||
| Buffer.isBuffer(body) | ||
| ? body.toString('utf8') | ||
| : typeof body === 'string' | ||
| ? body | ||
| : safeStringify(body) | ||
| }\n` | ||
| : undefined; | ||
| hmac.update( | ||
| `${method}\n${path}\n${bodyString}${timestamp.toISOString()}\n${nonce}` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Official Node.js documentation: Does crypto.Hmac.update()accept a Buffer, and how doesBuffer.toString('utf8') process invalid UTF-8 byte sequences?
💡 Result:
Yes, the crypto.Hmac.update() method in Node.js accepts a Buffer [1][2][3]. According to official Node.js documentation, when update() is called, the provided data can be a string, Buffer, TypedArray, or DataView. If the data is a Buffer, TypedArray, or DataView, the inputEncoding argument is ignored [4][1][3]. Regarding Buffer.toString('utf8'), when this method encounters invalid UTF-8 byte sequences while decoding a Buffer into a string, it replaces each invalid byte with the Unicode replacement character U+FFFD [5][6][7]. This process ensures that the resulting string is valid, though it means information from the original malformed byte sequence is lost during the conversion [8].
Citations:
- 1: https://nodejs.org/docs/latest-v21.x/api/crypto.html
- 2: https://nodejs.org/docs/latest-v19.x/api/crypto.html
- 3: https://nodejs.org/docs/v20.12.1/api/crypto.html
- 4: https://nodejs.org/api/crypto.html
- 5: https://nodejs.org/api/buffer.html
- 6: https://nodejs.org/docs/latest-v26.x/api/buffer.html
- 7: https://nodejs.org/dist/latest/docs/api/buffer.html
- 8: https://stackoverflow.com/questions/68756320/unexpected-result-when-calling-tostring-on-a-buffer-in-node
🏁 Script executed:
#!/bin/bash
set -eu
file=$(git ls-files | rg '(^|/)createHmacToken\.ts$' | head -n 1)
printf '%s\n' "FILE=$file"
wc -l "$file"
sed -n '1,140p' "$file"
printf '\n-- related references --\n'
rg -n --glob '!node_modules' 'createHmacToken|HmacToken|hmac\.update' .
printf '\n-- nearby tests --\n'
git ls-files | rg -i 'hmac|signature|token|http' | head -n 120Repository: forklaunch/forklaunch
Length of output: 8312
🏁 Script executed:
#!/bin/bash
set -eu
node - <<'JS'
const crypto = require('node:crypto');
const key = 'key';
const raw = Buffer.from([0xff, 0x00, 0xc3, 0x28]);
const decoded = raw.toString('utf8');
const direct = crypto.createHmac('sha256', key).update(raw).digest('hex');
const decodedDigest = crypto.createHmac('sha256', key).update(decoded).digest('hex');
const replacementDigest = crypto.createHmac('sha256', key)
.update(Buffer.from([0xef, 0xbf, 0xbd, 0x00, 0xef, 0xbf, 0xbd, 0x28]))
.digest('hex');
console.log(JSON.stringify({
decodedCodePoints: [...decoded].map((c) => c.codePointAt(0).toString(16)),
direct,
decodedDigest,
replacementDigest,
decodedChangesBytes: direct !== decodedDigest,
hmacAcceptsBuffer: direct.length === 64,
}, null, 2));
JSRepository: forklaunch/forklaunch
Length of output: 552
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '-- raw-body test --'
sed -n '130,220p' framework/express/__test__/rawBody.webhook.test.ts
printf '%s\n' '-- HMAC generation and verification call sites --'
sed -n '1,110p' framework/core/src/http/generateHmacAuthHeaders.ts
sed -n '210,265p' framework/core/src/http/discriminateAuthMethod.ts
sed -n '1,90p' framework/core/__test__/expressLikeRouterInstantiation.test.ts
sed -n '320,360p' framework/core/__test__/expressLikeRouterInstantiation.test.ts
printf '%s\n' '-- body handling and raw-body capture --'
rg -n -C 5 'rawBody|_rawBody|createHmacToken\(' framework/express framework/core/src/http framework/core/__test__Repository: forklaunch/forklaunch
Length of output: 21548
🏁 Script executed:
#!/bin/bash
set -eu
node - <<'JS'
const crypto = require('node:crypto');
function current({ method, path, body, timestamp, nonce, secretKey }) {
const hmac = crypto.createHmac('sha256', secretKey);
const bodyString = body
? `${Buffer.isBuffer(body) ? body.toString('utf8') : typeof body === 'string' ? body : JSON.stringify(body)}\n`
: undefined;
hmac.update(`${method}\n${path}\n${bodyString}${timestamp.toISOString()}\n${nonce}`);
return hmac.digest('hex');
}
function proposed({ method, path, body, timestamp, nonce, secretKey }) {
const hmac = crypto.createHmac('sha256', secretKey);
const bodyValue = body
? Buffer.isBuffer(body)
? body
: typeof body === 'string'
? body
: JSON.stringify(body)
: undefined;
hmac.update(`${method}\n${path}\n`);
if (Buffer.isBuffer(bodyValue)) {
hmac.update(bodyValue);
hmac.update('\n');
} else {
hmac.update(`${bodyValue}`);
}
hmac.update(`${timestamp.toISOString()}\n${nonce}`);
return hmac.digest('hex');
}
const base = {
method: 'POST',
path: '/hooks',
timestamp: new Date('2025-01-02T03:04:05.000Z'),
nonce: 'n',
secretKey: 'k',
};
for (const body of [
Buffer.from([0xff, 0x00, 0xc3, 0x28]),
'text',
{ value: 'text' },
Buffer.alloc(0),
'',
undefined,
]) {
console.log(JSON.stringify({
body: Buffer.isBuffer(body) ? `Buffer(${body.toString('hex')})` : body,
current: current({ ...base, body }),
proposed: proposed({ ...base, body }),
same: current({ ...base, body }) === proposed({ ...base, body }),
}));
}
JSRepository: forklaunch/forklaunch
Length of output: 1268
Keep Buffer input binary-safe.
Line 38 decodes raw bytes as UTF-8. Invalid bytes become U+FFFD, so the HMAC differs from the received payload. Pass the Buffer directly to hmac.update() and preserve the newline delimiter for all body types. Add a regression test with non-UTF-8 bytes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@framework/core/src/http/createHmacToken.ts` around lines 35 - 45, Update the
body canonicalization in createHmacToken to keep Buffer inputs binary-safe by
passing the original Buffer bytes to hmac.update rather than decoding them as
UTF-8, while retaining the newline delimiter for every body type. Preserve
existing string and object serialization behavior, and add a regression test
using non-UTF-8 bytes to verify the generated HMAC matches the raw payload.
| const PORT = 6480; | ||
| const HOST = `http://localhost:${PORT}`; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Avoid a fixed listening port.
Line 10 reserves port 6480. Parallel tests or another local process can cause EADDRINUSE. Bind to port 0, then build the host URL from server.address() after listening.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@framework/express/__test__/rawBody.webhook.test.ts` around lines 10 - 11,
Update the test server setup around PORT and HOST to listen on port 0 instead of
reserving fixed port 6480, then construct the host URL from the actual address
returned by server.address() after the server is listening. Ensure all webhook
requests use this dynamically resolved host.
Floors core at ~1.5.11 and express/hyper-express at ~1.2.38 so fresh scaffolds are guaranteed the raw-body webhook fix, and aligns blueprint interface/implementation floors with their latest published versions. Co-Authored-By: Claude Fable 5 <[email protected]>
Refreshes the vendored skill pack from forklaunch-platform@8ea64815 (.claude/skills) — the previous copy was a July 1 snapshot with 8 drifted files and cli/SKILL.md missing 38% of its content (fl observe, fl environment). - refresh all drifted skills; add the new user-facing infra skill (fl infra: list/status/resize/config-set/stop/delete) - remove db-query.md: an internal production-database runbook that embeds the production RDS hostname — must not ship to users - remove gstack, plan-devex-review, plan-design-review: third-party workflow tooling flagged in the skill-pack-sync proposal (#243), ~200KB of non-ForkLaunch content Co-Authored-By: Claude Fable 5 <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cli/assets/forklaunch-skills/backend-patterns/SKILL.md`:
- Around line 1108-1128: Update topologicalSort to track an active visiting set
separately from visited; throw when visit encounters a name already active,
remove names from the active set after dependencies complete, and add them to
visited only before pushing the resolved seeder into ordered. Preserve the
existing dependency traversal and return order for acyclic graphs.
In `@cli/assets/forklaunch-skills/cli/SKILL.md`:
- Around line 406-420: Update the “Remove project components” guidance so piping
“y” into forklaunch delete is documented only for already-approved automation.
Require explicit user approval before executing any piped delete command for
services, workers, libraries, or routers, including the repeated guidance at the
other referenced sections.
- Line 1421: Update the global dry-run guidance in the CLI skill documentation
to apply `--dry-run` only when the command supports it; otherwise instruct users
to use that command’s preview or confirmation flow. Replace the unconditional
“ALWAYS” wording while preserving the intent to preview effects before applying
changes.
- Line 1477: Update the seeder workaround guidance to use the shared
SEEDER_DEPENDENCIES and topologicalSort pattern from the backend seeder guidance
instead of replacing Object.values(seeders) with a manually maintained array;
retain auto-discovery and ensure UserSeeder is included so dependencies
determine execution order.
In `@cli/assets/forklaunch-skills/framework/SKILL.md`:
- Around line 269-281: Update the “Validating schemas programmatically” section
to document that schemify may return either Zod or TypeBox depending on the
configured validator. Use schemaValidator.validate(...) for backend-independent
validation, or split the example into backend-specific Zod and TypeBox flows; do
not present safeParse or result.success as universal APIs.
In `@cli/assets/forklaunch-skills/infra/SKILL.md`:
- Around line 39-41: Update the fenced code block containing the
<project-name>:<resource-type> syntax in SKILL.md to specify the text language
identifier, preserving its contents.
- Around line 101-115: Update the --yes description in the resize command’s
“Safety flags” section to say “non-interactive scripts with an existing JWT
session,” and explicitly retain that CI is unsupported; do not describe --yes as
CI support.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5096ca33-0471-43f5-8a99-ec229321a17c
📒 Files selected for processing (13)
cli/assets/forklaunch-skills/README.mdcli/assets/forklaunch-skills/backend-patterns/SKILL.mdcli/assets/forklaunch-skills/cli/SKILL.mdcli/assets/forklaunch-skills/common-tasks/SKILL.mdcli/assets/forklaunch-skills/db-query.mdcli/assets/forklaunch-skills/development-guidelines/SKILL.mdcli/assets/forklaunch-skills/framework/SKILL.mdcli/assets/forklaunch-skills/gstack/SKILL.mdcli/assets/forklaunch-skills/infra/SKILL.mdcli/assets/forklaunch-skills/infrastructure-and-utilities/SKILL.mdcli/assets/forklaunch-skills/plan-design-review/SKILL.mdcli/assets/forklaunch-skills/plan-devex-review/SKILL.mdcli/assets/forklaunch-skills/platform-architecture/SKILL.md
💤 Files with no reviewable changes (4)
- cli/assets/forklaunch-skills/db-query.md
- cli/assets/forklaunch-skills/plan-devex-review/SKILL.md
- cli/assets/forklaunch-skills/plan-design-review/SKILL.md
- cli/assets/forklaunch-skills/gstack/SKILL.md
| function topologicalSort( | ||
| all: typeof seeders, | ||
| deps: typeof SEEDER_DEPENDENCIES | ||
| ): (typeof seeders)[keyof typeof seeders][] { | ||
| const ordered: (typeof seeders)[keyof typeof seeders][] = []; | ||
| const visited = new Set<string>(); | ||
|
|
||
| function visit(name: keyof typeof seeders) { | ||
| if (visited.has(name)) return; | ||
| visited.add(name); | ||
| for (const dep of deps[name] ?? []) { | ||
| visit(dep); | ||
| } | ||
| ordered.push(all[name]); | ||
| } | ||
|
|
||
| for (const name of Object.keys(all) as (keyof typeof seeders)[]) { | ||
| visit(name); | ||
| } | ||
| return ordered; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Reject cyclic seeder dependencies instead of emitting an invalid order.
The algorithm adds name to visited before visiting its dependencies. For dependencies A -> B and B -> A, the second visit to A returns early, and the function emits an invalid order. DatabaseSeeder.run can then fail on foreign-key constraints.
Track nodes currently being visited separately. Throw when a back-edge is found. Add a node to visited only after all dependencies finish.
Proposed fix
const ordered: (typeof seeders)[keyof typeof seeders][] = [];
const visited = new Set<string>();
+ const visiting = new Set<string>();
function visit(name: keyof typeof seeders) {
if (visited.has(name)) return;
+ if (visiting.has(name)) {
+ throw new Error(`Cyclic seeder dependency detected at ${String(name)}`);
+ }
- visited.add(name);
+ visiting.add(name);
for (const dep of deps[name] ?? []) {
visit(dep);
}
+ visiting.delete(name);
+ visited.add(name);
ordered.push(all[name]);
}📝 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.
| function topologicalSort( | |
| all: typeof seeders, | |
| deps: typeof SEEDER_DEPENDENCIES | |
| ): (typeof seeders)[keyof typeof seeders][] { | |
| const ordered: (typeof seeders)[keyof typeof seeders][] = []; | |
| const visited = new Set<string>(); | |
| function visit(name: keyof typeof seeders) { | |
| if (visited.has(name)) return; | |
| visited.add(name); | |
| for (const dep of deps[name] ?? []) { | |
| visit(dep); | |
| } | |
| ordered.push(all[name]); | |
| } | |
| for (const name of Object.keys(all) as (keyof typeof seeders)[]) { | |
| visit(name); | |
| } | |
| return ordered; | |
| } | |
| function topologicalSort( | |
| all: typeof seeders, | |
| deps: typeof SEEDER_DEPENDENCIES | |
| ): (typeof seeders)[keyof typeof seeders][] { | |
| const ordered: (typeof seeders)[keyof typeof seeders][] = []; | |
| const visited = new Set<string>(); | |
| const visiting = new Set<string>(); | |
| function visit(name: keyof typeof seeders) { | |
| if (visited.has(name)) return; | |
| if (visiting.has(name)) { | |
| throw new Error(`Cyclic seeder dependency detected at ${String(name)}`); | |
| } | |
| visiting.add(name); | |
| for (const dep of deps[name] ?? []) { | |
| visit(dep); | |
| } | |
| visiting.delete(name); | |
| visited.add(name); | |
| ordered.push(all[name]); | |
| } | |
| for (const name of Object.keys(all) as (keyof typeof seeders)[]) { | |
| visit(name); | |
| } | |
| return ordered; | |
| } |
🧰 Tools
🪛 SkillSpector (2.5.1)
[error] 1021: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cli/assets/forklaunch-skills/backend-patterns/SKILL.md` around lines 1108 -
1128, Update topologicalSort to track an active visiting set separately from
visited; throw when visit encounters a name already active, remove names from
the active set after dependencies complete, and add them to visited only before
pushing the resolved seeder into ordered. Preserve the existing dependency
traversal and return order for acyclic graphs.
| Remove project components. | ||
|
|
||
| ```bash | ||
| # Delete service | ||
| forklaunch delete service <service_name> | ||
|
|
||
| # Delete worker | ||
| forklaunch delete worker <worker_name> | ||
|
|
||
| # Delete library | ||
| forklaunch delete library <library_name> | ||
| **CRITICAL: `delete` always prompts for confirmation and has NO flag to skip it** (`--confirm` is rejected). In scripts, CI, or AI-assistant contexts the prompt fails with `Error: EOF`. The only non-interactive workaround is piping "y" to stdin: | ||
|
|
||
| # Delete router (use --path to specify the service directory) | ||
| forklaunch delete router <router_name> --path <service_directory> | ||
| ```bash | ||
| # Non-interactive (required for scripts/agents): | ||
| printf 'y\n' | forklaunch delete service <service_name> --path . | ||
| printf 'y\n' | forklaunch delete worker <worker_name> --path . | ||
| printf 'y\n' | forklaunch delete library <library_name> --path . | ||
| printf 'y\n' | forklaunch delete router <router_name> --path <service_directory> | ||
|
|
||
| # Examples: | ||
| forklaunch delete service old-billing | ||
| forklaunch delete worker deprecated-processor | ||
| forklaunch delete router legacy-api --path ./src/modules/platform-management | ||
| printf 'y\n' | forklaunch delete service old-billing --path . | ||
| printf 'y\n' | forklaunch delete router legacy-api --path ./src/modules/platform-management | ||
| ``` |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Require explicit user approval before piping confirmation into delete commands.
printf 'y\n' | forklaunch delete ... bypasses the CLI's only destructive-action confirmation. This skill is user-invokable by Claude Code, so the workaround can allow an agent to delete a service, worker, library, or router without a separate approval step.
Document the pipe as an already-approved automation technique. Require explicit user approval before executing it.
Also applies to: 762-766, 1411-1416
🧰 Tools
🪛 SkillSpector (2.5.1)
[warning] 493: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.
(Excessive Agency (EA2))
[warning] 563: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.
(Excessive Agency (EA2))
[error] 578: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 581: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 595: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 596: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 1057: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 1058: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 1060: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 329: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.
(Tool Misuse (TM2))
[error] 710: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.
(Tool Misuse (TM2))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cli/assets/forklaunch-skills/cli/SKILL.md` around lines 406 - 420, Update the
“Remove project components” guidance so piping “y” into forklaunch delete is
documented only for already-approved automation. Require explicit user approval
before executing any piped delete command for services, workers, libraries, or
routers, including the repeated guidance at the other referenced sections.
Source: Linters/SAST tools
| - Version gate: a manifest `cli_version` mismatch prompts; non-TTY shows a bare `Error: EOF`. | ||
| - Expired login: platform commands silently open a browser device-auth flow and hang headless — check `forklaunch whoami` first. | ||
|
|
||
| - ALWAYS use `--dry-run` before applying changes to preview effects |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Qualify the global --dry-run rule.
The word ALWAYS is inaccurate. This file documents commands such as delete that do not provide a --dry-run option. Change the guidance to “Use --dry-run when supported. Otherwise, use the command’s preview or confirmation flow.”
🧰 Tools
🪛 SkillSpector (2.5.1)
[warning] 493: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.
(Excessive Agency (EA2))
[warning] 563: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.
(Excessive Agency (EA2))
[error] 578: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 581: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 595: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 596: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 1057: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 1058: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 1060: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 329: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.
(Tool Misuse (TM2))
[error] 710: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.
(Tool Misuse (TM2))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cli/assets/forklaunch-skills/cli/SKILL.md` at line 1421, Update the global
dry-run guidance in the CLI skill documentation to apply `--dry-run` only when
the command supports it; otherwise instruct users to use that command’s preview
or confirmation flow. Replace the unconditional “ALWAYS” wording while
preserving the intent to preview effects before applying changes.
| - **When a deployed service shows 0 replicas/"Degraded" with no logs, check the dashboard's CloudWatch log source before assuming you need AWS console access.** Dashboard → Monitoring → Logs defaults to a "Live (OTel)" source, which is silent for any crash that happens before the app's own OpenTelemetry collector initializes (e.g. a config-validation failure on boot) — that silence looks identical to "still starting." Switch the log source dropdown from "Live (OTel)" to "CloudWatch" instead: it shows the container's raw stdout/stderr, including the actual stack trace (e.g. `_ConfigInjector.validateConfigSingletons` failures, `Expected number, received nan` on a bad `DB_PORT`, `ELIFECYCLE ... exit code 1`) with no AWS console access required. | ||
|
|
||
| - **Empty secrets in generated `.env.local` / `.env.test`** (`ENCRYPTION_KEY=`, `HMAC_SECRET_KEY=`, `BETTER_AUTH_SECRET=`): `pnpm database:setup` fails with `MissingEncryptionKeyError` and tests fail env validation until filled. Generate values before first run: `openssl rand -base64 32` for ENCRYPTION_KEY, `openssl rand -hex 32` for the others. (`forklaunch environment sync` adds missing keys but only with blank values.) | ||
| - **iam seeder FK violation** (`account_user_id_foreign`): the generated `persistence/seeders/` omits `user.seeder.ts` even though `seed.data.ts` defines the user, and `DatabaseSeeder` runs seeders via `Object.values(namespace)` which enumerates ALPHABETICALLY (Account before User). Fix: add a `UserSeeder` (mirror `account.seeder.ts`) and replace `Object.values(seeders)` in `persistence/seeder.ts` with an explicit FK-ordered array `[UserSeeder, AccountSeeder, SessionSeeder, VerificationSeeder]`. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Use the shared topological-sort seeder guidance.
This workaround recommends replacing Object.values(seeders) with an explicit array. That conflicts with cli/assets/forklaunch-skills/backend-patterns/SKILL.md, Lines 1090-1139, which preserves auto-discovery and uses SEEDER_DEPENDENCIES plus topologicalSort.
The explicit array can omit newly added seeders unless developers update it manually. Update this workaround to use the shared dependency-map pattern, or scope it explicitly to legacy scaffolds.
🧰 Tools
🪛 SkillSpector (2.5.1)
[warning] 493: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.
(Excessive Agency (EA2))
[warning] 563: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.
(Excessive Agency (EA2))
[error] 578: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 581: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 595: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 596: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 1057: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 1058: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 1060: [PE3] Credential Access: Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
Remediation: Remove references to credential paths. Use environment variables or secrets managers. For docs, use placeholder paths (e.g., /path/to/config). Never load .env or token files in production code paths.
(Privilege Escalation (PE3))
[error] 329: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.
(Tool Misuse (TM2))
[error] 710: [TM2] Chaining Abuse: Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
Remediation: Limit tool chaining depth and validate the output of each tool before passing it to the next. Require explicit user approval for multi-step chains.
(Tool Misuse (TM2))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cli/assets/forklaunch-skills/cli/SKILL.md` at line 1477, Update the seeder
workaround guidance to use the shared SEEDER_DEPENDENCIES and topologicalSort
pattern from the backend seeder guidance instead of replacing
Object.values(seeders) with a manually maintained array; retain auto-discovery
and ensure UserSeeder is included so dependencies determine execution order.
| ### Validating schemas programmatically (outside a handler) | ||
|
|
||
| To validate a payload against a natural-notation schema directly (e.g. in a unit test, or a script), use `schemaValidator.schemify(...)` to resolve it to the underlying Zod/TypeBox schema, then call that schema's native validation method: | ||
|
|
||
| ```typescript | ||
| import { schemaValidator } from "@{{app-name}}/core"; | ||
|
|
||
| const compiled = schemaValidator.schemify(MySchema); | ||
| const result = compiled.safeParse(payload); // Zod: { success: boolean, data? / error? } | ||
| if (!result.success) { /* handle result.error */ } | ||
| ``` | ||
|
|
||
| `schemaValidator.compile(...)` is a **different** function — it expects an already-shaped Zod/TypeBox object schema (`ZodObject`/`TObject`), not a natural-notation object literal, and will produce confusing type errors if you pass it a plain schema object. Use `schemify`, not `compile`, when you just want to validate a natural-notation schema. Also note Zod's `safeParse` result has a `success` field, not `ok`. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n 'schemify|safeParse|TypeCompiler|Value\.Check|class .*SchemaValidator' \
framework cli blueprint --glob '*.ts' --glob '*.tsx' || trueRepository: forklaunch/forklaunch
Length of output: 12395
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- validator interface and implementations ---'
rg -n -A35 -B10 'compile\(|schemify\(|isValid\(|parse\(' \
framework/validator/src/shared/types/schema.types.ts \
framework/validator/src/zod/zodSchemaValidator.ts \
framework/validator/src/typebox/typeboxSchemaValidator.ts
printf '%s\n' '--- validator selection and exports ---'
rg -n -A25 -B15 'ZodSchemaValidator|TypeboxSchemaValidator|schemaValidator|VALIDATOR|validator' \
framework/core framework/validator --glob '*.ts' | head -n 500
printf '%s\n' '--- documentation context ---'
sed -n '250,305p' cli/assets/forklaunch-skills/framework/SKILL.mdRepository: forklaunch/forklaunch
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- compile implementations ---'
rg -n -A18 -B8 'compile' \
framework/validator/src/zod/zodSchemaValidator.ts \
framework/validator/src/typebox/typeboxSchemaValidator.ts \
framework/validator/src/shared/types/schema.types.ts
printf '%s\n' '--- validator factory and configuration ---'
rg -n -A30 -B15 'new (Zod|Typebox)SchemaValidator|ZodSchemaValidator|TypeboxSchemaValidator|schemaValidator' \
framework/validator/src framework/core/src \
--glob '*.ts' | grep -E '(^framework/|new |SchemaValidator|schemaValidator|validator)' | head -n 300
printf '%s\n' '--- exact documentation ---'
sed -n '265,285p' cli/assets/forklaunch-skills/framework/SKILL.mdRepository: forklaunch/forklaunch
Length of output: 40422
Document backend-specific validation.
schemify returns a Zod schema or a TypeBox schema, based on the selected validator. safeParse and result.success apply only to Zod. Use schemaValidator.validate(...) for backend-independent validation, or provide separate Zod and TypeBox examples.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cli/assets/forklaunch-skills/framework/SKILL.md` around lines 269 - 281,
Update the “Validating schemas programmatically” section to document that
schemify may return either Zod or TypeBox depending on the configured validator.
Use schemaValidator.validate(...) for backend-independent validation, or split
the example into backend-specific Zod and TypeBox flows; do not present
safeParse or result.success as universal APIs.
| ``` | ||
| <project-name>:<resource-type> | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language identifier to the fenced code block.
The block contains identifier syntax. Mark it as text to satisfy markdownlint and keep documentation lint clean.
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)
[warning] 39-39: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cli/assets/forklaunch-skills/infra/SKILL.md` around lines 39 - 41, Update the
fenced code block containing the <project-name>:<resource-type> syntax in
SKILL.md to specify the text language identifier, preserving its contents.
Source: Linters/SAST tools
| fl infra resize <project>:<type> --environment <env> [sizing flags] [--snapshot-before-change] [--yes|-y] [--dry-run] [--resource-id <id>] | ||
|
|
||
| # Sizing flags (pass at least one, or it fails fast before any network call): | ||
| --instance-class <class> # database, e.g. db.t3.small | ||
| --allocated-storage <GB> # database | ||
| --node-type <type> # cache | ||
| --num-cache-nodes <n> # cache | ||
| --number-of-broker-nodes <n> # queue (Kafka/MSK) | ||
| --ebs-storage-size <GB> # queue | ||
|
|
||
| # Safety flags: | ||
| --snapshot-before-change # database only | ||
| -y, --yes # skip the confirmation prompt (CI/scripted) | ||
| --dry-run # resolve + fetch current config + print diff, then STOP — | ||
| # no confirm prompt, no PATCH/deploy call, no AWS mutation |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not describe --yes as CI support.
Lines 33-35 state that fl infra does not support HMAC or CI authentication. Line 113 labels --yes as “CI/scripted”, but this flag only skips confirmation. It does not provide the required JWT session.
Change the description to “non-interactive scripts with an existing JWT session” and keep CI explicitly unsupported.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cli/assets/forklaunch-skills/infra/SKILL.md` around lines 101 - 115, Update
the --yes description in the resize command’s “Safety flags” section to say
“non-interactive scripts with an existing JWT session,” and explicitly retain
that CI is unsupported; do not describe --yes as CI support.
Summary
Fixes the reported defect: routes could not receive raw, unparsed request bytes, making Stripe/GitHub/Slack-style HMAC signature verification impossible. All three underlying defects are fixed, published (
[email protected],[email protected],[email protected]), and the blueprint's Stripe webhook now uses the fix.Primary fix — raw bytes via the parser
verifyhookcontentParsebuilds every body-parser with averifythat stashes the exact request bytes onreq._rawBody;req.bodystays the parsed value; user-suppliedverifycallbacks still runreq._rawBodyviareq.buffer()before parsing (body is cached and reused byjson()/text(); multipart excluded)_rawBody(??=keeps the pre-validation body as fallback for adapters without capture)Defect #1 — per-route
contentTypenow applies to the dispatched parserbody: { text: string, contentType: 'application/json' }parses JSON-typed requests as raw text. The override only applies when it differs from the parser's default, preserving default matching semantics.Defect #2 — application-level parser options reach router parsing
Parsers are now built lazily from
req._globalOptions()(the mount-time merge of app+router options) instead of eagerly at construction, soforklaunchExpress(sv, otel, { text: { type: [...] } })works as a global escape hatch.HMAC compatibility
createHmacTokensignsBuffer/stringbodies verbatim, so verification over captured raw bytes matches clients that sign an object and send its stringification — byte-for-byte, and now immune to validation-time property reordering.Blueprint
body: { text: string, contentType: 'application/json' }—req.bodyis the exact payload stringstripe.webhooks.constructEventverifies (previously the route 400'd before the handler ran)EraseResult/ExportResultspread intores.jsonliterals, andsurfacePermissions/surfaceRolespayload annotations where inference collapsed toneverTests
express/__test__/rawBody.webhook.test.ts(5 tests): parsed body + exact raw bytes (formatting preserved), defect Validator and Core Refactor #1 route shape, defect Package tests and http framework stubs #2 global widening, default content types unbroken, HMAC raw-bytes/object token equality🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Documentation
Chores