@vellumai/assistant 0.12.2-dev.202609162314.7778bc8 → 0.12.2-dev.202609170127.2eef54c

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.
@@ -63,6 +63,7 @@ const FIXTURES: Record<string, string> = {
63
63
  "Perplexity API Key": `pplx-${filler(40)}`,
64
64
  "Tavily API Key": `tvly-${filler(20)}`,
65
65
  "Firecrawl API Key": `fc-${filler(20)}`,
66
+ "Resend API Key": `re_${filler(8)}_${filler(24)}`,
66
67
  };
67
68
 
68
69
  describe("subpath export", () => {
@@ -230,6 +230,9 @@ export const PREFIX_PATTERNS: SecretPrefixPattern[] = [
230
230
 
231
231
  // -- Firecrawl --
232
232
  { label: "Firecrawl API Key", regex: /fc-[A-Za-z0-9]{20,}/ },
233
+ // Resend documents only the `re_` prefix; the segment shape is the observed
234
+ // key format. Strict on purpose: bare `re_` prefixes ordinary identifiers.
235
+ { label: "Resend API Key", regex: /re_[A-Za-z0-9]{8}_[A-Za-z0-9]{24,}/ },
233
236
  ];
234
237
 
235
238
  /**
@@ -362,7 +365,7 @@ export function isPlaceholderValue(value: string): boolean {
362
365
  // Strip known prefixes to isolate the variable part
363
366
  const variablePart = value
364
367
  .replace(
365
- /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
368
+ /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|re_|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
366
369
  "",
367
370
  )
368
371
  .replace(/[^A-Za-z0-9]/g, "");
@@ -63,6 +63,7 @@ const FIXTURES: Record<string, string> = {
63
63
  "Perplexity API Key": `pplx-${filler(40)}`,
64
64
  "Tavily API Key": `tvly-${filler(20)}`,
65
65
  "Firecrawl API Key": `fc-${filler(20)}`,
66
+ "Resend API Key": `re_${filler(8)}_${filler(24)}`,
66
67
  };
67
68
 
68
69
  describe("subpath export", () => {
@@ -230,6 +230,9 @@ export const PREFIX_PATTERNS: SecretPrefixPattern[] = [
230
230
 
231
231
  // -- Firecrawl --
232
232
  { label: "Firecrawl API Key", regex: /fc-[A-Za-z0-9]{20,}/ },
233
+ // Resend documents only the `re_` prefix; the segment shape is the observed
234
+ // key format. Strict on purpose: bare `re_` prefixes ordinary identifiers.
235
+ { label: "Resend API Key", regex: /re_[A-Za-z0-9]{8}_[A-Za-z0-9]{24,}/ },
233
236
  ];
234
237
 
235
238
  /**
@@ -362,7 +365,7 @@ export function isPlaceholderValue(value: string): boolean {
362
365
  // Strip known prefixes to isolate the variable part
363
366
  const variablePart = value
364
367
  .replace(
365
- /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
368
+ /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|re_|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
366
369
  "",
367
370
  )
368
371
  .replace(/[^A-Za-z0-9]/g, "");
@@ -63,6 +63,7 @@ const FIXTURES: Record<string, string> = {
63
63
  "Perplexity API Key": `pplx-${filler(40)}`,
64
64
  "Tavily API Key": `tvly-${filler(20)}`,
65
65
  "Firecrawl API Key": `fc-${filler(20)}`,
66
+ "Resend API Key": `re_${filler(8)}_${filler(24)}`,
66
67
  };
67
68
 
68
69
  describe("subpath export", () => {
@@ -230,6 +230,9 @@ export const PREFIX_PATTERNS: SecretPrefixPattern[] = [
230
230
 
231
231
  // -- Firecrawl --
232
232
  { label: "Firecrawl API Key", regex: /fc-[A-Za-z0-9]{20,}/ },
233
+ // Resend documents only the `re_` prefix; the segment shape is the observed
234
+ // key format. Strict on purpose: bare `re_` prefixes ordinary identifiers.
235
+ { label: "Resend API Key", regex: /re_[A-Za-z0-9]{8}_[A-Za-z0-9]{24,}/ },
233
236
  ];
234
237
 
235
238
  /**
@@ -362,7 +365,7 @@ export function isPlaceholderValue(value: string): boolean {
362
365
  // Strip known prefixes to isolate the variable part
363
366
  const variablePart = value
364
367
  .replace(
365
- /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
368
+ /^(?:AKIA|gh[pousr]_|github_pat_|glpat-|sk_live_|rk_live_|xoxb-|xoxp-|xapp-|sk-ant-|sk-proj-|sk-or-v1-|AIza|GOCSPX-|SK|SG\.|npm_|pypi-|key-|lin_api_|ntn_|fw_|pplx-|re_|-----BEGIN [A-Z ]*PRIVATE KEY-----)/,
366
369
  "",
367
370
  )
368
371
  .replace(/[^A-Za-z0-9]/g, "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.12.2-dev.202609162314.7778bc8",
3
+ "version": "0.12.2-dev.202609170127.2eef54c",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -204,6 +204,22 @@ describe("collectRevealRefsFromCommand", () => {
204
204
  );
205
205
  });
206
206
 
207
+ test("parses a legacy service/field path", () => {
208
+ expect(
209
+ collectRevealRefsFromCommand(
210
+ "assistant credentials reveal resend/api_key",
211
+ ),
212
+ ).toEqual([{ service: "resend", field: "api_key" }]);
213
+ });
214
+
215
+ test("legacy path survives malformed extra arguments", () => {
216
+ expect(
217
+ collectRevealRefsFromCommand(
218
+ "assistant credentials reveal resend/api_key --json extra",
219
+ ),
220
+ ).toEqual([{ service: "resend", field: "api_key" }]);
221
+ });
222
+
207
223
  test("unparseable invocation yields no ref (fails safe)", () => {
208
224
  expect(
209
225
  collectRevealRefsFromCommand("assistant credentials reveal --service x"),
@@ -437,6 +453,13 @@ describe("redactSecretsForChat", () => {
437
453
  expect(out).not.toContain(SYNTHETIC_OPENAI_PROJECT_KEY);
438
454
  });
439
455
 
456
+ test("Resend key in persisted assistant text is redacted without a candidate", () => {
457
+ const key = `re_${"a".repeat(8)}_${"b".repeat(24)}`;
458
+ const out = redactSecretsForChat(`the key is ${key} and it failed`, []);
459
+ expect(out).not.toContain(key);
460
+ expect(out).toContain("\u3014redacted:Resend API Key\u3015");
461
+ });
462
+
440
463
  test("no candidate match produces the plain sentinel — never a guess", () => {
441
464
  const out = redactSecretsForChat(`key: ${SYNTHETIC_OPENAI_PROJECT_KEY}`, [
442
465
  { service: "openai", field: "api_key", value: "different-value" },
@@ -187,6 +187,26 @@ describe("Anthropic keys", () => {
187
187
  });
188
188
  });
189
189
 
190
+ // ---------------------------------------------------------------------------
191
+ // Resend
192
+ // ---------------------------------------------------------------------------
193
+ describe("Resend keys", () => {
194
+ const key = `re_${"a".repeat(8)}_${"b".repeat(24)}`;
195
+
196
+ test("detects re_ key", () => {
197
+ expectMatch(key, "Resend API Key");
198
+ });
199
+
200
+ test("redacts a key echoed from shell output", () => {
201
+ const out = redactSecrets(`$ cat .env\nRESEND=${key}\n`);
202
+ expect(out).not.toContain(key);
203
+ });
204
+
205
+ test("ignores re_ identifiers that are not key-shaped", () => {
206
+ expectNoMatch("re_0_of_1_is_deprecated_6387");
207
+ });
208
+ });
209
+
190
210
  // ---------------------------------------------------------------------------
191
211
  // OpenAI
192
212
  // ---------------------------------------------------------------------------
@@ -10,7 +10,8 @@
10
10
  *
11
11
  * 1. While a turn runs, every shell-style tool command is scanned for
12
12
  * `credentials reveal` invocations and the referenced credentials
13
- * (`--service`/`--field` flags or a positional UUID) are recorded as
13
+ * (`--service`/`--field` flags, a positional UUID, or a legacy
14
+ * `service/field` path) are recorded as
14
15
  * *candidates* ({@link collectRevealRefsFromCommand}).
15
16
  * 2. At persist time, only those candidates' plaintexts are fetched from
16
17
  * the credential store — a scoped read of secrets that were already in
@@ -55,6 +56,7 @@ import {
55
56
  } from "../security/secret-scanner.js";
56
57
  import { getSecureKeyAsync } from "../security/secure-keys.js";
57
58
  import { getCredentialMetadataById } from "../tools/credentials/metadata-store.js";
59
+ import { parseServiceFieldRef } from "../tools/credentials/ref-parse.js";
58
60
  import { getLogger } from "../util/logger.js";
59
61
 
60
62
  const log = getLogger("chat-credential-redaction");
@@ -106,6 +108,12 @@ const SERVICE_FLAG_RE = new RegExp(String.raw`--service(?:=|\s+)${FLAG_VALUE}`);
106
108
  const FIELD_FLAG_RE = new RegExp(String.raw`--field(?:=|\s+)${FLAG_VALUE}`);
107
109
  const UUID_RE =
108
110
  /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
111
+ // The CLI's single positional argument, directly after `reveal`. A UUID is
112
+ // handled above; anything else is tried as a legacy `service/field` path via
113
+ // the shared grammar in `parseServiceFieldRef`. The route rejects that form
114
+ // as an id today, so no plaintext prints; staging the ref is defense in depth
115
+ // so a future lookup that does succeed is already proof-gated.
116
+ const POSITIONAL_RE = /\breveal\s+([^\s"'\\()<>`-][^\s"'\\()<>`]*)/;
109
117
 
110
118
  /**
111
119
  * Extract a flag's value from a segment, undoing the shell quoting the
@@ -265,6 +273,13 @@ export function collectRevealRefsFromCommand(
265
273
  const id = UUID_RE.exec(invocation)?.[0];
266
274
  if (id) {
267
275
  refs.push({ id });
276
+ continue;
277
+ }
278
+ const positional = POSITIONAL_RE.exec(invocation)?.[1];
279
+ const pathRef =
280
+ positional === undefined ? undefined : parseServiceFieldRef(positional);
281
+ if (pathRef) {
282
+ refs.push(pathRef);
268
283
  }
269
284
  // No parseable identity → no ref. The span still gets redacted at
270
285
  // persist; it just won't be revealable.
@@ -1,7 +1,13 @@
1
1
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { afterEach, beforeEach, expect, test } from "bun:test";
4
+ import {
5
+ afterEach,
6
+ beforeEach,
7
+ expect,
8
+ setDefaultTimeout,
9
+ test,
10
+ } from "bun:test";
5
11
 
6
12
  import sharp from "sharp";
7
13
 
@@ -14,6 +20,11 @@ import {
14
20
  renderDesktopWallpaper,
15
21
  } from "./desktop-wallpaper.js";
16
22
 
23
+ // Native resvg renders scan the system font database per instance, and on a
24
+ // loaded CI runner the file's first render runs past bun's 5s default. Each
25
+ // test file runs in its own process, so this doesn't leak.
26
+ setDefaultTimeout(15_000);
27
+
17
28
  let workspace: string;
18
29
  let previousWorkspace: string | undefined;
19
30
  beforeEach(() => {