(janitor/dedupe): consolidate Kilo v3 token signing onto signKiloToken - #5633
Merged
Conversation
webhook-agent-ingest, security-auto-analysis, and gastown each hand-rolled their own HS256 "Kilo user token v3" JWT signer (two of them near byte-identical Web Crypto implementations, one a direct jose SignJWT call), duplicating the same domain concept already owned by signKiloToken() in @kilocode/worker-utils. All three services already depend on @kilocode/worker-utils and run in the same Cloudflare Workers runtime as the canonical implementation, so this is a drop-in replacement: - webhook-agent-ingest: delete util/jwt.ts (hand-rolled base64url + HMAC-SHA256 signer); token-minting-service now calls signKiloToken. - security-auto-analysis: delete the local signJwt/parseExpiresIn/ hmacSha256 helpers from token.ts; generateApiToken and generateInternalServiceToken now delegate to signKiloToken. - gastown: kilo-token.util.ts's generateKiloApiToken now delegates to signKiloToken instead of calling jose's SignJWT directly. signKiloToken's `pepper` param is now optional: omitting it produces a token with no apiTokenPepper claim at all, matching the existing "internal service token" contract already documented and relied on by verifyKiloBearerAgainstCurrentPepper (an explicit `null` pepper is compared against the account's pepper; an absent claim skips that check). This preserves generateInternalServiceToken's original behavior, which never included the claim. No behavior change for the other call sites, which already always included apiTokenPepper (as a string or null).
Contributor
Author
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (2 files)
Previous Review Summary (commit dc28df9)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit dc28df9)Status: No Issues Found | Recommendation: Merge Files Reviewed (6 files)
Reviewed by grok-4.6 · Input: 37.9K · Output: 5.8K · Cached: 189.2K Review guidance: REVIEW.md from base branch |
Per review: the deleted webhook-agent-ingest hand-rolled signer always included typ: 'JWT' in its header, so signKiloToken should keep doing the same to preserve byte-level header compatibility with that migrated issuer and to make all signKiloToken outputs consistently self-identify as JWTs. verifyKiloToken does not (and did not) constrain typ, so this is purely additive and doesn't change verification behavior for any consumer. Added a test asserting the protected header shape.
eshurakov
approved these changes
Aug 28, 2026
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.
Summary
webhook-agent-ingest,security-auto-analysis, andgastowneach hand-rolledtheir own HS256 "Kilo user token v3" JWT signer instead of using the canonical
signKiloToken()already exported from@kilocode/worker-utils:services/webhook-agent-ingest/src/util/jwt.ts— a hand-rolled base64url +Web Crypto HMAC-SHA256 signer, used only by
token-minting-service.ts.services/security-auto-analysis/src/token.ts— a near byte-identicalhand-rolled implementation of the same base64url/HMAC signer.
services/gastown/src/util/kilo-token.util.ts— a third variant callingjose'sSignJWTdirectly.All three produce the same "Kilo user token v3" shape (
kiloUserId,apiTokenPepper,version: 3, plus optional extras likebotId/internalApiUse/createdOnPlatform) thatsignKiloToken()inpackages/worker-utils/src/kilo-token.tsalready owns, and all three servicesalready depend on
@kilocode/worker-utilsand run in the same CloudflareWorkers runtime as the canonical implementation — no cross-runtime coupling.
Today a bug fix, claim change, or hardening applied to the canonical
implementation would silently not propagate to these three independent
copies of the same security-sensitive signing logic.
Changes
util/jwt.tsentirely;token-minting-service.tsnow callssignKiloToken.signJwt/parseExpiresIn/hmacSha256/ base64url helpers fromtoken.ts;generateApiTokenandgenerateInternalServiceTokennow delegate tosignKiloToken.kilo-token.util.ts'sgenerateKiloApiTokennow delegates tosignKiloTokeninstead of callingjose'sSignJWTdirectly.signKiloToken'spepperparam optional. Omittingit produces a token with no
apiTokenPepperclaim at all, matching the"internal service token" contract already documented and relied on by
verifyKiloBearerAgainstCurrentPepper(an explicitnullpepper iscompared against the account's current pepper; an absent claim skips
that check). This preserves
generateInternalServiceToken's originalbehavior — it never included the claim — and is a purely additive,
backward-compatible change (every other caller still passes a pepper
explicitly). Added a regression test covering this in
kilo-token.test.ts.No behavior change for any other call site: they already always included
apiTokenPepper(string ornull).Verification
pnpm --filter @kilocode/worker-utils typecheck/test— 28 files, 356tests passed (including a new test asserting the omitted-pepper claim
behavior).
pnpm --filter cloudflare-webhook-agent-ingest typecheck/test— 10files, 89 tests passed.
pnpm --filter cloudflare-security-auto-analysis typecheck/test— 16files, 180 tests passed.
pnpm --filter cloudflare-gastown typecheck/test— 22 files, 316 testspassed.
oxlint/oxfmton all changed files — clean.Shared PostgreSQL/Redis were not started for this change; none of the touched
tests require them (pure unit-level JWT signing/verification).