feat(exporters): allowlist token-usage schema + assert metric export (CWE-212)

This commit is contained in:
Kjell Tore Guttormsen 2026-06-26 14:36:17 +02:00
commit a9c442c201
3 changed files with 81 additions and 0 deletions

View file

@ -11,6 +11,8 @@ import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { transformToPrometheus, normalizeMetricName } from '../../lib/exporters/textfile-format.mjs';
import { transformToOtlpJson } from '../../lib/exporters/otlp-format.mjs';
import { applyFieldAllowlist } from '../../lib/exporters/field-allowlist.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const FIXTURES = join(__dirname, '..', 'fixtures');
@ -74,6 +76,38 @@ test('normalizeMetricName: dots/dashes/spaces → underscore, lowercase, voyage_
assert.equal(normalizeMetricName('METRIC NAME'), 'voyage_metric_name');
});
test('SC#6: allowlisted token-usage record → Prometheus + OTLP metrics; PII absent (end-to-end)', () => {
// Raw record as written to token-usage-stats.jsonl (carries PII for upsert keying).
const raw = {
session_id: 'x',
transcript_path: '/p',
cwd: '/c',
ts: '2026-06-26T12:00:00.000Z',
scope: 'main-context',
model: 'claude-opus-4-8',
tokens_input: 12345,
tokens_output: 6789,
cost_usd: 0.42,
is_estimate: false,
price_table_version: '2026-06-26',
};
const rec = applyFieldAllowlist(raw, 'token-usage');
// Prometheus: numeric fields auto-promote to voyage_token_usage_* metrics.
const prom = transformToPrometheus([rec]);
assert.match(prom, /voyage_token_usage_tokens_input\b/);
assert.match(prom, /voyage_token_usage_cost_usd\b/);
// CWE-212: PII must never appear in exporter output.
assert.ok(!prom.includes('session_id'), 'session_id leaked into Prometheus output');
assert.ok(!prom.includes('transcript_path'), 'transcript_path leaked into Prometheus output');
assert.ok(!prom.includes('/p'), 'transcript_path value leaked into Prometheus output');
// OTLP parity: dot-separated, dash-preserved naming (NOT the Prometheus underscore form).
const otlp = JSON.stringify(transformToOtlpJson([rec]));
assert.ok(otlp.includes('voyage.token-usage.tokens_input'), 'OTLP token metric missing');
assert.ok(!otlp.includes('transcript_path'), 'transcript_path leaked into OTLP output');
});
test('determinism: identical input produces identical output (sorted keys)', () => {
const records = loadJsonl('stats-sample.jsonl');
const out1 = transformToPrometheus(records);