Agent security architecture

How to Connect AI Agents to Internal and On-Prem Applications Without a VPN

Give AI agents access to internal APIs and on-prem apps without a VPN or inbound ports, with per-agent permissions, approval for risky writes and audit.

Updated Oct 28, 2026

An AI agent that runs in the cloud cannot see applications that live only inside your network. A VPN or a tunnel can make them reachable, but reachable is not the same as controlled. This guide separates the two problems, walks through the common workarounds, and shows what governed access to an internal application looks like: an outbound-only connector, credentials that stay inside, permissions per agent and per action, approval for risky writes, and a record that the agent did not write itself.

Your AI agent is ready. Your internal systems are still out of reach.

A procurement agent spots the parts that need reordering. To finish the job, it has to check live stock and create a purchase order. Both live in the ERP inside your company network.

So someone still opens the ERP, looks up the records and types in the order.

To complete that work, the agent needs access to the internal system. And your team needs to decide exactly what it may do there: read stock, create an order with approval, never delete a supplier.

Contro1 connects those steps. A connector inside your network runs approved operations over an outbound HTTPS connection. You choose which actions each agent can use and which need a person's approval, and every call is recorded.

Watch an agent request a purchase order, a person approve it, and the connector run it inside the network:

An agent asks, a person approves, and the connector runs it inside the network: Claude Code interface recreated for illustration. Demo organization and data are fictional.

The same wall shows up for every team. On your laptop, the agent works. It sees what your machine sees: the VPN, the internal DNS, the staging API, the inventory system on the office network.

Then the agent moves. It becomes a cloud production agent, a scheduled job, or a coding agent running in a cloud sandbox. Suddenly the internal API does not resolve. The database is not there. The ERP is "intranet-only".

This is the question teams keep asking, in their own words: how do we let an agent reach internal APIs, VPN-only resources and private DNS, without making those systems public?

It shows up in two groups.

Agents that need internal systems

Platform, AI and IT teams want agents to read and update the ERP, the ticketing system, an internal REST API or a database. Their first question is control: who is allowed to make what happen.

Coding agents that need internal services

Developers using Claude Code, Cursor or Codex want the agent to reach a staging API, an internal tool or a private registry. Their first question is reach: why can't the agent see what my laptop sees.

Two problems that look like one: reachability and control

Most advice mixes these up. Separating them makes the choices much clearer.

ReachabilityControl
The questionCan the agent get a packet to the application?Should this agent do this action, on this record, right now?
Typical toolsVPN, tunnels, zero trust network access, a worker inside the networkPer-agent permissions, policy before execution, approval, audit
What it knows aboutHosts, ports, networks, devicesAgents, people, actions, arguments, risk
When it is missingThe agent fails with a timeout or a DNS errorThe agent succeeds at something nobody wanted

A network tool answers "may this machine reach that host". It does not know that the call is a DELETE on a customer record, which agent sent it, or who asked the agent to do it.

That is why security teams say permissions should be enforced outside the agent, at the tool or execution layer. The model decides what it wants to do. Something else decides whether it may.

For production agents: one workflow, end to end, without a VPN in the cloud

Take the procurement agent. It runs in the cloud. The Plant ERP runs on a server at the plant and has no public API. The agent should not get a VPN, and the ERP should not go on the internet.

Here is what the team decides, action by action:

Plant ERP actionFor the procurement agent
Read stock levelsRuns on its own. Reading is routine.
Create purchase orderWaits for Finance. A person approves each order before it runs.
Cancel purchase orderNot granted. The agent never sees it.
Delete supplierNot granted. Not even published as an Action.

With a connector, the cloud agent calls an Action. Contro1 checks the grant and the policy. The connector inside the network runs the one operation. The agent gets the answer.

The approver sees the request in plain words, with what the person asked and the reason the agent gave. They approve or reject. Only then does the job reach the connector.

How it works in Contro1

You install a small connector on a machine inside your network. Your agent never talks to the internal system directly. It asks Contro1, Contro1 decides, and the connector does the work inside. Here is the path of one call.

A few details matter to the people who will review this:

  • Outbound only. The connector asks Contro1 for work over HTTPS and waits for the answer, so a job reaches it without any connection coming in. No inbound firewall rule. It works through a corporate proxy.
  • Your address and credential stay with you. The internal address is written into the setup file in your browser, and an API key is typed into that file on the connector host. Contro1 never receives either.
  • One connector per computer. A connector serves every application set up on its computer, each with its own setup file.
  • The connector runs only reviewed operations. Each published operation is pinned by a fingerprint. A job cannot carry a new URL, a header or code. If it does not match, it does not run.
  • Jobs are signed. The connector checks each job's signature before it runs it. The result it sends back is signed too.
  • No automatic retry for an uncertain write. The connector writes a journal before every write. If it restarts mid-call, that write is reported as uncertain and is not retried automatically. A person decides.
  • What passes through Contro1. The Action's inputs and the application's answer travel through Contro1, so it can check policy and give the agent the result. Results are size-limited. The internal address and the credential never leave your host.
  • Offline means a clear failure. If no connector is online, the agent gets an immediate error, not a silent wait.
  • Two records, kept apart. Contro1 records what it authorized. The connector reports what the application answered. You can see both.

On-prem is one more app. The whole control layer comes with it.

The connector is how Contro1 reaches your internal application. Once it is published, that application lives in the same place as every other app your agents use.

That place is the same for every agent in the organization: production agents, coding agents like Claude Code, Codex and Cursor, and personal assistants. Instead of handing each agent its own key to an internal API, every agent reaches it through Contro1.

So the rules your organization already set apply to it too: who owns each agent, what each department, person or agent may do, which steps wait for a person, which instructions agents follow, and one record of everything.

For coding agents: reach internal services without handing over your access

Developers hit this first. The agent in the cloud sandbox has "no local tools, no MCP servers, none of your actual environment". The usual fix is to give the agent your VPN, your SSH keys, or an API key to the internal system.

That works. But nobody decides which records it may change, nobody approves the risky step, and nobody can tell later what it did.

With Contro1, a coding agent gets the same controls as a production agent, from the same place:

Internal APIs only through granted Actions

The coding agent connects to Contro1 over MCP and calls only the Actions it was granted. Reading order status can run at once. Changing a record waits for you or your lead. It never holds the internal address or the key.

Risky steps on the machine wait for a person

Hooks for Claude Code and Codex send the actions you select, such as deploys, database operations, infrastructure changes or privileged tools, to an approver before they run. Organization-managed settings keep that rule in place for every developer.

What it did is on the record

Each tool call is reported to Contro1, one trace per run, next to the Actions it ran. You see who asked, which agent, what ran and who approved.

Its own identity, not yours

Connecting registers the coding agent as an agent of its own, with its own grants in Access. Your personal access does not flow to it.

A coding agent reaches internal services throughWhat it can touch
Your VPN or your laptopWhatever your own access allows on the systems it reaches. No approval, no record.
An API key in its environmentEverything the key allows, for anyone who runs the agent
A shared service accountEverything that account can do, for every developer
Granted Actions through Contro1Only the operations you granted, approval where you set it, every call recorded
Connect a coding agent
contro1 connect claude-code   # or: contro1 connect codex, contro1 connect cursor

What you need to start

Three things. Most teams have them on day one.

An internal application with an API

REST over HTTPS, or plain HTTP on the same host. An OpenAPI file helps. A PDF of the API documentation also works.

A server that is always on

Best the application's own server: Windows Server 2016 or later, a Mac, or a Linux machine with systemd. Not someone's laptop, because calls fail while it is off. It needs outbound HTTPS to api.contro1.com. Nothing inbound.

The first two or three actions

Pick one task, such as reading stock and creating a purchase order. Start with reads, then add writes with approval.

Install steps for your IT team

The connector runs on a server inside your network. Send this section to the person who manages it. With the checks, it takes about 15 minutes.

Two computers, two jobs. On a computer where you are signed in to Contro1, you make the setup file in the on-prem builder. On the server, you install the connector with that file. The server needs no browser and no Contro1 sign-in. Do not run the install on an employee's computer: the connector looks for the application on the computer it runs on.

Which server. The server the application runs on: Windows Server 2016 or later, a Mac, or a Linux machine with systemd. If the application answers over plain http, this is required: the connector sends plain http only to the computer it runs on, at 127.0.0.1. Over https, any always-on server that reaches the application works.

Check first, on that server. The three checks below take two minutes: outbound HTTPS to Contro1, the application's port if you do not know it, and the application answering at 127.0.0.1. If outbound internet is restricted, allow HTTPS to api.contro1.com on port 443. Nothing inbound.

The setup file. In Contro1, open the application in the on-prem builder and type the address it answered on in check 3, for example http://127.0.0.1:3001/api/. Download the file and copy it to the server, into one folder, through Remote Desktop or your remote support tool. The code inside works once, for 10 minutes, so download it when the person on the server is ready. If the application needs an API key, type it into the file on the server. It never leaves it.

Link to send to your IT team
https://contro1.com/resources/connect-ai-agents-to-on-prem-applications#install-steps-for-your-it-team
Checks on the server (Windows)
# On the server, in PowerShell opened as Administrator

# 1. Outbound HTTPS to Contro1. Look for: TcpTestSucceeded : True
Test-NetConnection api.contro1.com -Port 443

# 2. Do not know the application's port? Which program listens on which port:
Get-NetTCPConnection -State Listen | Select-Object LocalPort, @{n='Program';e={(Get-Process -Id $_.OwningProcess).ProcessName}} | Sort-Object LocalPort -Unique

# 3. The application at 127.0.0.1, with its own port and path. Look for: StatusCode : 200
#    401 or 403 means it needs a key: add  -Headers @{KEY='<the key>'}
Invoke-WebRequest http://127.0.0.1:3001/api/ -UseBasicParsing

Then install. Step 1 downloads the Contro1 CLI, which needs outbound access to contro1.com and github.com as well. If the server cannot reach them, copy the CLI in instead, as shown under the steps.

Windows
# In PowerShell opened as Administrator, in the folder with the setup file

# 1. Install the Contro1 CLI
irm https://contro1.com/install.ps1 | iex

# 2. Install the connector as a Windows service
Get-Content .\contro1-<application>-connector.json -Raw | contro1 private-connector install

# 3. Check that it is connected
contro1 private-connector status
macOS
# In the folder with the setup file

# 1. Install the Contro1 CLI
curl -fsSL https://contro1.com/install.sh | sh

# 2. Install the connector as a background service
sudo "$(command -v contro1)" private-connector install < contro1-<application>-connector.json

# 3. Check that it is connected
sudo "$(command -v contro1)" private-connector status
Linux
# In the folder with the setup file

# 1. Install the Contro1 CLI
curl -fsSL https://contro1.com/install.sh | sh

# 2. Install the connector as a systemd service
sudo "$(command -v contro1)" private-connector install < contro1-<application>-connector.json

# 3. Check that it is connected
sudo "$(command -v contro1)" private-connector status

When status shows Cloud: connected_authenticated, the connector is online. Provider: reachable means the application answered at its address. The service starts again on its own after a restart. Nothing changes on the firewall.

In Contro1, Apps shows the application's state and the server it runs through, such as Working, Connected, address answers or Cannot reach the application, with the reason and what to do. The Connectors list in Apps shows each server and the applications it serves.

Then give it to agents. Until Access says so, nobody can use the application. In Access, give an agent one read first, such as listing records, run it once, and check the result in Activity. Then add writes, with approval.

Another application on the same server: download its setup file and run step 2 again. The same connector serves both.

Behind a corporate proxy, add a proxy_url line to the setup file before step 2. It applies only to traffic to Contro1.

Delete the setup file after installing. Its code is used up, and if you typed a key into it, the connector already keeps it.

When the server cannot download the CLI (Windows; macOS and Linux use the .tar.gz of the same release)
# When the server cannot download from github.com:
# 1. On another computer, open github.com/contro1-hq/contro1-cli/releases/latest
#    and download contro1_<version>_windows_amd64.zip
# 2. Take contro1.exe out of the zip and copy it to the server,
#    into the folder with the setup file
# 3. On the server, in PowerShell opened as Administrator, in that folder:
.\contro1.exe --version
Get-Content .\contro1-<application>-connector.json -Raw | .\contro1.exe private-connector install
.\contro1.exe private-connector status

What people run into most, and what to do:

What you seeWhat to do
Invalid URI: Invalid port specified in check 3The example port is still in the command. Type the application's own port, from check 2 or from its link.
Cannot find path ...connector.jsonThe setup file is not in this folder. Go to the folder you copied it to, or run dir *connector*.json there to see its exact name.
contro1 is not recognizedRun step 1 again in this window, or open a new PowerShell window as Administrator.
invalid character when installingThe CLI is older than 0.2.33. Run step 1 again; contro1 --version shows the version.
contro1 is running, so ... cannot be replacedYou are on a computer where Claude Code, Codex or Cursor runs Contro1, usually an employee's computer. Install on the server instead.
The code expired or was usedDownload a new setup file in the builder and run step 2 again. A file that was not installed stops working.
Cloud is not connected_authenticatedOutbound HTTPS to api.contro1.com on port 443 is blocked. Allow it, or add proxy_url to the setup file.
Cannot reach the application in AppsRefused: the application is stopped or the port is wrong. No answer: the address is wrong. Fix it in the builder and install a new setup file.
The application is in Apps but not offered in AccessAsk your Contro1 contact. The application's operations must be allowed for the organization; publishing does this.

Step by step: from an internal API to an Action an agent can use

This is the setup, from the first document to the first approved call.

Describe the application

Start a new app and choose "inside your network". Upload an OpenAPI 3 document, list the operations by hand, or send the vendor's API documentation as a PDF for us to read with you.

Review every operation

Each operation becomes a draft with its method, path, inputs, effect and risk. A POST is at least a medium-risk write. A DELETE is high-risk. Anything unclear stays marked "needs verification" and cannot be published.

Publish a version

Publishing freezes the reviewed operations. The app now appears in Apps, next to your other applications.

Install the connector

Type the internal address and download the setup file on your computer, copy it to the application's server, and install it there, adding the API key there if it needs one. It runs as a Windows service, a macOS daemon or a Linux systemd service. The install steps for your IT team are above.

Grant Actions to agents

In Access, choose which agents may use which Actions. A catalog entry alone grants nothing.

Set approval for risky writes

Decide which Actions run on their own and which wait for a person, and who that person is.

Watch the first runs

Every call appears in Activity: the agent, the Action, who approved it, and the result the connector reported.

Steps 1 to 3, in Contro1:

Add an on-prem application, review its operations and publish: Demo organization and data are fictional.

Step 4, the connector:

Install the on-prem connector with a one-use installation code: PowerShell interface recreated for illustration. Demo organization and data are fictional.

The common workarounds, and where they break

Every team we talk to has tried at least one of these. Each solves part of the problem.

WorkaroundWhy teams choose itWhere it breaks
VPN on the agent hostUses what already exists. Simple routing.Hard to run for many agents. Network rules decide which hosts it can reach, not which business actions it may take. Route changes can drop running sessions.
Run the agent inside the networkPrivate DNS and internal APIs just work.You now operate agent compute: patching, sandboxing, capacity. The agent still has whatever the host can reach.
Tunnel to the internal serviceOutbound connection, no inbound port. Tools such as Cloudflare Tunnel or ngrok make this quick.Makes the service reachable. You still need identity, per-action policy and approval on top, or reachable becomes callable by anything that holds the address.
Service account or API key in the agentEasy. Familiar.Keys spread across configs and prompts. The agent inherits more authority than the task needs.
Custom proxyInject credentials, limit endpoints, log calls.Becomes its own product: auth, rotation, retries, policy, audit, uptime.
MCP gatewayOne place for auth, routing, secrets and logs.Many gateways assume the tools are reachable from the cloud. The internal network part is often left to you.

What governed access to an internal application looks like

Here is a checklist built from what engineers and security teams actually ask for. Use it to judge any approach, including ours.

No inbound ports, no VPN on the agent

A small service inside your network makes outbound HTTPS calls only. Your firewall stays closed to incoming traffic.

The credential never leaves your host

The internal address and the API key are entered on your machine. The agent never sees them. The cloud never stores them.

The agent has its own identity

A person may be allowed to do something. That does not mean every agent acting for that person should inherit the same authority.

Permissions per action, not per server

"Can use the ERP" is too coarse. Read stock levels: yes. Create a purchase order: with approval. Delete a supplier: no.

Policy runs before the call, not in the prompt

The model is not trusted to enforce its own limits. Every call is checked against policy before anything reaches the application.

Approval for high-impact writes only

A person approves the writes that matter. Reads and routine actions run on their own, so approval does not become a button people click without reading.

An uncertain write is never retried on its own

If the connection drops in the middle of a write, nobody knows if it happened. That write is marked uncertain and a person decides. It is not retried automatically.

A record the agent did not write

Who asked, which agent, which action and arguments, which policy, who approved, and what the application answered.

OWASP Top 10 for Agentic Applications · NIST: AI Agent Standards Initiative

Questions your security team will ask

Bring these answers to the review. They are the questions we hear most.

QuestionAnswer
Do we open any inbound port?No. The connector makes outbound HTTPS calls only.
What must the firewall allow out?HTTPS to api.contro1.com on port 443, from the connector's server. The one-line install also downloads from contro1.com and github.com; copying the CLI in avoids that. Traffic to your application stays inside your network.
Does the cloud store our internal address or API key?No. They are entered and stored on the connector host.
What data passes through Contro1?The Action's inputs and the application's answer, so Contro1 can check policy and return the result to the agent. Results are size-limited. The address and the credential do not.
Can someone send the connector an arbitrary request?No. It runs only published operations pinned by fingerprint. Jobs carry inputs, not URLs, headers or code.
What if a job is forged or replayed?Jobs are signed and checked before they run, and each job is handed out once.
What if the connection drops during a write?The write is marked uncertain and never retried automatically. A person decides.
How is the connector host protected?It runs under its own service identity. On Windows, secrets are sealed to the machine and the state folder is limited to the service and administrators.
Can we use our corporate proxy?Yes, for the connector's traffic to Contro1. Traffic to your internal API stays on your network.
How do we turn it off?Revoke the agent's grant in Access, or stop the connector. Calls fail at once.

Model Context Protocol: Authorization

When you do not need this

If one developer needs a coding agent to reach a staging server for an afternoon, an SSH session or a private network tool is fine.

If the agent only reads public data, or the application already has a public API with good scopes, connect it like any other app.

If you want to turn an existing internal API into tools for many agents, for example one that runs on your own servers in AWS, Azure or GCP, the OpenAPI guide covers that case.

You need governed access when the agent writes to an internal system, more than one person or agent relies on it, it runs without someone watching, or you will need to explain later what it did and who allowed it.

Turn an internal API into AI agent tools, without building an MCP server · Armaor MCP: AI agents in Armaor rebar production software, through this connector

Which internal system is holding up your AI workflow?

Getting a packet from a cloud agent to an internal application is a solved problem. The open problem is what happens next: which agent is calling, for whom, which action, and who said yes.

Tell us which application your agent needs and the first task you want it to complete. We will review the API and deployment requirements with you, then scope an on-prem pilot around that workflow.

Frequently asked questions

How do I connect an AI agent to an internal API that is not on the internet?

Install a connector inside your network that makes outbound HTTPS calls only. The agent calls an Action in the cloud, policy and approval are checked there, and the connector runs the matching operation against your internal API. No inbound port and no VPN on the agent host.

Can an MCP server or AI agent work behind a firewall without opening ports?

Yes, if the component inside the firewall initiates the connection. An outbound-only connector pulls work from the cloud and sends back results, so the firewall never accepts incoming traffic.

Is a VPN enough to give AI agents access to on-prem applications?

A VPN gives reachability. It lets the agent host reach the network. It does not decide which actions the agent may take, which records it may change, or when a person must approve. You still need permissions per agent and per action, approval for risky writes, and an audit trail.

How do coding agents like Claude Code or Cursor access internal services safely?

Avoid giving the agent your VPN, SSH keys, an API key or a shared service account. Instead, connect the coding agent to a control layer over MCP and grant only the internal Actions it needs, with approval for writes. The agent never holds the internal address or credential, and every call is recorded.

Can I stop a coding agent from running dangerous commands?

Yes, for the actions you select. In Contro1, hooks for Claude Code and Codex send them, such as deploys, database operations or infrastructure changes, to an approver before they run, and organization-managed settings keep the rule in place for every developer. Each tool call is also reported, so you can see what the agent did.

Where should an AI agent's permissions be enforced?

Outside the agent, before the call reaches the application. The model can propose an action, but a separate layer should check the agent's identity, the person it acts for, the action, its arguments and the policy, and stop or route it for approval.

Which agent actions on internal systems should require human approval?

Writes with real impact: creating orders or payments, changing customer or financial records, deleting anything, and actions that are hard to undo. Reads and routine low-risk actions usually run on their own, so approvers are not flooded.

What happens if the connection drops while an agent is writing to an internal system?

In Contro1 the connector writes a journal entry before each write. If the call may have reached the application but no answer came back, the write is reported as uncertain and is never retried automatically. A person checks and decides.

Do I need an OpenAPI file to connect an internal application?

No. You can upload an OpenAPI 3 document, list the operations by hand, or send the vendor's API documentation as a PDF. In every case each operation is reviewed before it can be published and used by agents.

Related resources