Skip to main content
← Back to list
01Issue
BugClosedSwamp CLI
Assigneesstack72

Relationships

#514 vault://local_encryption token does not round-trip correctly for GCP OAuth2 access tokens

Opened by bixu · 6/1/2026

When storing a GCP OAuth2 access token via swamp vault put and then referencing it via vault:// in a model's globalArguments, the token returned to the extension method fails GCP API authentication with ACCESS_TOKEN_TYPE_UNSUPPORTED.

The same token works when used directly via curl with Bearer auth. The stored token is also shorter (256 chars) than the original (261 chars) when checked after a refresh_access_token call, suggesting truncation or encoding during storage or retrieval.

Tested with both @swamp/1password and local_encryption vault backends — same result.

Reproduction:

  1. swamp vault put my-vault GCP_TOKEN "$(gcloud auth print-access-token)"
  2. Reference vault://my-vault/GCP_TOKEN in a model globalArgument
  3. Run a method that uses the token to call the GCP IAM API
  4. 401 ACCESS_TOKEN_TYPE_UNSUPPORTED
02Bog Flow
OPENTRIAGEDIN PROGRESSCLOSED+ 2 MORETRIAGE+ 3 MORECLASSIFICATION

Closed

6/8/2026, 4:16:37 PM

No activity in this phase yet.

03Sludge Pulse
stack72 assigned stack726/1/2026, 2:11:25 PM
Editable. Press Enter to edit.

bixu commented 6/1/2026, 9:56:56 AM

Root cause identified: the issue is not truncation or encoding. The model was using vault:// URI syntax in globalArguments, which is resolved at definition-load time and cached. The working pattern from ai-infrastructure uses CEL expressions (${{ vault.get(vault-name, key) }}) which are evaluated at method-execution time, ensuring a fresh token on every call.

The vault:// syntax may still have a separate round-trip issue, but the practical fix is to use CEL vault expressions for short-lived credentials like OAuth tokens.

stack72 commented 6/1/2026, 4:32:53 PM

Hey @bixu — we picked this up and tried to reproduce the round-trip failure. Here's what we tested on the current build (20260601.095735.0-sha.4fad4b5d):

1. Raw vault round-trip Stored a real GCP OAuth2 access token (260 chars from gcloud auth print-access-token) via swamp vault put, then read it back with swamp vault read-secret. Byte-for-byte match, no truncation.

2. Full expression resolution path Created a @command/shell model with vault.get(test-vault, GCP_TOKEN) in method env, executed the method, and wrote the received token to a file. Compared the file content against vault read-secret output — exact match, 260 chars both sides.

3. Special characters Tested tokens containing +, /, =, . (common in base64/OAuth tokens). All round-tripped correctly through both the vault layer and the expression resolution pipeline (CEL evaluation → sentinel resolution → shell env-var injection).

We traced the full code path (CLI arg parsing → VaultService → local_encryption provider encrypt/decrypt → vault expression resolution → CEL evaluation → VaultSecretBag sentinel resolution → Zod validation → method execution) and couldn't find a truncation mechanism.

A few things that would help us narrow this down:

  • Can you confirm this still reproduces on the latest build? (swamp update then retry)
  • When you observed the 261→256 char difference, how did you measure it? (e.g., wc -c on the method's received value, or comparing vault read-secret output against the original?)
  • Is the model using @command/shell or a custom TypeScript extension? If custom, does the extension do anything with the token before passing it to the GCP API (e.g., trim, parse, or re-encode)?
  • Could you try swamp vault read-secret <vault> GCP_TOKEN --force --json and compare the .value length against the original token to isolate whether it's the vault layer or the expression layer?

stack72 commented 6/5/2026, 3:55:30 PM

Hey @bixu — we've dug into this further and need a bit more context from your side to nail down the root cause.

We traced every code path in the vault system and confirmed that ${{ vault.get(vault-name, key) }} CEL expressions resolve correctly at method-execution time (fresh on every call, no caching). The raw vault round-trip (put → read-secret) is also byte-for-byte correct.

However, we can't find a vault:// URI handler anywhere in swamp — that syntax doesn't exist as a feature. The only supported vault reference is the CEL expression ${{ vault.get(vault-name, key) }}. If you put vault://my-vault/GCP_TOKEN as a literal string in globalArguments, it would be stored and passed through as-is without resolving.

To help us figure out what's actually happening, could you share:

  1. A redacted snippet of the model definition that was failing — specifically the globalArguments: block. Was the value vault://my-vault/GCP_TOKEN, ${{ vault.get(my-vault, GCP_TOKEN) }}, or the raw token string?
  2. How the definition was created — via swamp model create --global-arg, hand-edited YAML, or generated by an agent?
  3. The 261→256 char measurement — was that comparing gcloud auth print-access-token output against swamp vault read-secret at the same moment, or at different times? GCP tokens can vary in length between refreshes.
  4. The model type@command/shell or a custom TypeScript extension? If custom, does it do anything with the token before passing it to the GCP API?

If you can share the definition snippet, we can reproduce your exact setup and pinpoint where the value diverges.

bixu commented 6/6/2026, 1:43:30 PM

Re-posting a diagnostic ripple from 2026-06-01 that was apparently lost — filed as swamp Lab #577.

Root cause identified: the issue is not truncation or encoding. The model was using vault:// URI syntax in globalArguments, which is resolved at definition-load time and cached. The working pattern from another extension repo uses CEL expressions (${{ vault.get(vault-name, key) }}) which are evaluated at method-execution time, ensuring a fresh token on every call.

The vault:// syntax may still have a separate round-trip issue, but the practical fix is to use CEL vault expressions for short-lived credentials like OAuth tokens.

bixu commented 6/6/2026, 1:47:48 PM

Filed swamp Lab #578 (feature) framing the broader UX gap underneath this round-trip bug: short-lived credentials (gcloud / aws-sso / kubectl-oidc) currently require a hand-rolled command/shell refresher model at the start of every workflow that uses them. A first-class refresh hook on the vault entry would remove the boilerplate and make freshness explicit instead of implicit.

https://swamp-club.com/lab/578

Sign in to post a ripple.