Skip to main content

Overview

Cloud agents such as Cursor Cloud Agents and Devin often need API keys to run and test your application. Teams usually store those keys as environment secrets. Every agent session can then read the raw key, print it, or send it anywhere. With Formal, the key never enters the sandbox:
  1. The runs inside the agent sandbox and authenticates with the agent’s short-lived OIDC token.
  2. A network rule intercepts requests to the API and forwards them to a .
  3. A on the Connector injects the key.
  4. restrict which requests the agent can make. Every request is logged with the agent and the human who started it.
The agent keeps calling the real API hostname. You do not change base URLs or client code. This guide uses the Stripe API with a bearer key. The same setup works for any HTTP API and for every Native User type, including Basic credentials, header and query API keys, and keys fetched from a vault by a hook.

Prerequisites

  • A deployed with a hostname that the agent sandbox can reach
  • A Cursor Cloud Agent environment or a Devin environment you can edit
  • A Formal machine user for your cloud agents
  • Permission to create OIDC integrations, resources, network rules, and policies in Formal
  • A Stripe API key

1. Store the key on the Connector

Set the key as the STRIPE_API_KEY environment variable on the Connector. Remove it from your Cursor and Devin secrets.

2. Create the Stripe resource and Native User

  1. Navigate to Resources and click Create Resource
  2. Set Technology to HTTP, Name to stripe, Hostname to api.stripe.com, and Port to 443
  3. Save the resource, then add a TLS configuration and select Verify Full
  4. Select Authentication, click Add User, and select HTTP Bearer
  5. Set Label to stripe-api-key
  6. Choose Static, set the header to Authorization, and choose Environment Variable
  7. Enter STRIPE_API_KEY and click Create
  8. Under Default Native User, enter this CEL expression and click Save
The Native User replaces any Authorization header the agent sends. Without a default Native User, the Connector forwards requests without a key, and Stripe returns 401. Verify: The stripe-api-key Native User appears under Native Users with type HTTP Bearer and source STRIPE_API_KEY.

3. Make Stripe reachable through the Connector

  1. Open your Connector and select Listeners
  2. Create a listener on an available port, such as 443
  3. Open the listener, click New Rule, set the type to Resource, and select stripe
The Endpoint forwards to the Connector’s hostname on this listener port. Verify: stripe appears under the Connector’s Reachable Resources.

4. Configure the OIDC integration

The Endpoint in the sandbox exchanges the agent’s OIDC token for a Formal machine session. No Formal API key is stored in the sandbox. Go to OIDC Integrations and click Create Integration. Select your provider’s preset, then select the machine user for your cloud agents.
Replace your-team-id in the Cursor preset claim condition. You can also restrict it to one repository:
Keep the preset End-User Email Expression, claims.owner_email. Formal then logs each request with the human who started the agent.
Copy the generated integrationoidc_... ID and the machine user’s user_... ID. To block the machine user from every other Control Plane operation, follow Restrict Control Plane access.

5. Forward Stripe traffic to the Connector

Create a network rule that intercepts requests to api.stripe.com from the cloud agent machine user and forwards them to the Connector. On macOS, pre_tcp checks the Endpoint identity, so Endpoints logged in as anyone else leave their traffic alone. Linux ignores pre_tcp, so pre_tls checks the identity again along with the hostname.
  1. Navigate to Network Rules
  2. Create a rule named stripe-cloud-agents
  3. Paste the CEL below and replace <CLOUD_AGENT_MACHINE_USER_ID>
  4. Save the rule and set it to Active
Forward to Connector must be true. Without it, the Endpoint evaluates the request locally, and no Native User injects the key.

6. Bake the Endpoint into the agent environment

Install the Endpoint when the environment image is built. The sandbox needs outbound access to:
  • api.joinformal.com, for authentication, network rules, and logs
  • Your Connector hostname, on the listener port from step 3
  • The Endpoint package download link, while the image is built
The Connector, not the sandbox, opens the connection to api.stripe.com. Each setup below downloads the headless Debian package from a link you copy from the Control Plane. Click Download Formal Endpoint at the bottom of the sidebar and open Linux. Right-click the headless package for your architecture and copy the link:
  • Ubuntu/Debian (x86, headless) for amd64
  • Ubuntu/Debian (ARM, headless) for arm64
Replace <FORMAL_ENDPOINT_PACKAGE_URL> below with the copied link and <INTEGRATION_ID> with the integration ID from step 4. To upgrade the Endpoint, copy the link again and update your configuration.
Node.js and most Python HTTP clients ignore the system trust store. The setups below point them at /etc/ssl/certs/ca-certificates.crt, which includes the Formal local CA once the Endpoint trusts it. See Runtime TLS trust for the variables other clients read.
Cursor environment builds keep installed files, but not running processes. The install script runs once per build. A named terminal starts the Endpoint again in every Cloud Agent run. The Cursor OIDC socket is local and does not need outbound access.
1

Create the install script

Add .cursor/install-formal-endpoint.sh:
2

Create the runtime script

Add .cursor/start-formal-endpoint.sh:
Cursor sets CURSOR_AGENT_SOCKET on Cloud Agent VMs. Do not hard-code its path, because self-hosted workers use a different one. The retry covers the moment before Cursor creates the socket during boot.
3

Make the scripts executable

4

Update the environment configuration

Merge these fields into .cursor/environment.json:
Commit all three files. New Cloud Agents build from the repository-managed configuration.You can instead give these steps to Cursor’s guided environment setup under Cloud Agents → Environments. Review the generated configuration before you activate the build.

7. Call Stripe without a key

Start a new Cursor Cloud Agent or Devin session. Confirm the Endpoint is signed in and intercepting:
formal auth whoami should show the cloud agent machine user, and Transparent Mode should be enabled. Then call Stripe with no key:
The response should be the account balance. Verify:
  1. Open Logs and filter on the stripe resource
  2. Confirm the request shows the cloud agent machine user and the human who started the session
  3. Confirm the response status is 200
  4. Run env | grep -i stripe in the sandbox and confirm no key is set

8. (Optional) Restrict Stripe requests with a policy

The Native User injects the key into every request that reaches the stripe resource. To limit what agents can do with it, add a policy. This one only allows reads of balances, charges, and customers:
  1. Navigate to Policies and click Create Policy
  2. Set name to stripe-cloud-agents-read-only
  3. Paste the Rego below and click Create Policy
Create the policy in Dry-run first. Set it to Active after reviewing the matching requests. See HTTP object for all request fields. Verify: With the policy active, curl -X POST https://api.stripe.com/v1/refunds returns the Formal block message.

Troubleshooting

Possible causes:
  • The network rule is in Draft or does not match the machine user
  • Forward to Connector is not set, so the request never reaches the Connector
  • The resource has no default Native User, or STRIPE_API_KEY is not set on the Connector
Fix: Confirm the request appears in Logs under the stripe resource. If it does not, check the network rule. If it does, check the Native User and the Connector environment.
Run:
Open a new shell so it loads ~/.bashrc, then retry. Do not disable TLS verification.
Confirm the sandbox can reach the Connector hostname on the listener port. With fail = "closed", a matched request fails when the Endpoint cannot reach the Connector.
For Cursor, start the Endpoint from terminals, not install, because install processes do not survive the build. For Devin, confirm lingering with loginctl show-user "$(id -un)" and read journalctl --user -u formal.service. In both cases, confirm the integration ID has no oidc.formal.ai/ prefix.

Next Steps

Native Users

Inject other credential types or fetch them with hooks

Block Agent Auto-Merge

Enforce GitHub policy inside cloud agents

Devin Database Access

Approve Devin database access in Slack

Network Rules

Choose which traffic the Endpoint intercepts