@vaultcompass/vault-guard 1.0.6 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -115,7 +115,7 @@ function buildCli() {
115
115
  // Check command
116
116
  program
117
117
  .command('check')
118
- .description('Quick check')
118
+ .description('Scan files with config and baselines')
119
119
  .argument('[files...]', 'Files to check')
120
120
  .action(async (files) => {
121
121
  const exitCode = await (0, check_1.checkCommand)(files);
@@ -1,39 +1,7 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.checkCommand = checkCommand;
7
- const vault_guard_core_1 = require("@vaultcompass/vault-guard-core");
8
- const chalk_1 = __importDefault(require("chalk"));
9
- const path_1 = __importDefault(require("path"));
10
- const scan_utils_1 = require("../utils/scan-utils");
4
+ const scan_1 = require("./scan");
11
5
  async function checkCommand(files) {
12
- const scanner = new vault_guard_core_1.SecretScanner();
13
- const filesToCheck = files.length > 0 ? files : ['.'];
14
- console.log(chalk_1.default.blue('✅ Quick check\n'));
15
- // Use async scanning logic with same safeguards as scan command
16
- const results = await (0, scan_utils_1.scanFilesAsync)(filesToCheck, scanner, {
17
- verbose: false,
18
- skipBinary: true,
19
- progress: false
20
- });
21
- if (results.length === 0) {
22
- console.log(chalk_1.default.green.bold('✅ Clean:'), chalk_1.default.white('No secrets found\n'));
23
- return 0; // Success exit code
24
- }
25
- else {
26
- const totalSecrets = results.reduce((sum, r) => sum + r.matches.length, 0);
27
- console.log(chalk_1.default.red.bold('🔴 BLOCKED:'), chalk_1.default.white(`Found ${totalSecrets} secret${totalSecrets > 1 ? 's' : ''}\n`));
28
- // Display simplified results
29
- for (const { file, matches } of results) {
30
- const relativePath = relativePathCwd(file);
31
- console.log(chalk_1.default.red('🔴'), chalk_1.default.white(`${relativePath}: ${matches.length} secret${matches.length > 1 ? 's' : ''}`));
32
- }
33
- console.log('');
34
- return 1; // Error exit code (secrets found)
35
- }
36
- }
37
- function relativePathCwd(filePath) {
38
- return path_1.default.relative(process.cwd(), filePath);
6
+ return (0, scan_1.scanCommand)(files.length > 0 ? files : '.', 'text', false);
39
7
  }
@@ -0,0 +1,19 @@
1
+ export interface SseUsage {
2
+ inputTokens: number;
3
+ outputTokens: number;
4
+ model: string | null;
5
+ }
6
+ /**
7
+ * Extract token usage from a buffered Anthropic SSE stream.
8
+ *
9
+ * Pure and synchronous — no HTTP dependencies — so it can be unit-tested
10
+ * without spinning up a server. Call after the full response has been teed.
11
+ *
12
+ * Anthropic usage token delivery:
13
+ * message_start → message.usage.input_tokens (and initial output_tokens)
14
+ * message_delta → usage.output_tokens (cumulative; last one wins)
15
+ * message_stop → no usage fields
16
+ *
17
+ * Unknown or malformed `data:` lines are silently skipped.
18
+ */
19
+ export declare function parseAnthropicSseUsage(raw: string, fallbackModel: string | null): SseUsage;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseAnthropicSseUsage = parseAnthropicSseUsage;
4
+ /**
5
+ * Extract token usage from a buffered Anthropic SSE stream.
6
+ *
7
+ * Pure and synchronous — no HTTP dependencies — so it can be unit-tested
8
+ * without spinning up a server. Call after the full response has been teed.
9
+ *
10
+ * Anthropic usage token delivery:
11
+ * message_start → message.usage.input_tokens (and initial output_tokens)
12
+ * message_delta → usage.output_tokens (cumulative; last one wins)
13
+ * message_stop → no usage fields
14
+ *
15
+ * Unknown or malformed `data:` lines are silently skipped.
16
+ */
17
+ function parseAnthropicSseUsage(raw, fallbackModel) {
18
+ let inputTokens = 0;
19
+ let outputTokens = 0;
20
+ let model = fallbackModel;
21
+ for (const line of raw.split('\n')) {
22
+ const trimmed = line.trim();
23
+ if (!trimmed.startsWith('data:'))
24
+ continue;
25
+ const payload = trimmed.slice('data:'.length).trim();
26
+ if (!payload || payload === '[DONE]')
27
+ continue;
28
+ let evt;
29
+ try {
30
+ evt = JSON.parse(payload);
31
+ }
32
+ catch {
33
+ continue;
34
+ }
35
+ if (evt.type === 'message_start' && evt.message) {
36
+ if (typeof evt.message.model === 'string')
37
+ model = evt.message.model;
38
+ const u = evt.message.usage ?? {};
39
+ if (typeof u.input_tokens === 'number')
40
+ inputTokens = u.input_tokens;
41
+ if (typeof u.output_tokens === 'number')
42
+ outputTokens = u.output_tokens;
43
+ }
44
+ else if (evt.type === 'message_delta' && evt.usage) {
45
+ if (typeof evt.usage.input_tokens === 'number')
46
+ inputTokens = evt.usage.input_tokens;
47
+ if (typeof evt.usage.output_tokens === 'number')
48
+ outputTokens = evt.usage.output_tokens;
49
+ }
50
+ }
51
+ return { inputTokens, outputTokens, model };
52
+ }
@@ -41,6 +41,7 @@ const http = __importStar(require("http"));
41
41
  const https = __importStar(require("https"));
42
42
  const path_1 = __importDefault(require("path"));
43
43
  const vault_guard_telemetry_1 = require("@vaultcompass/vault-guard-telemetry");
44
+ const proxy_sse_1 = require("./proxy-sse");
44
45
  /**
45
46
  * Bound inbound request buffering. 32 MB is a generous ceiling for
46
47
  * Anthropic-shaped JSON bodies (multi-megabyte system prompts, large
@@ -266,15 +267,47 @@ async function handleRequest(req, res, store, options, takeRateSlot) {
266
267
  const headers = { ...pres.headers };
267
268
  res.writeHead(pres.statusCode ?? 502, headers);
268
269
  if (stream) {
270
+ // Pipe to client immediately (bounded by the OS pipe, not us). Tee a
271
+ // bounded copy purely to parse SSE usage events after the stream ends.
272
+ // On tee overflow the client still receives the full stream; we record
273
+ // a distinct source so missing usage is visible in telemetry.
269
274
  pres.pipe(res);
275
+ const teeChunks = [];
276
+ let teeLen = 0;
277
+ let teeAbandoned = false;
278
+ pres.on('data', chunk => {
279
+ if (teeAbandoned)
280
+ return;
281
+ const b = chunk;
282
+ teeLen += b.length;
283
+ if (teeLen > MAX_TEE_BYTES) {
284
+ teeAbandoned = true;
285
+ teeChunks.length = 0;
286
+ return;
287
+ }
288
+ teeChunks.push(b);
289
+ });
270
290
  pres.on('end', () => {
291
+ const model = typeof bodyJson.model === 'string' ? bodyJson.model : null;
292
+ if (teeAbandoned) {
293
+ store.recordUsage({
294
+ provider: 'anthropic',
295
+ model,
296
+ cwd,
297
+ inputTokens: 0,
298
+ outputTokens: 0,
299
+ source: 'proxy-stream-overflow',
300
+ });
301
+ resolve();
302
+ return;
303
+ }
304
+ const usage = (0, proxy_sse_1.parseAnthropicSseUsage)(Buffer.concat(teeChunks).toString('utf8'), model);
271
305
  store.recordUsage({
272
306
  provider: 'anthropic',
273
- model: typeof bodyJson.model === 'string' ? bodyJson.model : null,
307
+ model: usage.model,
274
308
  cwd,
275
- inputTokens: 0,
276
- outputTokens: 0,
277
- estCostUsd: 0,
309
+ inputTokens: usage.inputTokens,
310
+ outputTokens: usage.outputTokens,
278
311
  source: 'proxy-stream',
279
312
  });
280
313
  resolve();
@@ -1,2 +1,2 @@
1
1
  export type OutputFormat = 'text' | 'json' | 'sarif';
2
- export declare function scanCommand(targetPath: string, format?: OutputFormat, staged?: boolean): Promise<number>;
2
+ export declare function scanCommand(targetPath: string | string[], format?: OutputFormat, staged?: boolean): Promise<number>;
@@ -9,6 +9,8 @@ const chalk_1 = __importDefault(require("chalk"));
9
9
  const scan_utils_1 = require("../utils/scan-utils");
10
10
  async function scanCommand(targetPath, format = 'text', staged = false) {
11
11
  const cwd = process.cwd();
12
+ const targetPaths = Array.isArray(targetPath) ? targetPath : [targetPath];
13
+ const targetLabel = targetPaths.length === 1 ? targetPaths[0] : `${targetPaths.length} paths`;
12
14
  let config;
13
15
  try {
14
16
  config = (0, vault_guard_core_1.loadConfig)(cwd);
@@ -54,7 +56,7 @@ async function scanCommand(targetPath, format = 'text', staged = false) {
54
56
  'you have audited every pattern.\n'));
55
57
  }
56
58
  if (format === 'text' && !staged) {
57
- console.log(chalk_1.default.blue('🔍 Scanning'), chalk_1.default.cyan(targetPath));
59
+ console.log(chalk_1.default.blue('🔍 Scanning'), chalk_1.default.cyan(targetLabel));
58
60
  }
59
61
  const stats = { filesScanned: 0, bytesScanned: 0 };
60
62
  const t0 = Date.now();
@@ -96,7 +98,7 @@ async function scanCommand(targetPath, format = 'text', staged = false) {
96
98
  });
97
99
  }
98
100
  else {
99
- results = await (0, scan_utils_1.scanFilesAsync)([targetPath], scanner, {
101
+ results = await (0, scan_utils_1.scanFilesAsync)(targetPaths, scanner, {
100
102
  verbose: format === 'text',
101
103
  skipBinary: true,
102
104
  progress: format === 'text',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaultcompass/vault-guard",
3
- "version": "1.0.6",
3
+ "version": "1.1.1",
4
4
  "description": "Block secrets at commit and in CI. Pre-commit hooks, SARIF output, and fast staged-file scans for AI-native workflows.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -36,12 +36,12 @@
36
36
  "dependencies": {
37
37
  "chalk": "^4.1.2",
38
38
  "commander": "^12.0.0",
39
- "@vaultcompass/vault-guard-core": "1.0.6",
40
- "@vaultcompass/vault-guard-telemetry": "1.0.6"
39
+ "@vaultcompass/vault-guard-core": "1.1.1",
40
+ "@vaultcompass/vault-guard-telemetry": "1.1.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/jest": "^30.0.0",
44
- "@types/node": "^25.9.1",
44
+ "@types/node": "^25.9.3",
45
45
  "jest": "^30.4.2",
46
46
  "ts-jest": "^29.4.11",
47
47
  "typescript": "^5.3.3"