protect PostHog API key via build-time env injection#23
Conversation
Replace hardcoded key with process.env injection at build time using tsdown's define option, so the key lives only in CI secrets rather than source code. Add POSTHOG_API_KEY secret to the release workflow build step. https://claude.ai/code/session_01Uo4pHtL5cM9N3tZ9VCU2k9
- Add POST /api/telemetry route in Next.js app — CLI sends events here, server forwards to PostHog using server-side POSTHOG_API_KEY secret - Add t3env + zod to apps/web for validated env management; migrate all direct process.env access to env.ts - Remove posthog-node from CLI, replace with a fetch call to the proxy - Remove POSTHOG_API_KEY from CLI build pipeline — key now lives only in server runtime (Vercel env vars) https://claude.ai/code/session_01Uo4pHtL5cM9N3tZ9VCU2k9
NODE_ENV and VERCEL_PROJECT_PRODUCTION_URL are covered by the Vercel preset — no need to redeclare them manually. https://claude.ai/code/session_01Uo4pHtL5cM9N3tZ9VCU2k9
|
✅ No new issues Reviewed by reactreview for commit c69eecb. Configure here. |
🦋 Changeset detectedLatest commit: c69eecb The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 32 minutes and 2 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughAdds a typed server env for POSTHOG_API_KEY, a Zod-validated POST /api/telemetry route that forwards events to PostHog, and migrates CLI telemetry to send events via HTTP to that endpoint. ChangesCentralized telemetry endpoint with typed environment configuration
Sequence DiagramsequenceDiagram
participant CLI
participant WebTelemetryRoute
participant PostHog
CLI->>WebTelemetryRoute: POST /api/telemetry { distinctId, event, email?, name?, properties }
WebTelemetryRoute->>PostHog: identify(distinctId, { email, name }) -- when email present
WebTelemetryRoute->>PostHog: capture(distinctId, event, properties)
WebTelemetryRoute-->>CLI: 204 No Content
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@apps/web/app/api/telemetry/route.ts`:
- Line 11: The telemetry schema uses z.record with a single argument which is
incompatible with Zod v4; update the call in apps/web/app/api/telemetry/route.ts
(the properties field using z.record) to supply both key and value schemas, e.g.
change properties: z.record(z.unknown()).optional().default({}) to properties:
z.record(z.string(), z.unknown()).optional().default({}) so the record key type
is explicit and TypeScript typechecking passes.
In `@packages/cli/src/analytics.ts`:
- Around line 57-72: The telemetry fetch to TELEMETRY_URL in analytics.ts
currently awaits indefinitely; wrap the POST call with an AbortController-based
timeout: create an AbortController, start a timer (e.g., 2s-5s) that calls
controller.abort(), pass controller.signal into the fetch options (alongside
body/headers/method), and clear the timer after fetch completes (in finally) so
the CLI won't hang; update the fetch call that sends distinctId, email, event,
name and properties to use this abort signal and handle the aborted/error case
appropriately.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1c48dfbb-dca6-473d-bf38-db2cb43b825d
📒 Files selected for processing (8)
apps/web/app/api/telemetry/route.tsapps/web/app/page.tsxapps/web/components/changelog.tsxapps/web/env.tsapps/web/lib/url.tsapps/web/package.jsonpackages/cli/package.jsonpackages/cli/src/analytics.ts
💤 Files with no reviewable changes (1)
- packages/cli/package.json
env.ts is only responsible for server secrets that need validation. Client/public env vars don't need a validation layer right now. https://claude.ai/code/session_01Uo4pHtL5cM9N3tZ9VCU2k9
zod v4 requires explicit key schema: z.record(z.string(), z.unknown()) https://claude.ai/code/session_01Uo4pHtL5cM9N3tZ9VCU2k9
zod v4 removed .email() from ZodString — use standalone z.email() instead. https://claude.ai/code/session_01Uo4pHtL5cM9N3tZ9VCU2k9
- Replace let+try/catch with safeParse+const in telemetry route - Add 2s AbortController timeout to CLI fetch so a stalled network never blocks command execution https://claude.ai/code/session_01Uo4pHtL5cM9N3tZ9VCU2k9
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
packages/cli/src/analytics.ts (1)
58-58: ⚡ Quick winExtract the timeout value into a named constant.
Using
2000directly hides intent; make it a descriptive constant for maintainability.Suggested change
const TELEMETRY_URL = "https://verno-studio.vercel.app/api/telemetry"; +const TELEMETRY_TIMEOUT_MS = 2_000; ... - const timeoutId = setTimeout(() => controller.abort(), 2000); + const timeoutId = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);As per coding guidelines, "Use meaningful variable names instead of magic numbers - extract constants with descriptive names".
🤖 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 `@packages/cli/src/analytics.ts` at line 58, Replace the magic number 2000 used in setTimeout with a descriptive constant: define a named constant (e.g., ANALYTICS_ABORT_TIMEOUT_MS or ANALYTICS_REPORT_TIMEOUT_MS) near the top of the module and use it in the setTimeout call that triggers controller.abort() (the line creating timeoutId). This makes the timeout intent clear and centralizes the value for future adjustments.
🤖 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 `@apps/web/.env.example`:
- Line 1: The .env.example currently sets POSTHOG_API_KEY="" which is an invalid
present-but-empty default and will fail validation in apps/web/env.ts where
POSTHOG_API_KEY is validated with startsWith("phc_").optional(); remove the
empty assignment or comment it out so the key is unset by default (e.g., change
POSTHOG_API_KEY="" to either omit the line or prefix with #) so teams copying
.env.example won't trigger the startsWith("phc_").optional() validation error.
In `@apps/web/app/api/telemetry/route.ts`:
- Around line 21-24: The handler currently calls await request.json() directly
which can throw on malformed/empty JSON and bypass the Zod check; wrap the
request.json() call in a try/catch, return NextResponse.json({ error: "Invalid
JSON" }, { status: 400 }) (or similar) when parsing throws, and only then call
bodySchema.safeParse(result) to validate the parsed body; update the block
around request.json(), bodySchema, and the existing NextResponse.json error
branch accordingly.
- Line 18: The handler returns JSON with status 204 (invalid) and calls await
request.json() without guarding against parse errors; update the error responses
so any response that includes a body uses a non-204 status (e.g., 400/401/500)
instead of 204 and if you must send a 204 return an empty NextResponse with no
body, and wrap the await request.json() call in a try/catch so malformed JSON
returns a NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
specifically, update the POST handler (exported default/POST function) to: 1)
check POSTHOG_API_KEY and return a JSON error with a 500/401 status (not 204) or
return NextResponse(null, { status: 204 }) if you intentionally want no body; 2)
wrap request.json() in try/catch before calling bodySchema.safeParse to catch
parse errors and return a 400 JSON error; and 3) do the same replacement for the
second occurrence where NextResponse.json(..., { status: 204 }) is used so no
JSON is ever sent with a 204 status.
---
Nitpick comments:
In `@packages/cli/src/analytics.ts`:
- Line 58: Replace the magic number 2000 used in setTimeout with a descriptive
constant: define a named constant (e.g., ANALYTICS_ABORT_TIMEOUT_MS or
ANALYTICS_REPORT_TIMEOUT_MS) near the top of the module and use it in the
setTimeout call that triggers controller.abort() (the line creating timeoutId).
This makes the timeout intent clear and centralizes the value for future
adjustments.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 63a5cac9-f5e2-4a36-9dff-e4963a341628
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (5)
.gitignoreapps/web/.env.exampleapps/web/app/api/telemetry/route.tsapps/web/env.tspackages/cli/src/analytics.ts
✅ Files skipped from review due to trivial changes (1)
- .gitignore
Replace hardcoded key with process.env injection at build time using
tsdown's define option, so the key lives only in CI secrets rather
than source code. Add POSTHOG_API_KEY secret to the release workflow
build step.
https://claude.ai/code/session_01Uo4pHtL5cM9N3tZ9VCU2k9
Summary by CodeRabbit
New Features
Chores