Skip to content

feat(agent-proxy): add policy-mode agent proxy for agent and user policy intersection - #361

Open
saifsmailbox98 wants to merge 3 commits into
mainfrom
agent-policies
Open

feat(agent-proxy): add policy-mode agent proxy for agent and user policy intersection#361
saifsmailbox98 wants to merge 3 commits into
mainfrom
agent-policies

Conversation

@saifsmailbox98

Copy link
Copy Markdown
Contributor

Description 📣

Adds the runtime for agent policies: a long-standing agent proxy that brokers credentials on the intersection of an agent's policies and a user's.

infisical agent-proxy start --token=<enrollment-token> enrols against an agent proxy registered under Networking, swaps the one-time token for an access token (saved to ~/.infisical/agent-proxy/access-token so a restart doesn't need a new one), signs a MITM intermediate off the org's agent proxy CA, and serves an HTTP forward proxy.

An agent points HTTP_PROXY at it with its session token as the proxy credential. Per request, the proxy resolves that session against Infisical and allows the request only when it matches at least one rule on an agent policy and at least one on a user policy. Neither side is a subset of the other, so this is a per-request check and not set arithmetic: a user rule of GET narrows an agent rule of Any on the same host. The matching agent policy is the most specific one, and that is what decides whose credentials get injected.

  • policy_match.go — host patterns with scheme and HTTP method, and the intersection evaluator. A rule naming https refuses a plaintext request so a credential can't leave in the clear. CONNECT only carries host and port, so the tunnel opens on a host match and the real decision happens inside it once the method and path are known
  • policy_resolver.go — per-session cache with a bounded size, refresh on a poll, and batched activity reporting. A hard auth failure drops the session and fails closed, so revocation needs no invalidation call
  • policy_server.go — the proxy itself, reusing this package's existing ca.go and rewrite.go rather than forking them
  • Blocked requests get a uniform 403 that doesn't say which side of the intersection refused

Companion PR: Infisical/infisical#7646

Type ✨

  • Bug fix
  • New feature
  • Improvement
  • Breaking change
  • Documentation

Tests 🛠️

Unit tests cover the evaluator, the pattern matcher and the proxy-auth parsing (policy_match_test.go), including the case the whole model rests on: agent allows Any, user allows GET, so the POST is refused and the GET is brokered.

Driven end to end against a local stack with one agent policy (Slack, any method) and one user policy (Slack, GET only):

GET  api.slack.com/api/auth.test        -> brokered, 200
POST api.slack.com/api/chat.postMessage -> 403, "no user policy allows this request"
CONNECT api.github.com                  -> 403, "no policy covers this host"
GET  registry.npmjs.org/express         -> passthrough, no credential (allowlist)

Substitution verified on the wire rather than inferred: with no credential Slack answers not_authed, and when the agent sends the placeholder it answers invalid_auth, so the upstream received a token the agent never held.


@infisical-review-police

Copy link
Copy Markdown

💬 Discussion in Slack: #pr-review-cli-361-feat-agent-proxy-add-policy-mode-agent-proxy-for-agent-and-use

Posted by Review Police — reviews, comments, new commits, and CI failures will stream into this channel.

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a persistent policy-mode agent proxy that enrolls with the backend, resolves and caches agent/user policy intersections, injects credentials for authorized requests, and reports activity.

  • Adds host, scheme, port, path, and method policy matching with specificity selection.
  • Adds session resolution, bounded caching, policy refresh, revocation handling, and batched activity reporting.
  • Adds CONNECT and plaintext forward-proxy handling backed by the existing MITM CA and credential rewriting components.
  • Adds API models and calls for enrollment, session resolution, heartbeat, activity reporting, and session creation.
  • Registers the new top-level infisical agent-proxy start command and persists its access token locally.

Confidence Score: 2/5

This PR should not merge until the reusable proxy session credential is protected in transit and hop-by-hop response headers are filtered.

The new proxy listens on every interface without TLS while accepting reusable Basic or Bearer session credentials, and its response path forwards connection-specific headers that a proxy must consume.

Files Needing Attention: packages/agentproxy/policy_server.go and packages/agentproxy/policy_match.go

Security Review

The new server exposes reusable session credentials over a plaintext all-interface listener. Its hostname-only outbound authorization also lacks resolved-IP restrictions, leaving a DNS-rebinding SSRF hardening gap.

Important Files Changed

Filename Overview
packages/agentproxy/policy_server.go Implements the policy proxy runtime, but exposes session authentication over a plaintext all-interface listener and omits response hop-by-hop filtering.
packages/agentproxy/policy_match.go Implements policy parsing, matching, intersection evaluation, and agent-policy specificity; hostname authorization does not bind decisions to resolved destinations.
packages/agentproxy/policy_resolver.go Adds bounded session caching, active-session refresh, revocation eviction, and batched activity reporting without an accepted finding.
packages/api/agent_policies.go Adds typed request and response models for agent-proxy enrollment, heartbeat, session resolution, activity, and creation endpoints.
packages/cmd/agent_proxy_server.go Registers and configures the policy-mode proxy command, enrollment-token persistence, logging, and startup options.
packages/agentproxy/policy_match_test.go Covers pattern parsing, scheme and method restrictions, wildcard behavior, policy intersection, specificity, allowlisting, and proxy-auth parsing.

Reviews (1): Last reviewed commit: "feat(agent-proxy): add policy-mode agent..." | Re-trigger Greptile

return fmt.Errorf("failed to get an intermediate CA signed by Infisical: %w", err)
}

listener, err := net.Listen("tcp", fmt.Sprintf(":%d", opts.Port))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Plaintext proxy authentication listener

When an agent connects across a shared or untrusted network, this all-interface plaintext listener exposes its reusable Basic or Bearer session token to on-path observers, allowing them to submit policy-authorized requests through the proxy.

How this was verified: The listener binds :<port> and serves a plain http.Server before sessionToken reads Proxy-Authorization.

Rule Used: TLS should be enabled by default for security best... (source)

Learned From
Infisical/cli#58

Knowledge Base Used: Agent Proxy Module

decision = "brokered"
}

resp, err := ps.transport.RoundTrip(r)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security Hostname-only destination authorization

If an allowed hostname resolves or rebinds to a loopback, link-local, or private address, the policy check still authorizes the textual hostname and the transport dials the internal destination, making the proxy an SSRF pivot and potentially sending an injected credential there.

How this was verified: The matching path checks only hostname strings, while RoundTrip performs DNS resolution without a guarded dialer or resolved-IP validation.

Context Used: Flag SSRF risks (source)

Knowledge Base Used: Agent Proxy Module

Comment on lines +372 to +376
for name, values := range resp.Header {
for _, value := range values {
w.Header().Add(name, value)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Hop-by-hop response headers forwarded

When an upstream response includes Connection or another hop-by-hop header, this loop forwards it unchanged instead of consuming it at the proxy boundary, causing downstream response-framing or connection-reuse failures.

Knowledge Base Used: Agent Proxy Module

return fmt.Errorf("failed to get an intermediate CA signed by Infisical: %w", err)
}

listener, err := net.Listen("tcp", fmt.Sprintf(":%d", opts.Port))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: Session tokens exposed on the proxy connection

This listener accepts plain HTTP on every interface, and Proxy-Authorization is transmitted before the inner CONNECT tunnel is established. An observer between an agent and the proxy can capture and replay the session token to issue requests under that session; protect the ingress with TLS or mTLS rather than relying on the tunneled upstream TLS.

func (p policyPattern) match(scheme, host, port, path, method string) (bool, matchDetail) {
detail := matchDetail{}

if p.scheme != "" && p.scheme != strings.ToLower(scheme) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: Scheme-less policies permit plaintext credential injection

An authenticated agent can select an http:// URL for any rule whose host pattern omits a scheme, causing the proxy to inject the policy's credential into a plaintext upstream request. Treat omitted schemes as HTTPS for credential-bearing policies, or require an explicit scheme and keep bare-host allowlist matching separate.

Comment thread packages/agentproxy/policy_server.go Outdated

resp, err := ps.transport.RoundTrip(r)
if err != nil {
ps.record(session, "error", r.Method, hostname, port, r.URL.Path, http.StatusBadGateway, policyName(matched), err.Error())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: Substituted credentials are written to activity records

applyCredentials can replace a placeholder in r.URL.Path, after which this branch and the success branch record the mutated path; transport errors may also include the rewritten URL in err.Error(). An agent can therefore force a path-substitution secret into local logs and the activity API. Snapshot the original escaped path before applying credentials and record only that value, with a sanitized transport-error reason.

@veria-ai

veria-ai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

PR overview

This pull request adds a policy-mode agent proxy that intersects agent and user policies when authorizing and forwarding requests. It also supports policy-based credential application and activity recording.

Four security issues remain open, including a path canonicalization flaw that can bypass policy restrictions and send injected credentials to an unintended upstream path. Plaintext proxy ingress and scheme-less policy matching can also expose replayable session tokens or policy credentials, while substituted credentials may leak into activity records and errors. No reported issues have yet been addressed.

Open issues (4)

Fixed/addressed: 0 · PR risk: 7/10

return
}

matched, matchedUser := evaluate(session.agentPolicies, session.userPolicies, scheme, hostname, port, r.URL.Path, r.Method)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: Uncanonicalized path bypasses policy rules

A request such as /allowed/../admin or its percent-encoded equivalent matches an /allowed/* rule, but an upstream that normalizes dot segments can process it as /admin with the injected credential. Reject non-canonical paths before policy evaluation, or canonicalize once and use the same canonical path for both authorization and forwarding.

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.

1 participant