@signalvaultio/node 0.5.0 → 0.5.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/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import type OpenAI from 'openai';
2
2
  import { ToolsAPI } from './tools';
3
3
  export type { ToolRecordOptions, ToolsAPI } from './tools';
4
4
  /** Kept in sync with package.json by a unit test. */
5
- export declare const SDK_VERSION = "0.5.0";
5
+ export declare const SDK_VERSION = "0.5.1";
6
6
  /**
7
7
  * Normalizes the base URL and refuses plaintext HTTP to anything but the local
8
8
  * machine. The API key and full prompts travel in every request, so an
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ exports.normalizeBaseUrl = normalizeBaseUrl;
5
5
  const crypto_1 = require("crypto");
6
6
  const tools_1 = require("./tools");
7
7
  /** Kept in sync with package.json by a unit test. */
8
- exports.SDK_VERSION = '0.5.0';
8
+ exports.SDK_VERSION = '0.5.1';
9
9
  const DEFAULT_BASE_URL = 'https://api.signalvault.io';
10
10
  const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
11
11
  /** Longest Retry-After the SDK will wait out before retrying a background event once. */
@@ -38,25 +38,67 @@ function assertNodeEnvironment() {
38
38
  * `http://` URL would expose both before the server could redirect to HTTPS.
39
39
  */
40
40
  function normalizeBaseUrl(raw) {
41
+ // Errors never quote `raw`: it may contain credentials.
41
42
  let url;
42
43
  try {
43
44
  url = new URL(raw);
44
45
  }
45
46
  catch {
46
- throw new Error(`[SignalVault] baseUrl is not a valid URL: ${raw}`);
47
+ throw new Error('[SignalVault] baseUrl is not a valid URL.');
48
+ }
49
+ if (url.username || url.password) {
50
+ throw new Error('[SignalVault] baseUrl must not contain credentials (user:password@). Pass the key as apiKey.');
47
51
  }
48
52
  if (url.protocol === 'http:' && !LOCAL_HOSTS.has(url.hostname)) {
49
53
  throw new Error('[SignalVault] baseUrl must use https:// (plain http:// is only allowed for localhost). ' +
50
54
  'Your API key and prompts would otherwise be sent unencrypted.');
51
55
  }
52
56
  if (url.search || url.hash) {
53
- throw new Error(`[SignalVault] baseUrl must not contain a query or fragment: ${raw}`);
57
+ throw new Error('[SignalVault] baseUrl must not contain a query or fragment.');
54
58
  }
55
59
  if (url.protocol !== 'https:' && url.protocol !== 'http:') {
56
60
  throw new Error(`[SignalVault] baseUrl must be an https:// URL, got ${url.protocol}`);
57
61
  }
58
62
  return raw.replace(/\/+$/, '');
59
63
  }
64
+ /**
65
+ * Strips surrounding whitespace and refuses keys that can't go in a header.
66
+ * A key read from a file or a secret often ends in a newline. Sent as is,
67
+ * every request fails before it leaves the process, so with failMode 'open'
68
+ * every call would go through unchecked. Errors never include the key.
69
+ */
70
+ function normalizeApiKey(raw) {
71
+ if (typeof raw !== 'string') {
72
+ throw new TypeError(`[SignalVault] apiKey must be a string, got ${raw === null ? 'null' : typeof raw}.`);
73
+ }
74
+ const key = raw.trim();
75
+ if (!key) {
76
+ throw new Error('[SignalVault] apiKey is empty.');
77
+ }
78
+ if (/[^\x21-\x7e]/.test(key)) {
79
+ throw new Error('[SignalVault] apiKey contains whitespace, control or non-ASCII characters. Check how the key is loaded.');
80
+ }
81
+ return key;
82
+ }
83
+ /**
84
+ * One line describing an error and its causes, for debug output. Never
85
+ * includes `secret`, and never throws.
86
+ */
87
+ function describeError(error, secret) {
88
+ const parts = [];
89
+ let current = error;
90
+ for (let depth = 0; current !== undefined && current !== null && depth < 5; depth++) {
91
+ try {
92
+ parts.push(current instanceof Error ? `${current.name}: ${current.message}` : String(current));
93
+ current = current instanceof Error ? current.cause : undefined;
94
+ }
95
+ catch {
96
+ parts.push('<unprintable error>');
97
+ break;
98
+ }
99
+ }
100
+ return parts.join(' <- ').split(secret).join('[redacted]');
101
+ }
60
102
  /** Thrown when a guardrail rule blocks the request. */
61
103
  class SignalVaultBlockedError extends Error {
62
104
  constructor(requestId, violations, dashboardUrl) {
@@ -236,7 +278,7 @@ class SignalVaultClient {
236
278
  if (config.failMode !== undefined && config.failMode !== 'open' && config.failMode !== 'closed') {
237
279
  throw new Error("[SignalVault] failMode must be 'open' or 'closed'.");
238
280
  }
239
- this.svApiKey = config.apiKey;
281
+ this.svApiKey = normalizeApiKey(config.apiKey);
240
282
  this.svBaseUrl = normalizeBaseUrl(config.baseUrl || DEFAULT_BASE_URL);
241
283
  this.svEnvironment = config.environment || 'production';
242
284
  this.debugMode = config.debug || false;
@@ -282,8 +324,9 @@ class SignalVaultClient {
282
324
  track(promise) {
283
325
  const tracked = promise
284
326
  .catch((err) => {
285
- if (this.debugMode)
286
- console.error('[SignalVault] Background event failed:', err);
327
+ if (this.debugMode) {
328
+ console.error('[SignalVault] Background event failed:', describeError(err, this.svApiKey));
329
+ }
287
330
  })
288
331
  .finally(() => this.pending.delete(tracked));
289
332
  this.pending.add(tracked);
@@ -699,8 +742,9 @@ class SignalVaultClient {
699
742
  }
700
743
  }
701
744
  catch (error) {
702
- if (this.debugMode)
703
- console.error('[SignalVault] Event send failed:', error);
745
+ if (this.debugMode) {
746
+ console.error('[SignalVault] Event send failed:', describeError(error, this.svApiKey));
747
+ }
704
748
  if (!isTimeout(error))
705
749
  retryDelayMs = 250 + Math.random() * 500;
706
750
  }
@@ -712,8 +756,9 @@ class SignalVaultClient {
712
756
  return result;
713
757
  }
714
758
  catch (error) {
715
- if (this.debugMode)
716
- console.error('[SignalVault] Event retry failed:', error);
759
+ if (this.debugMode) {
760
+ console.error('[SignalVault] Event retry failed:', describeError(error, this.svApiKey));
761
+ }
717
762
  }
718
763
  }
719
764
  this.logUndelivered(result);
@@ -734,8 +779,9 @@ class SignalVaultClient {
734
779
  }
735
780
  catch (error) {
736
781
  const timedOut = isTimeout(error);
737
- if (this.debugMode)
738
- console.error('[SignalVault] Pre-flight failed:', error);
782
+ if (this.debugMode) {
783
+ console.error('[SignalVault] Pre-flight failed:', describeError(error, this.svApiKey));
784
+ }
739
785
  return this.unavailable(requestId, timedOut ? 'timeout' : 'network', timedOut
740
786
  ? `SignalVault pre-flight check timed out after ${this.preflightTimeout}ms`
741
787
  : 'SignalVault API is unreachable');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalvaultio/node",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "SignalVault SDK - AI audit logs and guardrails for safe production AI",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",