|
8 | 8 | * - Counts live in ONE local file: ~/.chaingpt-mcp/usage.json. |
9 | 9 | * - Nothing is ever transmitted anywhere. There is no remote endpoint, |
10 | 10 | * no analytics SDK, no phone-home. Grep this file to verify. |
11 | | - * - Only tool NAMES and counts are stored — never arguments, addresses, |
12 | | - * amounts, or results. |
| 11 | + * - Only tool NAMES, counts and last-called timestamps are stored — |
| 12 | + * never arguments, addresses, amounts, or results. |
13 | 13 | * - Disable entirely with CHAINGPT_USAGE=off. |
14 | 14 | * |
15 | 15 | * Write strategy: in-memory increment + debounced flush (500ms, unref'd so |
@@ -38,6 +38,9 @@ function enabled(): boolean { |
38 | 38 | let cache: UsageFile | null = null; |
39 | 39 | let flushTimer: NodeJS.Timeout | null = null; |
40 | 40 |
|
| 41 | +// Single-process assumption: the cache is read once and flushes overwrite the |
| 42 | +// file. Two server processes sharing one file = last-writer-wins (acceptable |
| 43 | +// for best-effort stats; do not build billing on this). |
41 | 44 | function load(): UsageFile { |
42 | 45 | if (cache) return cache; |
43 | 46 | try { |
@@ -66,6 +69,9 @@ function scheduleFlush(): void { |
66 | 69 | /** Record one tool invocation. Synchronous, allocation-light, never throws. */ |
67 | 70 | export function recordToolUse(tool: string): void { |
68 | 71 | if (!enabled()) return; |
| 72 | + // A non-compliant client can send arbitrary tool names; don't let garbage |
| 73 | + // grow the file or pollute the dashboard table. |
| 74 | + if (!tool || tool.length > 128 || !tool.startsWith('chaingpt_')) return; |
69 | 75 | try { |
70 | 76 | const u = load(); |
71 | 77 | u.counts[tool] = (u.counts[tool] ?? 0) + 1; |
|
0 commit comments