Releases: inflightsec/agent-vault-proxy
v0.4.3
Single-line follow-up to v0.4.2. No proxy code changes — v0.4.2's proxy runtime was already byte-for-byte identical to v0.4.1 (all v0.4.2 diffs were in tests/, pyproject.toml, README.md, and CHANGELOG.md).
Fixed
src/agent_vault_proxy/__init__.py__version__now agrees withpyproject.toml. The v0.4.2 release bumpedpyproject.tomlto0.4.2but left__version__ = "0.4.1"insrc/agent_vault_proxy/__init__.py. The published v0.4.2 wheel installed correctly viapip(PyPI metadata = 0.4.2) but reportedagent_vault_proxy.__version__ == "0.4.1"at runtime. Thewheel-smokejob in.github/workflows/test.ymlis designed to catch exactly this skew (its comment lists it as the canonical failure mode) and went red onmainafter the v0.4.2 push.scripts/smoke-test-wheel.shentry-point check switched frompython -m agent_vault_proxy --helpto an import-only check matching.github/workflows/test.yml'swheel-smokejob. mitmdump's argparse behavior on--helpwith a-s addonflag is fragile across versions and returns exit 1 in some environments, masking real wheel issues as entry-point failures. The CI job already learned this lesson and switched to importingagent_vault_proxy,agent_vault_proxy.__main__.main,agent_vault_proxy.addon,agent_vault_proxy.config, andagent_vault_proxy.backends.BACKEND_REGISTRY; the local pre-release tool now does the same. Without this,scripts/pre-release.shstep 11 (which invokessmoke-test-wheel.sh) couldn't go green.- Release-tooling fixes from v0.4.2 are preserved unchanged in v0.4.3 (
tests/pypi-smoke/run.shNEGATIVE assertion accepting"type":"deny",tests/pypi-smoke/docker-compose.ymlempty-default forTEST_SECRET). - Release handoff now gates on
scripts/pre-release.shbetween commit and tag — that script's section 3 (Version constants agree?) does the exact pyproject vs__init__.pycomparison that would have caught the v0.4.2 skew. Order matters:pre-release.sh's first check isgit status --porcelainfor a clean working tree, so it runs AFTERgit commit, not before. This is a process fix, not a code fix; recorded here for traceability.
Changed
- Version pointers in
README.md,tests/pypi-smoke/README.md,tests/pypi-smoke/run.shusage examples,docker-compose.ymlimage tag, andscripts/smoke-test-wheel.shusage examples bumped to0.4.3. The historical reference to "v0.4.2 changelog" intests/pypi-smoke/run.sh:324is preserved — the harness fix is documented in the v0.4.2 entry.
v0.4.2
Release-tooling-only patch on top of v0.4.1. No proxy code changes — v0.4.1's substitution and audit guarantees are unchanged. v0.4.1 was correctly published to PyPI and the core wire-format behavior was verified by the smoke harness's positive test on the published wheel. The release.yml pypi-install-smoke gate did its job: it caught a real signal mismatch and held back the GitHub Release until investigation was complete. The investigation found the proxy was behaving correctly; the harness's negative-assertion grep needed updating.
Fixed (release tooling)
tests/pypi-smoke/run.shNEGATIVE assertion now accepts the"type":"deny"audit event in addition to the"decision":"denied"/"decision":"forwarded_unmodified"shapes. The addon has two distinct deny paths for an unbound destination: (1) theunmatched_destination_policy: denypolicy gate fires BEFORE placeholder analysis when the request's host is in no binding at all and emits{"type":"deny","reason":"unmatched_destination",...}; (2) thedestination_not_in_bindingcheck inside the inject path fires when a placeholder IS present but the matched secret's bindings don't cover the request host and emits{"type":"inject_decision","decision":"denied",...}. The pypi-smoke negative test aims atexample.invalid(in no binding at all), so the policy gate fires first. The previous grep only knew path (2) and the smoke went red despite the proxy correctly returning 403 + auditing the deny.tests/docker-e2e/run.sh:236already greps the"type":"deny"shape, so this was a pypi-smoke-only regression introduced when the pypi-smoke harness was first added in v0.4.1.tests/pypi-smoke/docker-compose.ymluses${TEST_SECRET:-}instead of the strict${TEST_SECRET:?...}for theavp-initenv reference. The strict form blockeddocker compose down -vteardown whenTEST_SECRETwasn't exported, because compose interpolates all referenced vars at parse time even fordown. The empty default unblocks teardown without weakening the test:run.shstill exports the real value beforecompose up, and a manualcompose upwithoutTEST_SECRETproduces a brokensecrets.ymlthat fails the positive assertion at run time rather than at compose time.
Changed
- README.md and tests/pypi-smoke/README.md version pointers bumped from
0.4.1to0.4.2. The "Status" prose now leads with the v0.4.2 framing (release-tooling patch only; v0.4.1 guarantees unchanged) before recapping v0.4.1 and v0.4.0.
v0.4.0
First public release.
The adapter refactor (originally proposed for v0.3.0 in docs/adapter-architecture.md) is bundled with composite secrets and shipped together as v0.4.0.
Added
- Composite secret bindings (
inject.template+compose:). Bindings can now assemble a credential from 1-4 atomic BWS values at fetch time, instead of requiring the operator to pre-concatenate values in BWS (which made single-key rotation silently stale). Templates use Jinja2 syntax evaluated throughImmutableSandboxedEnvironmentwith a strict filter/function whitelist (b64encode,b64decode,sha256,urlencode,hmac_sha256,hmac_sha512). An AST-level deny-by-default validator at config-load rejects every Jinja2 construct outside the small set the proxy supports, class-walk escapes, control flow, attribute traversal, subscript, arithmetic are all structurally impossible. Seebindings.example.yamlfor a Jira / Atlassian Cloud example anddocs/architecture.md§4.2 for the reference table and architectural sketch. CachingSecretsClient.composite_fetch(names): atomically fetches multiple secrets under a single generation snapshot; empty BWS values raiseBackendUnavailableError(never compose partial credentials); flush during assembly raises_StaleAfterFlushErrorso the caller restarts.- Same-UUID heuristic: when two distinctly-named compose entries resolve to the same value (suggesting an operator typo pointing both names at one BWS secret), the addon logs a one-shot WARNING to its own logger. The warning never includes the actual secret value.
jinja2 >= 3.1is now a declared direct dependency (previously transitive via mitmproxy).SecretsBackendprotocol + adapter architecture.bindings.yamluses a discriminatedbackend: {type: bws, config: {...}}block.agent_vault_proxy.backends.bws.BitwardenBackendis the reference implementation; new backends (1Password Service Accounts, HashiCorp Vault, Doppler, etc.) plug in by registering atypediscriminator.agent_vault_proxy.caching.CachingSecretsClientprovides generic TTL+jitter+LRU+singleflight caching on top of any backend. Protocol design indocs/adapter-architecture.md.- mypy in CI + pre-commit. Strict-ish config (
warn_return_any,warn_unused_ignores,no_implicit_optional,check_untyped_defs); third-party libs without stubs (mitmproxy,bitwarden_sdk,yaml) explicitly silenced. - Ruff C90 cyclomatic-complexity gate (
max-complexity = 10). Three pre-existing functions carry# noqa: C901with one-line provenance justifications. - Hash-pinned dev dependencies.
requirements-dev.lockis now hash-pinned alongsiderequirements.lock. New helper scripts:scripts/check-lockfile-hashes.py(zero-dep structural check - every pinned package must carry a--hash=sha256:continuation) andscripts/check-lockfile-drift.sh(re-runsuv pip compilewith the 7-day cooldown and diffs against committed). Both wired into pre-commit; the CIverify-lockfilejob runs the same hash check before the drift diff.
Changed
- Config:
InjectSpec.formatis nowOptional[str]and a peer of the newInjectSpec.template: Optional[str]. Exactly one of the two must be set per binding (validator-enforced). Existinginject.formatbindings continue to work unchanged. - Addon: request handlers now capture
(config, client, audit)at handler entry, preventing a mid-requestconfigure()reload from producing a torn state (Silas F3). - Dependency advances captured by the 7-day-cooldown regen:
bitwarden-sdk2.0.0 → 2.1.0,certifi2026.4.22 → 2026.5.20,click8.4.0 → 8.4.1,ruff0.15.13 → 0.15.14. - Security CI stack refactored to drop GitHub Advanced Security dependency. CodeQL (Python SAST) replaced with Bandit + Semgrep (community rulesets:
p/security-audit+p/python+p/secrets). Gitleaks (which required a paid license for org-owned repos as of Aug 2022) replaced with TruffleHog (AGPL-3, free for everyone). OSV-Scanner switched from the action wrapper to direct binary install (the wrapper passesscan-argsas a single positional arg, breaking multi-flag invocation). All security jobs now gate the merge via job-fail instead of SARIF-upload to the Security tab, workflows portable to any git host, no GHAS settings to babysit. The same three Docker-based scanners (TruffleHog, OSV-Scanner, Semgrep) also run in pre-commit so most commits hit the same gates locally before push.
Removed
agent_vault_proxy.bwsmodule (theBwsClientfacade) and the top-levelbws:config block deprecation shim inconfig.py. v0.4.0 is the first public release; there are no public v0.2.0 users to migrate. Newbindings.yamlfiles must use thebackend: {type: bws, config: {...}}form (shown inbindings.example.yaml).- CodeQL job from
.github/workflows/security.yml(replaced by Bandit + Semgrep: see above). - Gitleaks pre-commit hook + CI job (replaced by TruffleHog - see above).