Overview
You may want employees to use a subset of a third-party production API, such as Stripe, for a narrow set of tasks. Without Formal, this usually means giving them a production API key with broader permissions than you want to allow. That key then sits on their laptop, in a.env file or shell profile.
With Formal, you can use the and a to:
- Keep the API key off employee laptops. Only the Connector holds it.
- Let employees make authenticated requests to the Stripe API without a credential.
- Write fine-grained policies against every request between the laptop and Stripe.
curl on macOS.
How It Works
- The Formal Endpoint on macOS intercepts
curlrequests toapi.stripe.comwith a network rule. - The Endpoint forwards them to the Connector with the developer’s Formal identity.
- A on the Stripe injects
Authorization: Bearer <STRIPE_API_KEY>. - Stripe receives the real key. The laptop never does.
https://api.stripe.com. You do not change base URLs.
Prerequisites
- on macOS with Transparent Mode enabled
- A deployed the Endpoint can reach
- Stripe API Key
1. Store the key on the Connector
Set the key as theSTRIPE_API_KEY environment variable on the Connector.
2. Create the Stripe resource
- Control Plane
- Terraform
- Navigate to Resources and click Create Resource
- Set Technology to HTTP
- Set Name to
stripe - Set Hostname to
api.stripe.comand Port to443 - Save the resource
- Add a TLS configuration to the resource and select Verify Full
api.stripe.com before it sends the API key.
3. Add a Native User for the key
The Native User tells the Connector to setAuthorization: Bearer <key> on every Stripe request. It replaces any Authorization header the client sent.
- Control Plane
- Terraform
- Open the
striperesource and select Authentication - Click Add User and select HTTP Bearer
- Set Label to
stripe-api-key - Choose Static, set the header to
Authorization, and choose Environment Variable - Enter
STRIPE_API_KEYand click Create - Copy the Native User ID from its action menu
- Under Default Native User, paste that ID as the CEL expression and click Save
stripe-api-key Native User appears under Native Users with type HTTP Bearer and source STRIPE_API_KEY.
4. Make Stripe reachable through the Connector
- Control Plane
- Terraform
- Open your Connector and select Listeners
- Create a listener on an available port, such as
443. Port8080is reserved for health checks. - Open the listener, click New Rule, set the type to Resource, and select
stripe
stripe appears under the Connector’s Reachable Resources.
5. Forward Stripe traffic to the Connector
Create a network rule that interceptscurl requests to api.stripe.com and forwards them to the Connector.
Match curl by its code signing identifier in pre_tcp, and check the hostname in pre_tls. Find the identifier with codesign:
- Control Plane
- Terraform
- Navigate to Network Rules
- Create a rule named
stripe-curl - Paste the CEL below
- Save the rule and set it to Active
6. Restrict Stripe requests with a policy
The default Native User injects the key into every request that reaches thestripe resource. Add a policy that blocks every Stripe request except the ones your developers need.
This example lets the payments-engineers group read balances, charges, and customers. It blocks all other Stripe requests, including writes and requests from other users.
- Control Plane
- Terraform
- Navigate to Policies
- Click Create Policy
- Set name to
stripe-read-onlyand add a description - Paste the Rego below into the editor
- Click Create Policy to save
allowed_paths, the methods, and the group to match what your developers need. Find Stripe paths in the Stripe API reference. See HTTP object for all request fields.
Verify: A GET /v1/balance from a payments-engineers member succeeds. A POST /v1/refunds returns the Formal block message.
7. Call Stripe without a key
Remove Stripe keys from.env files, shell profiles, and CLI configs on the laptop. Then call Stripe with curl and no key:
- Open Logs and filter on the
striperesource - Confirm the request shows the developer’s Formal identity
- Confirm the response status is
200
Troubleshooting
Requests do not appear in Formal logs
Requests do not appear in Formal logs
Possible causes:
- Transparent Mode is off
- The network rule is in Draft
curlis not/usr/bin/curl, for example Homebrewcurl, so its code signing identifier differs
- Run
formal transparent-proxy status - Set the
stripe-curlnetwork rule to Active - Run
codesign -dvv $(which curl)and use itsIdentifierinpre_tcp
Next Steps
Stripe API Keys
Manage Stripe API keys
Native Users
Select credentials by identity or with hooks
HTTP Resources
Write policies for HTTP traffic
Network Rules
Choose which traffic the Endpoint intercepts
Encrypt MCP OAuth Tokens
Keep OAuth tokens encrypted on the device