Local credential proxy for AI agents (proxy MVP)#780
Open
theoephraim wants to merge 24 commits into
Open
Conversation
| let out = ''; | ||
| while (out.length < length) { | ||
| const byte = crypto.randomBytes(1)[0]!; | ||
| out += alphabet[byte % alphabet.length]; |
123c3b6 to
b76e527
Compare
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
varlock-website | 3101d12 | Commit Preview URL Branch Preview URL |
Jun 17 2026, 06:42 AM |
Builds on the phase-1 proxy guardrails (PR #755) with security and usability hardening for local, no-sandbox agent runs: - Per-item domain scoping: an item's secret is injected only on hosts its own @Proxy rule matches (was: all managed items on any ruled host). - In-memory ephemeral CA via @peculiar/x509 over WebCrypto (EC P-256); CA + per-host leaf private keys never touch disk, drops the openssl dependency. IP-literal ruled domains now get IP SANs. - Schema-fingerprint enforcement actually compares on nested commands, closing the @sensitive-downgrade re-load. - Process-ancestry context detection: guards + placeholder overrides resolve the session by process tree, not just the env marker, closing the `env -u __VARLOCK_PROXY_CHILD` bypass. - Streaming: SSE/unknown-length responses stream through instead of buffering; body redaction limited to bounded small text bodies. - Placeholder generation: drop @example derivation; data-type generatePlaceholder() is the blessed source; warn on generic fallback. - Revert global Accept-Encoding force (kept header + bounded-body redaction). - Correctness: byte-accurate content-length, strict-mode 403 length. - Tests: per-item scoping, ancestry, cert authority, end-to-end TLS MITM (CONNECT + leaf trust + injection + streaming), placeholder priority.
…, cleartext guard, response scrub) - Invariant #1: bind secret injection to the verified upstream TLS identity (public-PKI validation + cert identity matches the rule host, optional per-rule cert pinning). DNS-poison / rebound host → failed connection, never a leaked secret. Regression test included. - Invariant #2/#5: refuse to inject a secret into a cleartext (http://) connection; fail closed. Upstream-error path tears down the client connection rather than half-delivering. - Invariant #6: scrub real values back to placeholders in responses, now including streamed (SSE) text scrubbed chunk-by-chunk without breaking streaming; bounded-response post-scrub fail-safe withholds rather than leaks. - Tests moved onto the real HTTPS/MITM path (injection, redaction, per-item scoping, DNS-poison, cleartext guard, SSE stream + scrub).
…ny + scoped injection) Adds a facts→verdict policy layer (packages/varlock/src/proxy/policy.ts): - Requests are normalized to facts (host + method + path) and evaluated against @Proxy rules. A matching block=true rule denies the request (fail closed — never reaches upstream): static per-call authorization. - Credential injection is scoped by path/method, not just host (getRequestScopedManagedItems), so a secret can be limited to specific endpoints/methods. - Glob path matching (* within a segment, ** across); comma-separated methods. - Modeled as facts→verdict (allow|deny|require-approval) over a generic fact bag so domain plugins / non-HTTP protocols slot onto the same seam later. The require-approval verdict + approval provider is a follow-up. Note: short MITM-tunnel responses (deny 403, upstream-error 502) don't flush reliably through the CONNECT tunnel, so they fail closed by tearing the connection down. Clean status-code delivery over the tunnel is a follow-up. Tests: policy unit tests (matching, block precedence, scoped injection) + end-to-end block-deny over the HTTPS/MITM path.
Record one no-secrets JSON line per proxied request (host, method, path, request-hash, rule, decision, injected keys) under ~/.config/varlock/proxy/audit/<uuid>.jsonl; view with `varlock proxy audit`.
These ProxyRule fields were parsed but never used: pin (cert pinning) is deferred for delegated external identity verification, and sign/transform belong to a future @proxyResign decorator, not routing rules. Removing avoids implying features that don't exist. Invariant #1 (PKI + SAN identity check) is unchanged.
@Proxy(approve=true) holds a request for an out-of-band, request-bound approval (method+host+path+body-hash+nonce+expiry) before forwarding. Precedence block>require-approval>allow. MVP approver = TTY prompt under proxy start; fails closed (deny) on timeout/no-TTY/no-approver. Outcomes audited as approval-granted/denied.
@Proxy is registered as both item and root decorator, but the header placement validator rejected any item-registered name, making detached rules (and header block/approve rules) unauthorable. Accept a name that is also a root decorator. Also fixes attached rules being dropped when a header @Proxy was present.
Replace the fail-closed block (session refused when a sensitive item wasn't @proxy-managed or @proxyPassthrough) with default-omit: such items are withheld from the child (dropped from vars + the __VARLOCK_ENV blob) with a notice. Least privilege by default; no need to annotate every secret to start a session. Rename getBlockedSensitiveKeys → getOmittedSensitiveKeys.
…itive vars Reword the default-omit message as a startup warning explaining the vars were omitted because no proxy policy is set for them.
Reserved _VARLOCK_* keys are varlock's own internal plumbing, not user secrets, so getOmittedSensitiveKeys skips them — they're never flagged as omitted/needing a rule and pass through as infrastructure (consistent with normal varlock run).
…ing @proxyPassthrough Adds isFunctionOrValue decorator capability: @Proxy can be a function (@Proxy(domain=...) to route) or a value (@Proxy=passthrough to inject the real value, @Proxy=omit to withhold explicitly). The two forms are mutually exclusive per item. Removes @proxyPassthrough. @Proxy=omit suppresses the no-policy omit warning.
…anding grants TTY prompt now offers [y]once [s]session [m]15min. Session/duration approvals persist as standing grants (per-session file, no secrets) so matching requests auto-approve without re-prompting. New approval-grants.ts decouples the grant store from the approver via createGrantingApprovalProvider, so the future phone/native-app approver reuses the same store. Enabled for proxy start; proxy run stays fail-closed.
…on cap @Proxy(approval=true) gains approvalEach (host|endpoint|request) and approvalMaxDuration (e.g. 15m, or 0=always-ask). Grants are keyed by rule + granularity so one rule yields fine-grained approvals; the lifetime is clamped to approvalMaxDuration proxy-side (schema is the ceiling). Renames approve→approval and runtime ApprovalScope→ApprovalLifetime. Interim flat props (object form when that branch lands).
…-defs + decorators) Rebuild buildProxySchemaFingerprint to hash per-item value definitions (pre-resolution) + all non-inert decorators + root decorators, canonical and location-independent. Add an 'inert' decorator flag (marks @example/@docs/@docsUrl/@icon/@deprecated) excluded from the fingerprint. Closes gaps the shape-only fingerprint missed (proxied→passthrough flip, domain/egress changes). Prereq for proxy run --session attach.
…o / --new) proxy run now attaches to a proxy start daemon for the current dir (single cwd-match, or --session <id>) instead of always spawning a fresh auto-deny proxy — so the daemon's terminal handles approvals. Validates the schema fingerprint and fails loudly on drift; --new forces a fresh proxy. Reuses the session's env + placeholders; no new runtime/approver in attach mode.
9822048 to
9a88dd1
Compare
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
varlock-docs-mcp | 9a88dd1 | Jun 16 2026, 10:40 PM |
Contributor
|
The changes in this PR will be included in the next version bump.
|
commit: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Local credential proxy for AI agents (Tier 0 of the proxy roadmap). A developer can run an agent through the local proxy so the agent only ever sees placeholder secrets, real secrets are injected at the wire bound to a verified upstream identity, responses are scrubbed, and every request is policy-checked, optionally gated on approval, and audited.
varlock proxycommand + sessions — proxy is its own command with a session registry;varlock runno longer takes--proxy(rejected with a clear error). Placeholder→real rewrite for items tagged@proxy, with placeholder-safe loading.proxy runattaches to a runningproxy startdaemon for the current directory (so its terminal handles approval prompts), validating the schema fingerprint and failing loudly on drift;--session <id>targets one,--newforces a fresh proxy.@proxy+ default-omit —@proxy(...)configures a rule; the value forms@proxy=passthrough/@proxy=omitopt a single item in/out (mutually exclusive with the function form on the same item). Unhandled sensitive items are omitted from the child env by default (warned, not blocked);_VARLOCK_*reserved keys are never proxied.block(deny) and scoped injection per request. Precedence is block > require-approval > allow.require-approvalverdict (Invariantvarlock initcommand #8) —@proxy(approval=true)holds a request for an out-of-band, request-bound approval (method + verified host + path + body-hash + nonce + expiry) before forwarding, so a future signed phone-approval relay drops in unchanged. MVP approver is a TTY prompt underproxy start; everything fails closed.approvalEach(host/endpoint/request) sets what one grant covers andapprovalMaxDurationcaps how long a "yes" is remembered — enforced (clamped) proxy-side, so the schema is the ceiling even if a client over-claims (0⇒ always-ask).varlock proxy audit [--session <id>] [--format text|json].~/.config/varlock/proxy/sessions/<uuid>/holding itssession.json,audit.jsonl, andgrants.jsonltogether. Stopping marks a session ended rather than deleting it;proxy statusshows active sessions by default,--allincludes ended ones.varlock loadformats), and a schema-fingerprint guard that blocks proxied loads if the schema changes after the proxy starts. The fingerprint covers the full definition (pre-resolution value sources + all non-inertdecorators), so flipping@sensitive/@proxy, egress, or a resolver is caught — cosmetic decorators (@example,@docs, …) are markedinertand ignored.@peculiar/x509); secrets stay on the host.Cert pinning and request re-signing (
pin/sign/transform) were removed as dead config — pinning is deferred for delegated external identity verification, and re-signing is designed as a future@proxyResigndecorator.Rebased onto latest
main; redaction conflicts resolved in favor of main's per-stream TTY auto-detect model.