Repository navigation
Conversation
The signer used Math.floor(Date.now() / 1000), a seconds-scale value, while the Gemini API's per-key nonce floor is an epoch-millisecond timestamp. Any key that has ever accepted a millisecond nonce rejects every subsequent seconds nonce, so every authenticated tool failed with: InvalidNonce: Nonce '1793808885' has not increased since your last call Nonces are now epoch milliseconds and monotonic within the process, so concurrent tool calls issued in the same millisecond no longer collide.
Change Control Evidence CheckAuthorization — ✅ PassEvidence:
Testing — ✅ PassEvidence:
Approval — ⏳ Awaiting approvalEvidence: Segregation of Duties — ⏳ Awaiting approvalValidated:
Last checked: 2026-11-05 05:24 UTC |
…ounts /v1/account/list is a master-key endpoint, so every scoped key gets a bare 400 InvalidApiKey. gemini_get_accounts promised 'list all accounts' while it can only ever fail for scoped keys. Map the rejection to an actionable error pointing at gemini_get_account, and say the scope requirement in the tool description.
deeputom
marked this pull request as ready for review
November 4, 2026 20:02
Nostradamus Risk Rating — HighThe PR modifies High/Critical — a full threat model follows. |
|
Nostradamus Threat Model - Passed - APPSEC-4773 |
…nto fix/mcp-server-millisecond-nonce # Conflicts: # packages/mcp-server/src/auth/signer.test.ts
This branch has not been deployed
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.
Linear: https://linear.app/gemini/issue/INVEST-681/fix-gemini-mcp-server-nonce-units-seconds-scale-nonce-permanently
Problem
Every authenticated tool on the MCP server fails with:
src/auth/signer.tsbuilt the nonce asMath.floor(Date.now() / 1000)— epochseconds. The Gemini API tracks the highest nonce accepted per API key, and
that value is an epoch millisecond timestamp. Once a key has accepted one
millisecond nonce (
~1.79e12), every seconds nonce (~1.79e9) is three ordersof magnitude too small and is rejected permanently. The server never recovers on
its own, and the error names no caller, so it reads like a concurrent-client
collision.
README.md:76already documents a "millisecond timestamp nonce"; the codedisagreed with its own documentation.
Second commit:
gemini_get_accountstargetsPOST /v1/account/list, amaster-key-only endpoint. Every scoped key gets a bare
400 InvalidApiKeywhilethe tool description promised "list all accounts". The tool now maps that
rejection to an actionable error and documents the scope requirement.
Measured on
https://api.qa100.aurora7.net, read-onlyPOST /v1/account, fivedistinct staging keys:
400 InvalidNonceon all 5 keys200on all 5 keysFix
nextNonce()keeps nonces strictly increasing within the process, so toolcalls issued in the same millisecond cannot share a nonce.
gemini_get_accountsmaps the scoped-keyInvalidApiKeyto an error namingthe master-key requirement and
gemini_get_accountas the alternative.Test Plan
npx tsx --test 'src/**/*.test.ts': 346/346 pass (after mergingmain, whose SDK + WebSocket work landed in the same package) — nonce unit, same-millisecond strict increase, frozen clock, wall-clock catch-up, master-key error mapping, unrelated-error passthrough.dist: seconds nonce →400 InvalidNonce; ms nonce →200on all 5 staging keys; 5 concurrent signed/v1/accountcalls per key → 5/5 ok on all keys.GEMI-BTC2611041700-HI79200: 5 YES @ 0.95, 5 NO @ 0.144 avg); the contract settled and payouts landed (lg1 +$0.24, lg2 −$0.75).gemini_get_accountsfor a scoped key now returns: "requires a master API key with account-list permission … use gemini_get_account".Merge
Merged
main(6dd34be) ind4a30ec: kept this branch's REST millisecond nonce, preservedmain's WebSocket auth (which documents an epoch-seconds nonce), and unioned bothsigner.test.tssuites.npm run typecheckandnpm run buildclean; dependency install refreshed for@gemini-markets/sdk.SDK custom-host regression repair (
d38deda)Main migrated predictions to the SDK, whose environment selector routed QA100 requests to api.gemini.com despite GEMINI_API_BASE_URL. The shared SDK factory now routes custom REST hosts through the supported fetch hook and shares the legacy millisecond nonce allocator for custom targets. Standard endpoint SDK behavior and WebSockets are unchanged.
Verification
npm run typecheck,npm test(348/348 pass),npm run buildsucceeded.