You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit c81fe3c
Browse filesBrowse the repository at this point in the historyBrowse files
perf(vault): cached credentials and safe singleflight refresh (#46)
* perf(vault): cache tokens and coalesce refreshes
Prevent concurrent OAuth refreshes from invalidating rotating tokens. Keep decrypted tokens in a bounded, expiry-aware local LRU.
Signed-off-by: Shreyansh Sancheti <43677304+shreyanshjain7174@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(vault): bound refreshes and guard writes
Apply a separate 15s deadline to shared refreshes.
Use conditional writes so deletes and re-links beat stale tokens.
Retry rotated-token writes without logging credentials.
Escape OAuth callback HTML to satisfy the expanded Semgrep scan.
Signed-off-by: Shreyansh Sancheti <43677304+shreyanshjain7174@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(vault): recover shared refresh panics
Always release waiters and clear flights after a panic.
Use fixed errors and logs so panic values cannot expose credentials.
Persist successful rotations even after the exchange deadline.
Signed-off-by: Shreyansh Sancheti <43677304+shreyanshjain7174@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Signed-off-by: Shreyansh Sancheti <43677304+shreyanshjain7174@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy file name to clipboardExpand all lines: README.md
+34Lines changed: 34 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -305,13 +305,47 @@ AgentGate is deterministic. Agents never see tokens, calls go only to configured
305
305
6.**OAuth state encrypted** with AES-256-GCM and expires after 10 minutes.
306
306
7.**OAuth refresh.** Configured OAuth providers refresh tokens expiring within 5 minutes before dispatch. A failed refresh returns `token_expired` without calling the SaaS API.
307
307
308
+
### Vault cache and refresh
309
+
310
+
Vault reads cache decrypted access and refresh tokens in process memory.
311
+
The default TTL is `60s`, with an LRU limit of 10,000 user-service pairs.
312
+
An entry expires earlier when its token reaches 30 seconds before expiry.
313
+
Tokens without an expiry use the TTL alone.
314
+
Tokens already within that 30-second window aren't cached.
315
+
316
+
Token strings cannot be reliably zeroed. Keep the cache bounded.
317
+
Delete drops cached references. Go may retain string bytes until garbage collection.
318
+
Expired entries aren't reused. Lookup or eviction removes their cached references.
319
+
Put writes SQLite before caching the new token. Failed writes invalidate the local entry.
320
+
All writes must pass through the decorated store.
321
+
322
+
Concurrent refreshes share one result per store and user-service pair within a process.
323
+
Current waiters share refresh failures. Later calls retry.
324
+
Canceling a waiter doesn't cancel the shared refresh.
325
+
Each shared refresh has a separate `15s` deadline. OAuth HTTP exchanges also have a `15s` client timeout.
326
+
Refresh functions receive that context and must honor cancellation.
327
+
Successful exchanges still persist their results after the refresh deadline. That deadline doesn't cancel persistence retries.
328
+
Shared refresh panics return a fixed error without panic values. The flight is cleared so later requests can retry.
329
+
Timeouts and canceled waits return HTTP `504` with receipt error code `vault_refresh_timeout`, not `token_expired`.
330
+
Refresh writes compare the stored refresh token inside a SQLite transaction.
331
+
Deleted credentials stay deleted. A concurrent re-link keeps its replacement token and scopes.
332
+
On a comparison mismatch, refresh returns the current credential only when it remains valid.
333
+
Rotated refresh tokens get up to 3 conditional write attempts.
334
+
If every write fails, the current request still receives the new access token.
335
+
The failure is logged without token values. Later requests may require re-linking because the rotation wasn't saved.
336
+
Replicas maintain separate caches and refresh independently.
337
+
Rotating refresh tokens still need cross-process coordination when replicas share credentials.
338
+
External writes or deletes can leave stale local credentials until cache expiry.
339
+
Set `AGENTGATE_VAULT_CACHE_TTL=0` to disable plaintext caching. Refresh sharing remains enabled.
340
+
308
341
## Configuration
309
342
310
343
### Environment Variables
311
344
312
345
| Variable | Description | Required |
313
346
|----------|-------------|----------|
314
347
|`AGENTGATE_VAULT_KEY`| 32-byte vault key. Also derives receipt encryption keys and the API-key HMAC pepper. | Yes |
348
+
|`AGENTGATE_VAULT_CACHE_TTL`| Decrypted-token cache duration. Default `60s`; `0` disables caching. Negative or invalid durations stop startup. | No |
315
349
|`AGENTGATE_ADMIN_SECRET`| Secret for admin API access | Yes |
316
350
|`AGENTGATE_PUBLIC_URL`| Base URL used to build the OAuth callback (default `http://localhost:8080`) | No |
0 commit comments