fix: fail closed when CRON_SECRET is absent in WakaTime sync endpoint#1780
Merged
Priyanshu-byte-coder merged 1 commit intoMay 31, 2026
Conversation
Root cause
----------
The WakaTime sync endpoint used process.env.CRON_SECRET inline without
first checking whether it was defined:
if (authHeader !== `Bearer ${process.env.CRON_SECRET}` && ...)
When CRON_SECRET is undefined, JavaScript coerces it to the string
"undefined", making "Bearer undefined" the expected credential. Any
caller who sends that literal header would pass the authentication check
and trigger a full WakaTime credential decryption and API sweep.
Every other cron endpoint in the codebase (cron/weekly-digest,
notifications/discord-sync, sponsors/sync) already follows the correct
pattern: extract the secret first, return 500 if it is absent, then
compare. This endpoint was the only exception.
Fix
---
Mirror the established pattern:
const cronSecret = process.env.CRON_SECRET;
if (!cronSecret) {
return NextResponse.json(
{ error: "CRON_SECRET is not configured" },
{ status: 500 }
);
}
if (authHeader !== `Bearer ${cronSecret}` && NODE_ENV !== "development") {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
When CRON_SECRET is absent the endpoint now returns 500 before any
credential comparison occurs, so "Bearer undefined" and every other
header value are rejected unconditionally.
test/wakatime-sync-auth.test.ts — 10 new tests:
- Missing CRON_SECRET → 500, Supabase never called
- Authorization: Bearer undefined with missing CRON_SECRET → non-200
(regression test for Priyanshu-byte-coder#1746)
- Correct secret → 200 and sync executes
- Wrong secret in production → 401
- No Authorization header in production → 401
- Plaintext secret without Bearer prefix → 401
- Development mode: any header passes (existing intentional behavior)
- Missing CRON_SECRET in development → still 500 (fail closed)
- Decryption failure counts as a sync failure (not a crash)
- Full happy-path: user decrypted, WakaTime called, stats upserted
Closes Priyanshu-byte-coder#1746
|
@Ridanshi is attempting to deploy a commit to the PRIYANSHU DOSHI's projects Team on Vercel. A member of the Team first needs to authorize it. |
GSSoC Label Checklist 🏷️@Priyanshu-byte-coder — please apply the appropriate labels before merging: Difficulty (pick one):
Quality (optional):
Validation (required to score):
|
9cf5476
into
Priyanshu-byte-coder:main
4 of 5 checks passed
|
🎉 Merged! Thanks for contributing to DevTrack. If the project has been useful to you, a ⭐ star on the repo is the easiest way to support it — it helps DevTrack get discovered by more developers. Keep an eye on open issues for your next contribution! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1746
Problem
GET /api/wakatime/syncusedprocess.env.CRON_SECRETdirectly inside a template literal without first checking whether the variable is defined:When
CRON_SECRETis not set, JavaScript coercesundefinedto the string"undefined", so the expected credential becomes the literal string"Bearer undefined". Any caller who sends that header in production passes authentication and triggers:Endpoint comparison
Every other cron endpoint in the repository already follows the correct pattern:
cron/weekly-digestif (!cronSecret) return 500then comparenotifications/discord-syncif (!cronSecret) return 500then comparesponsors/syncif (!cronSecret) return 500then comparewakatime/syncprocess.env.CRON_SECRETwithout null checkFix
Mirror the established pattern used by every other endpoint:
When
CRON_SECRETis absent the endpoint now returns 500 before any string comparison occurs, soAuthorization: Bearer undefinedand every other header value are unconditionally rejected.Development mode intentionally bypasses the header check (existing behavior, preserved); however, the
!cronSecretguard now fires even in development, ensuring misconfigured environments are surfaced regardless ofNODE_ENV.Tests —
test/wakatime-sync-auth.test.ts(10 new tests)CRON_SECRET→ 500Bearer undefinedwith missing secret → non-200Bearer <secret>→ 200Bearer→ 401CRON_SECRETin development → 500All 10 pass. The one pre-existing failure in
test/dateUtils.test.tsis a timezone boundary issue unrelated to this change.