Skip to main content

Overview

You may want Devin to query a database while it debugs or answers a question. A shared database password in the Devin environment would give every session standing access, with no record of who asked for it. With Formal, Devin authenticates with its own short-lived OIDC token. Formal maps the token to a machine user and to the human who started the session. A blocks the machine user by default. When Devin needs data, it requests access, and the requester approves it in Slack. The approval suspends the block for that requester and Devin session only, and expires after the requested duration.

Prerequisites

  • A Postgres behind a Formal listener that the Devin VM can reach
  • A Slack integration in Integrations, with every Devin user in your Slack workspace
  • A Formal user for every Devin user, with the same email address
  • Permission to create machine users, OIDC integrations, Permissions, policies, and workflows in Formal
  • Permission to edit your Devin environment

Create the machine users

Go to Users and create two machine users: Copy both user_... IDs. Keep the workflow actor separate from Devin, so Devin can never create its own suspension.

Configure the OIDC integration

Go to OIDC Integrations and click Create Integration. Select the Devin preset and the devin machine user.
1

Restrict the claim condition

Replace your-org-id in the preset claim condition with your Devin organization ID:
See Devin’s OIDC documentation for all available claims.
2

Map the session requester

Keep the preset End-User Email Expression:
Formal resolves this email to a Formal user when Devin mints a federation token. The Connector then logs and evaluates each session with that person as the .
3

Copy the audience

Copy the generated audience, oidc.formal.ai/integrationoidc_.... Devin requests OIDC tokens for this audience.

Restrict Control Plane access

Devin only needs to request access and mint federation tokens. The workflow actor only needs to create policy suspensions. Go to Permissions, click Create Permission, name it devin-database-access, and paste:
Set the Permission to Active.

Block Devin by default

Go to Policies, click Create Policy, name it devin-default-block, and paste:
Set the policy to Active and copy its policy_... ID. Approved access still goes through your other policies, so masking and read-only rules keep applying to Devin.

Create the approval workflow

Go to Workflows, click Create Workflow, name it devin-database-access, and paste:
Replace the three placeholders, then set the workflow to Active and copy its workflow_... ID. The suspension condition uses only verified token claims: the requester’s email and the Devin session (devin_id). Never interpolate payload fields into input_condition, since Devin controls the payload and could inject CEL. Devin chooses the reason and duration, and the approver sees both before approving. A new Devin session needs a new approval.
To manage this setup with Terraform, use formal_user, formal_integration_oidc, formal_permission, formal_policy, and formal_workflow. Inside a Terraform heredoc, escape workflow expressions as $${{ ... }}.

Configure the Devin environment

1

Install the OIDC helper and clients

Add these steps to the initialize section of your environment blueprint:
The action installs the devin-oidc CLI, which exchanges the session token for an audience-scoped OIDC token.
2

Reach the Connector

The Devin VM must reach the Connector listener. If the Connector is on a private network, connect the VM to it, for example with a Tailscale subnet router.
3

Add the access script

Commit scripts/formal-db.sh to the repository Devin works in:
Replace the placeholders. If the listener serves more than one resource, set <DATABASE> to <DATABASE>@<RESOURCE_NAME>.The script mints a new federation token for each connection. Devin OIDC tokens are short-lived, and a federation token never outlives the OIDC token it came from.
4

Tell Devin how to use it

Add instructions like these to Devin, for example as Knowledge:

Verify the setup

Start a new Devin session and ask it to run:
The Connector should reject the connection with Devin is blocked by default; request JIT access. Then run:
You should receive a Slack DM from the Formal app. Click Yes, wait for the approval message, then run the SELECT 1 again. It should return one row. Open Logs. The session should show the devin machine user, your email as the end user, and the Devin claims under user.oidc.claims. The suspension appears in Policy Suspensions until it expires.

Troubleshooting

Confirm the integration is Active and its issuer is https://app.devin.ai. Confirm the script’s audience matches the integration audience exactly. Confirm the token’s org_id matches the claim condition.
The allow expression rejected the caller. Confirm the machine user ID in the expression is the integration’s machine user.
The requester’s email has no matching Formal user. Create the Formal user or fix the email, then retry.
Check that the workflow run created a suspension. The suspension covers only the requesting Devin session, so a new session needs a new request.

Next steps

OIDC Integrations

Configure claims and federated access

Workflows

Build other approval flows

Policy Suspensions

Review and revoke approved access

Block Agent Auto-Merge

Govern Devin’s GitHub access with the Formal Endpoint