Skip to content

feat(env-injection): value filters on resolved references (#138)#143

Merged
arimxyer merged 1 commit into
mainfrom
feat/138-value-filters
Jul 2, 2026
Merged

feat(env-injection): value filters on resolved references (#138)#143
arimxyer merged 1 commit into
mainfrom
feat/138-value-filters

Conversation

@arimxyer

@arimxyer arimxyer commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Closes #138. Completes item 3 of the #115 env-injection epic.

Summary

A credential reference may end with | filter to transform the resolved value before injection. It's parsed in the one shared grammar, so it works everywhere a reference is accepted — ${pass:...} templates (inject, exec --env-file), --set (exec/export), and .pass-cli.toml manifests.

${pass:api/token | base64}     # base64 a Bearer token
${pass:api | basicauth}        # base64("username:password") for an HTTP Basic header
pass-cli exec --set 'AUTH=api|basicauth' -- ...

Filters (one per reference):

filter result
base64 standard base64 of the value
base64url URL-safe base64 of the value
basicauth base64("username:password") from the credential (takes no field)

Design

  • Parse once in the shared SplitPath (internal/envmap): peel an optional | filter, trim only when a pipe is present (filterless refs are byte-for-byte unchanged), validate the name fail-closed, reserve the first |. New 4-return signature propagated to ParseSetSpec, parseTemplate, ParseManifest.
  • Carry via a Filter field on envmap.Mapping and TemplateRef.
  • Apply client-side in resolver.ResolveValuesFiltered, after the backend returns raw values — so the agent's values-only protocol is untouched and output is identical in direct and daemon modes. basicauth resolves username + password through the normal (batched) value path and base64s user:pass.
  • Single-source registry (internal/envmap/filter.go): IsKnownFilter (parse-time) and ApplyValueFilter (apply-time) both derive from one map, so they can't drift.

Intentional limitations (documented)

  • The positional <service> form is never parsed by SplitPath, so filters apply only to --set, --from manifests, and ${pass:...}.
  • On --set the | must be shell-quoted.
  • Fail-closed: an unknown/empty filter is a hard error and nothing is injected; for --set/manifests this is caught before the vault is opened.

Test

  • Unit: SplitPath filter parsing (spaces, legacy colon, empty/unknown/chaining/multi-segment errors, basicauth-no-field); the filter registry; ResolveValuesFiltered (base64 + basicauth + unknown propagation).
  • Integration: inject (base64, basicauth, unknown-fails-closed), export --set (base64, proving filter-then-shell-quote order), and an agent-path case proving the filter is applied client-side while the daemon serves the raw value.

go build ✓ · go vet ./... ✓ · go test ./... ✓ · go test -tags=integration (filter set) ✓ · golangci-lint run ./... → 0 issues ✓

Out of scope (future)

Filter chaining (| trim | base64), base64url-nopad, a general two-reference join operator, and more filters (hex, upper/lower, trim).

🤖 Generated with Claude Code

Completes item 3 of the #115 epic. A credential reference may end with
"| filter" to transform the value before injection:

  ${pass:api/token | base64}      # base64 a token
  ${pass:api | basicauth}         # base64("username:password") for Basic auth
  --set 'AUTH=api|basicauth'      # same, on the mapping forms

Filters: base64 (standard), base64url (URL-safe), and basicauth (combines a
credential's own username + password). One filter per reference.

Design:
- Parse once in the shared SplitPath (envmap): peel an optional "| filter",
  trim only when a pipe is present (filterless refs unchanged byte-for-byte),
  validate the name fail-closed, reserve the first '|'. New 4-return signature
  propagated to ParseSetSpec, parseTemplate, ParseManifest, so every surface
  (--set, manifest, ${pass:...}) gets it at once.
- Carry via a new Filter field on envmap.Mapping and TemplateRef.
- Apply client-side in resolver.ResolveValuesFiltered, after the backend
  returns raw values — the agent's values-only protocol is untouched, so
  output is identical in direct and daemon modes. basicauth resolves username
  and password via the normal value path (batched) and base64s "user:pass".
- Single-source filter registry in internal/envmap/filter.go: IsKnownFilter
  (parse-time validation) and ApplyValueFilter (apply-time) both derive from
  one map, so they can't drift.

Filters are not applied to the positional <service> form (never parsed by
SplitPath); on --set the '|' must be shell-quoted. Both documented.

Tests: SplitPath filter parsing (spaces, colon form, empty/unknown/chaining/
multi-segment errors), filter registry, ResolveValuesFiltered (base64 +
basicauth + unknown), and integration for inject (base64/basicauth/unknown),
export (--set base64, filter-then-quote), and an agent-path case proving the
filter is applied client-side while the daemon serves the raw value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014kVLjbUL4F4CkoA7RbMYy8
@arimxyer arimxyer merged commit 743ff7b into main Jul 2, 2026
11 checks passed
@arimxyer arimxyer deleted the feat/138-value-filters branch July 2, 2026 00:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

env-injection: value transforms / filters (e.g. base64) on resolved references

1 participant