Skip to main content
← Back to list
01Issue
BugShippedSwamp CLITeam
Assigneesstack72

Relationships

#1169 readResource + vaulted sensitive fields: cross-run reads return the unresolved ref, and per-instance vault keys appear to collide

Opened by kneel · 7/15/2026· Shipped 7/16/2026

Summary

While building an extension that vaults sensitive fields (z.string().meta({ sensitive: true })) I ran into two things around writeResource/readResource and ${{ vault.get(...) }} references that I wanted to check — I'm not sure whether either is intended.

  1. Across runs, readResource() returns the sensitive field as the raw ${{ vault.get(...) }} string rather than the resolved value.
  2. The auto-generated vault key for a sensitive field doesn't seem to include the writeResource instance name, so two instances writing a same-named sensitive field in one method resolve to the same secret.

Both reproduce with a local_encryption vault; minimal snippets below.

1. Cross-run readResource doesn't resolve the ref

api.md:348 says refs "are automatically resolved when a vault service is available." That matches what I see within a single run. Across runs it seems different — reading a resource written in an earlier run returns the literal ref:

// run 1
await ctx.writeResource("thing", "main", { secret: "<base64>", label: "x" });
// run 2 (fresh invocation, local_encryption vault present)
const d = await ctx.readResource("main");
d.secret; // => "${{ vault.get('myvault', '<key>') }}"  (raw, unresolved)

Is cross-run resolution expected to work here? If yes, this looks like a resolution gap; if it's intentionally scoped to CEL inputs / same-run use, it'd be great to (a) note that on api.md:348 and (b) document the recommended way to read a vaulted value back in a later run — I couldn't find an existing pattern for that.

2. Vault key seems to omit the writeResource instance name

Writing the same sensitive field from two different instances within one method produced a single vault key rather than one per instance — the key looked like <type>-<modelId>-<method>-<fieldPath>, with no instance component:

// within one method's execute():
await ctx.writeResource("thing", "instance-A", { secret: "AAAA", label: "a" });
await ctx.writeResource("thing", "instance-B", { secret: "BBBB", label: "b" });
// One vault key for `secret`; it holds "BBBB", and instance-A's stored ref
// resolves to "BBBB" as well.

If that reading is right, it means an extension can't independently vault a same-named sensitive field across multiple instances in one method — the later write overwrites the earlier one. Is the instance name meant to be part of the key? Including the instance (and spec) name would keep them separate. Happy to be told I'm holding it wrong if there's a supported way to scope these.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 4 MOREPR_MERGED+ 1 MORECONTRIBUTOR_NOTIFIED

Shipped

7/16/2026, 6:34:47 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack727/16/2026, 2:50:01 PM
Editable. Press Enter to edit.

stack72 commented 7/16/2026, 6:34:56 PM

Thanks @kneel for reporting this! The fix has been merged and a release is on its way. We appreciate your contribution to swamp.

kneel commented 7/16/2026, 9:20:15 PM

Confirmed fixed — thank you @stack72! 🙏

Verified live against a post-#1866 runtime (CLI 20260716.194319.0), zero extension changes:

  1. Vault-key collision — new writes now key as …-<method>-<specName>-<instanceName>-<fieldPath>; my ccache / serviceTicket / negotiate resources that previously collided on ticketB64 now land in distinct vault entries. 👍

  2. Cross-run resolution — this is the big one for me: an in-model ctx.readResource() of a resource written by an earlier run now returns the resolved plaintext instead of the literal ${{ vault.get(...) }} ref. Three consecutive workflow runs (no destroy between) all hit the cache path — deserialize succeeds, the cached credential is reused, and the re-persisted ref stays a clean single-level vault.get (no ref-of-ref corruption).

Net effect: cross-run credential caching that was previously impossible now Just Works. I had shipped a safe-degradation workaround (treat an unresolved ref as a cache-miss and re-auth); with this fix that fall-through is simply never taken. Really appreciate the quick turnaround on both halves.

Minor aside while I have you: swamp issue get <n> surfaces commentCount but I could not find any flag to actually read ripple bodies from the CLI — had to pull the PR to see the resolution. Happy to file separately if useful.

Sign in to post a ripple.