# Create AI assessment label
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/create-ai-assessment-label
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/CreateAIAssessmentLabel
Create a label that AI assessments can assign to LLM requests. Requires AI assessments to be enabled.
# CreateAIAssessmentCriterion
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/createaiassessmentcriterion
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/CreateAIAssessmentCriterion
# Delete AI assessment label
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/delete-ai-assessment-label
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/DeleteAIAssessmentLabel
Delete a label that AI assessments can assign to LLM requests. Requires AI assessments to be enabled.
# DeleteAIAssessmentCriterion
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/deleteaiassessmentcriterion
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/DeleteAIAssessmentCriterion
# Get AI assessment label
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/get-ai-assessment-label
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/GetAIAssessmentLabel
Get a label that AI assessments can assign to LLM requests
# Get AI assessment settings
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/get-ai-assessment-settings
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/GetAIAssessmentSettings
Get whether AI assessments are enabled for the organization
# GetAIAssessmentCriterion
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/getaiassessmentcriterion
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/GetAIAssessmentCriterion
# List AI assessment labels
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/list-ai-assessment-labels
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/ListAIAssessmentLabels
List the labels that AI assessments can assign to LLM requests
# ListAIAssessmentCriteria
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/listaiassessmentcriteria
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/ListAIAssessmentCriteria
# Update AI assessment label
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/update-ai-assessment-label
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/UpdateAIAssessmentLabel
Update a label by sending the full object. All mutable fields are replaced. Requires AI assessments to be enabled.
# Update AI assessment settings
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/update-ai-assessment-settings
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/UpdateAIAssessmentSettings
Enable or disable AI assessments for the organization. Enabling sends LLM
traffic to Typesafe for assessment. The first update creates the default labels.
# UpdateAIAssessmentCriterion
Source: https://docs.formal.ai/api-reference/corev1aiassessmentservice/updateaiassessmentcriterion
/docs/api/openapi/ai_assessment_openapi.json post /core.v1.AIAssessmentService/UpdateAIAssessmentCriterion
# CreateAccessRequest
Source: https://docs.formal.ai/api-reference/corev1approvalservice/createaccessrequest
/docs/api/openapi/approval_openapi.json post /core.v1.ApprovalService/CreateAccessRequest
# ListAccessRequests
Source: https://docs.formal.ai/api-reference/corev1approvalservice/listaccessrequests
/docs/api/openapi/approval_openapi.json post /core.v1.ApprovalService/ListAccessRequests
# ListApprovalRequests
Source: https://docs.formal.ai/api-reference/corev1approvalservice/listapprovalrequests
/docs/api/openapi/approval_openapi.json post /core.v1.ApprovalService/ListApprovalRequests
# UpdateApprovalRequest
Source: https://docs.formal.ai/api-reference/corev1approvalservice/updateapprovalrequest
/docs/api/openapi/approval_openapi.json post /core.v1.ApprovalService/UpdateApprovalRequest
# Create a signing key
Source: https://docs.formal.ai/api-reference/corev1changeauthorizationservice/create-a-signing-key
/docs/api/openapi/change_authorization_openapi.json post /core.v1.ChangeAuthorizationService/CreateSigningKey
Register a public key for cryptographic change authorization
# Delete a signing key
Source: https://docs.formal.ai/api-reference/corev1changeauthorizationservice/delete-a-signing-key
/docs/api/openapi/change_authorization_openapi.json post /core.v1.ChangeAuthorizationService/DeleteSigningKey
Delete a change authorization signing key
# List signing keys
Source: https://docs.formal.ai/api-reference/corev1changeauthorizationservice/list-signing-keys
/docs/api/openapi/change_authorization_openapi.json post /core.v1.ChangeAuthorizationService/ListSigningKeys
List all change authorization signing keys for the organization
# Update a signing key
Source: https://docs.formal.ai/api-reference/corev1changeauthorizationservice/update-a-signing-key
/docs/api/openapi/change_authorization_openapi.json post /core.v1.ChangeAuthorizationService/UpdateSigningKey
Update a change authorization signing key status for key rotation
# Update signing key (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1changeauthorizationservice/update-signing-key-full-object-replacement
/docs/api/openapi/change_authorization_openapi.json post /core.v1.ChangeAuthorizationService/UpdateSigningKeyV2
Update a signing key by sending the full object. All mutable fields are replaced.
# Create connector
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnector
Create a connector
# Create connector AI provider
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-ai-provider
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorAiProvider
Create an AI provider configuration for a connector
# Create connector CloudFormation template
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-cloudformation-template
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorCloudformationTemplate
Generate and upload a CloudFormation template for connector deployment
# Create connector configuration
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-configuration
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorConfiguration
Create a connector configuration
# Create connector Helm values
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-helm-values
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorHelmValues
Generate and upload a Helm values file for connector deployment
# Create connector hostname
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-hostname
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorHostname
Create a connector hostname
# Create connector instance shutdown request
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-instance-shutdown-request
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorInstanceShutdownRequest
Create a shutdown request for a connector instance
# Create connector listener
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-listener
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorListener
Create a connector listener
# Create connector listener link
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-listener-link
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorListenerLink
Create a connector listener link
# Create connector listener rule
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-listener-rule
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorListenerRule
Create a connector listener rule
# Create connector satellite link
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-satellite-link
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorSatelliteLink
Create a new connector satellite link
# Create connector token encryption key
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-connector-token-encryption-key
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateConnectorTokenEncryptionKey
Register a KMS KEK for HTTP policy token encryption on a connector.
# Create federation token
Source: https://docs.formal.ai/api-reference/corev1connectorservice/create-federation-token
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/CreateFederationToken
Mint a short-lived federated connector credential for an OIDC-authenticated machine user.
# Delete connector
Source: https://docs.formal.ai/api-reference/corev1connectorservice/delete-connector
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/DeleteConnector
Delete a connector
# Delete connector AI provider
Source: https://docs.formal.ai/api-reference/corev1connectorservice/delete-connector-ai-provider
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/DeleteConnectorAiProvider
Delete an AI provider configuration
# Delete connector configuration
Source: https://docs.formal.ai/api-reference/corev1connectorservice/delete-connector-configuration
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/DeleteConnectorConfiguration
Delete a connector configuration
# Delete connector hostname
Source: https://docs.formal.ai/api-reference/corev1connectorservice/delete-connector-hostname
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/DeleteConnectorHostname
Delete a connector hostname
# Delete connector listener
Source: https://docs.formal.ai/api-reference/corev1connectorservice/delete-connector-listener
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/DeleteConnectorListener
Delete a connector listener
# Delete connector listener link
Source: https://docs.formal.ai/api-reference/corev1connectorservice/delete-connector-listener-link
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/DeleteConnectorListenerLink
Delete a connector listener link
# Delete connector listener rule
Source: https://docs.formal.ai/api-reference/corev1connectorservice/delete-connector-listener-rule
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/DeleteConnectorListenerRule
Delete a connector listener rule
# Delete connector satellite link
Source: https://docs.formal.ai/api-reference/corev1connectorservice/delete-connector-satellite-link
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/DeleteConnectorSatelliteLink
Delete a connector satellite link
# Delete connector token encryption key
Source: https://docs.formal.ai/api-reference/corev1connectorservice/delete-connector-token-encryption-key
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/DeleteConnectorTokenEncryptionKey
Delete a connector token encryption key by ID.
# Get connector
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnector
Get a connector
# Get connector AI provider
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-ai-provider
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorAiProvider
Get an AI provider configuration by provider ID or connector ID
# Get connector API key
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-api-key
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorApiKey
Get the API key for a connector
# Get connector configuration
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-configuration
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorConfiguration
Get a connector configuration
# Get connector configuration by connector ID
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-configuration-by-connector-id
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorConfigurationByConnectorId
Get a connector configuration by connector ID
# Get connector hostname
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-hostname
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorHostname
Get a connector hostname
# Get connector listener
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-listener
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorListener
Get a connector listener
# Get connector listener link
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-listener-link
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorListenerLink
Get a connector listener link
# Get connector listener rule
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-listener-rule
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorListenerRule
Get a connector listener rule
# Get connector satellite link
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-satellite-link
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorSatelliteLink
Get a connector satellite link
# Get connector SSH host key
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-ssh-host-key
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorSSHHostKey
Get the SSH host key for a connector for host verification
# Get connector TLS certificate
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-tls-certificate
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorTLSCertificate
Get the PEM-encoded TLS certificate full chain for a connector hostname
# Get connector token encryption key
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-token-encryption-key
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorTokenEncryptionKey
Get the token encryption key for a connector (at most one per connector).
# Get connector VPN
Source: https://docs.formal.ai/api-reference/corev1connectorservice/get-connector-vpn
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/GetConnectorVpn
Get the Formal VPN settings of a connector. A connector without settings reports the VPN as disabled.
# List connector instances
Source: https://docs.formal.ai/api-reference/corev1connectorservice/list-connector-instances
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/ListConnectorInstances
List all instances for a connector
# List connector listener links
Source: https://docs.formal.ai/api-reference/corev1connectorservice/list-connector-listener-links
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/ListConnectorListenerLinks
List all links for a connector listener
# List connector listener rules
Source: https://docs.formal.ai/api-reference/corev1connectorservice/list-connector-listener-rules
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/ListConnectorListenerRules
List all rules for a connector listener
# List connector listeners
Source: https://docs.formal.ai/api-reference/corev1connectorservice/list-connector-listeners
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/ListConnectorListeners
List all listeners for a connector
# List connector satellite links
Source: https://docs.formal.ai/api-reference/corev1connectorservice/list-connector-satellite-links
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/ListConnectorSatelliteLinks
List all connector satellite links
# List connector VPN regions
Source: https://docs.formal.ai/api-reference/corev1connectorservice/list-connector-vpn-regions
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/ListConnectorVpnRegions
List the cloud regions a connector can declare as its location for the Formal VPN.
# List connectors
Source: https://docs.formal.ai/api-reference/corev1connectorservice/list-connectors
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/ListConnectors
List all connectors
# Update connector
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnector
Update a connector
# Update connector AI provider
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-ai-provider
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorAiProvider
Update an AI provider configuration
# Update connector AI provider (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-ai-provider-full-object-replacement
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorAiProviderV2
Update a connector AI provider by sending the full object. All mutable fields are replaced.
# Update connector configuration
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-configuration
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorConfiguration
Update a connector configuration
# Update connector configuration (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-configuration-full-object-replacement
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorConfigurationV2
Update a connector configuration by sending the full object. All mutable fields are replaced.
# Update connector (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-full-object-replacement
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorV2
Update a connector by sending the full object. All mutable fields are replaced.
# Update connector hostname
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-hostname
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorHostname
Update a connector hostname
# Update connector hostname (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-hostname-full-object-replacement
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorHostnameV2
Update a connector hostname by sending the full object. All mutable fields are replaced.
# Update connector listener
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-listener
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorListener
Update a connector listener
# Update connector listener (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-listener-full-object-replacement
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorListenerV2
Update a connector listener by sending the full object. All mutable fields are replaced.
# Update connector listener link
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-listener-link
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorListenerLink
Update a connector listener link
# Update connector listener link (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-listener-link-full-object-replacement
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorListenerLinkV2
Update a connector listener link by sending the full object. All mutable fields are replaced.
# Update connector listener rule
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-listener-rule
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorListenerRule
Update a connector listener rule
# Update connector listener rule (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-listener-rule-full-object-replacement
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorListenerRuleV2
Update a connector listener rule by sending the full object. All mutable fields are replaced.
# Update connector VPN
Source: https://docs.formal.ai/api-reference/corev1connectorservice/update-connector-vpn
/docs/api/openapi/connectors_openapi.json post /core.v1.ConnectorService/UpdateConnectorVpn
Replace the Formal VPN settings of a connector.
# Create a desktop routing rule
Source: https://docs.formal.ai/api-reference/corev1desktopservice/create-a-desktop-routing-rule
/docs/api/openapi/desktop_openapi.json post /core.v1.DesktopService/CreateDesktopRoutingRule
Create a new desktop routing rule for the organization.
# Delete a desktop routing rule
Source: https://docs.formal.ai/api-reference/corev1desktopservice/delete-a-desktop-routing-rule
/docs/api/openapi/desktop_openapi.json post /core.v1.DesktopService/DeleteDesktopRoutingRule
Delete an existing desktop routing rule.
# Get a desktop routing rule
Source: https://docs.formal.ai/api-reference/corev1desktopservice/get-a-desktop-routing-rule
/docs/api/openapi/desktop_openapi.json post /core.v1.DesktopService/GetDesktopRoutingRule
Get a single desktop routing rule by ID.
# List desktop routing rules
Source: https://docs.formal.ai/api-reference/corev1desktopservice/list-desktop-routing-rules
/docs/api/openapi/desktop_openapi.json post /core.v1.DesktopService/ListDesktopRoutingRules
List all desktop routing rules for the organization.
# List user devices
Source: https://docs.formal.ai/api-reference/corev1desktopservice/list-user-devices
/docs/api/openapi/desktop_openapi.json post /core.v1.DesktopService/ListUserDevices
List all desktop devices for a specific user.
# List users with desktop status
Source: https://docs.formal.ai/api-reference/corev1desktopservice/list-users-with-desktop-status
/docs/api/openapi/desktop_openapi.json post /core.v1.DesktopService/ListDesktopUsers
List org users seen on a desktop device in the last 90 days, most recently seen first, with their latest device summary.
# Update a desktop routing rule
Source: https://docs.formal.ai/api-reference/corev1desktopservice/update-a-desktop-routing-rule
/docs/api/openapi/desktop_openapi.json post /core.v1.DesktopService/UpdateDesktopRoutingRule
Update an existing desktop routing rule.
# Get access graph
Source: https://docs.formal.ai/api-reference/corev1graphservice/get-access-graph
/docs/api/openapi/graph_openapi.json post /core.v1.GraphService/GetAccessGraph
Returns a multi-layer access graph showing identities (human/agent/machine), connectors, and resources with top-N selection, triggered policy stats, and per-layer summaries. The query field accepts Quickwit syntax as the primary filter on the log universe.
# Get access graph node details
Source: https://docs.formal.ai/api-reference/corev1graphservice/get-access-graph-node-details
/docs/api/openapi/graph_openapi.json post /core.v1.GraphService/GetAccessGraphNodeDetails
Returns detailed control-plane metadata and policy breakdown for a specific node. Receives the same base query to keep drilldown scoped.
# Create group
Source: https://docs.formal.ai/api-reference/corev1groupservice/create-group
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/CreateGroup
Create a group
# Create user group link
Source: https://docs.formal.ai/api-reference/corev1groupservice/create-user-group-link
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/CreateUserGroupLink
Create a user group link
# Delete group
Source: https://docs.formal.ai/api-reference/corev1groupservice/delete-group
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/DeleteGroup
Delete a group
# Delete user group link
Source: https://docs.formal.ai/api-reference/corev1groupservice/delete-user-group-link
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/DeleteUserGroupLink
Delete a user group link
# Delete user group link by user id and group id
Source: https://docs.formal.ai/api-reference/corev1groupservice/delete-user-group-link-by-user-id-and-group-id
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/DeleteUserGroupLinkByUserIdAndGroupId
Remove a user from a group using user_id and group_id
# Get group
Source: https://docs.formal.ai/api-reference/corev1groupservice/get-group
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/GetGroup
Get a group
# Get user group link
Source: https://docs.formal.ai/api-reference/corev1groupservice/get-user-group-link
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/GetUserGroupLink
Get a user group link
# List groups
Source: https://docs.formal.ai/api-reference/corev1groupservice/list-groups
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/ListGroups
List all groups
# List user group links
Source: https://docs.formal.ai/api-reference/corev1groupservice/list-user-group-links
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/ListUserGroupLinks
List all user group links
# Update group
Source: https://docs.formal.ai/api-reference/corev1groupservice/update-group
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/UpdateGroup
Update a group
# Update group (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1groupservice/update-group-full-object-replacement
/docs/api/openapi/group_openapi.json post /core.v1.GroupService/UpdateGroupV2
Update a group by sending the full object. All mutable fields are replaced.
# Create hook
Source: https://docs.formal.ai/api-reference/corev1hookservice/create-hook
/docs/api/openapi/hooks_openapi.json post /core.v1.HookService/CreateHook
Create a policy evaluation hook
# Delete hook
Source: https://docs.formal.ai/api-reference/corev1hookservice/delete-hook
/docs/api/openapi/hooks_openapi.json post /core.v1.HookService/DeleteHook
Delete a policy evaluation hook
# Get hook
Source: https://docs.formal.ai/api-reference/corev1hookservice/get-hook
/docs/api/openapi/hooks_openapi.json post /core.v1.HookService/GetHook
Get a policy evaluation hook
# List hooks
Source: https://docs.formal.ai/api-reference/corev1hookservice/list-hooks
/docs/api/openapi/hooks_openapi.json post /core.v1.HookService/ListHooks
List policy evaluation hooks
# Update hook
Source: https://docs.formal.ai/api-reference/corev1hookservice/update-hook
/docs/api/openapi/hooks_openapi.json post /core.v1.HookService/UpdateHook
Update a policy evaluation hook by sending the full object. All mutable fields are replaced.
# CreateInsightEvaluation
Source: https://docs.formal.ai/api-reference/corev1insightsservice/createinsightevaluation
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/CreateInsightEvaluation
# GetInsight
Source: https://docs.formal.ai/api-reference/corev1insightsservice/getinsight
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/GetInsight
# GetInsightEvaluation
Source: https://docs.formal.ai/api-reference/corev1insightsservice/getinsightevaluation
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/GetInsightEvaluation
# GetInsightsOverview
Source: https://docs.formal.ai/api-reference/corev1insightsservice/getinsightsoverview
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/GetInsightsOverview
# GetInsightsSummary
Source: https://docs.formal.ai/api-reference/corev1insightsservice/getinsightssummary
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/GetInsightsSummary
Returns the latest LLM-generated summary of recent insights for the caller's org.
# ListInsightCategoryDefinitions
Source: https://docs.formal.ai/api-reference/corev1insightsservice/listinsightcategorydefinitions
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/ListInsightCategoryDefinitions
# ListInsights
Source: https://docs.formal.ai/api-reference/corev1insightsservice/listinsights
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/ListInsights
# UpdateBulkInsightStatus
Source: https://docs.formal.ai/api-reference/corev1insightsservice/updatebulkinsightstatus
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/UpdateBulkInsightStatus
# UpdateInsightsEngineEnabled
Source: https://docs.formal.ai/api-reference/corev1insightsservice/updateinsightsengineenabled
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/UpdateInsightsEngineEnabled
Enables Insights for the caller's org so scheduled cron evaluations run.
# UpdateInsightStatus
Source: https://docs.formal.ai/api-reference/corev1insightsservice/updateinsightstatus
/docs/api/openapi/insights_openapi.json post /core.v1.InsightsService/UpdateInsightStatus
# CreateIntegrationAIProvider
Source: https://docs.formal.ai/api-reference/corev1integrationaiproviderservice/createintegrationaiprovider
/docs/api/openapi/integration_ai_provider_openapi.json post /core.v1.IntegrationAIProviderService/CreateIntegrationAIProvider
# DeleteIntegrationAIProvider
Source: https://docs.formal.ai/api-reference/corev1integrationaiproviderservice/deleteintegrationaiprovider
/docs/api/openapi/integration_ai_provider_openapi.json post /core.v1.IntegrationAIProviderService/DeleteIntegrationAIProvider
# GetIntegrationAIProvider
Source: https://docs.formal.ai/api-reference/corev1integrationaiproviderservice/getintegrationaiprovider
/docs/api/openapi/integration_ai_provider_openapi.json post /core.v1.IntegrationAIProviderService/GetIntegrationAIProvider
# ListIntegrationsAIProvider
Source: https://docs.formal.ai/api-reference/corev1integrationaiproviderservice/listintegrationsaiprovider
/docs/api/openapi/integration_ai_provider_openapi.json post /core.v1.IntegrationAIProviderService/ListIntegrationsAIProvider
# Create BI integration
Source: https://docs.formal.ai/api-reference/corev1integrationbiservice/create-bi-integration
/docs/api/openapi/integration_bi_openapi.json post /core.v1.IntegrationBIService/CreateBIIntegration
Create a BI integration
# Delete BI integration
Source: https://docs.formal.ai/api-reference/corev1integrationbiservice/delete-bi-integration
/docs/api/openapi/integration_bi_openapi.json post /core.v1.IntegrationBIService/DeleteBIIntegration
Delete a BI integration
# Get BI integration
Source: https://docs.formal.ai/api-reference/corev1integrationbiservice/get-bi-integration
/docs/api/openapi/integration_bi_openapi.json post /core.v1.IntegrationBIService/GetBIIntegration
Get a BI integration
# List BI integrations
Source: https://docs.formal.ai/api-reference/corev1integrationbiservice/list-bi-integrations
/docs/api/openapi/integration_bi_openapi.json post /core.v1.IntegrationBIService/ListBIIntegrations
List all BI integrations
# Refresh BI integrations
Source: https://docs.formal.ai/api-reference/corev1integrationbiservice/refresh-bi-integrations
/docs/api/openapi/integration_bi_openapi.json post /core.v1.IntegrationBIService/RefreshBIIntegrations
Refresh all BI integrations
# Activate a GCP cloud integration
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/activate-a-gcp-cloud-integration
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/SetGCPCloudIntegrationActivation
Report the created GCP resources and activate a GCP cloud integration.
# Activate an Azure cloud integration
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/activate-an-azure-cloud-integration
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/SetAzureCloudIntegrationActivation
Report the created Azure resources and activate an Azure cloud integration.
# Create cloud integration
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/create-cloud-integration
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/CreateCloudIntegration
Create a cloud integration
# Delete cloud integration
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/delete-cloud-integration
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/DeleteCloudIntegration
Delete a cloud integration
# Get Azure cloud integration setup parameters
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/get-azure-cloud-integration-setup-parameters
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/GetAzureCloudIntegrationSetup
Return the parameters needed to provision an Azure cloud integration, authenticated by the integration's security key.
# Get cloud integration
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/get-cloud-integration
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/GetIntegrationCloud
Get a cloud integration
# Get GCP cloud integration setup parameters
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/get-gcp-cloud-integration-setup-parameters
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/GetGCPCloudIntegrationSetup
Return the parameters needed to provision a GCP cloud integration, authenticated by the integration's security key.
# List cloud integrations
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/list-cloud-integrations
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/ListIntegrationClouds
List all cloud integrations
# Update cloud integration
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/update-cloud-integration
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/UpdateCloudIntegration
Update a cloud integration
# Update cloud integration (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1integrationcloudservice/update-cloud-integration-full-object-replacement
/docs/api/openapi/integration_cloud_openapi.json post /core.v1.IntegrationCloudService/UpdateCloudIntegrationV2
Update a cloud integration by sending the full object. All mutable fields are replaced.
# Create integration MDM
Source: https://docs.formal.ai/api-reference/corev1integrationmdmservice/create-integration-mdm
/docs/api/openapi/integration_mdm_openapi.json post /core.v1.IntegrationMDMService/CreateIntegrationMDM
Create an integration MDM
# Delete integration MDM
Source: https://docs.formal.ai/api-reference/corev1integrationmdmservice/delete-integration-mdm
/docs/api/openapi/integration_mdm_openapi.json post /core.v1.IntegrationMDMService/DeleteIntegrationMDM
Delete an integration MDM
# Get integration MDM
Source: https://docs.formal.ai/api-reference/corev1integrationmdmservice/get-integration-mdm
/docs/api/openapi/integration_mdm_openapi.json post /core.v1.IntegrationMDMService/GetIntegrationMDM
Get an integration MDM
# List integration MDM devices
Source: https://docs.formal.ai/api-reference/corev1integrationmdmservice/list-integration-mdm-devices
/docs/api/openapi/integration_mdm_openapi.json post /core.v1.IntegrationMDMService/ListIntegrationMDMDevices
List MDM devices for an integration
# List integrations MDM
Source: https://docs.formal.ai/api-reference/corev1integrationmdmservice/list-integrations-mdm
/docs/api/openapi/integration_mdm_openapi.json post /core.v1.IntegrationMDMService/ListIntegrationsMDM
List all integrations MDM
# Create OIDC integration
Source: https://docs.formal.ai/api-reference/corev1integrationoidcservice/create-oidc-integration
/docs/api/openapi/integration_oidc_openapi.json post /core.v1.IntegrationOIDCService/CreateIntegrationOIDC
Create an OIDC trust configuration for control-plane authentication
# Delete OIDC integration
Source: https://docs.formal.ai/api-reference/corev1integrationoidcservice/delete-oidc-integration
/docs/api/openapi/integration_oidc_openapi.json post /core.v1.IntegrationOIDCService/DeleteIntegrationOIDC
Delete an OIDC trust configuration
# Get OIDC integration
Source: https://docs.formal.ai/api-reference/corev1integrationoidcservice/get-oidc-integration
/docs/api/openapi/integration_oidc_openapi.json post /core.v1.IntegrationOIDCService/GetIntegrationOIDC
Get an OIDC trust configuration
# List OIDC integrations
Source: https://docs.formal.ai/api-reference/corev1integrationoidcservice/list-oidc-integrations
/docs/api/openapi/integration_oidc_openapi.json post /core.v1.IntegrationOIDCService/ListIntegrationsOIDC
List OIDC trust configurations
# Update OIDC integration
Source: https://docs.formal.ai/api-reference/corev1integrationoidcservice/update-oidc-integration
/docs/api/openapi/integration_oidc_openapi.json post /core.v1.IntegrationOIDCService/UpdateIntegrationOIDC
Update an OIDC trust configuration by sending the full object. All mutable fields are replaced.
# Create integration log
Source: https://docs.formal.ai/api-reference/corev1integrationslogservice/create-integration-log
/docs/api/openapi/integration_log_openapi.json post /core.v1.IntegrationsLogService/CreateIntegrationLog
Create an integration log
# Delete integration log
Source: https://docs.formal.ai/api-reference/corev1integrationslogservice/delete-integration-log
/docs/api/openapi/integration_log_openapi.json post /core.v1.IntegrationsLogService/DeleteIntegrationLog
Delete an integration log
# Get integration log
Source: https://docs.formal.ai/api-reference/corev1integrationslogservice/get-integration-log
/docs/api/openapi/integration_log_openapi.json post /core.v1.IntegrationsLogService/GetIntegrationLog
Get an integration log
# List integrations log
Source: https://docs.formal.ai/api-reference/corev1integrationslogservice/list-integrations-log
/docs/api/openapi/integration_log_openapi.json post /core.v1.IntegrationsLogService/ListIntegrationLogs
List all integrations log
# Create data label
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/create-data-label
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/CreateDataLabel
Create a data label
# Create inventory object
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/create-inventory-object
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/CreateInventoryObject
Create an inventory object
# Create inventory tag
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/create-inventory-tag
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/CreateInventoryTag
Create an inventory tag
# Delete data label
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/delete-data-label
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/DeleteDataLabel
Delete a data label
# Delete inventory object
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/delete-inventory-object
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/DeleteInventoryObject
Delete an inventory object
# Delete inventory objects by resource
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/delete-inventory-objects-by-resource
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/DeleteInventoryObjectsByResource
Delete all inventory objects for a resource
# Delete inventory tag
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/delete-inventory-tag
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/DeleteInventoryTag
Delete an inventory tag
# Get data label
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/get-data-label
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/GetDataLabel
Get a data label
# Get inventory object
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/get-inventory-object
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/GetInventoryObject
Get an inventory object
# List data labels
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/list-data-labels
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/ListDataLabels
List all data labels
# List inventory objects
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/list-inventory-objects
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/ListInventoryObjects
List all inventory objects
# List inventory tags
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/list-inventory-tags
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/ListInventoryTags
List all inventory tags
# Update column
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/update-column
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/UpdateColumn
Update a column
# Update data label
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/update-data-label
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/UpdateDataLabel
Update a data label
# Update data label (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/update-data-label-full-object-replacement
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/UpdateDataLabelV2
Update a data label by sending the full object. All mutable fields are replaced.
# Update inventory object tags
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/update-inventory-object-tags
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/UpdateInventoryObjectTags
Update the tags of an inventory object
# Update inventory tag
Source: https://docs.formal.ai/api-reference/corev1inventoryservice/update-inventory-tag
/docs/api/openapi/inventory_openapi.json post /core.v1.InventoryService/UpdateInventoryTag
Update an inventory tag name
# Create encryption key
Source: https://docs.formal.ai/api-reference/corev1logsservice/create-encryption-key
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/CreateEncryptionKey
Create an encryption key
# Create log configuration
Source: https://docs.formal.ai/api-reference/corev1logsservice/create-log-configuration
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/CreateLogConfiguration
Create a log configuration
# Create log query
Source: https://docs.formal.ai/api-reference/corev1logsservice/create-log-query
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/CreateLogQuery
Create a log query
# CreateLogSchema
Source: https://docs.formal.ai/api-reference/corev1logsservice/createlogschema
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/CreateLogSchema
Create log schema
# Delete encryption key
Source: https://docs.formal.ai/api-reference/corev1logsservice/delete-encryption-key
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/DeleteEncryptionKey
Delete an encryption key
# Delete log configuration
Source: https://docs.formal.ai/api-reference/corev1logsservice/delete-log-configuration
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/DeleteLogConfiguration
Delete a log configuration
# Delete log query
Source: https://docs.formal.ai/api-reference/corev1logsservice/delete-log-query
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/DeleteLogQuery
Delete a log query
# DeleteLogSchema
Source: https://docs.formal.ai/api-reference/corev1logsservice/deletelogschema
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/DeleteLogSchema
Delete log schema
# Get encryption key
Source: https://docs.formal.ai/api-reference/corev1logsservice/get-encryption-key
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/GetEncryptionKey
Get an encryption key by ID
# Get encryption keys by key ID
Source: https://docs.formal.ai/api-reference/corev1logsservice/get-encryption-keys-by-key-id
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/GetEncryptionKeysByKeyId
Get encryption keys by key ID
# Get group by aggregation
Source: https://docs.formal.ai/api-reference/corev1logsservice/get-group-by-aggregation
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/GetGroupByAggregation
Get a group by aggregation
# Get log
Source: https://docs.formal.ai/api-reference/corev1logsservice/get-log
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/GetLog
Get a log
# Get log configuration
Source: https://docs.formal.ai/api-reference/corev1logsservice/get-log-configuration
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/GetLogConfiguration
Get a log configuration
# Get log query
Source: https://docs.formal.ai/api-reference/corev1logsservice/get-log-query
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/GetLogQuery
Get a log query
# Get logs from natural language
Source: https://docs.formal.ai/api-reference/corev1logsservice/get-logs-from-natural-language
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/GetLogsFromNaturalLanguage
Translates a natural language query to a Quickwit query and returns matching logs in a single request. Internally fetches aggregations for field context, translates via LLM, and queries logs.
# Get metric aggregation
Source: https://docs.formal.ai/api-reference/corev1logsservice/get-metric-aggregation
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/GetMetricAggregation
Get a metric aggregation
# GetLogSchema
Source: https://docs.formal.ai/api-reference/corev1logsservice/getlogschema
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/GetLogSchema
Get log schema
# List encryption keys
Source: https://docs.formal.ai/api-reference/corev1logsservice/list-encryption-keys
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/ListEncryptionKeys
List encryption keys with pagination and filtering
# List log aggregations
Source: https://docs.formal.ai/api-reference/corev1logsservice/list-log-aggregations
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/ListLogAggregations
Get log aggregations for a time window
# List log configurations
Source: https://docs.formal.ai/api-reference/corev1logsservice/list-log-configurations
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/ListLogConfigurations
List all log configurations
# List log policy evaluation inputs
Source: https://docs.formal.ai/api-reference/corev1logsservice/list-log-policy-evaluation-inputs
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/ListLogPolicyEvaluationInputs
Paginate through log policy evaluation inputs filtered by stage
# List log queries
Source: https://docs.formal.ai/api-reference/corev1logsservice/list-log-queries
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/ListLogQueries
List all log queries
# List logs V2
Source: https://docs.formal.ai/api-reference/corev1logsservice/list-logs-v2
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/ListLogsV2
List logs data only (without aggregations) for fast pagination
# ListLogSchemas
Source: https://docs.formal.ai/api-reference/corev1logsservice/listlogschemas
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/ListLogSchemas
List log schemas
# Update encryption key
Source: https://docs.formal.ai/api-reference/corev1logsservice/update-encryption-key
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/UpdateEncryptionKey
Update an encryption key
# Update encryption key (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1logsservice/update-encryption-key-full-object-replacement
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/UpdateEncryptionKeyV2
Update an encryption key by sending the full object. All mutable fields are replaced.
# Update log configuration
Source: https://docs.formal.ai/api-reference/corev1logsservice/update-log-configuration
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/UpdateLogConfiguration
Update a log configuration
# Update log configuration (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1logsservice/update-log-configuration-full-object-replacement
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/UpdateLogConfigurationV2
Update a log configuration by sending the full object. All mutable fields are replaced.
# Update log query
Source: https://docs.formal.ai/api-reference/corev1logsservice/update-log-query
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/UpdateLogQuery
Update a log query
# Update log query (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1logsservice/update-log-query-full-object-replacement
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/UpdateLogQueryV2
Update a log query by sending the full object. All mutable fields are replaced.
# UpdateLogSchema
Source: https://docs.formal.ai/api-reference/corev1logsservice/updatelogschema
/docs/api/openapi/logs_openapi.json post /core.v1.LogsService/UpdateLogSchema
Update log schema
# Create MCP server
Source: https://docs.formal.ai/api-reference/corev1mcpservice/create-mcp-server
/docs/api/openapi/mcp_openapi.json post /core.v1.McpService/CreateMcpServer
Create a managed MCP server.
# Delete MCP server
Source: https://docs.formal.ai/api-reference/corev1mcpservice/delete-mcp-server
/docs/api/openapi/mcp_openapi.json post /core.v1.McpService/DeleteMcpServer
Delete a managed MCP server.
# Get MCP server
Source: https://docs.formal.ai/api-reference/corev1mcpservice/get-mcp-server
/docs/api/openapi/mcp_openapi.json post /core.v1.McpService/GetMcpServer
Get a managed MCP server by ID.
# List MCP servers
Source: https://docs.formal.ai/api-reference/corev1mcpservice/list-mcp-servers
/docs/api/openapi/mcp_openapi.json post /core.v1.McpService/ListMcpServers
List managed MCP servers for the organization.
# List shadow MCP servers
Source: https://docs.formal.ai/api-reference/corev1mcpservice/list-shadow-mcp-servers
/docs/api/openapi/mcp_openapi.json post /core.v1.McpService/ListShadowMcps
List observed MCP servers that are not yet managed.
# Update MCP server
Source: https://docs.formal.ai/api-reference/corev1mcpservice/update-mcp-server
/docs/api/openapi/mcp_openapi.json post /core.v1.McpService/UpdateMcpServer
Update a managed MCP server.
# Create native user
Source: https://docs.formal.ai/api-reference/corev1nativeuserservice/create-native-user
/docs/api/openapi/native_user_openapi.json post /core.v1.NativeUserService/CreateNativeUserV3
Creates a native user for a resource.
# Delete native user
Source: https://docs.formal.ai/api-reference/corev1nativeuserservice/delete-native-user
/docs/api/openapi/native_user_openapi.json post /core.v1.NativeUserService/DeleteNativeUserV3
Deletes a native user.
# Get native user
Source: https://docs.formal.ai/api-reference/corev1nativeuserservice/get-native-user
/docs/api/openapi/native_user_openapi.json post /core.v1.NativeUserService/GetNativeUserV3
Gets a native user. Secret values are not returned.
# List native users
Source: https://docs.formal.ai/api-reference/corev1nativeuserservice/list-native-users
/docs/api/openapi/native_user_openapi.json post /core.v1.NativeUserService/ListNativeUsersV3
Lists native users for a resource. Secret values are not returned.
# Update native user
Source: https://docs.formal.ai/api-reference/corev1nativeuserservice/update-native-user
/docs/api/openapi/native_user_openapi.json post /core.v1.NativeUserService/UpdateNativeUserV3
Updates a native user. Leave credentials unset to keep existing
credentials. The credential type cannot be changed.
# CreatePermission
Source: https://docs.formal.ai/api-reference/corev1permissionsservice/createpermission
/docs/api/openapi/permissions_openapi.json post /core.v1.PermissionsService/CreatePermission
# DeletePermission
Source: https://docs.formal.ai/api-reference/corev1permissionsservice/deletepermission
/docs/api/openapi/permissions_openapi.json post /core.v1.PermissionsService/DeletePermission
# GetPermission
Source: https://docs.formal.ai/api-reference/corev1permissionsservice/getpermission
/docs/api/openapi/permissions_openapi.json post /core.v1.PermissionsService/GetPermission
# ListPermissions
Source: https://docs.formal.ai/api-reference/corev1permissionsservice/listpermissions
/docs/api/openapi/permissions_openapi.json post /core.v1.PermissionsService/ListPermissions
# Update permission (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1permissionsservice/update-permission-full-object-replacement
/docs/api/openapi/permissions_openapi.json post /core.v1.PermissionsService/UpdatePermissionV2
Update a permission by sending the full object. All mutable fields are replaced.
# UpdatePermission
Source: https://docs.formal.ai/api-reference/corev1permissionsservice/updatepermission
/docs/api/openapi/permissions_openapi.json post /core.v1.PermissionsService/UpdatePermission
# Create policy
Source: https://docs.formal.ai/api-reference/corev1policiesservice/create-policy
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/CreatePolicy
Create a policy
# Create policy report
Source: https://docs.formal.ai/api-reference/corev1policiesservice/create-policy-report
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/CreatePolicyReport
Generate a policy report from policy code
# Create policy stage configuration
Source: https://docs.formal.ai/api-reference/corev1policiesservice/create-policy-stage-configuration
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/CreatePolicyStageConfiguration
Create a policy stage configuration
# Create policy suspension
Source: https://docs.formal.ai/api-reference/corev1policiesservice/create-policy-suspension
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/CreatePolicySuspension
Create a policy suspension for a specific identity
# Delete policy
Source: https://docs.formal.ai/api-reference/corev1policiesservice/delete-policy
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/DeletePolicy
Delete a policy
# Delete policy stage configuration
Source: https://docs.formal.ai/api-reference/corev1policiesservice/delete-policy-stage-configuration
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/DeletePolicyStageConfiguration
Delete a policy stage configuration
# Delete policy suspension
Source: https://docs.formal.ai/api-reference/corev1policiesservice/delete-policy-suspension
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/DeletePolicySuspension
Delete a policy suspension
# Get policy
Source: https://docs.formal.ai/api-reference/corev1policiesservice/get-policy
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/GetPolicy
Get a policy
# Get policy code validity
Source: https://docs.formal.ai/api-reference/corev1policiesservice/get-policy-code-validity
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/GetPolicyCodeValidity
Get the validity of a policy code
# Get policy report
Source: https://docs.formal.ai/api-reference/corev1policiesservice/get-policy-report
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/GetPolicyReport
Get a specific policy report by ID
# Get policy report connector graph
Source: https://docs.formal.ai/api-reference/corev1policiesservice/get-policy-report-connector-graph
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/GetPolicyImpactReportGraph
Get the connector graph for a specific policy report
# Get policy suspension
Source: https://docs.formal.ai/api-reference/corev1policiesservice/get-policy-suspension
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/GetPolicySuspension
Get a policy suspension by ID
# GetPolicyStageConfiguration
Source: https://docs.formal.ai/api-reference/corev1policiesservice/getpolicystageconfiguration
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/GetPolicyStageConfiguration
# GetPolicyVersion
Source: https://docs.formal.ai/api-reference/corev1policiesservice/getpolicyversion
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/GetPolicyVersion
# List policies
Source: https://docs.formal.ai/api-reference/corev1policiesservice/list-policies
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/ListPolicies
List all policies
# List policy impact report logs
Source: https://docs.formal.ai/api-reference/corev1policiesservice/list-policy-impact-report-logs
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/ListPolicyImpactReportLogs
List logs that would have been triggered by the policy in the report
# List policy suspensions
Source: https://docs.formal.ai/api-reference/corev1policiesservice/list-policy-suspensions
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/ListPolicySuspensions
List all policy suspensions for the organization
# ListPolicyStageConfigurations
Source: https://docs.formal.ai/api-reference/corev1policiesservice/listpolicystageconfigurations
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/ListPolicyStageConfigurations
# ListPolicyVersions
Source: https://docs.formal.ai/api-reference/corev1policiesservice/listpolicyversions
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/ListPolicyVersions
# Update policy
Source: https://docs.formal.ai/api-reference/corev1policiesservice/update-policy
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/UpdatePolicy
Update a policy
# Update policy (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1policiesservice/update-policy-full-object-replacement
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/UpdatePolicyV2
Update a policy by sending the full object. All mutable fields are replaced.
# Update policy stage configuration
Source: https://docs.formal.ai/api-reference/corev1policiesservice/update-policy-stage-configuration
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/UpdatePolicyStageConfiguration
Update a policy stage configuration
# Update policy stage configuration (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1policiesservice/update-policy-stage-configuration-full-object-replacement
/docs/api/openapi/policies_openapi.json post /core.v1.PoliciesService/UpdatePolicyStageConfigurationV2
Update a policy stage configuration by sending the full object. All mutable fields are replaced.
# Create policy data loader
Source: https://docs.formal.ai/api-reference/corev1policydataloaderservice/create-policy-data-loader
/docs/api/openapi/policy_data_loaders_openapi.json post /core.v1.PolicyDataLoaderService/CreatePolicyDataLoader
Create a policy data loader
# Delete policy data loader
Source: https://docs.formal.ai/api-reference/corev1policydataloaderservice/delete-policy-data-loader
/docs/api/openapi/policy_data_loaders_openapi.json post /core.v1.PolicyDataLoaderService/DeletePolicyDataLoader
Delete a policy data loader
# Get policy data loader
Source: https://docs.formal.ai/api-reference/corev1policydataloaderservice/get-policy-data-loader
/docs/api/openapi/policy_data_loaders_openapi.json post /core.v1.PolicyDataLoaderService/GetPolicyDataLoader
Get a policy data loader
# List policy data loaders
Source: https://docs.formal.ai/api-reference/corev1policydataloaderservice/list-policy-data-loaders
/docs/api/openapi/policy_data_loaders_openapi.json post /core.v1.PolicyDataLoaderService/ListPolicyDataLoaders
List all policy data loaders
# Update policy data loader
Source: https://docs.formal.ai/api-reference/corev1policydataloaderservice/update-policy-data-loader
/docs/api/openapi/policy_data_loaders_openapi.json post /core.v1.PolicyDataLoaderService/UpdatePolicyDataLoader
Update a policy data loader
# Update policy data loader (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1policydataloaderservice/update-policy-data-loader-full-object-replacement
/docs/api/openapi/policy_data_loaders_openapi.json post /core.v1.PolicyDataLoaderService/UpdatePolicyDataLoaderV2
Update a policy data loader by sending the full object. All mutable fields are replaced.
# Create a native user
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-a-native-user
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateNativeUserV2
Create a native user
# Create data discovery configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-data-discovery-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateDataDiscoveryConfiguration
Create a data discovery configuration
# Create native user identity link
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-native-user-identity-link
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateNativeUserIdentityLink
Create a native user identity link
# Create resource
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-resource
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResource
Create a resource
# Create resource alias
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-resource-alias
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResourceAlias
Create a new alias for a resource
# Create resource dial configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-resource-dial-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResourceDialConfiguration
Create a resource dial configuration
# Create resource health check
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-resource-health-check
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResourceHealthCheck
Create a resource health check
# Create resource hostname
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-resource-hostname
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResourceHostname
Create a resource hostname
# Create resource OAuth settings
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-resource-oauth-settings
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResourceOAuthSettings
Enable OAuth for an MCP server. OAuth clients can then connect to it through
the Connector, and, unless the upstream OAuth mode is "none", users can link
their own upstream accounts. No network discovery happens until a user
starts linking.
# Create resource SSH host key
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-resource-ssh-host-key
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResourceSshHostKey
Pin an upstream SSH host public key for a resource
# Create resource tag
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-resource-tag
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResourceTag
Create a new tag for a resource
# Create resource tls configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/create-resource-tls-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResourceTlsConfiguration
Create a resource tls configuration
# CreateResourceClassifierConfiguration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/createresourceclassifierconfiguration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateResourceClassifierConfiguration
# Delete data discovery configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-data-discovery-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteDataDiscoveryConfiguration
Delete a data discovery configuration
# Delete native user
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-native-user
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteNativeUser
Delete a native user
# Delete native user identity link
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-native-user-identity-link
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteNativeUserIdentityLink
Delete a native user identity link
# Delete resource
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResource
Delete a resource
# Delete resource alias
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource-alias
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceAlias
Delete an alias from a resource
# Delete resource dial configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource-dial-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceDialConfiguration
Delete a resource dial configuration
# Delete resource health check
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource-health-check
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceHealthCheck
Delete a resource health check
# Delete resource hostname
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource-hostname
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceHostname
Delete a resource hostname
# Delete resource native user selection
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource-native-user-selection
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceNativeUserSelection
Clear a resource's optional native user selection expression. This does
not disable Native Users V3 or delete its native users.
# Delete resource OAuth settings
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource-oauth-settings
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceOAuthSettings
Disable OAuth for an MCP server. Every pending link, every user's stored
grant, and every OAuth client's authorization for the server is deleted.
# Delete resource SSH host key
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource-ssh-host-key
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceSshHostKey
Delete a pinned upstream SSH host public key
# Delete resource tag
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource-tag
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceTag
Delete a tag from a resource by key
# Delete resource tls configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/delete-resource-tls-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceTlsConfiguration
Delete a resource tls configuration
# DeleteResourceClassifierConfiguration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/deleteresourceclassifierconfiguration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/DeleteResourceClassifierConfiguration
# DEPRECATED: Create a native user. Use CreateNativeUserV2 instead.
Source: https://docs.formal.ai/api-reference/corev1resourceservice/deprecated:-create-a-native-user-use-createnativeuserv2-instead
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/CreateNativeUser
DEPRECATED: Create a native user. Use CreateNativeUserV2 instead.
# DEPRECATED: Update native user. Use UpdateNativeUserV2 instead.
Source: https://docs.formal.ai/api-reference/corev1resourceservice/deprecated:-update-native-user-use-updatenativeuserv2-instead
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateNativeUser
DEPRECATED: Update a native user. Use UpdateNativeUserV2 instead.
# Get data discovery configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-data-discovery-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetDataDiscoveryConfiguration
Get a data discovery configuration
# Get native user
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-native-user
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetNativeUser
Get a native user
# Get native user identity link
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-native-user-identity-link
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetNativeUserIdentityLink
Get a native user identity link
# Get resource
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-resource
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetResource
Get a resource
# Get resource dial configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-resource-dial-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetResourceDialConfiguration
Get a resource dial configuration
# Get resource health check
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-resource-health-check
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetResourceHealthCheck
Get a resource health check
# Get resource hostname
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-resource-hostname
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetResourceHostname
Get a resource hostname
# Get resource OAuth settings
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-resource-oauth-settings
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetResourceOAuthSettings
Get the OAuth settings of an MCP server
# Get resource SSH host key
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-resource-ssh-host-key
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetResourceSshHostKey
Get a pinned upstream SSH host public key by ID
# Get resource tls configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/get-resource-tls-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetResourceTlsConfiguration
Get a resource tls configuration
# GetResourceClassifierConfiguration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/getresourceclassifierconfiguration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/GetResourceClassifierConfiguration
# List data discovery configurations
Source: https://docs.formal.ai/api-reference/corev1resourceservice/list-data-discovery-configurations
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/ListDataDiscoveryConfigurations
List all data discovery configurations
# List native user identity links
Source: https://docs.formal.ai/api-reference/corev1resourceservice/list-native-user-identity-links
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/ListNativeUserIdentityLinks
List all native user identity links
# List native users
Source: https://docs.formal.ai/api-reference/corev1resourceservice/list-native-users
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/ListNativeUsers
List all native users
# List resource hostnames
Source: https://docs.formal.ai/api-reference/corev1resourceservice/list-resource-hostnames
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/ListResourceHostnames
List all resource hostnames
# List resource SSH host keys
Source: https://docs.formal.ai/api-reference/corev1resourceservice/list-resource-ssh-host-keys
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/ListResourceSshHostKeys
List pinned upstream SSH host public keys for a resource
# List resources
Source: https://docs.formal.ai/api-reference/corev1resourceservice/list-resources
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/ListResources
List all resources
# ListResourceClassifierConfigurations
Source: https://docs.formal.ai/api-reference/corev1resourceservice/listresourceclassifierconfigurations
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/ListResourceClassifierConfigurations
# ListResourceConnectorAccess
Source: https://docs.formal.ai/api-reference/corev1resourceservice/listresourceconnectoraccess
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/ListResourceConnectorAccess
# Update a single resource tag
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-a-single-resource-tag
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceTag
Update the value of a single resource tag by key
# Update data discovery configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-data-discovery-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateDataDiscoveryConfiguration
Update a data discovery configuration
# Update data discovery configuration (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-data-discovery-configuration-full-object-replacement
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateDataDiscoveryConfigurationV2
Update a data discovery configuration by sending the full object. All mutable fields are replaced.
# Update native user
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-native-user
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateNativeUserV2
Update a native user
# Update native user identity link
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-native-user-identity-link
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateNativeUserIdentityLink
Update a native user identity link
# Update native user identity link (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-native-user-identity-link-full-object-replacement
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateNativeUserIdentityLinkV2
Update a native user identity link by sending the full object. All mutable fields are replaced.
# Update Native Users V3 enabled
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-native-users-v3-enabled
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceNativeUsersV3Enabled
Enable or disable Native Users V3 for a resource.
# Update resource
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResource
Update a resource
# Update resource alias
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-alias
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceAlias
Rename an existing alias for a resource
# Update resource classifier configuration (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-classifier-configuration-full-object-replacement
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceClassifierConfigurationV2
Update a resource classifier configuration by sending the full object. All mutable fields are replaced.
# Update resource dial configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-dial-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceDialConfiguration
Update a resource dial configuration by sending the full object. All mutable fields are replaced.
# Update resource (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-full-object-replacement
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceV2
Update a resource by sending the full object. All mutable fields are replaced.
# Update resource health check
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-health-check
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceHealthCheck
Update a resource health check
# Update resource health check (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-health-check-full-object-replacement
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceHealthCheckV2
Update a resource health check by sending the full object. All mutable fields are replaced.
# Update resource hostname
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-hostname
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceHostname
Update a resource hostname
# Update resource hostname (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-hostname-full-object-replacement
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceHostnameV2
Update a resource hostname by sending the full object. All mutable fields are replaced.
# Update resource SSH host key
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-ssh-host-key
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceSshHostKey
Update a pinned upstream SSH host public key
# Update resource tls configuration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-tls-configuration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceTlsConfiguration
Update a resource tls configuration
# Update resource tls configuration (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1resourceservice/update-resource-tls-configuration-full-object-replacement
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceTlsConfigurationV2
Update a resource tls configuration by sending the full object. All mutable fields are replaced.
# UpdateResourceClassifierConfiguration
Source: https://docs.formal.ai/api-reference/corev1resourceservice/updateresourceclassifierconfiguration
/docs/api/openapi/resource_openapi.json post /core.v1.ResourceService/UpdateResourceClassifierConfiguration
# Create satellite
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/create-satellite
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/CreateSatellite
Create a new satellite
# Create satellite helm values
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/create-satellite-helm-values
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/CreateSatelliteHelmValues
Create a new satellite helm values
# Create satellite hostname
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/create-satellite-hostname
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/CreateSatelliteHostname
Create a new satellite hostname
# Create satellite link
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/create-satellite-link
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/CreateSatelliteLink
Create a link between two satellites (e.g., link a data discovery satellite to an AI satellite for classification)
# Delete satellite
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/delete-satellite
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/DeleteSatellite
Delete a satellite
# Delete satellite hostname
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/delete-satellite-hostname
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/DeleteSatelliteHostname
Delete a satellite hostname
# Delete satellite link
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/delete-satellite-link
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/DeleteSatelliteLink
Delete a satellite link
# Get satellite
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/get-satellite
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/GetSatellite
Get a satellite
# Get satellite api key
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/get-satellite-api-key
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/GetSatelliteApiKey
Get a satellite api key
# Get satellite hostname
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/get-satellite-hostname
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/GetSatelliteHostname
Get a satellite hostname
# Get satellite hostname by satellite id
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/get-satellite-hostname-by-satellite-id
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/GetSatelliteHostnameBySatelliteId
Get a satellite hostname by satellite id
# Get satellite link
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/get-satellite-link
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/GetSatelliteLink
Get a satellite link by ID
# List satellite instances
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/list-satellite-instances
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/ListSatelliteInstances
List all satellite instances
# List satellite links
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/list-satellite-links
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/ListSatelliteLinks
List all satellite links for a source satellite
# List satellites
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/list-satellites
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/ListSatellites
List all satellites
# Update satellite
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/update-satellite
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/UpdateSatellite
Update a satellite
# Update satellite (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/update-satellite-full-object-replacement
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/UpdateSatelliteV2
Update a satellite by sending the full object. All mutable fields are replaced.
# Update satellite hostname
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/update-satellite-hostname
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/UpdateSatelliteHostname
Update a satellite hostname
# Update satellite hostname (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1satelliteservice/update-satellite-hostname-full-object-replacement
/docs/api/openapi/satellite_openapi.json post /core.v1.SatelliteService/UpdateSatelliteHostnameV2
Update a satellite hostname by sending the full object. All mutable fields are replaced.
# CreateScenarioMonitor
Source: https://docs.formal.ai/api-reference/corev1scenariomonitoringservice/createscenariomonitor
/docs/api/openapi/scenario_monitoring_openapi.json post /core.v1.ScenarioMonitoringService/CreateScenarioMonitor
Add a scenario for LLM SSH Monitoring
# DeleteScenarioMonitor
Source: https://docs.formal.ai/api-reference/corev1scenariomonitoringservice/deletescenariomonitor
/docs/api/openapi/scenario_monitoring_openapi.json post /core.v1.ScenarioMonitoringService/DeleteScenarioMonitor
Delete a scenario from LLM SSH Monitoring
# GetScenarioMonitor
Source: https://docs.formal.ai/api-reference/corev1scenariomonitoringservice/getscenariomonitor
/docs/api/openapi/scenario_monitoring_openapi.json post /core.v1.ScenarioMonitoringService/GetScenarioMonitor
Retrieve all LLM session monitoring scenarios and their statuses
# ListScenarioMonitors
Source: https://docs.formal.ai/api-reference/corev1scenariomonitoringservice/listscenariomonitors
/docs/api/openapi/scenario_monitoring_openapi.json post /core.v1.ScenarioMonitoringService/ListScenarioMonitors
# Update scenario monitor (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1scenariomonitoringservice/update-scenario-monitor-full-object-replacement
/docs/api/openapi/scenario_monitoring_openapi.json post /core.v1.ScenarioMonitoringService/UpdateScenarioMonitorV2
Update a scenario monitor by sending the full object. All mutable fields are replaced.
# UpdateScenarioMonitor
Source: https://docs.formal.ai/api-reference/corev1scenariomonitoringservice/updatescenariomonitor
/docs/api/openapi/scenario_monitoring_openapi.json post /core.v1.ScenarioMonitoringService/UpdateScenarioMonitor
Update the status of a scenario used for LLM session monitoring
# List search results
Source: https://docs.formal.ai/api-reference/corev1searchservice/list-search-results
/docs/api/openapi/search_openapi.json post /core.v1.SearchService/ListSearchResults
Performs a global search across multiple entity types.
Results are grouped by category and scoped to the authenticated user's organization.
Returns up to 10 results per category, ordered by updated_at descending.
# Create session termination
Source: https://docs.formal.ai/api-reference/corev1sessionsservice/create-session-termination
/docs/api/openapi/sessions_openapi.json post /core.v1.SessionsService/CreateSessionTermination
Terminate an active session
# Get a session
Source: https://docs.formal.ai/api-reference/corev1sessionsservice/get-a-session
/docs/api/openapi/sessions_openapi.json post /core.v1.SessionsService/GetSession
Get details of a specific session
# List session aggregations
Source: https://docs.formal.ai/api-reference/corev1sessionsservice/list-session-aggregations
/docs/api/openapi/sessions_openapi.json post /core.v1.SessionsService/ListSessionAggregations
Get session aggregations for a time window
# List sessions V2
Source: https://docs.formal.ai/api-reference/corev1sessionsservice/list-sessions-v2
/docs/api/openapi/sessions_openapi.json post /core.v1.SessionsService/ListSessionsV2
List sessions data only (without aggregations) for fast pagination
# CreateFormalSkill
Source: https://docs.formal.ai/api-reference/corev1skillservice/createformalskill
/docs/api/openapi/skill_openapi.json post /core.v1.SkillService/CreateFormalSkill
# DeleteFormalSkill
Source: https://docs.formal.ai/api-reference/corev1skillservice/deleteformalskill
/docs/api/openapi/skill_openapi.json post /core.v1.SkillService/DeleteFormalSkill
# ListShadowSkills
Source: https://docs.formal.ai/api-reference/corev1skillservice/listshadowskills
/docs/api/openapi/skill_openapi.json post /core.v1.SkillService/ListShadowSkills
# ListSkills
Source: https://docs.formal.ai/api-reference/corev1skillservice/listskills
/docs/api/openapi/skill_openapi.json post /core.v1.SkillService/ListSkills
# Create space
Source: https://docs.formal.ai/api-reference/corev1spaceservice/create-space
/docs/api/openapi/spaces_openapi.json post /core.v1.SpaceService/CreateSpace
Create a space
# Delete space
Source: https://docs.formal.ai/api-reference/corev1spaceservice/delete-space
/docs/api/openapi/spaces_openapi.json post /core.v1.SpaceService/DeleteSpace
Delete a space
# Get space
Source: https://docs.formal.ai/api-reference/corev1spaceservice/get-space
/docs/api/openapi/spaces_openapi.json post /core.v1.SpaceService/GetSpace
Get a space
# List spaces
Source: https://docs.formal.ai/api-reference/corev1spaceservice/list-spaces
/docs/api/openapi/spaces_openapi.json post /core.v1.SpaceService/ListSpaces
List all spaces
# Update space
Source: https://docs.formal.ai/api-reference/corev1spaceservice/update-space
/docs/api/openapi/spaces_openapi.json post /core.v1.SpaceService/UpdateSpace
Update a space
# Update space (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1spaceservice/update-space-full-object-replacement
/docs/api/openapi/spaces_openapi.json post /core.v1.SpaceService/UpdateSpaceV2
Update a space by sending the full object. All mutable fields are replaced.
# Create blocked user
Source: https://docs.formal.ai/api-reference/corev1userservice/create-blocked-user
/docs/api/openapi/user_openapi.json post /core.v1.UserService/CreateBlockedUser
Create a blocked user
# Create human user password
Source: https://docs.formal.ai/api-reference/corev1userservice/create-human-user-password
/docs/api/openapi/user_openapi.json post /core.v1.UserService/CreateHumanUserPassword
Create a human user password
# Create machine user password
Source: https://docs.formal.ai/api-reference/corev1userservice/create-machine-user-password
/docs/api/openapi/user_openapi.json post /core.v1.UserService/CreateMachineUserPassword
Create a machine user password
# Create user
Source: https://docs.formal.ai/api-reference/corev1userservice/create-user
/docs/api/openapi/user_openapi.json post /core.v1.UserService/CreateUser
Create a user
# Create user external id
Source: https://docs.formal.ai/api-reference/corev1userservice/create-user-external-id
/docs/api/openapi/user_openapi.json post /core.v1.UserService/CreateUserExternalId
Create a user external id
# Create user private key
Source: https://docs.formal.ai/api-reference/corev1userservice/create-user-private-key
/docs/api/openapi/user_openapi.json post /core.v1.UserService/CreateUserKey
Create a new private/public keypair for a user and return the private key (PEM) and store the public key.
# Delete blocked user
Source: https://docs.formal.ai/api-reference/corev1userservice/delete-blocked-user
/docs/api/openapi/user_openapi.json post /core.v1.UserService/DeleteBlockedUser
Delete a blocked user
# Delete user
Source: https://docs.formal.ai/api-reference/corev1userservice/delete-user
/docs/api/openapi/user_openapi.json post /core.v1.UserService/DeleteUser
Delete a user
# Delete user external id
Source: https://docs.formal.ai/api-reference/corev1userservice/delete-user-external-id
/docs/api/openapi/user_openapi.json post /core.v1.UserService/DeleteUserExternalId
Delete a user external id
# Get blocked user
Source: https://docs.formal.ai/api-reference/corev1userservice/get-blocked-user
/docs/api/openapi/user_openapi.json post /core.v1.UserService/GetBlockedUser
Get a blocked user
# Get machine user credentials
Source: https://docs.formal.ai/api-reference/corev1userservice/get-machine-user-credentials
/docs/api/openapi/user_openapi.json post /core.v1.UserService/GetMachineUserCredentials
Get a machine user credentials
# Get user
Source: https://docs.formal.ai/api-reference/corev1userservice/get-user
/docs/api/openapi/user_openapi.json post /core.v1.UserService/GetUser
Get a user
# List user external ids
Source: https://docs.formal.ai/api-reference/corev1userservice/list-user-external-ids
/docs/api/openapi/user_openapi.json post /core.v1.UserService/ListUserExternalIds
List all user external ids
# List users
Source: https://docs.formal.ai/api-reference/corev1userservice/list-users
/docs/api/openapi/user_openapi.json post /core.v1.UserService/ListUsers
List all users
# Update user
Source: https://docs.formal.ai/api-reference/corev1userservice/update-user
/docs/api/openapi/user_openapi.json post /core.v1.UserService/UpdateUser
Update a user
# Update user expiry
Source: https://docs.formal.ai/api-reference/corev1userservice/update-user-expiry
/docs/api/openapi/user_openapi.json post /core.v1.UserService/UpdateUserExpiry
Update a user's expiry without changing the user identity.
# Update user (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1userservice/update-user-full-object-replacement
/docs/api/openapi/user_openapi.json post /core.v1.UserService/UpdateUserV2
Update a user by sending the full object. All mutable fields are replaced.
# Create form
Source: https://docs.formal.ai/api-reference/corev1workflowservice/create-form
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/CreateForm
Create a new form
# Create workflow
Source: https://docs.formal.ai/api-reference/corev1workflowservice/create-workflow
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/CreateWorkflow
Create a new workflow
# Create workflow trigger
Source: https://docs.formal.ai/api-reference/corev1workflowservice/create-workflow-trigger
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/CreateWorkflowTrigger
Trigger a workflow with api-request trigger type. The workflow's allow CEL expression is evaluated with the requesting user's context.
# Delete form
Source: https://docs.formal.ai/api-reference/corev1workflowservice/delete-form
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/DeleteForm
Delete a form
# Delete workflow
Source: https://docs.formal.ai/api-reference/corev1workflowservice/delete-workflow
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/DeleteWorkflow
Delete a workflow
# Get form
Source: https://docs.formal.ai/api-reference/corev1workflowservice/get-form
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/GetForm
Get a form by ID
# Get workflow
Source: https://docs.formal.ai/api-reference/corev1workflowservice/get-workflow
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/GetWorkflow
Get a workflow by ID
# List forms
Source: https://docs.formal.ai/api-reference/corev1workflowservice/list-forms
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/ListForms
List all forms
# List workflows
Source: https://docs.formal.ai/api-reference/corev1workflowservice/list-workflows
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/ListWorkflows
List all workflows
# Update form
Source: https://docs.formal.ai/api-reference/corev1workflowservice/update-form
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/UpdateForm
Update an existing form
# Update form (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1workflowservice/update-form-full-object-replacement
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/UpdateFormV2
Update a form by sending the full object. All mutable fields are replaced.
# Update workflow
Source: https://docs.formal.ai/api-reference/corev1workflowservice/update-workflow
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/UpdateWorkflow
Update an existing workflow
# Update workflow (full object replacement)
Source: https://docs.formal.ai/api-reference/corev1workflowservice/update-workflow-full-object-replacement
/docs/api/openapi/workflow_openapi.json post /core.v1.WorkflowService/UpdateWorkflowV2
Update a workflow by sending the full object. All mutable fields are replaced.
# AWS SigV4 Authentication
Source: https://docs.formal.ai/docs/api/aws-sigv4-authentication
Authenticate to Formal API using AWS IAM credentials
## Overview
Formal's API supports authentication using AWS Signature Version 4 (SigV4). This enables you to authenticate using your AWS IAM credentials directly, eliminating the need to manage separate API keys.
## Prerequisites
Before using AWS SigV4 authentication with Formal, ensure:
1. **Cloud integration**: Your AWS account is connected to Formal via a [cloud integration](https://app.formal.ai/cloud-accounts)
2. **AWS permissions**: Your IAM user or role has permission to call `sts:GetCallerIdentity`
3. **Machine identity**: A machine identity exists in Formal with the same name as your AWS principal (IAM user or role name)
You must create a machine identity in Formal with the exact same name as your
AWS principal before authentication will work. See [Machine Identity
Setup](#machine-identity-setup) below.
## How It Works
AWS SigV4 authentication with Formal follows this flow:
1. Your client creates a presigned AWS STS `GetCallerIdentity` URL using your AWS credentials
2. The client includes this presigned URL in the `Authorization` header with format: `Authorization: AWS4-Presigned-URL `
3. Formal verifies the signature by calling AWS STS with the presigned URL
4. Formal extracts your AWS account ID and principal name from the STS response
5. Formal matches your AWS account to your organization via the cloud integration
6. Formal authorizes the request using your machine identity and authorization policies
## Machine Identity Setup
Before using AWS SigV4 authentication, create a machine identity in Formal that matches your AWS principal name:
1. Navigate to [Users](https://app.formal.ai/users) in the Control Plane
2. Click **Create User** and choose **Machine**
3. Use the exact name of your AWS principal:
* For IAM user `arn:aws:iam::123456789012:user/DataPipeline`, use name: `DataPipeline`
* For IAM role `arn:aws:iam::123456789012:role/ETLService`, use name: `ETLService`
* For assumed role `arn:aws:sts::123456789012:assumed-role/MyRole/session`, use name: `MyRole`
The machine identity name must exactly match the AWS principal name for
authentication to succeed.
## Usage Examples
### TypeScript/JavaScript
```typescript theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
import { defaultProvider } from "@aws-sdk/credential-provider-node";
import { HttpRequest } from "@aws-sdk/protocol-http";
import { Sha256 } from "@aws-crypto/sha256-js";
import { SignatureV4 } from "@aws-sdk/signature-v4";
import { stringify } from "qs";
// Create a presigned STS GetCallerIdentity URL
async function createPresignedURL(): Promise {
const region = process.env.AWS_REGION || "us-east-1";
const signer = new SignatureV4({
credentials: defaultProvider(),
region: region,
service: "sts",
sha256: Sha256,
});
const host = `sts.${region}.amazonaws.com`;
const req = new HttpRequest({
headers: { "Content-Type": "application/json", host },
hostname: host,
method: "GET",
path: "/",
query: {
Action: "GetCallerIdentity",
Version: "2011-06-15",
},
});
const signed = await signer.presign(req, { expiresIn: 60 * 10 });
return `https://${signed.hostname}${signed.path}?${stringify(signed.query)}`;
}
// Make authenticated request to Formal API
async function callFormalAPI(endpoint: string, body: any) {
const presignedURL = await createPresignedURL();
const url = new URL(endpoint, "https://api.formal.ai");
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `AWS4-Presigned-URL ${presignedURL}`,
},
body: JSON.stringify(body),
});
return response.json();
}
// Example: List resources
const resources = await callFormalAPI(
"/core.v1.ResourceService/ListResources",
{ limit: 100 },
);
```
### Python
```python theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
import boto3
import requests
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest
# Create a presigned STS GetCallerIdentity URL
def create_presigned_url():
session = boto3.Session()
credentials = session.get_credentials()
region = session.region_name or 'us-east-1'
url = f'https://sts.{region}.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15'
request = AWSRequest(method='GET', url=url)
SigV4Auth(credentials, 'sts', region).add_auth(request)
return request.url
# Make authenticated request to Formal API
def call_formal_api(endpoint, body):
presigned_url = create_presigned_url()
url = f'https://api.formal.ai{endpoint}'
response = requests.post(
url,
json=body,
headers={
'Content-Type': 'application/json',
'Authorization': f'AWS4-Presigned-URL {presigned_url}'
}
)
return response.json()
# Example: List resources
resources = call_formal_api(
'/core.v1.ResourceService/ListResources',
{'limit': 100}
)
```
### Go
```go theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"bytes"
"io"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
// Create a presigned STS GetCallerIdentity URL
func createPresignedURL(ctx context.Context) (string, error) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return "", err
}
stsClient := sts.NewFromConfig(cfg)
presignClient := sts.NewPresignClient(stsClient)
presignedReq, err := presignClient.PresignGetCallerIdentity(ctx, &sts.GetCallerIdentityInput{},
func(opts *sts.PresignOptions) {
opts.Expires = 10 * time.Minute
})
if err != nil {
return "", err
}
return presignedReq.URL, nil
}
// Make authenticated request to Formal API
func callFormalAPI(ctx context.Context, endpoint string, body interface{}) ([]byte, error) {
presignedURL, err := createPresignedURL(ctx)
if err != nil {
return nil, err
}
apiURL := "https://api.formal.ai" + endpoint
bodyBytes, _ := json.Marshal(body)
req, _ := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("AWS4-Presigned-URL %s", presignedURL))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
// Example: List resources
func main() {
ctx := context.Background()
body := map[string]interface{}{"limit": 100}
result, err := callFormalAPI(ctx, "/core.v1.ResourceService/ListResources", body)
if err != nil {
panic(err)
}
fmt.Println(string(result))
}
```
### cURL
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Step 1: Generate presigned URL using AWS CLI
PRESIGNED_URL=$(aws sts get-caller-identity --output text --query 'Account' 2>/dev/null && \
aws sts get-caller-identity --generate-cli-skeleton | \
aws sts get-caller-identity --cli-input-json file:///dev/stdin --endpoint-url https://sts.us-east-1.amazonaws.com)
# For a simpler approach, use a script to generate the presigned URL
# Then use it with curl:
curl -X POST https://api.formal.ai/core.v1.ResourceService/ListResources \
-H "Content-Type: application/json" \
-H "Authorization: AWS4-Presigned-URL " \
-d '{"limit": 100}'
```
## Authorization
Requests authenticated with AWS SigV4 use machine identities for authorization. The principal name extracted from your AWS ARN becomes your identity in Formal's authorization system.
### Principal Name Extraction
Formal extracts the principal name from your AWS ARN as follows:
* `arn:aws:iam::123456789012:user/alice` → Principal name: `alice`
* `arn:aws:iam::123456789012:role/DataPipeline` → Principal name: `DataPipeline`
* `arn:aws:sts::123456789012:assumed-role/ETLService/session` → Principal name: `ETLService`
### Policy-Based Access Control
Use Formal's authorization policies to control access based on machine identities and groups:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Example: Allow specific machine identities to list resources
package formal.app
default allow := false
allow if {
input.request.path == "/core.v1.ResourceService/ListResources"
input.user.name in ["DataPipeline", "ETLService"]
}
```
Assign machine identities to groups for easier access management. This allows
you to write policies based on group membership instead of individual
identities.
## Security Considerations
### Signature Expiration
Presigned URLs expire after 10 minutes by default. Generate a fresh URL for each API request or batch of requests within that timeframe.
### Organization Isolation
Formal enforces strict organization isolation:
* Only active cloud integrations are considered for authentication
* Your AWS account ID must match an active cloud integration
* All API requests are scoped to your organization
* A matching machine identity must exist
## Troubleshooting
### Invalid AWS signature
**Error**: "Invalid AWS signature" or "Signature verification failed"
**Solutions**:
* Verify your AWS credentials are valid and not expired
* Ensure the presigned URL was generated correctly
* Check that the presigned URL hasn't expired (10-minute limit)
* Confirm your IAM user/role has `sts:GetCallerIdentity` permission
### No active cloud integration
**Error**: "No active cloud integration found for this AWS account"
**Solutions**:
* Verify your AWS account is connected to Formal via a [cloud integration](https://app.formal.ai/cloud-accounts)
* Ensure the cloud integration is marked as active
* Confirm the AWS account ID matches your cloud integration
### No machine identity found
**Error**: "No machine identity found with name 'X'"
**Solutions**:
* Create a machine identity in Formal with the exact same name as your AWS principal
* For IAM user `arn:aws:iam::123:user/alice`, create identity named `alice`
* For IAM role `arn:aws:iam::123:role/DataPipeline`, create identity named `DataPipeline`
* For assumed role `arn:aws:sts::123:assumed-role/MyRole/session`, create identity named `MyRole`
### Forbidden access
**Error**: "Forbidden" or "Permission denied"
**Solutions**:
* Check your authorization policies allow the machine identity to access the requested endpoint
* Verify the machine identity is assigned to the correct groups
* Confirm the principal name is correctly extracted from your AWS ARN
* Review your group-based authorization policies
### Missing or invalid Authorization header
**Error**: "No Authorization header found" or "Authorization header must start with 'AWS4-Presigned-URL '"
**Solutions**:
* Ensure you're including the `Authorization` header in your request
* Verify the header format is exactly `Authorization: AWS4-Presigned-URL `
* Check that the presigned URL is properly formatted and included after the space
## Additional Resources
* [Formal API Introduction](/docs/api/introduction)
* [Authorization Policies](/docs/guides/policies/introduction)
* [AWS SigV4 Documentation](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
# Introduction
Source: https://docs.formal.ai/docs/api/introduction
Introduction to Formal API endpoints
Welcome to the Formal API! You can use our API to access Formal API endpoints.
## Base URL
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
https://api.formal.ai
```
`https://api.joinformal.com` is a legacy hostname that serves the same API.
## Formal client libraries
* Go: [Go-SDK](https://github.com/formalco/go-sdk)
## Overview
Formal API is built using [gRPC](https://grpc.io/), a highly performant and open source universal Remote Procedure Call (RPC) framework.
gRPC requires inputs and outputs to be formatted as protocol buffers (protobufs), Google's mature open source mechanism for serializing structured data. To leverage gRPC, Formal implements a protocol-buffer-based API. gRPC ensures that both the client and server agree on the same data structures and interfaces.
For more information about gRPC, see [the official gRPC documentation](http://www.grpc.io/docs/guides/).
The API also accepts JSON over HTTP through the [Connect protocol](https://connectrpc.com/docs/protocol/). Send a `POST` request to `https://api.formal.ai/./`, such as `/core.v1.UserService/CreateUser`. The API does not expose REST paths like `/v1/users`.
## Authentication
The Formal API uses API keys to authenticate requests. You can view and manage your API keys in the [Control Plane](https://app.formal.ai/api-keys).
To connect an MCP client to organization reads, see [Connect Formal MCP](/docs/api/mcp-server).
Keep your API keys secure! Do not share your secret API keys in publicly accessible areas like GitHub, Google Drive, and so forth.
To authenticate use `-H x-api-key: {your_api_key}` in your curl command
All API requests must be made over HTTPS. Calls made over plain HTTP will fail. API requests without authentication will also fail.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
curl -X POST 'https://api.formal.ai/core.v1.UserService/ListUsers' \
-H 'x-api-key: {your_api_key}' \
-H 'Content-Type: application/json' \
-d '{"limit": 10}'
```
To prove which key authorized a change, sign requests with a registered signing key instead. See [Sign Changes with Signing Keys](/docs/api/change-authorization).
## Errors
In general: Codes in the 2xx range indicate success. Codes in the 4xx range indicate an error that failed given the information provided. Codes in the 5xx range indicate an error with Formal's servers.
### HTTP
**Formal API HTTP error codes:**
| Code | HTTP Status | Meaning |
| - | - | - |
| invalid\_argument | 400 Bad Request | Request is invalid, regardless of system state. |
| out\_of\_range | 400 Bad Request | The operation was attempted past the valid range. |
| unauthenticated | 401 Unauthorized | Caller doesn't have valid authentication credentials for the operation. |
| permission\_denied | 403 Forbidden | Caller isn't authorized to perform the operation. |
| not\_found | 404 Not Found | User requested a resource (for example, a file or directory) that can't be found. |
| unimplemented | 404 Not Found | The operation isn't implemented, supported, or enabled. |
| canceled | 408 Request Timeout | RPC canceled, usually by the caller. |
| deadline\_exceeded | 408 Request Timeout | Deadline expired before RPC could complete or before the client received the response. |
| already\_exists | 409 Conflict | Caller attempted to create a resource that already exists. |
| aborted | 409 Conflict | The operation was aborted, often because of concurrency issues like a database transaction abort. |
| failed\_precondition | 412 Precondition Failed | Operation can't be completed because the system isn't in the required state. |
| resource\_exhausted | 429 Too Many Requests | Operation can't be completed because some resource is exhausted. |
| unsupported\_protocol | 464 Unsupported Protocol | The API requires HTTP/2. Requests using HTTP/1.1 or other protocols will receive this error. |
| internal | 500 Internal Server Error | An invariant expected by the underlying system has been broken. Reserved for serious errors. |
| unknown | 500 Internal Server Error | Catch-all for errors of unclear origin and errors without a more appropriate code. |
| data\_loss | 500 Internal Server Error | Unrecoverable data loss or corruption. |
| unavailable | 503 Service Unavailable | The service is currently unavailable, usually transiently. Clients should back off and retry. |
## Pagination
All top-level API resources have support for bulk fetches through "list" API methods. These list API methods share a common structure and accept, at a minimum, the following parameters: `limit`, `cursor`, and `order`.
Formal's list API methods use cursor-based pagination through the `cursor` parameter. The parameter accepts an existing object ID value and returns objects in specified chronological `order`.
### Parameters
* **limit** (integer, required): Specifies a limit on the number of objects to return, ranging between 1 and 100.
* **cursor** (string): A cursor to use in pagination.
* **order** (string, default: "asc"): Specifies in which chronological order the list should be sorted. Potential values: `ASC`, `DESC`
### List Response Format
* **count** (integer, required): Number of objects returned.
* **next\_cursor** (string, required): Next cursor in the list.
# Connect Formal MCP
Source: https://docs.formal.ai/docs/api/mcp-server
Connect an MCP client to Formal API behavior and organization state
## Overview
Formal MCP is Formal's hosted MCP server at
[`https://api.formal.ai/mcp`](https://api.formal.ai/mcp).
The MCP server has only read-only tool calls.
The server stays current with shipped product behavior. It can search
the API reference and query the state of your organization.
This is Formal's hosted server. It is not an
[MCP resource](/docs/guides/core-concepts/resources/mcp). MCP
resources proxy other MCP traffic through a Connector.
## What Formal MCP can do
| Capability | Use it to |
| - | - |
| Search the API reference | Find the current RPC, fields, and validation rules. |
| Search for a keyword | Resolve a name, email, hostname, or ID in your org. |
| Query organization state | Read resources, policies, connectors, and other objects you can already see. |
| Read access logs | Inspect recent traffic and group it by user, resource, or policy. |
Calls run as the authenticated identity. The client never sees more than
you can see in the Control Plane.
It cannot create or update objects.
## Connect with an API key
Authentication matches the rest of the Formal API. Send an API key in
the `x-api-key` header. See
[API authentication](/docs/api/introduction#authentication).
Go to [API Keys](https://app.formal.ai/api-keys) and click **Create
API Key**. Copy the key once.
Point the client at `https://api.formal.ai/mcp` and send the key on
every request.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
claude mcp add formal https://api.formal.ai/mcp \
--header "x-api-key: " \
--transport http
```
```json theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"mcpServers": {
"formal": {
"url": "https://api.formal.ai/mcp",
"headers": {
"x-api-key": ""
}
}
}
}
```
Use HTTP transport against `https://api.formal.ai/mcp`. Send
`x-api-key: ` on each request. The same header
works in a CLI flag or a config file.
Ask the client to list your connectors. You should get live results
from your organization.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
curl -s -X POST https://api.formal.ai/mcp \
-H "x-api-key: " \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```
The response lists the read-only tools above.
Treat the API key like any other Formal secret. Do not commit it to
source control or share it in a public prompt.
# AI Satellite
Source: https://docs.formal.ai/docs/changelog/ai-satellite
Release notes for Formal AI Satellite
## 1.4.1
### Changed
* Add a `/ready` endpoint for Kubernetes deployments
* Update dependencies and base images
## 1.4.0
### Changed
* Treat default column label classifications as custom user-owned labels so they can be edited or deleted
## 1.3.2
### Changed
* Improve PHI classification for large individual JSON payloads.
## 1.3.1
### Fixed
* Upgrade dependencies causing startup errors
## 1.3.0
### New
* Upgrade PHI LLM model to a newer version
## 1.2.1
### Fixed
* Fix PHI max output token cap limit errors
## 1.2.0
### New
* General availability of stream session analysis for SSH, SSM, and Kubernetes exec sessions
## 1.1.1
### Fixed
* Increased the default PHI max output token cap to 32768 to reduce truncation
## 1.1.0
### New
* Enable classifications of database columns in conjunction with the data discovery satellite
## 1.0.5
### Improved
* Enhanced stability and performance
* Optimized default configuration for production workloads
* Simplified deployment with automatic GPU detection and configuration
## 1.0.4
* Minor deployment improvements
## 1.0.3
### New
* Updated model to newer version
* vLLM v0.11.0 upgrade
* vLLM Engine 1 Usage
* FP8 cache quantization for performance
* GPU Parallelization Support
* File processing support up to 45KB at a time
### Changed
* Improved stability
* Cache Stabilization
* Prompt shortening + tweaks
* Post-processing enhancements
## 1.0.2
### Fixed
* Token length handling fix
* Label recognition improvements
## 1.0.1
### New
* Upgraded to 14B Instruct + Different Model Class
### Changed
* Simplified PHI fields
* Removed label normalization
## 1.0.0
### New
* 12B parameter model
* Full PHI/PII detection capabilities
* Connector integration
# CloudFormation
Source: https://docs.formal.ai/docs/changelog/cloudformation
Release notes for the Formal AWS CloudFormation template used by Cloud Integrations
## 1.5.0
### Changed
* Add `ec2:DescribeTags` to the IAM policy when EC2 autodiscovery is enabled
## 1.4.0
### New
* Add the `EnableS3Autodiscovery` stack parameter and `s3:ListAllMyBuckets` / `s3:HeadBucket` IAM permissions for S3 bucket autodiscovery
### Fixed
* Fix S3 IAM policy syntax for log integrations
## 1.3.0
### New
* Add the `EnableEC2Autodiscovery` and `EnableECSAutodiscovery` stack parameters
* Add EC2 and ECS autodiscovery IAM permissions, scoped by those parameters
## 1.2.0
### New
* Add stack parameters to toggle autodiscovery and log delivery independently:
* `EnableEKSAutodiscovery`
* `EnableRDSAutodiscovery`
* `EnableRedshiftAutodiscovery`
* `AllowS3Access`
* Scope autodiscovery IAM permissions to the enabled resource types
## 1.1.0
### New
* Add the `S3BucketARN` stack parameter to scope log delivery to a specific S3 bucket ARN
## 1.0.0
### New
* Initial versioned CloudFormation template for AWS Cloud Integrations
# Connector
Source: https://docs.formal.ai/docs/changelog/connector
Release notes for Formal Connector
## 2.21.3
### Changed
* Speed up Connector startup for organizations with a large data inventory.
### Fixed
* Fix a memory leak in Postgres, MySQL, and Snowflake Connectors.
## 2.21.2
### New
* Support encrypting `Struct` and `Value` fields in log schemas.
* Support log schema actions on repeated fields.
### Fixed
* Fix a race at startup where a Connector could miss some resources if the control plane switched snapshots while the Connector was loading its state.
* Recognize EC2 instance and ECS cluster ARN hostnames as AWS SSM targets in every AWS partition, such as GovCloud.
## 2.21.1
### Fixed
* Stop logging policy data loader data when a patch from the Satellite fails to apply, which happened whenever the Satellite restarted.
* Refresh IAM tokens 30 seconds before expiry to prevent health checks from erroring intermittently
## 2.21.0
### New
* Support connecting to a Redis upstream without authentication
### Changed
* Improve Redis logs
## 2.20.0
### New
* Support request policies on MongoDB, with the command, collection and projected fields available in `input.mongodb`.
### Changed
* Improve MongoDB logs readability.
* Reject legacy MongoDB wire protocol messages (e.g. `OP_QUERY`) after the handshake, so they can't bypass policies.
* Only fetch policy data loader data when the Satellite has something newer (requires Satellite 1.3.7).
* Report policy data loader errors in the Connector (requires Satellite 1.3.7).
### Fixed
* Apply policies and masking to compressed MongoDB connections (e.g. `compressors=zstd`).
* Make sure a Satellite restart doesn't empty the policy data loaders data (requires Satellite 1.3.7).
## 2.19.0
### Changed
* Reduce hook evaluation latency by pre-warming hook executors and preloading `fetch`.
### Fixed
* Fix EC2 SSM shell sessions not accepting input.
## 2.18.0
### New
* Scope policies to Spaces with `included_spaces` and `excluded_spaces`.
* Expose the SSH command the client asked to run to session policies, along with `is_pty` and `is_shell`.
* Support OAuth clients for MCP resources and injecting OAuth credentials into upstream MCP connections.
## 2.17.3
### New
* Support log schemas for field-level log transformations.
## 2.17.2
### Fixed
* Fix a bug where connectors without hostnames would crash on startup.
## 2.17.1
### Changed
* Asynchronously re-evaluate hook-based policies during active SSH sessions.
## 2.17.0
### New
* Support v3 native users.
## 2.16.2
### Fixed
* Make `response.time.processing` in logs accurately reflect Connector processing time.
## 2.16.1
### New
* Support `X-Claude-Gateway-User-Email` as an end-user identity source.
* Support policy evaluation and logging using OIDC claims.
## 2.16.0
### New
* Add Support for the Socket Registry Firewall technology.
## 2.15.0
### New
* Revamp ClickHouse support.
* Add Support for MongoDB SRV hostnames.
### Fixed
* Fix Postgres latency on large JSON/JSONB values when no row-level masking or filtering applies.
* Deliver SSH policy block messages instead of closing the connection silently.
* Fix connections to the smaller MongoDB Atlas tiers which could be failing during the secure handshake.
## 2.14.9
### Changed
* Improve Kubernetes proxy performance when handling large response bodies.
## 2.14.8
### New
* Support GCP IAM native users for Kubernetes.
## 2.14.7
### New
* Support Azure IAM native users for Kubernetes.
## 2.14.6
### Changed
* Increase sidecar list-fetch context timeouts from 10 minutes to 15 minutes for large paginated startups
## 2.14.5
### Changed
* Increase sidecar list-fetch context timeouts (including GetNativeUsers) from 100s to 10 minutes for large paginated startups
## 2.14.4
### Fixed
* Fix SSH and Kubernetes session logs being dropped when terminal output contains non-UTF-8 bytes.
### Changed
* Increase ListResourceTags context timeout for large numbers of resource tags
## 2.14.2
### Fixed
* Fix Connector requests to the control plane hanging during API deployments.
## 2.14.1
### New
* Add support for logging files transferred during an SFTP session.
### Fixed
* Fixed an issue with re-signing chunked puts to S3 through the AWS proxy.
## 2.14.0
### New
* Support type-safe masking on MongoDB.
### Changed
* Improve the accuracy and coverage of MongoDB audit logs.
## 2.13.2
### Changed
* Log AWS native-user credential resolution failures, including the assumed role ARN and the STS error.
## 2.13.1
### New
* Support host-based smart routing and `X-Formal` header authentication for Kubernetes resources.
## 2.13.0
### New
* Support rewriting HTTP paths in policies
* Surface `input.session.id` and `input.request.id` where applicable as policy evaluation inputs.
* Surface session ID and request IDs in the default formal block message across technologies
### Fixed
* Respect disabled inline discovery for nested JSON fields and other discovery paths.
## 2.12.1
### Fixed
* Fix AWS SDK uploads that use `Content-Encoding: aws-chunked`, which the HTTP proxy previously rejected.
## 2.12.0
### New
* Select a native user on the HTTP proxy with the `@` syntax, the same way the other protocols already allow.
### Fixed
* Fix a listener scoping issue where a user connected to one listener could reach a resource served by another listener on the same Connector.
* Fix an issue where masking a `NUMERIC` column on Postgres returned a text placeholder instead of a number.
* Fix an issue where a mask that falls back to NULL on MySQL returned an empty value instead of NULL.
## 2.11.6
### Fixed
* Fix an issue where masking a non-text column over MySQL failed instead of applying the mask.
## 2.11.5
### Fixed
* Fix an issue where S3 listing prefixes were not included in Rego policy input or audit logs.
## 2.11.4
### Changed
* Log whether the Connector is connected to a policy data loader and how much data it received, instead of printing whole datasets in debug logs.
* Record web resource login redirects and callbacks in the session logs.
### Fixed
* Fix an issue where a rejected Postgres login was also recorded as a successful session.
## 2.11.3
### Fixed
* Fix an issue where syncing a large policy data loader dataset to the Connector could fail, and compress the transfer.
## 2.11.2
### Changed
* Speed up policy evaluation when policies match against large policy data loader datasets.
## 2.11.1
### Fixed
* Fix native-auth fallback behavior in the AWS proxy.
## 2.11.0
### New
* Add support for AWS API native user authentication on the Formal Connector.
## 2.10.0
### New
* Add Redis smart routing based on TLS server name (`.`).
## 2.9.3
### New
* Add support for federated token authentication with end-user identity propagation.
### Fixed
* Redirect browsers to re-authenticate when a web resource session cookie is expired or rejected, instead of returning 401 Unauthorized.
## 2.8.0
### Changed
* Make the Connectors schema inline discovery configurable from Formal Control Plane.
## 2.7.0
### New
* Add support for encrypting and decrypting HTTP tokens with a cloud KMS key on the Connector.
## 2.6.0
### New
* Initial release of the FIPS-compliant build of the Connector (`formalco-prod-connector-fips`).
## 2.5.2
### Changed
* Speed up queries that return masked data. Large result sets are now several times faster, especially with many concurrent sessions.
## 2.5.1
### Fixed
* Improved gRPC support.
## 2.5.0
### New
* Support end-user identity propagation for Kubernetes, so requests made through a shared or machine identity are attributed to the real user in policies and audit logs.
* Support rewriting HTTP request and response bodies from policies, including raw, JSON, and form-encoded payloads.
* Add a block action for unknown MCP servers that suggests approved alternatives.
### Changed
* Run health checks for web and other HTTP-based resources that were previously skipped.
## 2.4.0
### New
* Add support for Amazon Redshift.
* Support Azure IAM authentication for native database users.
* Support rewriting HTTP response headers from policies, mirroring existing request header rewrite support.
### Fixed
* Tighten transparent proxy listener rule matching. Connectors must be explicitly configured to proxy for a technology/resource via a listener rule.
## 2.3.2
### Fixed
* Fix a bug where recently created or updated OTel connection settings were not applied at startup.
## 2.3.0
### New
* Add support for writing to and reading from a cache from hooks.
### Changed
* Encrypt clustering gossip communications between connectors. Note: when upgrading to this version via a rolling restart, a *second* rolling restart is required to ensure the cluster is not partitioned. Without a second restart, rate limit enforcement may be inconsistent.
* Add support for allowlisting CIDR ranges for hook outbound traffic.
### Fixed
* Fix an issue where a MySQL connection could drop after a query failed partway through its results, forcing users to reconnect.
* Fix an issue where an empty text value in a MySQL result could be returned as NULL.
## 2.2.0
### Changed
* Require allowlisting environment variables and upstream network destinations for hooks.
## 2.1.0
### New
* Initial release of Formal Hooks
## 2.0.1
### Fixed
* Fix a bug affecting end-user identity propagation for newly created human users
## 2.0.0
### New
* General availability of our overhauled system for syncing data between the Connector and the control plane.
## 1.55.2
### Fixed
* Fix an issue where users could not log in over RDP with NLA using SPNEGO
## 1.55.1
### Fixed
* Keep hashed UUID values as valid UUIDs so they can be written back to UUID columns
## 1.55.0
### New
* Add IAM authentication for Redis on Amazon ElastiCache (Valkey 7.2+, Redis OSS 7+)
## 1.54.5
### Fixed
* Fix an issue with NLA over SPNEGO in the RDP proxy
## 1.54.4
### Fixed
* Allow RDP clients that authenticate over SPNEGO to connect
## 1.54.3
### Fixed
* Fix an issue where large Snowflake query results could fail to load through the Connector
## 1.54.2
### Changed
* Add more detail to RDP connection logs to help diagnose handshake and negotiation failures
## 1.54.1
### Changed
* Improve logging for RDP connections that fail during the handshake, making these issues easier to diagnose
## 1.54.0
### New
* Allow connection to upstream resources using mTLS
* Mask JSON fields that are wrapped in casts or functions
* Add support for the Redis Cluster protocol
### Fixed
* Mask JSON fields whose keys contain special characters
* Preserve JSON field types when masking
## 1.53.0
### New
* Mask JSON fields however a query navigates to them, including path expressions and array-index access
* Mask values stored directly in JSON arrays, including columns whose entire value is an array
### Fixed
* Mask JSON fields combined with other columns inside a single output expression
* Preserve valid JSON encoding when masking a field selected directly from a JSON column
* Fix Postgres connections for stateless JWT machine users when clients send `options=-c ...` startup parameters
## 1.52.0
### New
* Add Postgres support for field masking in JSON/JSONB columns
### Fixed
* Fix an issue where fields nested inside JSON arrays were not always masked
## 1.51.0
### New
* Encrypt sensitive log fields in the standard JWE format, so they can be decrypted with any standard JWE library
* Add support for GCP Cloud KMS encryption keys
## 1.50.3
### Fixed
* Ensure SSM sessions are cleaned up after resource connection tests
## 1.50.2
### Changed
* Improve performance of large Snowflake queries by streaming results directly when no masking policy applies
## 1.50.1
### Changed
* Improve logging and policy evaluation on LLM requests by parsing OpenAI and Anthropic events
* The `gcp_cloudsql` dial method now targets the Cloud SQL instance's private IP. The Connector must run in the instance's VPC, or in a peered or PSC-connected VPC.
* Cloud SQL Admin API calls now identify the client as `formal-connector/`
## 1.50.0
### New
* Add support for resource dial configurations, enabling connection to Google Cloud SQL Postgres and MySQL instances natively
### Fixed
* Preserve quoting in commands run through the SSH proxy
* Keep standard output and standard error separate over the SSH proxy
## 1.49.12
### Fixed
* Propagate stdin EOF through the SSH proxy
## 1.49.11
### Fixed
* Sanitize assume role session names for SSM connections
## 1.49.10
### Fixed
* Restore forwarding of `X-Formal-End-User-Identity` to HTTP resources
## 1.49.9
### New
* Allow the policy engine to read environment variables prefixed by `FORMAL_POLICY_ENV_`
## 1.49.8
### New
### Changed
* Send policy action details in logs along triggered policies, such as which columns a masking policy would affect
* Improve policy evaluation performance
### Fixed
* Fail loudly when the Connector can't handle a Snowflake request or response
## 1.49.7
### Fixed
* Fix connector startup failure after issuance of a new TLS certificate
## 1.49.6
### Changed
* Improve response policy evaluation performance
## 1.49.5
### Fixed
* Fix connector policy inclusion bug for Kubernetes sessions
## 1.49.4
### Changed
* Improve connection pooling behavior for S3 connections
## 1.49.3
### Changed
* Improve policy evaluation performance
## 1.49.2
### Changed
* Propagate upstream exit codes from SSH sessions
### Fixed
* Fix failures when connecting to an EKS cluster in a different region than the Connector
## 1.49.1
### Changed
* Improve S3 connection performance
## 1.49.0
### New
* Add support for device trust on MySQL connections
* Allow policies that match on the HTTP request hostname
* Add or rewrite HTTP request headers from a policy
* See which client application opened each MySQL connection in logs and policies
* Add support for asymmetric AWS KMS keys
### Changed
* Improve S3 connection performance
* Improve MySQL health check performance
* Cap on-disk log buffering to 90% of available disk space to avoid filling up the host
* Improve on-disk log buffering reliability and performance
### Fixed
* Show Formal block messages inside Codex when an action is blocked
## 1.48.2
### Fixed
* Improve masking accuracy on CTE queries
## 1.48.1
### Changed
* Faster Connector startup when running multiple MySQL listeners
## 1.48.0
### New
* Add support for GCP IAM authentication in the new MySQL proxy implementation
### Fixed
* Respect the `disable` TLS configuration on backend connections in the new MySQL proxy
## 1.47.2
### Fixed
* `hash.with_salt` masking now produces properly salted, non-reproducible outputs
* Improve data label accuracy on `SELECT` query outputs
## 1.47.1
### Fixed
* Ensure compatibility with strict Snowflake clients in DataFrame writeback flows
## 1.47.0
### New
* Add device trust support for MongoDB
### Changed
* MongoDB session policies are now enforced on every connection, not just SCRAM clients
### Fixed
* Include Anthropic cache tokens in input token counting
* Strip `X-Formal-*` headers before forwarding HTTP requests to upstream
## 1.46.0
### New
* Add support for IAM (Assume Role) native users for S3
### Fixed
* Fix Snowflake DataFrame writeback flows
## 1.45.2
### New
* Add support for SSH certificate native users
## 1.45.1
### New
* Add response policy support in the new MySQL proxy implementation
* Support Postgres response-policy masking on advanced SQL queries (UNION, CTE, subqueries, JOIN).
* Surface column lineage through more SQL expression shapes in policy inputs.
## 1.44.1
### Fixed
* Fix keypair and password authentication for Snowflake resources
## 1.44.0
### New
* Add support for request-level policies in the new MySQL proxy implementation
### Fixed
* Fixed a Rego policy handling that could prevent data masking.
* Fixed Snowflake data masking for non-string columns.
## 1.43.5
### New
* Add 'input.native\_user\_assignment' as an available policy input.
### Fixed
* Fix 'email\_mask\_while\_preserving' so that outputs match Formal documentation.
## 1.43.4
### Improvements
* Throttle SSM concurrency
* Support custom block messages for Claude Code tool calls
## 1.43.3
### Improvements
* Improve parsing of Postgres `options` parameters
## 1.43.2
### Improvements
* Improve performance of AWS IAM (assume role) authentication for RDS and SSM health checks
## 1.43.1
### Improvements
* Allow S3 browser objects to be addressed using unescaped path segments. This creates a **breaking change** from previous versions where objects must be downloaded by appending `?download=1` to the URL rather than `/dl`.
* Parse Hex tool metadata SQL comments in Snowflake
## 1.42.7
### Fixed
* Fix TLS handling for Redis connections
## 1.42.6
### Fixed
* Fix overly verbose debug-level logging for control plane updates
## 1.42.5
### Fixed
* Parse Snowflake queries using `GROUPING SETS` with non-parenthesized items
* Redact passwords in Snowflake `ALTER USER ... SET PASSWORD = ...` statements
## 1.42.4
### Fixed
* Fix health checks for Redis resources
## 1.42.3
### New
* More gracefully handle SSM rate limiting errors
## 1.42.2
### New
* Add `yolo_mode` to agent info for connector sessions
## 1.42.1
### New
* Improve health checks for MCP resources
## 1.42.0
### New
* Add TCP reachability health check for RDP resources
## 1.41.10
### Changed
* Inventory loading performance improvements
## 1.41.7
### Changed
* Remove welcome messages from the SSH proxy
* Suppress noisy logging during device info signature verification
## 1.41.5
### Changed
* Improve robustness of SSM resource reachability checks
## 1.41.4
### Changed
* Improve speed of connector resource reachability checks
## 1.41.3
### Changed
* Improve performance of resource data loading at connector startup
* Improve health check startup time
## 1.41.2
### Changed
* Increase snowflake connection concurrency limits
## 1.41.1
### Changed
* Improve resource hostname loading performance at connector startup
### Fixed
* Fix MySQL `USE` statements with smart routing
## 1.41.0
### New
* Add support for Postgres query cancellation
## 1.40.12
### New
* Add support for logging macOS process signatures in device info
## 1.40.9
### New
* Add raw HTTP request and response bodies to policy inputs
## 1.40.8
### New
* Add support for device info parsing for HTTP connections
## 1.40.7
### Fixed
* Fix error logs when retrieving search paths in Postgres with GCP IAM Authentication
## 1.40.6
### New
* Log bytes sent and received for SSH sessions
* Log bind parameters for extended Postgres queries
## 1.40.5
### Changed
* Reliability and performance improvements
## 1.40.4
### Fixed
* Improve MySQL access token syncing reliability
## 1.40.3
### Fixed
* Fix a bug where Kubernetes API servers on port 443 were detected as duplicate contexts in kubeconfig files
## 1.40.2
### New
* Support Omni end user identity propagation for Snowflake
## 1.40.1
### New
* Add support for hosted AI providers (OpenAI, Anthropic, Gemini, Google Vertex AI, AWS Bedrock, Azure AI) for session analysis
## 1.40.0
### New
* Introduce RDP protocol support with session-level logs and policies
### Changed
* Migrate auth redirects from app.joinformal.com to app.formal.ai
## 1.39.1
### New
* Surface `transaction_history` and `transaction_progress` in query policy inputs for postgres
## 1.39.0
### New
* Add support for request-stage policy evaluation and end-user identity propagation for Redis
### Changed
* Reduce log error noise for Postgres connections
## 1.38.2
### Fixed
* Support input conditions for time-based policy suspensions
## 1.38.1
### Fixed
* Fixed an issue where changes to the PostgreSQL `search_path` were not properly tracked in the session state, causing unqualified table names (e.g., `pg_class`) to be resolved against outdated schema listings
## 1.38.0
### Changed
* Restrict the Snowflake stage download proxy to known cloud storage endpoints (S3, Azure Blob, GCS) and cap decompressed response bodies at 256 MB
* Enforce `ReadHeaderTimeout` on all HTTP-based proxy servers to mitigate slow-client resource exhaustion
* Defer listener readiness until the initial control plane event stream catch-up completes, ensuring consistent configuration before accepting traffic
### Fixed
* Support WebSocket-based `kubectl port-forward` introduced in kubectl v1.30+
* Preserve parsed table context when best-effort inventory lookups fail, so policy evaluation still has access to table names and schemas
* Resolve a concurrency issue in listener port lookups that could surface under high connection churn
## 1.37.1
### Fixed
* Fix PostgreSQL extended query protocol support for `PortalSuspended` messages when `Execute` is called with a row limit
* Fix incorrect query attribution in PostgreSQL logs when standard queries (e.g. `BEGIN`) are sent between `Parse` and `Bind` in the extended query protocol
## 1.37.0
### New
* General availability of stream session analysis for SSH, SSM, and Kubernetes exec sessions
## 1.36.5
### New
* Add support for agent forwarding for SSH connections
## 1.36.4
### New
* Add support for assuming roles from command line arguments for SSM (EC2 and ECS) connections
## 1.36.3
### Fixed
* Fixed a bug with PostgreSQL health checks using GCP Cloud SQL IAM authentication
## 1.36.2
### Fixed
* Fix PostgreSQL `table_paths` not including unqualified table names when session schemas are unavailable
## 1.36.1
### New
* Add API Request verb for Kubernetes policy evaluations
## 1.36.0
### New
* Add support for Kubernetes WebSockets, v5 stream protocol, and follow requests
### Fixed
* Block PostgreSQL `COPY TO` (`\copy`) operations when response policies are configured
## 1.35.0
### New
* Initial release of the Formal Connector on Google Cloud Artifact Registry
### Changed
* Enhanced metadata extraction from PostgreSQL queries, enabling better policy evaluation against DML operations
## 1.34.10
### Fixed
* Fix a bug with IAM (Assume Role) authentication for EKS clusters
## 1.34.9
### New
* Customers can now specify the frequency of the resources health check
## 1.34.8
### Changed
* Improve performance of policy evaluation
### Fixed
* Fixed a bug where dry-run rewrite policies were being applied to requests
## 1.34.6
### Changed
* Improve performance of sending logs
## 1.34.5
### Changed
* Improve performance of responses with many rows
## 1.34.4
### Fixed
* Improve reliability of large snowflake arrow responses
## 1.34.3
### New
* Emit the total number of bytes transferred during SFTP sessions in SSH session logs
## 1.34.2
### New
* Support CEL expressions in policy suspension input conditions
* Added rate-limiting support for postgres and mysql and refactored s3 rate-limiting
### Changed
* Logs are now persisted to disk and retried automatically during Control Plane outages, preventing log loss
* Support multiple policy suspensions for the same policy and identity id
### Fixed
* Fixed a bug with the `nullify` redaction option
* Fixed a bug where query-rewrite policies didn't allow adding `LIMIT 0`
## 1.34.1
### New
* Support a custom OTEL metric collector hostname and port
### Changed
* Improve the behavior of the Connector when Formal Control Plane can't be reached
## 1.34.0
### New
* Smart routing is now available for MySQL resources when using the new proxy implementation
### Changed
* Parallelized Rego policy evaluation to improve performance on large numbers of policies
### Fixed
* Reduce log noise for timestamp formatting
* Enforce stricter connection timeouts during SSH health checks
## 1.33.2
### Fixed
* Reduce log noise for certain policy evaluations
## 1.33.1
### New
* Add a parameter for request/response scope for HTTP AI body analysis
## 1.33.0
### New
* Add support for MySQL end-user identity propagation in the new proxy implementation
### Changed
* Include parsed query information in the new MySQL implementation logs
## 1.32.7
### Fixed
* Fix connector startup failure when using spaces
## 1.32.6
### Changed
* Add support for DynamoDB resource health checks
### Fixed
* Address binary protocol issues affecting PlanetScale/Vitess connections when using prepared statements with the new MySQL proxy implementation
* Fix DynamoDB authentication when using the desktop app
## 1.32.5
### Fixed
* Fix end-user not appearing in logs for SSH connections
## 1.32.4
### Changed
* Add support for gRPC resource health checks
## 1.32.3
### Changed
* Add support for custom message and timeout parameters in MFA policy actions
## 1.32.2
### New
* Add support in the policy engine for tags on resources
## 1.32.1
### Fixed
* Always use the incoming request port if it's different from the listener port for Snowflake S3 support
## 1.32.0
### New
* Add a new MySQL proxy implementation. This is a fundamentally different and more robust implementation, starting with a minimal feature set that we plan to expand quickly. This feature is behind a feature flag, contact us to enable it for your organization.
* Add MFA policy enforcement across all technologies via the Desktop App
## 1.31.31
### Changed
* Downgrade health check failure logs to debug level to reduce log noise
## 1.31.30
### Fixed
* Fix AWS RDS IAM authentication for resources in a different AWS region than the connector
## 1.31.29
### New
* Log policy version in triggered policy logs
## 1.31.28
### Fixed
* Fix a race condition on concurrent cache queries in BigQuery
## 1.31.26
### Changed
* Emit `formal.connector.resource_health_check` metric for earlier health check failure stages
### Fixed
* Fix window resize handling for SSH connections
## 1.31.22
### New
* Add `formal.connector.resource_health_check` metric
## 1.31.21
### New
* Support seamless authentication via the Control Plane for the S3 browser. Users do not need to manually enter their Formal username and access token to access the S3 browser.
## 1.31.20
### New
* Add support for `scp` (SFTP) to the SSH proxy for SSH resources
### Fixed
* Fix session log entry values for failed SSH logins.
## 1.31.19
### New
* Support optionally sending policy evaluation inputs to the Formal Control Plane based on log configuration settings.
* Support "request" and "response" as evaluation stage names in policies instead of "pre\_request" and "post\_request".
* Enable row-level filtering for Snowflake responses.
### Changed
* Preserve end-user across BigQuery job lifecycle requests to avoid authentication failures when impersonation is used.
### Fixed
* Fix session log entry values for SSM (EC2/ECS) resources.
* Don't attempt to start the Connector state server if it can't write on disk.
## 1.31.12
### New
* Support cross-account AWS role assumption for EC2/ECS SSM
* Support custom error messages for Postgres and MySQL request evaluation
### Fixed
* Fix downloads for S3 objects with special characters in object keys
* Fix S3 technology failures when using the AWS JavaScript SDK
## 1.29.12
### New
* Add AWS account ID and name to resource health check events for easier multi-account tracking
## 1.29.11
### Fixed
* Fix S3 request logs to include formal user name, improving auditability
## 1.29.10
### New
* Add per-bucket S3 health checks with autodiscovery, showing bucket names for faster diagnostics
## 1.29.9
### New
* Add policy input logging for session and request stages with request/session IDs for better traceability
* Add sync of autodiscovered S3 buckets to the connector, with pagination and live updates
### Changed
* Standardize request/session ID propagation across connectors, including Snowflake IDs, for consistent logs
* Enforce stricter policy engine capabilities for safer, more predictable evaluations
## 1.29.8
### New
* Add S3 bucket access metrics to policies for rate limiting and blocking
* Add connector, resource, and space IDs to policy input logs for filtering
### Changed
* Improve S3 access counting accuracy, include current request and drop daily counts
* Add bucket, path, action, and last modified to S3 policy inputs for finer control
* Provide richer user and query context in post-request policy checks across databases
### Fixed
* Fix S3 auth failures to return 403 Forbidden with AWS-style XML
* Fix MySQL auth to forward native error packets to clients
## 1.29.7
### New
* Add structured S3 logs with action, bucket, path, and last-modified, consistent across access styles
### Fixed
* Fix S3 PutObject authentication by honoring payload hashes and signing required headers to prevent auth errors
* Fix query aggregation failures for long SQL statements in analytics to improve reliability
## 1.29.6
### New
* Add normalized SQL queries to datastore request logs for easier analysis
* Add response-stage policy input logging in PostgreSQL, improving policy analysis
### Changed
* Treat S3 hostnames as global endpoints, enabling ListBuckets and vhost/path access
## 1.29.5
### Fixed
* Fix column matching to ignore case and spaces, preventing mismatches from formatting differences
## 1.29.4
### New
* Add support for MCP resources, enabling MCP traffic parsing and accurate technology logs for auditing
## 1.29.3
### New
* Add AI satellite integration, enforcing one link only with data classifier to prevent conflicts
* Add request log evaluation in policy backtests for fuller coverage
* Add MySQL and MariaDB support for hashed tokens, avoiding password length limits
### Fixed
* Fix HTTP response handling for non-JSON payloads to prevent misclassification
## 1.29.2
### Fixed
* Fix TLS defaults when no config is present, restoring secure connections and preventing connection errors
## 1.29.1
### New
* Add configurable retention for policy evaluation input logs
## 1.29.0
### New
* Add AI satellite integration for HTTP with automatic fallback for compatibility
* Add audit logs for frontend API create, update, delete actions for traceability
### Changed
* Enforce required environment variables across environments to prevent misconfigurations
* Include uses row data flag in policies for consistent behavior across services
## 1.28.10
### New
* Add support for duplicate columns in SQL queries, ensuring results match source databases.
### Fixed
* Fix returning MySQL responses when analysis fails, ensuring accurate error reporting.
## 1.28.9
### New
* Add qualified wildcards (table.*, alias.*) and column alias detection for accurate multi-table queries
### Fixed
* Fix metric setup with automatic startup retries, ensuring monitoring works when agents start late
## 1.28.8
### Fixed
* Fix device trust errors in Postgres when using custom native usernames, preventing login failures.
## 1.28.7
### Fixed
* Fix production connector image to include required files, preventing startup failures
## 1.28.5
### Changed
* Limit inventory to labeled objects, tracking label add/removal updates
## 1.28.4
### New
* Add native DB user context to PostgreSQL policy checks
## 1.28.3
### Changed
* Change S3 health checks to use network connectivity
## 1.28.2
### Changed
* Improve MySQL query performance when policies are enabled
## 1.28.1
### New
* Add multi stage resource health checks for HTTP, SSH, and SSM
* Make the connector health check port configurable to prevent port conflicts in shared environments.
## 1.28.0
### New
* Add resource health checks for Kubernetes, ClickHouse, and Snowflake with connection and authentication validation
### Changed
* Remove policy evaluation details from logs
## 1.27.2
### Fixed
* Fix incorrect query parameters in Postgres
* Fix JIT SSO
### Changed
* Update Datadog profiling: support agent URL via env vars, check reachability with retries to reduce startup errors
## 1.27.1
### New
* Add auto-creation of MySQL discovery connections, preventing failures when no prior connection exists
## 1.27.0
### New
* Add per-stage connection health logs covering network, security, login, and data
## 1.26.11
### Changed
* Apply log level changes from the UI instantly during CDC syncs, no restart needed
## 1.26.10
### Changed
* Standardize log timestamps for consistent parsing across tools
## 1.26.9
### New
* Add support for MariaDB databases, expanding compatibility
### Fixed
* Fix MySQL connection failures when clients omit auth plugin, improving compatibility with older clients
### Changed
* Enable on-demand test connection checks from the Control Plane for faster troubleshooting
## 1.26.8
### New
* Add password redaction for SQL queries in PostgreSQL and Snowflake, with CLI support
* Add formal user type to SSH stream logs for clearer auditing
### Changed
* Remove Redshift technology support
* Remove query fingerprints from logs
## 1.26.7
### Changed
* Stop publishing the Redshift connector, making it unavailable in new releases
## 1.26.6
### New
* Add MCP technology support using HTTP flow, simplifying MCP integrations
* Add native auth types: SSH key, Snowflake key, HTTP basic, bearer, API key
* Enhance logging with unique request IDs, event types
### Fixed
* Fix inconsistent stage labels in logs for clearer filtering
## 1.26.5
### Changed
* Allow startup without TLS when no certificate is provided, simplifying initial deployment; enable TLS later
## 1.26.4
### New
* Add API to generate and upload CloudFormation templates, simplifying connector deployment automation
### Changed
* Update TLS handling to run without a certificate, preventing errors in non-TLS environments
## 1.26.3
### Fixed
* Fix remote access checks for ECS clusters with cross-region configs, enabling successful connections
* Fix empty service name display when connecting to ECS containers using a service ARN, improving clarity
## 1.26.2
### New
* Add GCP autodiscovery for Compute Engine, GKE, and Cloud SQL to speed setup
### Fixed
* Fix errors during data classification when JSON contains empty or null fields
* Fix SSH session logs missing resource details on startup
### Changed
* Remove field-level encryption, simplifying setup and avoiding unused complexity
## 1.26.1
### New
* Add AI-driven scenario monitoring for Kubernetes exec with risk scores, policy enforcement, and session-end audit logs
* Terminate Kubernetes exec sessions automatically when users become blocked
### Fixed
* Fix typos in Kubernetes exec error messages for clearer troubleshooting
## 1.26.0
### New
* Add secure satellite and policy data loader connections with certificate loading, on-demand issuance, and auto renewal
### Changed
* Ensure columns are consistently ordered at runtime for predictable output
### Fixed
* Fix missing resource technology in session listings
## 1.25.7
**Fixed**
* Fixed a bug in the TLS certificate renewal process
## 1.25.6
**Changed**
* Streamlined TLS certificate handling at connector startup
## 1.25.5
**Fixed**
* Fixed a bug that would prevent connector TLS certificates from being updated on renewal unless the connector is restarted
## 1.25.4
**New**
* Enabled satellite hostnames linked to a Connector to be configured from the Control Plane and read by the Connector
* Added 'ConnectorName' attribute for better tracking and logging during telemetry operations
## 1.25.3
**Changed**
* Removed a specific MongoDB configuration created for a legacy client, streamlining the process for connecting to DocDB with client-side options
## 1.25.1
**New**
* Introduced metrics to monitor opened, closed, and currently active connections, enhancing visibility into connection management for performance optimization and troubleshooting
* Implemented new metrics to monitor the number of received Control Plane pings, increasing observability of system interactions
**Fixed**
* Fixed some errors with S3 authentication
## 1.25.0
**New**
* Regularly send connector instance heartbeat as a gauge metric via OpenTelemetry, bolstering real-time monitoring capabilities
* Default to starting up etcd without failing if the etcd cluster doesn't come up
**Changed**
* Cleaned up configuration and feature flag storage
## 1.24.12
**Changed**
* Removed unnecessary quotes from table names when parsing SQL queries to make policies easier to write
## 1.24.11
**Changed**
* Moved classifier-related environment variables into the new `formal_resource_classifier_configuration` Control Plane objects
## 1.24.10
**Fixed**
* Fixed column detection issues with parsing UNION statements with wildcards within Common Table Expressions (CTEs)
## 1.24.9
**Changed**
* Enhanced logging to include connector ID, improving traceability for debugging and monitoring purposes
## 1.24.8
**Fixed**
* Resolved connection issues in the MySQL proxy that were caused by handshake problems with MariaDB
## 1.24.7
**New**
* Enabled automatic connection to the first task's first container within a specified ECS service when no specific task or container is selected
**Changed**
* Simplified S3 connection establishment
## 1.24.6
**Fixed**
* Resolved issue with Snowflake private key authentication for enhanced connection security
**Changed**
* Removed unused PII\_SAMPLING\_RATE ensuring cleaner code base and improved performance
**New**
* Included desktop app device trust keys in critical data load process for strengthened data security
## 1.24.5
**Fixed**
* Improved robustness of desktop app device trust keys
## 1.24.4
**Changed**
* Improved logging around device trust signature verification errors
## 1.24.3
**New**
* Added stateless authentication support for Clickhouse
**Fixed**
* Fixed the parsing of device info to ensure accurate timestamping in UTC
## 1.24.2
**Changed**
* Reduced wait time for PROXY protocol headers from 10 seconds to 200 milliseconds, enhancing performance and speeding up the handling of new connections
**Fixed**
* Fixed MySQL column detection and data label matching for policies
## 1.24.1
**New**
* Implemented stateless authentication for Snowflake
## 1.24.0
**New**
* Introduced stateless JWT authentication for Kubernetes, PostgreSQL, HTTP, and SSH, enhancing security and streamlining user verification process
## 1.23.6
**Fixed**
* Improved reliability for MySQL resources
## 1.23.2
**Changed**
* Enhanced MySQL error messages for better readability during the handshake process
## 1.23.1
**New**
* Added support for hashed token authentication for Postgres proxy, providing a new option for users facing length restrictions on database passwords
## 1.23.0
**New**
* Added support for SSH private key authentication to upstream, enhancing secure connection options
**Changed**
* Updated MySQL to utilize our unified SQL semantic analyzer, ensuring consistent behavior across databases
* Enabled port fields in resources to be updated for more flexible configurations
## 1.22.2
**New**
* Introduced a readiness endpoint for the connector (on health check port 8080 `/ready`), allowing it to signify when all listeners are initialized and ready to receive traffic
**Changed**
* Achieved consistency in logging and assertion libraries across the system, improving reliability of error logging
## 1.22.1
**New**
* Added support for handling Snowflake positional reference syntax in SQL queries
* Introduced capability to resolve Snowflake positional arguments based on actual column names from inventory
* Extended support for LATERAL queries, enabling more complex SQL queries that depend on preceding tables' columns
**Fixed**
* Resolved possible crash when retrieving outbound IP in telemetry, improving stability
**Changed**
* Streamlined MySQL TLS environment variables into a centralized TLS config
* Dropped ability of the connector to exit if a health check fails, boosting connector resilience
## 1.22.0
**New**
* Refactored log encryption configuration and improved encryption of exec streams (SSH, SSM, Kubernetes)
**Breaking Change**
* Removed the ability to configure log encryption and encryption keys via environment variables; such configuration will need to be done via the Control Plane or Terraform provider
# Data Discovery
Source: https://docs.formal.ai/docs/changelog/data-discovery
Release notes for Formal Data Discovery
## 0.11.1
### Changed
* Add a `/ready` endpoint for Kubernetes deployments
## 0.11.0
### New
* Add MongoDB and Atlas support via document sampling
## 0.10.1
### Fixed
* Retry transient control plane failures during inventory writes
## 0.10.0
### New
* Add Redshift support
* Use the resource mTLS configuration when connecting to resources
### Fixed
* Fix an edge case where MySQL connections ignored the resource TLS configuration
* Fix Redshift IAM authentication
## 0.9.0
### Changed
* Treat default column label classifications as custom user-owned labels so they can be edited or deleted
## 0.8.1
### New
* Add support for using AWS Bedrock for column classification
## 0.7.4
### Fixed
* Fix handling of unset resource TLS configurations
## 0.7.3
### New
* Add support for using the AI satellite for column classification
### Changed
* Remove support for using the legacy data classifier satellite for column classification
## 0.6.5
### New
* Add Snowflake key‑pair auth with auto‑detect, TLS options, and PrivateLink OCSP control
## 0.6.4
### Fixed
* Fix TLS initialization for Postgres to prevent connection failures
* Fix certificate trust for Postgres and Redshift to accept PEM contents, avoiding path errors
## 0.5.1
### Changed
* Improve Snowflake PrivateLink connection speed and reliability, reducing scan timeouts
## 0.4.1
### Fixed
* Fix secure connection setup for Postgres to prevent failures and improve reliability
## 0.4.0
### New
* Add per-resource TLS settings across supported connectors, simplifying secure setup
### Changed
* Improve Snowflake PrivateLink connections by skipping revocation checks, reducing timeouts
* Improve satellite job orchestration for more reliable scans and labeling
### Fixed
* Fix CA trust store handling for Postgres and Redshift, allowing inline PEM content
* Fix Metabase connection when hostname includes a trailing slash
* Fix missing PII classifications for views and prevent premature deletions during scans
## 0.3.5
### Fixed
* Fix DSPM workflow failures with better timeouts, paged results, and non-blocking inventory updates
## 0.2.0
### Changed
* Improve DSPM workflow reliability and throughput, reducing timeouts and failed runs
* Speed up Snowflake PrivateLink connections to reduce scan delays
### Fixed
* Fix Metabase connections failing when hostname ends with a trailing slash
## 0.1.5
### Fixed
* Fix intermittent DSPM workflow failures, reducing timeouts and stalled runs
### Changed
* Update token generation to a managed workflow, improving reliability and expiration handling
# Formal Endpoint
Source: https://docs.formal.ai/docs/changelog/endpoint
Release notes for Formal Endpoint
## 0.35.8
### New
* Support encrypting `Struct` and `Value` fields in log schemas, such as MCP tool parameters and results.
## 0.35.7
### New
* Support log schema actions on repeated fields.
* Support dropping, encrypting and truncating session device info.
* Support for [Formal Assessments](https://docs.formal.ai/docs/guides/core-concepts/assessments). LLM responses can now be assessed for risk and misalignment and policy decisions can be made based on these assessments.
### Fixed
* OIDC tokens are now refreshed 10 minutes before they expire.
## 0.35.6
### Fixed
* Record a session and emit session login logs for the first turn of a new ChatGPT conversation in Chrome.
## 0.35.5
### New
* Expose the destination `hostname` to `pre_tcp` network rules on macOS.
### Fixed
* Fix code signing of the `formal` CLI binary inside `Formal.app` on macOS.
## 0.35.4
### Changed
* Support CEL macros such as `exists`, `all` and `filter` in `pre_tcp` network rules on macOS.
## 0.35.3
### New
* Support GCP as an OIDC identity provider.
* Support the `process` input in network rules, including `process.is_agent`.
* Update `is_agent`, `is_browser`, and `has_agent_ancestor` network rule inputs to matches known agents by process name on Linux.
* Add network rule inputs specific to individual agents.
## 0.35.2
### Changed
* `formal connect` now listens on port 37017 by default for MongoDB resources, so it no longer conflicts with a local MongoDB server on 27017
## 0.35.1
### Fixed
* Stop recording the contents of SSH file transfers (SFTP, scp, rsync and git) in session logs
## 0.35.0
### New
* Add SSH support to the Formal Endpoint transparent proxy
## 0.34.8
### New
* Apply body rewrite policies to Codex traffic.
### Fixed
* Improve Anthropic LLM classification when Claude CLI is proxied.
## 0.34.7
### New
* Support the `pre_tcp` stage for network rules on macOS.
* Narrow ChatGPT Desktop LLM interception.
## 0.34.6
### New
* Support log schemas for field-level log transformations.
## 0.34.5
### Fixed
* Exclude LuLu and Little Snitch traffic from Transparent Mode on macOS.
## 0.34.4
### New
* Support `is_llm_hostname` in network rules.
### Fixed
* Exclude CrowdStrike Falcon traffic from Transparent Mode on macOS.
## 0.34.3
### Changed
* Minimize interaction with macOS app tracking domains.
## 0.34.2
### Fixed
* Exclude link-local traffic on macOS.
* Exclude Cisco AnyConnect traffic on macOS.
* Infer the AWS region from EC2 instance metadata during OIDC login.
## 0.34.1
### New
* Add support for Cursor and Devin as OIDC issuers for Endpoint authentication.
### Fixed
* Fix an issue where the transparent proxy interfered with AirDrop on macOS.
## 0.34.0
### New
* Add support for Endpoint OIDC login.
### Changed
* Improve the reliability of the browser login flow.
## 0.33.2
### Fixed
* Make `response.time.processing` in logs accurately reflect Connector processing time.
## 0.33.1
### Fixed
* Allow Aikido Device Protection to coexist with transparent mode on macOS.
## 0.33.0
### Changed
* Long structured LLM logs are no longer truncated.
### Fixed
* The transparent proxy on Linux no longer intercepts traffic to Docker containers.
## 0.32.0
### New
* Support Socket Registry Firewall for package registry traffic.
* Support selecting a native user with `formal@` on MySQL and MongoDB.
### Changed
* Detect package registry hostnames as `socket` instead of `http`.
### Fixed
* Fix an issue where DNS could stop working on macOS in transparent mode.
* Only match network rules on `resource.hostname` when the host is an actual Formal resource, so unrelated traffic is no longer intercepted.
## 0.31.8
### Changed
* Clarify macOS instructions for how to enable the Network Extension via System Settings.
## 0.31.7
### New
* Support ClickHouse HTTP traffic in transparent mode.
* Support `connection_process.is_browser` in network rules.
## 0.31.6
### Changed
* Stop reporting Cursor as an AI agent while support is in beta.
## 0.31.5
### New
* Support password-protecting formal uninstall when configured via MDM.
## 0.31.4
### Fixed
* Allow Tunnelblick and Aikido Device Protection to work alongside transparent mode on macOS.
## 0.31.3
### Changed
* Cap audit log bodies at 500 KB per event, and record when a body was truncated.
### Fixed
* Fix an issue with policy blocked tool call in Claude Desktop, which sends tool calls in its own format rather than standard MCP and so was still reporting an unexplained error.
## 0.31.2
### Changed
* Improve performance of retrieving credentials when initiating Kubernetes connections.
## 0.31.1
### Changed
* Return the policy reason inside the MCP response when a tool call is blocked, so the agent can see why instead of treating it as a broken connection.
## 0.31.0
### New
* Add `formal upgrade` to install the latest version from the command line on macOS and Linux.
### Changed
* `formal version` now reports the latest available version alongside the installed one.
### Fixed
* Fix an issue where traffic could briefly bypass transparent mode on Linux while a network interface was coming up.
* Capture device-bound IPv6 connections on Linux when the interface has no IPv6 default route.
* Fix an issue where CPU could spike when the Formal API was unreachable.
## 0.30.6
### Changed
* Support localhost interception configuration via an MDM provider.
## 0.30.5
### Changed
* By default, transparent mode no longer intercepts loopback traffic. Enable `transparent_proxy.intercept_localhost` as specified in the [managed preferences table](/docs/guides/client-apps/configuration#mdm-overrides-macos) to intercept loopback traffic.
## 0.30.4
### Changed
* Allow using CEL string, list, and set extension functions in network rules.
## 0.30.3
### New
* Surface `input.session.id` and `input.request.id` where applicable as policy evaluation inputs.
* Surface session ID and request IDs in the default formal block message across technologies
### Fixed
* Allow Netskope and GlobalProtect to work alongside transparent mode on macOS.
## 0.30.2
### Fixed
* Allow OpenVPN Connect to work alongside transparent mode on macOS.
* Fix AWS SDK uploads that use `Content-Encoding: aws-chunked`, which the AWS transparent proxy previously rejected.
## 0.30.1
### Fixed
* Recognize Claude Code launched from Claude Desktop as `connection_process.is_agent`.
## 0.30.0
### Changed
* Require `forward_to_connector` to be `true` when passing Postgres traffic to the Connector. Note: review your network rules and update them accordingly.
### Fixed
* Fix NetworkManager connectivity checks while transparent mode is enabled on Linux.
## 0.29.0
### New
* Support Cursor Desktop & Cursor CLI
* Support Devin Desktop (windsurf) and Devin CLI
## 0.28.4
### New
* Support `connection_process.is_agent` in network rules
* Support specifying resource hostnames in `formal connect`
### Fixed
* Surface tool names for MCP calls initiated by Claude Cowork
## 0.28.3
### Changed
* Support intercepting HTTP2 in transparent mode
### Fixed
* Fix an issue in enabling transparent mode with automatic login in macOS
## 0.28.2
### Changed
* Do not support transparent mode until CAs are trusted on macOS.
## 0.28.1
### Changed
* Support dynamic groups for automated MDM deployments of automatic login.
## 0.28.0
### Fixed
* Network rules that forward websites to web resources (via a connector) now work.
## 0.27.0
### New
* Add support for connecting to Redshift resources, both directly and in transparent mode.
## 0.26.4
### Fixed
* Support intercepting MCP tool calls for Claude Prebuilt Integration Connectors in Cowork.
## 0.26.3
### Fixed
* Enable customers to switch Cloudflare WARP modes while transparent mode is enabled.
## 0.26.2
### Fixed
* Fix a bug in the Linux headless deb package where trusting the local CA certificate failed on older Ubuntu versions.
## 0.26.0
### New
* Add support for writing to and reading from a cache from hooks.
* Add `encrypt` / `decrypt` policy actions for HTTP-based protocols.
### Changed
* Add support for allowlisting CIDR ranges for hook outbound traffic.
## 0.25.2
### New
* Support Formal Hooks on macOS.
## 0.25.1
### Changed
* Block pasting in TUI forms.
## 0.25.0
### Changed
* Move desktop tokens from "Login" keychain to "Local Items" keychain on macOS. Note: existing installations will have to re-authenticate.
* Require allowlisting environment variables and upstream network destinations for hooks.
## 0.24.0
**New**
* Add multi-factor authentication on Windows using Windows Hello (fingerprint, face, or PIN).
## 0.23.1
**Fixed**
* Fix a bug where the Linux headless binary was not statically linked.
## 0.23.0
**New**
* Initial release of Formal Hooks
## 0.22.3
**Fixed**
* Use posix\_spawn internally to work around bug introduced on macOS 26.4+.
## 0.22.2
**Fixed**
* Fix a bug with forward-to-connector network rules on Linux endpoints
## 0.22.1
**New**
* Support setting checkForUpdates, hideSystrayIcon, transparentProxyEnable, transparentProxyEnforce, transparentProxyFail, transparentProxyAllowQuic as mcx preference settings.
## 0.22.0
**Changed**
* Bumped the minimum supported macOS version to 15.0.
* No longer start the desktop app during the base package postinstall.
* Replace custom MDM post-install scripts with managed preference settings. *Note*: MDM deployments that upgrade to this version should remove the custom post-install script.
**Fixed**
* Fix transparent proxy toggle knob rendering.
## 0.21.1
**Changed**
* Rewrote the macOS menu bar: faster and more efficient.
* Transparent proxy toggle now stays in sync and gives clearer feedback when toggled.
* Transparent proxy toggle now requires being logged in with a working CA.
## 0.21.0
**New**
* Support the Gemini API as an LLM technology.
* Add support for using the detected resource (hostname and port match) in CEL expressions in network rules.
## 0.20.6
**Fixed**
* Support Sequoia macOS versions.
* Smooth the visual toggling of transparent mode.
## 0.20.5
**New**
* Add the `loaded_files` field.
## 0.20.4
**Fixed**
* Surface llm and input output tokens as numeric counts in logs.
## 0.20.3
**New**
* Surface `available_skills` and `loaded_skills` in LLM logs.
## 0.20.2
**Changed**
* Transparent proxy mode now requires login, avoiding unnecessary TLS interception when logged out
## 0.20.1
**New**
* Improve experience of intercepting AI agent traffic in network rules
## 0.20.0
**New**
* Add support for GCP APIs as a technology for the transparent proxy
## 0.19.1
**Changed**
* Avoid CA trust dialog when device login managed preference is set.
## 0.19.0
**New**
* Add support for AWS APIs as a technology for the transparent proxy
## 0.18.2
**Changed**
* Updates CA trust at login to not require any CLI commands
## 0.18.1
**New**
* Allow CA trust prompt to be shown at login time after MDM installation
## 0.18.0
**New**
* Support automatic login and CA trust.
## 0.17.1
**Changed**
* Fall back to password authentication for MFA challenges when Touch ID is not available
## 0.17.0
**New**
* Support autodetection of Postgres, MySQL, and MongoDB connections via the transparent proxy and in network rules
* Support TLS interception for Postgres, MySQL, and MongoDB connections through `formal connect`/`formal ls` local proxy
**Changed**
* Rename the MacOS bundle from `Formal Desktop` to `Formal`
* Surface "Formal" in certificate trust settings dialog instead of "security" during installation
**Fixed**
* Fix a bug where the CA trust dialog would not appear during MacOS package installation via MDM
* Fix a bug where `formal transparent-proxy status` would report the extension not being installed when it actually was
## 0.16.0
**New**
* Add first-class support for granular logging and policy evaluation on LLM request bodies
## 0.15.4
**Fixed**
* Fix an issue where MySQL connections could crash the agent when the client did not send connection attributes
## 0.15.3
**Fixed**
* Fix a bug where network rules weren't passing HTTP resources properly to connector listeners with smart routing enabled.
## 0.15.2
**Changed**
* Allow protocol detection on HTTP requests when the TLS client hello doesn't specify an ALPN protocol
* Improve performance of non-interactive `formal ls`
**Fixed**
* Fix a bug where network rules were lost after disabling and re-enabling the transparent proxy
## 0.15.1
**Fixed**
* Fix a bug where transparent connections to a resource would hang if there was a previous direct connection to the local listener for that resource
## 0.15.0
**New**
* Initial release of headless Linux deb package
**Changed**
* Automatically generate and trust transparent proxy CA certificate on Linux
## 0.14.3
**Fixed**
* Fix a bug where Postgres connections from GSS-enabled clients could hang when using the local TCP port instead of the Unix socket.
## 0.14.2
**Changed**
* Improve performance of forwarding transparently proxied HTTP traffic to the Connector
## 0.14.1
**New**
* Support header rewrite actions for HTTP resources
## 0.14.0
**New**
* Support scenario monitors (LLM-as-a-judge) as a policy evaluation input
## 0.13.1
**Changed**
* Improve MFA policy enforcement
## 0.13.0
**New**
* Emit HTTP policy evaluation inputs alongside logs
* Add support for MongoDB SCRAM-SHA-256 authentication
**Changed**
* Improve policy evaluation latency
## 0.12.2
**Changed**
* Improve policy block messages for HTTP connections
## 0.12.1
**Changed**
* Improve LLM protocol autodetection
## 0.12.0
**New**
* Support protocol autodetection for LLM requests
* Persist logs to disk so they survive network outages, capped at 1 GB
* Support encrypting logs with asymmetric encryption keys
**Changed**
* Improve network rule format for readability. See our "Guide: Desktop App Setup" for more information.
* Simplify the codesigning configuration of our macOS Desktop App. See our "Guide: Desktop App Setup" for more information.
## 0.11.0
**New**
* Add an `uninstall` command to completely remove Formal from the machine
* Add an `input.source` field to allow policies to differentiate desktop policy enforcement from connector policy enforcement
**Changed**
* Improve performance of fetching updates from the control plane
* Improve performance of `formal ls`
## 0.10.1
**New**
* Support [filling out Forms](/docs/guides/configuration/forms#filling-out-forms) using `formal form fill`
**Changed**
* Change macOS CA trust dialog back to the default Security dialog
## 0.10.0
**New**
* Add device trust support for MySQL
* Forward MySQL policy block messages to clients
## 0.9.21
**New**
* Support Codex block messages
* Support HTTP header rewrites for HTTP technology
**Changed**
* Decrease bundle size by 50%
* Rename transparent proxy to transparent mode
* Rename `ca install` for Linux to `ca trust` for macOS-Linux parity
## 0.9.20
**Changes**
* Only check for updates if `check_for_updates` is set in `config.toml`.
## 0.9.18
**Changes**
* Show a custom Formal dialog when asking the user for permission to add CAs
## 0.9.17
**New**
* By default, block QUIC traffic when the transparent proxy is enabled. Control this via the `allow_quic` configuration parameter.
* Support Claude Chat for LLM sessions when `allow_quic` is false.
## 0.9.16
**New**
* Support enabling the transparent proxy on desktop app startup via config.toml
## 0.9.15
**New**
* Support auto-classification of HTTP and MCP traffic
* Support device trust for MongoDB
**Changed**
* Install the network extension as part of the installer
## 0.9.14
**New**
* Support desktop evaluation and logging of MCP traffic
* Support network rules that don't require resources
**Fixed**
* Indicate to users that sudo is required for `formal ca trust`
* Use total input tokens instead of input tokens
* Correct CA installation out of sync race conditions
## 0.9.13
**New**
* Support user identity in policy evaluation
* Support transparent proxy for Linux
## 0.9.12
**New**
* Support LLM header rewrites
* Remove the `--only-agents`, `--transparent`, and `--local-tls` flags
## 0.9.10
**New**
* Support session stage evaluation for the LLM technology
## 0.9.9
**New**
* Support policy enforcement for the MCP technology without a connector
* New `formal transparent-proxy install` and `formal transparent-proxy uninstall` commands
## 0.9.8
**Changed**
* Add Formal CA to keychain via installer
**New**
* Support policy enforcement for HTTP resources without a connector
* Support connection process and user information in network rules
## 0.9.7
**Changed**
* Automatically add Formal CA to Keychain when enabling the transparent proxy
## 0.9.6
**Changed**
* Enable the transparent proxy via the system tray.
* Support users in network rules
## 0.9.5
**Changed**
* Support custom block messages for Claude Code tool calls
## 0.9.4
**Changed**
* Support network rules in transparent proxy
## 0.9.3
**Changed**
* Support policy enforcement of LLM sessions without a Connector
## 0.9.2
**Changed**
* Improve performance of transparent mode
## 0.9.0
**New**
* Add support for machine user auth via AWS SigV4
## 0.8.4
**Changed**
* Improve LLM session id saving for replay
## 0.8.3
**New**
* Support connecting to MCP resources
* Support log encryption for LLM technology
## 0.8.2
**New**
* Separate TLS settings for LLM technology
**Changed**
* Emit session events for LLM technology sessions
## 0.8.1
**New**
* Do not require a Connector to use the LLM technology
## 0.8.0
**New**
* Support connecting with `--transparent` mode
* Support connecting with `--only-agents` mode
* Support connecting with `--local-tls` mode
* Pass process ancestor information
* Support LLM technology
**Changed**
* Reject CLI arguments for formal-agent
## 0.7.3
**New**
* Managed Connector SSH host keys in an isolated host keys file.
## 0.7.2
**Fixed**
* Fixed a bug where the resource alias was used for smart routing rather than the resource name.
## 0.7.1
**New**
* Added support for [resource aliases](/docs/guides/core-concepts/resources/introduction#creating-a-resource)
* Added a `--launch` argument to `formal connect` to launch the appropriate CLI program after connecting (for example `psql` or `ssh`).
**Fixed**
* Fixed a bug where the interactive `formal ls` parent process didn't exit when launching a CLI program, obscuring connection errors and policy block messages.
## 0.7.0
**Changed**
* Improved `formal ls` performance by moving search filtering server-side
* Reduced unnecessary API calls during credential renewal
## 0.6.0
**Fixed**
* Fixed a bug where the desktop app CLI would attempt to launch the agent when run without a TTY.
* Note: this introduces a small breaking change where running `formal` without a TTY will now exit with an error and show help text. Please run `formal agent` instead.
## 0.5.7
**Fixed**
* Fix smart routing for MySQL resources
## 0.5.6
**Fixed**
* Fix a bug where the desktop app would timeout when listing resources in non-interactive mode
## 0.5.5
**New**
* Add support for JSONL and YAML output format to `formal ls`
## 0.5.4
**Fixed**
* Fix MySQL authentication with clients that send a null byte for empty passwords
## 0.5.3
**Changed**
* Refresh MFA challenges every second
## 0.5.2
**New**
* Add support for the new MySQL proxy implementation
**Fixed**
* Fix DynamoDB authentication
## 0.5.1
**Changed**
* Improve MFA user experience
## 0.5.0
**New**
* Add support for MFA policy enforcement
## 0.4.4
**Changed**
* It is no longer required to specify a resource name with `formal disconnect`. By default, it disconnects all resources.
## 0.4.3
**New**
* Added support for Windows.
* Added support for wrapping AWS S3 commands to funnel traffic through the Connector.
**Changed**
* Change SSH config configuration to be compatible with SCP via the Connector.
* Change SSH config configuration to be use resource names as SSH host names.
## 0.4.2
**Fixed**
* Fixed a build issue where the desktop app would not provide an icon on Linux.
## 0.2.7
**Fixed**
* Fixed a bug that prevented the desktop app from logging out from the command line.
## 0.2.6
**New**
* Added a `-y` flag to `formal auth login` to bypass the browser confirmation prompt.
**Fixed**
* Fixed a bug where the desktop agent could be launched multiple times.
## 0.2.5
**New**
* Implemented pagination of resources to reduce startup delay of `formal ls`.
**Changed**
* Reworked connecting/disconnecting to resources to be asynchronous, which prevents a connection from blocking the TUI from rendering.
**Fixed**
* Fixed a bug where the desktop app would show multiple instances of the same resource while filtering and connecting.
* Fixed a bug where the autolaunch functionality could freeze the TUI.
## 0.2.4
**New**
* Added support for using token authentication in headless mode.
* Introduced a configurable timeout for the `formal agent` command with `--timeout`.
**Changed**
* Changed the `formal ls` command to lazily load resources and show a loading spinner instead of blocking the TUI from launching.
## 0.2.3
**New**
* Added a `formal agent` command to launch the Formal Agent through the CLI.
* Added a headless mode, which can be launched through `formal agent --headless` and allows usage of the Formal Desktop without a GUI. See [Desktop App](/docs/guides/client-apps/linux#headless-mode) for more details.
## 0.2.2
**New**
* Added the ability to open a `psql` session automatically when connecting to a PostgreSQL resource through `formal ls`.
* Added a permanent "latest" URL to download the latest version of the desktop app on Linux.
**Fixed**
* Fixed a bug where the desktop app could become unresponsive when connecting to a resource.
## 0.2.1
**Fixed**
* Fixed a visual bug with the `formal ls` UI where a resource would not update its status after a connection request
## 0.2.0
**New**
* Added the ability to open an SSH session automatically when connecting to an SSH resource through `formal ls`
## 0.1.4
**New**
* Added support for Linux
## 0.1.0
**New**
* Added support for configuring the SSH client (i.e., `~/.ssh/config`) to more seamlessly connect to SSH, EC2, and ECS Fargate resources using the `formal ls` UI
## 0.0.91
**Fixed**
* Fixed versioning issue
## 0.0.90
**Fixed**
* Fixed versioning issue
## 0.0.89
**New**
* Added a link to download the latest version of the desktop app if an update is available
## 0.0.88
**Changed**
* Ensured errors are printed to `stderr` rather than `stdout` to provide a clean output stream to users
## 0.0.87
**New**
* Exposed certain classes of connection errors between the local proxy and the Formal Connector to users
## 0.0.86
**Fixed**
* Fixed authentication failures
## 0.0.85
**New**
* Added support for using `formal@` as the username to specify the native user
## 0.0.84
**New**
* Added support for `json` option to `formal auth credentials -o`
## 0.0.83
**New**
* Added support for `json` option to `formal auth credentials -o`
## 0.0.82
**Fixed**
* Avoided crashing on EOF for `postgres`
## 0.0.81
**Fixed**
* Fixed desktop app auth status
## 0.0.80
**Fixed**
* Fixed desktop app auth status
## 0.0.79
**New**
* Added support for MongoDB smart routing
## 0.0.78
**Fixed**
* Fixed typo in error message `missing S3 resource name`
## 0.0.77
**New**
* Added support for TCP Proxy and Relays
## 0.0.75
**Changed**
* Removed the use of AWS profile
## 0.0.74
**Changed**
* Removed the use of AWS profile
## 0.0.73
**Changed**
* Added more efficient routing
## 0.0.72
**Fixed**
* Fixed support for `dynamodb`
## 0.0.71
**New**
* Added resource subdomain to Kubernetes resources for connectors
## 0.0.70
**New**
* Added support for connectors
## 0.0.69
**New**
* Added support for basic auth for `clickhouse`
## 0.0.68
**New**
* Added resource to policy evaluation
## 0.0.67
**Changed**
* Updated desktop app to renew key every 18 hours
## 0.0.66
**New**
* Added support for connector in policy evaluation
## 0.0.65
**Changed**
* Updated log message for fetchCABundle
## 0.0.64
**Changed**
* Added more logs to the `dynamodb` command
## 0.0.63
**Changed**
* Removed posthog
## 0.0.62
**Fixed**
* Fixed AWS CLI
## 0.0.61
**Fixed**
* Fixed AWS CLI
## 0.0.60
**New**
* Added AWS CLI to building process
## 0.0.59
**New**
* Added support for certificate bundle `dynamodb`
## 0.0.58
**New**
* Added support for API Token expiration
# Overview
Source: https://docs.formal.ai/docs/changelog/index
Product updates and release notes for Formal
Stay up to date with the latest releases, features, and improvements across all Formal products.
## Product Changelogs
Protocol-aware reverse proxy for secure infrastructure access
Formal Endpoint on macOS, Windows, Linux, and Kubernetes
Infrastructure as code for Formal resources
Automated data discovery and classification
Policy data loading and management
Autonomous security monitoring and threat detection
Kubernetes-native management of Formal resources
AWS Cloud Integration stack template versions
# Kubernetes Operator
Source: https://docs.formal.ai/docs/changelog/kubernetes-operator
Release notes for Formal Kubernetes Operator
## 0.7.0
### New
* Authenticate to the Formal API with AWS or Azure OIDC (`oidc.integration_id` plus `aws: {}` or `azure: {}` in the operator ConfigMap)
## 0.6.0
### New
* Load metrics and reconcile settings from a YAML file at `FORMAL_CONFIG`
* Helm mounts that file from a ConfigMap and rolls the pod when it changes
* `METRICS_ADDR` and `RECONCILE_INTERVAL` still override file fields
## 0.5.0
### New
* Add the `FormalPolicy` resource to define policies as code
## 0.4.0
### New
* Add `identityLinks` to `FormalNativeUser` to bind users, groups, and resource hostnames
## 0.3.0
### New
* Add `spaceId` field to `FormalResource` for organizing resources into spaces
## 0.2.0
### New
* Add support for Snowflake key pair, HTTP basic, HTTP bearer, HTTP API key, and Kubernetes native user secret types
## 0.1.0
### New
* Initial release of the Formal Kubernetes Operator
# Policy Data Loader
Source: https://docs.formal.ai/docs/changelog/policy-data-loader
Release notes for Formal Policy Data Loader
## 1.3.8
### Changed
* Add a `/ready` endpoint for Kubernetes deployments
## 1.3.7
### Changed
* Let the Connector skip downloading policy data again after a reconnection when no loader has run since, so large datasets are no longer transferred on every reconnection (requires Connector 2.20.0)
### Fixed
* Fix an issue where restarting the satellite emptied policy data on the Connector until each loader's next scheduled run, which could stop policies that rely on that data from applying (requires Connector 2.20.0)
## 1.3.6
### Changed
* Log when a Connector subscribes to policy data and how much data was sent, instead of printing whole datasets in debug logs
## 1.3.5
### Changed
* Compress the data sent to the Connector, so loaders with very large datasets sync reliably
* Update the satellite base images to pick up the latest security patches
* Report the running satellite version in logs and telemetry, so it is easier to tell which version is deployed
### Fixed
* Fix policy data loader instances showing up as data discovery instances in the connected instances list
* Improve health check reliability so the satellite no longer reports unhealthy while it is running fine
* Fix an issue where the satellite logged errors for configuration it does not handle, or when no satellite hostname was set
## 1.3.4
### New
* Add the psycopg2, SQLAlchemy and pandas Python packages to the loader runtime, so loaders can query databases and shape the results directly
## 1.3.3
### New
* Add row data capture in logs for more precise policy evaluation, while keeping log processing reliable
* Add API to create policy reports from policy code for faster reviews
## 1.3.0
### New
* Add TLS for the policy data loader gRPC server with automatic cert renewal, encrypting traffic and reducing downtime
# Subscribe to Updates
Source: https://docs.formal.ai/docs/changelog/subscribe
Get notified of new Formal product releases via RSS feeds
> **Outcome:** After this guide, you can subscribe to product update notifications.
> **Prerequisites:** Slack workspace (recommended) or email service.
## Overview
Each Formal product changelog publishes an RSS feed. Subscribe to feeds to receive automatic notifications when new versions release.
## Get the RSS Feed URL
Click the **RSS icon** at the top of any changelog page. Your browser opens the RSS feed for that product.
**Verify:**
The URL ends with `/rss.xml` (for example, `https://docs.formal.ai/docs/changelog/connector/rss.xml`).
## Subscribe via Slack
Send changelog updates to a dedicated Slack channel.
Create a new channel in your workspace for product updates. Name it
something like `#formal-updates`.
Visit the [RSS app in Slack
Marketplace](https://slack.com/help/articles/218688467-Add-RSS-feeds-to-Slack)
and click **Add to Slack**.
Paste the RSS feed URL from step one. Select your updates channel. Click
**Subscribe**.
**Verify:**
```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
The Slack RSS bot posts a confirmation message in your channel.
```
## Subscribe via Zapier
Use Zapier to trigger email notifications or custom workflows.
See [Send new RSS feed entries via email](https://zapier.com/apps/email/integrations/rss/1441/send-new-rss-feed-entries-via-email) to configure email delivery.
You can use RSS updates as triggers for any Zapier workflow.
## Additional Resources
See [Mintlify changelog
documentation](https://www.mintlify.com/docs/create/changelogs) for technical
details about RSS feed implementation.
## Next Steps
Browse product release notes
View Connector updates
View API updates
View Formal Endpoint updates
View Terraform Provider updates
View Data Discovery updates
View Policy Data Loader updates
View AI Satellite updates
# Terraform Provider
Source: https://docs.formal.ai/docs/changelog/terraform-provider
Release notes for Formal Terraform Provider
## 4.32.0
### Added
* `gcp {}` OIDC token source in the provider `oidc` block, which authenticates with GCP ambient identity on GKE, Compute Engine, Cloud Run, or with Application Default Credentials for a service account
## 4.31.1
### Fixed
* Honor `retrieve_sensitive_values = false` in the `formal_connector` resource and data source, so the Connector API key is no longer stored in state
## 4.31.0
### New
* Add the `formal_users` data source.
## 4.30.0
### New
* Add support for log schemas with the new `formal_log_schema` resource
## 4.28.0
### New
* Add an optional `url` argument to the provider to target on-prem or regional control planes. Only `http` and `https` URLs with a hostname are accepted; unset keeps the default control plane
## 4.27.1
### Fixed
* Fix `formal_group_user_link` imports. Import these resources with their `usergrouplink_...` ID, and the provider now retrieves the link directly to reconstruct its state.
## 4.27.0
### New
* Add `tags` to `formal_policy` to attach key-value labels to policies
* Add write-only attributes (`literal_wo`, `literal_wo_version`) to every secret on `formal_native_user_v3` to avoid storing secrets in Terraform state (requires Terraform 1.11+)
## 4.26.0
### New
* Add `formal_native_user_v3` resource for v3 native users
* Add `formal_resource_native_user_selection` and `native_users_v3_enabled` to `formal_resource` to opt into v3 native users
### Changed
* Enable Native Users V3 by default, only for newly created resources.
## 4.25.1
### New
* Add Azure managed identity and AKS Workload Identity authentication
* Add OIDC authentication for fixed-audience providers such as Spacelift
* Add Azure support to `formal_integration_cloud` and the new `formal_integration_cloud_azure_activation` resource
* Add an AKS Connector deployment example with Azure Workload Identity and authenticated GAR image pulls
### Fixed
* Preserve allowed GCP log storage when updating unrelated Cloud Integration settings
* Skip `formal_policy` code validation during planning when the policy module is not yet known
## 4.24.0
### New
* Add support for OIDC authentication to the provider
### Changed
* Make `template_version` optional for `formal_integration_cloud` resources
## 4.23.2
### New
* Add `end_user_email_expression` to `formal_integration_oidc` to extract the email address of the end user from the claims
## 4.23.0
### New
* Add `compression` to `formal_integration_log` to compress logs written to S3 and Google Cloud Storage, with gzip or zstd
* Add the `formal_integration_oidc` resource to manage OIDC integrations
### Fixed
* Fix an issue where AWS `formal_integration_cloud` resources always showed a pending change in plans
## 4.22.0
### New
* Add the `formal_connector_token_encryption_key` resource to manage token encryption keys for the Connector
* Add the `formal_ssh_host_key` resource to manage SSH host keys for the Connector
### Changed
* Remove the deprecated `formal_sidecar` and `formal_sidecar_resource_link` resources
* Improve GCP and AWS deployment Examples
* Update to Formal Go SDK v3. This improves supply chain security by removing hosted Protobuf dependencies.
## 4.21.1
### New
* Add support for `allowlisted_network_hosts` on the `formal_hook` resource
* Add support for more GCP autodiscovery options to `formal_integration_cloud`
## 4.21.0
### New
* Add the `formal_hook` resource to manage policy evaluation hooks
* Add GCP support to `formal_integration_cloud` with the new `gcp` block, including autodiscovery for Cloud SQL instances, Compute Engine instances, and GKE clusters, and log writes to GCS
* Add the `formal_integration_cloud_gcp_activation` resource to activate a GCP Cloud Integration by reporting its service account and workload identity pool provider back to Formal
* Add Google Cloud Storage as a log destination on `formal_integration_log` with the new `gcs` block
* Add Fleet and Jamf Pro integrations to `formal_integration_mdm`, alongside the existing Kandji integration
### Changed
* `formal_integration_mdm`: MDM API keys (Kandji, Fleet, Jamf) are now write-only and no longer stored in Terraform state (requires Terraform 1.11 or later)
* `formal_integration_cloud`: `cloud_region` is now optional and applies to AWS only
## 4.20.0
### New
* Add a `formal_user` data source to look up an existing user by ID or identity (`idp:formal:human:`)
## 4.19.0
### New
* Create AWS and GCP resources with the new `aws` and `gcp` technology types
* `formal_resource_tls_configuration`: present a client certificate to upstream resources with the new mutual TLS fields (`tls_client_cert`, `tls_client_key`)
* `formal_inventory_object`: declare labeled JSON sub-columns so nested JSON fields can be masked by data label
## 4.18.0
### New
* Support GCP KMS encryption keys, alongside AWS KMS
* Reach resource upstreams through AWS Systems Manager with the new `aws_ssm` dial method
### Changed
* Encryption keys now use asymmetric RSA by default. Symmetric and deterministic algorithms are no longer supported, and the `algorithm` field is deprecated.
* The `aws` key provider is renamed to `aws-kms` (`aws` still works as a deprecated alias)
## 4.17.2
### Changed
* `formal_resource`: `port` can now be updated in place instead of forcing the resource to be recreated
## 4.17.1
### Fixed
* `formal_resource_dial_configuration`: `dial_target` can now reference an attribute from another resource (e.g. a Cloud SQL instance created in the same plan) without `terraform plan` failing
## 4.17.0
### New
* Add `formal_resource_dial_configuration` resource to control how the connector dials a resource's upstream (`tcp` or `gcp_cloudsql`)
## 4.16.3
### New
* Add `formal_network_rule` resource to define CEL-based network routing rules
## 4.16.2
### Removed
* Remove `owner` and `notification` fields on the `formal_policy` resource.
## 4.16.1
### New
* Add `s3_bucket_prefix` to `formal_integration_log` AWS S3 configuration to write logs under a specific bucket prefix
### Changed
* Update `formal_sidecar_resource_link` deprecation message to reference `formal_connector_listener` with `connector_id`
## 4.16.0
### New
* Add `formal_inventory_object` resource to register inventory objects (database, schema, table, column) directly, instead of relying on discovery
* Add `input_json` field on `formal_form` options source command for payloads with non-string JSON values
### Removed
* Remove `formal_integration_data_catalog` resource (deprecated since 4.12.7)
## 4.15.2
### New
* Add `autodiscovery_regions` to `formal_integration_cloud` AWS config to enable multi-region resource autodiscovery
## 4.15.1
### Changed
* Bump generated protobuf dependency
## 4.15.0
### New
* Allow `llm` as a technology in `formal_connector_listener_rule`
### Removed
* Remove `formal_data_domain` resource (data domains have been removed)
## 4.14.13
### New
* Add `formal_connector_ai_provider` resource for managing hosted AI providers
* Add hosted AI provider fields to `formal_connector_configuration`
## 4.14.12
### New
* Support formal\_form resources
## 4.14.11
### New
* Add support for resource aliases
## 4.14.10
### Changed
* Store the Formal IAM role ARN in the Terraform state for AWS cloud integration resources.
## 4.14.9
### Changed
* Add validation for write-only attribute version for native users
## 4.14.8
### New
* Add support for `ai` satellite type to `formal_connector_satellite_link` resource
## 4.14.7
### Changed
* Support `status` on `formal_workflow` resources.
## 4.14.6
### New
* Add the `formal_workflow` resource.
* Add `resources_health_checks_frequency_seconds` to control resource health check frequency.
* Introduce `connector` and `space` data sources.
## 4.14.1
### New
* Add `otel_endpoint_hostname` and `otel_endpoint_port` fields to `formal_connector_configuration` resource to support custom OpenTelemetry collector endpoints
## 4.14.0
### Changed
* Deprecate `data_classifier` satellite type in favor of `ai` satellite type
## 4.13.2
### New
* Add satellite link resource
## 4.13.1
### New
* Add policy\_eval\_input\_retention attributes to log configuration resources
## 4.13.0
### New
* Add write-only attributes (`native_user_secret_wo`, `native_user_secret_wo_version`) for native users to avoid storing secrets in Terraform state (requires Terraform 1.11+)
## 4.12.8
### Changed
* Require ai\_analysis\_timeout\_seconds to avoid stalled AI analyses and ensure predictable timeouts
## 4.12.7
### New
* Add S3 autodiscovery option for Cloud integration to discover buckets
### Changed
* Deprecate Data Catalog integration resource to signal phase-out
* Require specific fields in Sidecar, Sidecar Link, MFA, and TLS to prevent invalid configs
## 4.12.6
### Fixed
* Fix formal\_integration\_mdm creation to prevent apply crashes and use the resource name
## 4.12.5
### Changed
* Make decryptor\_uri optional for encryption\_key to avoid unnecessary configuration
## 4.12.3
### Fixed
## 4.12.2
### New
* Add S3 bucket autodiscovery to integration\_cloud to reduce manual setup
### Changed
* Update RPC library to latest version for improved stability and compatibility
* Update documentation generator to latest version for clearer provider docs
## 4.11.3
### New
* Add optional connector\_id to connector\_listener to link on create
## 4.11.2
### New
* Add AI timeout and strict result count to resource\_classifier\_configuration
## 4.11.1
### Changed
* Require satellite\_type on satellite resource; use data\_discovery, data\_classifier, or policy\_data\_loader
## 4.11.0
### New
* Add no-sync mode for Metabase integrations; create integrations without Metabase credentials
* Add support for customer-managed TLS certificates
## 4.10.4
### Changed
* Revert self-supplied TLS on connector\_hostname; remove certificate/private\_key args to restore prior behavior
## 4.10.3
### New
* Add satellite\_hostname and connector\_satellite\_link resources to manage satellites
### Fixed
* Set default log payload size limit to -1 for consistent log behavior
### Changed
* Remove health check port from connector\_configuration
## 4.10.2
### New
* Add option to set customer AWS role ARN in integration\_cloud to simplify setup without CloudFormation
## 4.10.1
### Fixed
* Make technology\_provider optional unless using SSH with SSM, reducing config errors
## 4.10.0
### Changed
* Redesign log\_configuration with request/response/scope blocks for clearer control
* Require name and encryption\_key\_id; remove legacy encryption and size settings for clarity
## 4.9.1
### Changed
* Require decryptor\_uri on encryption\_key resource for frontend decryption
## 4.9.0
### New
* Add formal\_group data source to reference existing groups by name
### Fixed
* Block creating connector listeners on healthcheck port 8080 to avoid conflicts
### Changed
* Update dependencies to improve stability and compatibility
## 4.8.0
### New
* Add per-resource classifier preference for fine-grained control
## 4.7.2
### Fixed
* Prevent forced recreation when updating provider field, allowing in-place updates
## 4.7.1
### Changed
* Require provider for SSH resources using SSM (aws-ec2 or aws-ecs)
* Switch KMS encryption key input to use key ARN instead of key ID
* Update TLS validation options for clearer, safer configuration
## 4.7.0
### New
* Add formal\_resource data source to fetch a resource by name for easier lookups
## 4.6.2
### Fixed
* Improve permission update errors to reflect 'code' field name
### Changed
* Update permission resource to use 'code' and remove timestamps to prevent plan drift
## 4.6.1
### New
* Add optional path to Data Discovery to limit scans to a specific subpath
## 4.6.0
### New
* Add resource formal\_inventory\_object\_data\_label\_link to link data labels to inventory objects
* Add EC2 and ECS autodiscovery to cloud integration, with computed status fields
## 4.5.3
### New
* Add formal\_connector\_configuration to set connector log level and health check port for better monitoring
## 4.5.2
### Changed
* Allow in-place updates to primary hostname to reduce downtime
## 4.5.1
### New
* Add plan-time validation for policy Rego code to catch errors before apply
## 4.5.0
**New**
* Add autodiscovery and log delivery fields to the `aws` configuration block of `formal_integration_cloud` resources:
* `enable_eks_autodiscovery` toggles EKS clusters autodiscovery
* `enable_rds_autodiscovery` toggles RDS instances (PostgreSQL, MySQL, MongoDB) autodiscovery
* `enable_redshift_autodiscovery` toggles Redshift clusters autodiscovery
* `allow_s3_access` allows Formal to send logs to S3 buckets in your AWS account through Log Integrations
* Add read-only fields to the root level of `formal_integration_cloud` resources for CloudFormation stack parameters:
* `aws_enable_eks_autodiscovery` provides the value for the `EnableEKSAutodiscovery` parameter
* `aws_enable_rds_autodiscovery` provides the value for the `EnableRDSAutodiscovery` parameter
* `aws_enable_redshift_autodiscovery` provides the value for the `EnableRedshiftAutodiscovery` parameter
* `aws_allow_s3_access` provides the value for the `AllowS3Access` parameter
* `aws_s3_bucket_arn` provides the value for the `S3BucketARN` parameter
Template changes for this release are in the [CloudFormation Changelog](/docs/changelog/cloudformation).
## 4.4.1
**Changed**
* Bumped Formal Go SDK from 2.8.1 to 2.8.2
**Fixed**
* Brought back the `type` field of `formal_integration_cloud` resources, which was mistakenly removed in version 4.4.0
## 4.4.0
**New**
* Added `aws` configuration block to `formal_integration_cloud` resources, including `template_version` and `s3_bucket_arn`. See the [CloudFormation Changelog](/docs/changelog/cloudformation) for template version history.
**Changed**
* Provider now uses version 2.8.1 of the Formal Go SDK and version 2025-02-24 of the API
* The `type` field of `formal_integration_cloud` resources is now deprecated in favor of `aws`
## 4.3.2
**Changed**
* Bumped to version 2.7.0 of the Formal SDK
## 4.3.1
**New**
* Added encryption keys and log configurations resources
**Changed**
* Added deprecated notification for `managed_tls` field in `formal_connector_hostname`
* Deprecated `managed_tls` field in `formal_connector_hostname`
**Fixed**
* Fixed the list of update-able resource fields for formal resources
## 4.3.0
**New**
* Added `dns_record` to `formal_connector_hostname`
**Changed**
* Removed ForceNew for `formal_resource_hostname.hostname`
## 4.2.2
**New**
* Added `resource_hostname` as supported identity type for `native_user_link`
## 4.2.1
**New**
* Added `formal_resource_hostname` resource
## 4.2.0
**New**
* Support for new form cloud integrations and log integrations
# Glossary
Source: https://docs.formal.ai/docs/glossary/index
Essential terms and concepts for understanding Formal
Master the key concepts behind Formal's infrastructure security platform. Terms are organized from basic to advanced.
## Getting Started
### Connector
**Security gateway that protects your infrastructure**
The Connector sits between identities (employees, ai agents...) and your databases, APIs, Kubernetes clusters, and other resources. It intercepts every connection, enforces access policies, logs all activity, and can mask sensitive data—without requiring changes to your applications.
*Also known as: Proxy*
*Related: Resource, Listener, Space*
### Formal Endpoint
**Client app that connects devices to Formal**
The Formal Endpoint runs on macOS, Windows, Linux, and Kubernetes. It handles authentication and connections to Resources. In Transparent Mode, it intercepts traffic that matches network rules and can forward it to a Connector.
*Related: Connector, Resource*
### Resource
**Any system protected by Formal**
A Resource is any infrastructure you want to secure: PostgreSQL databases, MongoDB clusters, Snowflake warehouses, SSH servers, Kubernetes pods, APIs, and more. Resources are accessed through the Connector, which enforces policies and logs all activity.
*Examples: Production database, staging API, Kubernetes cluster*
*Related: Connector, Native User*
### Policy
**Rules that control who can access what**
Policies are security rules that the Connector evaluates for every connection and query. They determine who can connect, what data they can see, which queries they can run, and whether to mask sensitive information. Policies are written in Rego, a purpose-built policy language.
*Examples: "Engineers can only read from dev databases", "Mask SSNs for support team"*
*Related: Rego, Policy Stage, Masking*
### Session
**A recorded connection to a resource**
A Session is a logged connection from a user through the Connector to a Resource. Sessions capture all queries, commands, responses, and metadata, providing a complete audit trail for compliance and security investigations.
*Also known as: Connection, Access Session*
*Related: Audit Log, Session Recording*
***
## Deployment & Architecture
### Space
**Isolated environment for your resources**
A Space is a logical grouping of Connectors, Satellites, and Resources that can securely communicate with each other. Spaces provide network isolation and organizational boundaries—think of them like separate environments (production, staging) or teams.
*Also known as: Environment, Deployment Zone*
*Related: Connector, Satellite*
### Satellite
**Optional add-on for advanced features**
A Satellite is an application deployed alongside Connectors to enable advanced capabilities like automatic PII detection, data discovery, and schema scanning. Satellites help you classify sensitive data and maintain an up-to-date inventory of your resources.
*Also known as: Data Scanner, Add-on Service*
*Related: Data Label, PII Detection*
### Listener
**Port configuration for routing connections**
A Listener is a configuration on the Connector that accepts connections on a specific port and routes them to designated Resources. Listeners enable a single Connector to handle multiple protocols (PostgreSQL, MongoDB, SSH) simultaneously.
*Also known as: Port Configuration, Connection Rule*
*Related: Connector, Protocol Detection*
### Control Plane
**Management layer and web interface for Formal**
The Control Plane is Formal's management layer at [app.formal.ai](https://app.formal.ai). It coordinates Connectors and Satellites, and provides the web interface, APIs, Terraform provider, and CLI. It's completely separate from your data—the Control Plane never sees or stores your actual data.
*Also known as: Management UI*
*Related: Data Plane, Connector, Formal MCP*
### Formal MCP
**Hosted read-only MCP server for Formal**
Formal MCP is Formal's hosted MCP at `https://api.formal.ai/mcp`. It has only read-only tool calls. It is not MCP resource proxying.
*Related: Control Plane*
### Data Plane
**Where your data flows**
The Data Plane consists of your Connectors, which handle actual data traffic. All database queries and API requests flow through the Data Plane, not the Control Plane. This separation ensures Formal never has access to your data.
*Also known as: Data Gateway, Traffic Layer*
*Related: Control Plane, Connector*
***
## Access & Authentication
### Native User
**Credential used to connect to resources**
A Native User defines credentials that the Connector uses to authenticate to a
Resource. Each Native User has a label and a typed credential configuration.
Credentials can be static, environment-backed, cloud IAM identities, or generated
by connection-time hooks.
*Also known as: Database Credential, Resource Credential, Service Account*
*Related: Dynamic Secrets, End-user Identity*
### End-user Identity
**Tracking who's actually making requests**
End-user Identity is the process of identifying the real person behind a connection, even when they're using tools like Metabase, Retool, or other applications. Formal extracts user information from application parameters to attribute all queries to specific individuals.
*Also known as: Identity Propagation, User Attribution*
*Related: Native User, Session*
### Just-in-Time Access
**Temporary access granted on request**
Just-in-Time (JIT) Access means users request temporary permissions that expire after a set time period. Instead of permanent access, engineers can request 2-hour database access that automatically revokes, reducing the risk of compromised credentials.
*Also known as: Temporary Access, Time-bound Access*
*Related: Approval Workflow, Break Glass*
### Dynamic Secrets
**Temporary credentials that auto-expire**
Dynamic Secrets are short-lived credentials automatically generated for each connection and revoked when the session ends. This eliminates the risk of long-lived passwords being leaked or compromised.
*Also known as: Temporary Credentials, Ephemeral Secrets*
*Related: Native User, Just-in-Time Access*
### MFA (Multi-Factor Authentication)
**Two-step verification for high-risk access**
MFA requires users to provide a second form of authentication (like a code from their phone) in addition to their password. Formal can enforce MFA for specific resources, time periods, or query types.
*Also known as: Two-Factor Authentication, 2FA*
*Related: SSO, Policy*
### SSO (Single Sign-On)
**Log in once, access everything**
SSO allows users to authenticate through your company's identity provider (Okta, Azure AD, Google) instead of managing separate Formal credentials. Once logged in, users can access all permitted resources without additional logins.
*Also known as: SAML, Identity Federation*
*Related: Directory Sync, MFA*
***
## Policies & Rules
### Rego
**Policy language for writing access rules**
Rego is the language used to write Formal policies. It's designed specifically for access control and uses a JSON-like syntax. Rego policies evaluate user attributes, query patterns, and context to make allow/deny/mask decisions.
*Also known as: Policy Language*
*Related: Policy, OPA*
### OPA (Open Policy Agent)
**Engine that executes policy decisions**
OPA is the open-source policy engine that powers Formal's policy evaluation. It reads Rego policies and evaluates them in real-time for every connection and query, making access decisions in milliseconds.
*Also known as: Policy Engine*
*Related: Rego, Policy*
### Policy Stage
**When a policy is evaluated**
Policies can run at three stages:
* **Connection Time**: When a user first connects (check credentials, enforce MFA)
* **Before Query**: Before a query executes (block unauthorized queries)
* **After Query**: After query execution (mask sensitive data in results)
*Related: Policy, Masking*
### Policy Data Loader
**Connect external data to policies**
A Policy Data Loader is custom code that fetches data from external systems (HR databases, ticketing systems, APIs) and makes it available to policies. This enables dynamic decisions like "allow if user is on-call" or "deny if user has open security tickets."
*Also known as: External Data Connector, Policy Data Source*
*Related: Policy, Rego*
### Masking
**Hide sensitive data in query results**
Masking is the process of replacing sensitive data with obfuscated values. Formal can mask data in real-time as query results flow through the Connector, showing "XXX-XX-1234" instead of full social security numbers.
**Masking Types:**
* **Redaction**: Replace with fixed characters (`***`)
* **Partial Redaction**: Show first/last characters (`joh***@example.com`)
* **Hashing**: One-way encryption (always same output for same input)
* **Random**: Replace with realistic fake data
*Also known as: Redaction, Data Obfuscation*
*Related: Policy, PII, Data Label*
***
## Data Governance
### Data Label
**Automatically detected sensitive data**
Data Labels are PII entities automatically identified by Satellites scanning your databases. They tag columns containing emails, phone numbers, SSNs, credit cards, and other sensitive data, making it easy to write policies that protect all PII without manually cataloging every column.
*Also known as: Sensitivity Tag, Data Classification*
*Related: Tags, PII, Satellite*
### Tags
**Custom labels for organizing data**
Tags are user-defined labels you attach to database columns for organization, search, and discovery. Unlike Data Labels (which are auto-detected), Tags are manually created to track business context like "customer\_data", "finance", or "deprecated".
*Related: Data Label*
### PII (Personally Identifiable Information)
**Personal data that identifies individuals**
PII is any data that can identify a specific person: names, email addresses, social security numbers, phone numbers, IP addresses, etc. Formal's Satellites automatically detect PII in your databases and help you mask or restrict access to it.
*Examples: Email, SSN, Credit Card, Phone Number, Home Address*
*Related: Data Label, Masking, Satellite*
***
## Logging & Compliance
### Audit Log
**Tamper-proof record of all activity**
Audit Logs capture every connection, query, policy decision, and system action within Formal. They're cryptographically secured to prevent tampering and provide evidence for compliance audits (SOC 2, HIPAA, GDPR).
*Also known as: Activity Log, Compliance Log*
*Related: Session, Session Recording*
### Session Recording
**Complete capture of terminal sessions**
For SSH, Kubernetes, and other terminal-based resources, Formal can record the entire session including all commands typed and output displayed. This creates a video-like playback for security investigations.
*Also known as: Terminal Recording, Command Logging*
*Related: Session, Audit Log*
### Log Encryption
**Encrypt logs with your own keys**
Log Encryption allows you to encrypt all session logs and audit data using your own encryption keys. This ensures Formal cannot decrypt your logs without your explicit permission, even if compelled by law enforcement.
*Related: Encryption Key, Audit Log*
***
## Integrations
### Log Integration
**Security monitoring and alerting platform**
SIEM (Security Information and Event Management) systems like Splunk and Datadog collect and analyze security logs. Formal can stream all audit logs and policy violations to your SIEM for centralized monitoring.
*Also known as: Log Analysis Platform, Security Monitoring*
*Examples: Splunk, Datadog,*
*Related: Audit Log*
### Cloud Integration
**Automatic discovery of cloud resources**
Cloud Integrations connect Formal to your AWS, GCP, or Azure account to automatically discover databases, Kubernetes clusters, and other resources. This eliminates manual configuration and keeps your inventory up-to-date as infrastructure changes.
*Also known as: Auto-Discovery, Cloud Connector*
*Related: Resource, Satellite*
### Directory Sync
**Automatically sync users from identity providers**
Directory Sync (SCIM) automatically imports users and groups from identity providers like Okta, Azure AD, or Google Workspace. As employees join, change roles, or leave, their Formal access updates automatically.
*Also known as: SCIM, User Provisioning*
*Related: SSO, Groups*
***
## Advanced Concepts
### Protocol Detection
**Automatic identification of database types**
Protocol Detection allows the Connector to automatically identify what type of connection it's receiving (PostgreSQL, MongoDB, MySQL, etc.) without manual configuration. This enables "smart routing" where a single port can handle multiple database types.
*Also known as: Smart Routing, Wire Protocol Detection*
*Related: Listener, Connector*
### Break Glass Access
**Emergency access with full audit trail**
Break Glass is a mechanism for granting immediate emergency access during incidents, bypassing normal approval workflows. All break glass access is heavily logged and triggers alerts, providing accountability during emergencies.
*Also known as: Emergency Access*
*Related: Just-in-Time Access, Approval Workflow*
### Approval Workflow
**Multi-step access requests**
Approval Workflows define who must approve access requests before they're granted. You can configure multi-stage approvals (manager → security team → DBA) with time limits and escalation rules.
*Also known as: Access Request, Approval Chain*
*Related: Just-in-Time Access, Slack Integration*
# Approve Access Requests
Source: https://docs.formal.ai/docs/guides/ai-governance/access-requests
Review employee requests for MCP servers and policy exceptions in the Control Plane
## Overview
[Access Requests](https://app.formal.ai/access-requests) is the approval queue for employee requests. Employees ask for access in the employee Catalog at [catalog.formal.ai](https://catalog.formal.ai). Admins approve or reject each request in the Control Plane, and Formal applies the approved change.
Access Requests is separate from the Slack approval flow built with
[Forms](/docs/guides/configuration/forms) and
[Workflows](/docs/guides/configuration/workflows). Both can run in the same
organization.
## What Employees Can Request
| Request | Where the employee starts | What approval does |
| - | - | - |
| A new MCP server | **Request access** for an MCP server that isn't in the Catalog. They enter a **Name**, the **MCP URL**, and a **Reason**. | Adds the server to the Catalog. You can choose its Space. |
| Access to an MCP server in the Catalog | **Request access** on the server's page | Adds the requester to the server's access controls |
| A one-time exception | **Request access** on an action that a policy blocked, then **One-time exception** | Creates a one-off [policy suspension](/docs/guides/policies/suspensions) for that action |
| A time-bound exception | **Request access** on a blocked action, then **Time-bound access** and a **Duration** | Creates a time-bound policy suspension. Durations range from 30 minutes to 7 days. |
| Permanent access | **Request access** on a blocked action, then **Permanent access** | If an MCP server's Catalog access controls blocked the action, adds the requester. For other policies, records the approval only: edit the policy yourself. |
Employees see blocked actions under **Activity** in the employee Catalog. They follow their requests on its **Requests** page.
## Review a Request
Go to [Access Requests](https://app.formal.ai/access-requests). Filter by **Status**: **Pending**, **Approved**, or **Rejected**. The list shows the resource, the requester, the requested access, when it was submitted, and its status.
Click a request to see its details and the requester's reason.
Under **Decision for this request**, add context for the requester and the audit trail.
Click the approve button, or **Reject Request**. The approve button names the outcome:
* **Approve and create** for a new MCP server
* **Approve MCP access** for an MCP server in the Catalog
* **Approve one-time exception**, **Approve time-bound exception**, or **Approve permanent exception** for policy exceptions
Formal applies the change, then marks the request **Approved**. The duration of a time-bound exception is the one the employee chose. You can't change it during approval.
**Verify:** The request moves to **Approved**. For an exception, the suspension appears in [Policy Suspensions](https://app.formal.ai/policies?tab=suspensions).
Formal doesn't send notifications for Access Requests. Check the queue
regularly, or build a Slack approval flow with
[Workflows](/docs/guides/configuration/workflows) for time-sensitive requests.
## Permissions
The Access Requests page requires the **Resource** permission. Approvals that create suspensions or change access controls also require the **Policies** permission.
## API
Use `core.v1.ApprovalService` to work with requests programmatically:
| Method | Purpose |
| - | - |
| `ListApprovalRequests` | List requests, for example to build a report |
| `UpdateApprovalRequest` | Record a decision |
| `ListAccessRequests` | List requests made by a user |
| `CreateAccessRequest` | File a request on behalf of a user. Only machine users can call it, and they must set `requester_user_id`. |
`UpdateApprovalRequest` only records the decision. It doesn't create the MCP server, the access change, or the suspension. Approve from the Control Plane to apply the change.
## Next Steps
Manage the exceptions that approvals create
Learn how the Catalog and access controls work
# Set Guardrails for Unknown MCPs and Skills
Source: https://docs.formal.ai/docs/guides/ai-governance/guardrails
Choose whether MCP servers and skills outside your Catalog are allowed and monitored, or blocked
## Overview
[Guardrails](https://app.formal.ai/ai-security) set the default handling for MCP servers and skills that aren't in your [Catalog](/docs/guides/ai-governance/overview#the-catalog). Items in the Catalog follow their own access controls instead.
The page has two settings:
| Setting | Applies when |
| - | - |
| **Unknown MCPs** | An agent uses an MCP server that is not in the Catalog |
| **Unknown Skills** | An agent loads a skill that is not in the Catalog |
For each setting, choose a **Default action**:
* **Allow:** Formal lets the traffic through and logs it. New items appear in [Shadow AI](/docs/guides/ai-governance/shadow-ai) for review.
* **Block:** Formal blocks the traffic. For unknown MCP servers, the agent receives a list of approved alternatives. For unknown skills, the user sees Formal's standard block message.
Changing settings requires the **Policies** permission.
## Configure a Guardrail
Go to [Guardrails](https://app.formal.ai/ai-security).
Under **Unknown MCPs** or **Unknown Skills**, select **Allow** or **Block**.
With **Block** selected, enter **Message shown to blocked users**. Formal stores it as the policy's `reason`, which appears in Logs. Users don't see this text yet: they get the alternatives list or Formal's standard block message.
Click **Save Changes**.
**Verify:** Go to [Policies](https://app.formal.ai/policies). With **Block** selected, you see **Block unknown MCPs** or **Block unknown skills**.
## What Formal Creates
Each guardrail set to **Block** is a generated [policy](/docs/guides/policies/introduction). Selecting **Allow** removes it.
### Block Unknown MCPs
The policy blocks MCP tool calls to servers that have no Formal Resource:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
request := {
"action": "block",
"type": "mcp_suggest_alternatives",
"reason": "This MCP is not approved by your organization.",
} if {
input.resource.technology == "mcp"
input.resource.name == ""
input.mcp.method == "tools/call"
}
```
The `mcp_suggest_alternatives` block tells the agent which approved MCP servers it can use instead. It also explains how to request the blocked server. See [Suggest approved MCP alternatives](/docs/guides/policies/enforcement#suggest-approved-mcp-alternatives).
Only the [Formal Endpoint](/docs/guides/client-apps/desktop-app) sees MCP servers that have no Formal Resource. This guardrail applies on devices that run the Endpoint with Transparent Mode.
### Block Unknown Skills
The policy blocks LLM requests that load a skill whose name is not in an approved list:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
import future.keywords.in
unknown_skill := {
"approvedSkills": ["code-review", "release-notes"],
"blockMessage": "This skill is not approved by your organization.",
}
request := {
"action": "block",
"type": "block_with_formal_message",
"reason": unknown_skill.blockMessage,
} if {
input.resource.technology == "llm"
some skill in input.llm.loaded_skills
not skill.name in unknown_skill.approvedSkills
}
```
`approvedSkills` holds the names of the managed skills in your Catalog. Formal updates the list when someone with the **Policies** permission opens the Catalog, Shadow AI, or Guardrails page.
Formal detects loaded skills in Claude Code traffic only, so this guardrail doesn't affect other agents. It names each loaded skill after the folder that holds its `SKILL.md`.
Change guardrails from the Guardrails page, not in the policy editor. Saving
a guardrail rewrites its generated rules. See
[Policy Tags](/docs/guides/policies/introduction#policy-tags) to recognize
generated policies.
## Troubleshooting
**Cause:** The traffic doesn't pass through the Formal Endpoint, or the server has a Formal Resource. **Fix:** Confirm that a [network rule](/docs/guides/network-rules) intercepts `mcp` traffic on the device. Servers with a Formal Resource follow their Catalog access controls.
**Cause:** The skill's access controls exclude the user. **Fix:** Open the skill in the Catalog and check **Access Controls**.
## Next Steps
Review items that people already use
Approve employee requests for blocked MCP servers
# AI Governance
Source: https://docs.formal.ai/docs/guides/ai-governance/overview
Discover, approve, and monitor the MCP servers, skills, and AI tools your organization uses
## Overview
Formal governs how people and AI agents use MCP servers, skills, and LLM providers. The **AI Tools** section of the Control Plane groups these pages:
| Page | Use it to |
| - | - |
| [Catalog](https://app.formal.ai/catalog) | Keep the approved MCP servers and skills, and choose who can use each one |
| [Shadow AI](/docs/guides/ai-governance/shadow-ai) | Review MCP servers and skills that people use but that aren't in the Catalog |
| [Access Requests](/docs/guides/ai-governance/access-requests) | Approve or reject employee requests for MCP servers and policy exceptions |
| [Guardrails](/docs/guides/ai-governance/guardrails) | Choose what happens to MCP servers and skills outside the Catalog |
| [Assessment](/docs/guides/core-concepts/assessments) | Score LLM conversations for alignment and risk |
| [Usage, Models, MCPs, Skills, Applications, and Providers](/docs/guides/ai-governance/usage) | See AI activity, usage, and policy hits |
[Insights](/docs/guides/observability/insights) adds AI-generated security findings about LLM traffic.
## How the Pieces Fit
1. **Capture AI traffic.** Formal sees AI activity that flows through the [Formal Endpoint](/docs/guides/client-apps/desktop-app) or a Connector.
2. **Discover.** Shadow AI and the usage pages show which MCP servers, skills, models, and AI clients people use.
3. **Decide.** Add approved items to the Catalog with access controls. Set Guardrails for everything else.
4. **Handle exceptions.** Employees request access from the employee Catalog. Admins decide in Access Requests.
5. **Investigate.** Review Insights, Logs, and the usage pages for risky activity.
## Capture AI Traffic
Each page reads logs, so it only shows traffic that Formal proxies:
| Traffic | How to capture it | Pages that use it |
| - | - | - |
| LLM requests from laptops and servers | Formal Endpoint with [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode) and a [network rule](/docs/guides/network-rules) that intercepts `llm` traffic | Usage, Models, Applications, Providers, Skills, Shadow AI skills |
| LLM requests from applications | An [LLM Resource](/docs/guides/core-concepts/resources/llm) on a Connector | Models, Applications, Providers, Skills |
| MCP servers in the Catalog | The [MCP gateway](/docs/guides/core-concepts/connectors/mcp-gateway) on a Connector | MCPs |
| MCP servers outside the Catalog | Formal Endpoint with Transparent Mode and a network rule that intercepts `mcp` traffic | Shadow AI MCP servers |
This network rule intercepts LLM and MCP traffic from known AI agents on macOS:
```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"condition": {
"pre_tcp": process.is_agent || process.has_agent_ancestor,
"pre_tls": true,
"post_tls": technology in ["mcp", "llm"]
}
}
```
**Verify:** Use an AI agent on a device that runs the Endpoint. Then open [Logs](https://app.formal.ai/logs), select the **Endpoint** source, and filter on `resource.technology:llm`.
## The Catalog
The [Catalog](https://app.formal.ai/catalog) lists the MCP servers and skills your organization approves. Click **Add MCP** to add a server from a template or a custom endpoint. See [MCP Gateway](/docs/guides/core-concepts/connectors/mcp-gateway#add-an-mcp-server) for the steps. Click **Add Skill** to add a skill from its `SKILL.md`.
Adding items requires the **Resource** permission. Changing access requires the **Policies** permission.
### Access Controls
Open an MCP server or a skill and go to **Access Controls**. Under **Who can use this MCP?** or **Who can use this skill?**, choose:
* **Everyone:** Every user can use it.
* **Specific Users:** Only the selected users and groups can use it.
* **No One:** Nobody can use it. Formal keeps the item in the Catalog as blocked.
For an MCP server, you can also answer **Which tools can they use?** with **All Tools** or **Specific Tools**.
Formal enforces restrictions with a generated policy. It stores **Message shown when access is denied** as the policy's `reason`, which appears in Logs. Blocked users currently see Formal's standard block message instead. An item open to **Everyone**, with **All Tools** for an MCP server, needs no policy, so Formal deletes it. You can find these policies on the [Policies](https://app.formal.ai/policies) page, tagged as described in [Policy Tags](/docs/guides/policies/introduction#policy-tags). Edit access from the Catalog rather than in the policy code.
### The Employee Catalog
Employees use the employee Catalog at [catalog.formal.ai](https://catalog.formal.ai). There they can:
* Browse the MCP servers and skills available to them
* Follow setup steps for their MCP clients
* Link upstream accounts and approve MCP clients during sign-in
* Request access to MCP servers, or exceptions to policies that blocked them
* Review their governed AI activity and their requests
Control who can use the employee Catalog with the **Catalog** application in [Permissions](/docs/guides/core-concepts/permissions). See [Grant access to the Catalog](/docs/guides/core-concepts/connectors/mcp-gateway#grant-access-to-the-catalog).
## Next Steps
Review unmanaged MCP servers and skills
Block MCP servers and skills outside the Catalog
Approve employee requests
See which AI tools people use
# Review Shadow AI
Source: https://docs.formal.ai/docs/guides/ai-governance/shadow-ai
Find the MCP servers and skills people use outside your Catalog, then allow or block each one
## Overview
[Shadow AI](https://app.formal.ai/shadow-ai) is a review queue for MCP servers and skills that people use but that aren't in your [Catalog](/docs/guides/ai-governance/overview#the-catalog). Review each item, then add it to the Catalog with the right access, or block it.
Until you review an item, the matching [Guardrail](/docs/guides/ai-governance/guardrails) decides what happens to it. The banner at the top of the page shows whether new items are **allowed and monitored** or **blocked**. Click **Change in Guardrails** to change that default.
## How Formal Discovers Items
| Tab | Source | Refresh |
| - | - | - |
| **MCP Servers** | MCP requests that the [Formal Endpoint](/docs/guides/client-apps/desktop-app) intercepted for servers with no Formal Resource | Every 15 minutes |
| **Skills** | Skills that Claude Code requests list as available or loaded | Every 24 hours |
Formal finds items in proxied traffic only. To capture it, see [Capture AI Traffic](/docs/guides/ai-governance/overview#capture-ai-traffic).
## Review an MCP Server
Go to [Shadow AI](https://app.formal.ai/shadow-ai) and open the **MCP Servers** tab. Each row shows the server name, its endpoint, and **Last Used**. Servers reached through Claude's connector proxy show a **Claude Connector** badge.
Click **Review**. The drawer shows how the server was used and who can use it.
Click **Allow** to add the server to the Catalog with the access shown in the drawer. Click **Block** to add it with no access.
Both choices create an MCP Resource and a generated access policy. The server then leaves the queue, and you manage it from the Catalog.
**Verify:** The server appears in the [Catalog](https://app.formal.ai/catalog) with the access you chose.
## Review a Skill
Open the **Skills** tab. Each row shows whether the skill was **Loaded** into a conversation or only **On device**. Filter with **All**, **Available on Device**, or **Loaded in Conversation**.
Click the skill. Under **Who can use this skill?**, choose **Everyone**, **Specific Users**, or **No One**.
The button follows your choice:
* **Add to catalog:** Everyone can use the skill.
* **Add with limited access:** Only the users and groups you selected can use it.
* **Block skill:** Nobody can use it.
Each outcome adds the skill to the Catalog. **Add with limited access** and **Block skill** also create an access policy for it.
**Verify:** The skill leaves the queue and appears in the Catalog.
## Permissions
Deciding on an item requires permissions to manage both Resources and Policies. Without them, the drawer shows **Resource and policy management permissions are required to decide.**
## Troubleshooting
**Cause:** No device intercepted MCP traffic to a server without a Formal Resource. **Fix:** Confirm the Endpoint runs in Transparent Mode with a network rule for `mcp` traffic. Wait up to 15 minutes for discovery.
**Cause:** Formal only detects skills in proxied Claude Code requests, and refreshes skills once a day. Skills used by other agents don't appear. **Fix:** Confirm that Claude Code traffic reaches Formal, then check again the next day.
## Next Steps
Set the default for items you haven't reviewed
See which AI tools people use
# Monitor AI Usage
Source: https://docs.formal.ai/docs/guides/ai-governance/usage
See which AI clients, models, providers, MCP servers, and skills people use, and what policies they trigger
## Overview
The **AI Tools** section of the Control Plane includes six pages built from AI traffic that Formal proxies:
| Page | Shows |
| - | - |
| [Usage](https://app.formal.ai/ai-usage) | Sessions, requests, and tokens across every AI client and model |
| [Models](https://app.formal.ai/ai-models) | Every model in proxied AI traffic, with usage and policy activity |
| [MCPs](https://app.formal.ai/ai-mcps) | Every MCP server in proxied traffic, the tools agents call, and the tools it offers |
| [Skills](https://app.formal.ai/ai-skills) | Every skill loaded into agent context, against everyone it was offered to |
| [Applications](https://app.formal.ai/ai-applications) | Every AI client application, such as Claude Code or Cursor |
| [Providers](https://app.formal.ai/ai-providers) | Every LLM provider, such as Anthropic or OpenAI |
These pages require the **Logs** permission. They only read data. To act on what you find, use [Guardrails](/docs/guides/ai-governance/guardrails), the [Catalog](/docs/guides/ai-governance/overview#the-catalog), or [policies](/docs/guides/policies/introduction).
## Data Sources
| Page | Traffic it reads |
| - | - |
| Usage | LLM requests captured by the [Formal Endpoint](/docs/guides/client-apps/desktop-app) |
| Models, Applications, Providers, Skills | LLM requests from the Endpoint or from [LLM Resources](/docs/guides/core-concepts/resources/llm) on a Connector |
| MCPs | MCP JSON-RPC requests to MCP Resources, such as servers on the [MCP gateway](/docs/guides/core-concepts/connectors/mcp-gateway) |
If a page is empty, see [Capture AI Traffic](/docs/guides/ai-governance/overview#capture-ai-traffic).
## Usage
The **AI Usage** page summarizes Endpoint AI activity for **Last 24 Hours**, **Last 7 Days**, or **Last 30 Days**. The header shows **Number of Sessions**, **LLM Requests**, **Triggered Policies**, and **Insights Count**. **LLM Requests** and **Triggered Policies** link to the matching logs, and **Insights Count** links to [Insights](/docs/guides/observability/insights).
* The **Usage** tab charts sessions, requests, triggered policies, and risks over time. It breaks activity down by application, provider, model, and user, and lists recent sessions.
* The **Security** tab shows triggered policy actions and ranks users by requests, sessions, and triggered policies.
## Models, MCPs, Skills, Applications, and Providers
These pages cover the last 30 days. Each one has a **Usage** tab with key figures and charts, and an inventory tab with one row per item. Click a row to open its detail page, with links to the matching logs.
Every inventory includes these columns: **Users (30d)**, **Requests**, **Policy Hits**, **First Seen**, and **Last Used**. Show **Users (7d)**, **Sessions**, and **Devices** from the column picker. Pages for LLM traffic also show **Tokens**.
| Page | Key figures | Extra columns |
| - | - | - |
| Models | Models in use, tool-calling requests | **Provider** |
| Applications | Applications in use, devices | **Models** |
| Providers | Providers in use, failed requests, authentication failures | **Models** |
| MCPs | Tool calls, tool discoveries, Formal Resources | **Tool Calls**, **Tools Used** |
| Skills | Skill loads, users loading skills, policy hits on loads | **Available To**, **Loaded By** |
The MCPs page identifies a server by its hostname and URL path. The Skills page only covers Claude Code traffic, because Formal detects skills in Claude Code requests only. It counts a load for each request that carries a skill's instructions, so a skill counts again on every later turn of the same conversation.
## Next Steps
Review MCP servers and skills outside the Catalog
Review AI-generated security findings
# CLI Reference
Source: https://docs.formal.ai/docs/guides/client-apps/cli
Authenticate, list resources, and connect with the Formal CLI
See [Configuration](/docs/guides/client-apps/configuration) for `~/.formal/config.toml`.
### Authentication
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Log in
formal auth login
# Get your credentials (for direct connections)
formal auth credentials
```
### List Resources
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal ls
```
Interactive TUI with:
* `↑/k` - Move up
* `↓/j` - Move down
* `enter` - Connect to resource
* `/` - Filter resources
* `q` - Quit
* `?` - Show help
### Connect to Resources
#### Databases (PostgreSQL, MySQL, MongoDB)
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Connect to a database
formal connect production-postgres
# Output:
# Connected to production-postgres on localhost:6432
```
Then connect with your database client:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# PostgreSQL
psql -h localhost -p 6432 -d postgres
# MySQL
mysql -h localhost -P 4306 -D mysql
# MongoDB
mongosh "mongodb://localhost:37017/mydb"
```
**Formal automatically injects your credentials** - no username/password needed!
Note that the database name is required for the connection to be successful.
You can also use the `--launch` argument to launch the appropriate CLI program after connecting (for example `psql` or `ssh`), e.g.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# uses the database name from the config file in ~/.formal/config.toml
formal connect --launch production-postgres
```
#### Specify Native User
To request a Native User instead of the Resource's assigned default, append its label using `formal@`.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# PostgreSQL with the "readonly" Native User label
psql -h localhost -p 6432 -U formal@readonly
```
See [Native Users](/docs/guides/core-concepts/resources/native-users) for label selection and policy controls.
#### Specify Resource Hostname
If a resource has multiple [Resource Hostnames](/docs/guides/core-concepts/resources/tls#resource-hostnames) (for example, separate reader and writer endpoints), target one by appending `@` to the resource name:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Connect to the "postgres-reader" hostname on the production-postgres resource
formal connect production-postgres@postgres-reader
```
Resource hostname targeting is currently supported for PostgreSQL and MySQL resources.
Connect with your database client as usual, using the port from the `formal connect` output:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# PostgreSQL
psql -h localhost -p 6432 -d postgres
# MySQL
mysql -h localhost -P 4306 -D mysql
```
Use the same `RESOURCE_NAME@HOSTNAME_NAME` target to disconnect:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal disconnect production-postgres@postgres-reader
```
#### SSH
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# List resources
formal ls
# Select an SSH resource and press enter
```
This updates your `~/.ssh/config` automatically.
#### Configure SSH without write access
Formal adds the following block to the top of `~/.ssh/config` on your first SSH connection:
```ssh-config theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# START FORMAL-MANAGED BLOCK
# Do not hand-edit this section.
Include ~/.formal/ssh_config
# END FORMAL-MANAGED BLOCK
```
If Formal cannot modify this file, add the block manually before any `Host` entries. Then run `formal connect ` again.
Formal writes subsequent SSH resource changes to `~/.formal/ssh_config`.
#### Kubernetes
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Configure kubectl
formal ls
# Select a Kubernetes resource and press enter
```
This updates your `~/.kube/config` to route through the Connector.
### Disconnect
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Disconnect from a resource
formal disconnect production-postgres
```
### Decrypt Data
Decrypt Formal-encrypted data:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal decrypt --ciphertext "formalencrypt:second:third:fourth:fifth"
```
### S3 Operations
The Formal `s3` command automatically routes traffic to the right Connector so your users don't have to worry about formatting the right parameters for S3.
Interact with S3 through Formal:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal s3 ls s3://my-bucket
```
See [AWS CLI S3 commands](https://docs.aws.amazon.com/cli/latest/userguide/cli-services-s3-commands.html) for more operations.
# Configuration
Source: https://docs.formal.ai/docs/guides/client-apps/configuration
Configure Formal Endpoint with ~/.formal/config.toml and macOS MDM profiles
## Overview
Formal Endpoint reads `~/.formal/config.toml` on startup. Restart Formal Endpoint after you edit the file.
On macOS, Forced `.mobileconfig` keys override `~/.formal/config.toml`. See [MDM overrides](#mdm-overrides-macos).
## `config.toml` settings
Every key the Endpoint reads from `~/.formal/config.toml`:
| Key | Type | Default | Description |
| - | - | - | - |
| `check_for_updates` | bool | `false` | Notify in the systray when a newer Endpoint version is present. If omitted, the systray does not show an update. |
| `hide_systray_icon` | bool | `false` | Hide the system tray icon. The agent keeps running in the background. |
| `postgres_db` | string | unset | Default database name for PostgreSQL connections (`formal connect --launch` and the TUI). If omitted, Formal prompts for a name. |
| `launch_commands` | bool | unset | Launch the matching CLI after you connect, such as `psql` or `ssh`. If omitted, the TUI asks once and saves the answer. |
| `auth_mode` | string | unset | Set to `"oidc"` to authenticate with an OIDC token source. `FORMAL_API_KEY` takes precedence. |
| `oidc.provider` | string | unset | OIDC token source: `aws`, `azure`, `gcp`, `cursor`, or `devin`. Mutually exclusive with `oidc.env` and `oidc.file`. Requires `oidc.integration_id`. |
| `oidc.env` | string | unset | Environment variable that holds an OIDC JWT. Mutually exclusive with `oidc.provider` and `oidc.file`. |
| `oidc.file` | string | unset | Path to a file that holds an OIDC JWT. The Endpoint reads it on every refresh. Mutually exclusive with `oidc.provider` and `oidc.env`. |
| `oidc.integration_id` | string | unset | Formal OIDC integration ID. Required with `oidc.provider`. Also set it when the JWT audience is not `oidc.formal.ai/`. |
| `transparent_proxy.enable` | bool | `false` | Enable Transparent Mode when the Endpoint starts. |
| `transparent_proxy.enforce` | bool | `false` | Lock `formal transparent-proxy install`, `formal transparent-proxy enable`, and the tray toggle. |
| `transparent_proxy.fail` | string | `"open"` | If the Endpoint errors: `open` preserves connectivity; `closed` keeps the proxy enforced. |
| `transparent_proxy.allow_quic` | bool | `false` | Allow QUIC (UDP/443). Default `false` blocks QUIC so clients fall back to TCP/TLS. Blocking QUIC is required to intercept Claude Chat in the Claude Desktop App. |
| `transparent_proxy.intercept_localhost` | bool | `false` | Intercept IPv4 loopback traffic, such as local tunnels on `127.0.0.1`. |
```toml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
check_for_updates = false
hide_systray_icon = false
postgres_db = "postgres"
launch_commands = true
auth_mode = "oidc"
[oidc]
provider = "aws"
integration_id = "integrationoidc_"
[transparent_proxy]
enable = true
enforce = true
fail = "open"
allow_quic = false
intercept_localhost = false
```
Omit `[oidc]` and `auth_mode` unless the Endpoint should authenticate with OIDC. Use `oidc.env` or `oidc.file` instead of `oidc.provider` when the JWT comes from an environment variable or a file. See [Log In with OIDC](/docs/guides/client-apps/oidc).
## Configuring Transparent Mode on Startup
To avoid toggling Transparent Mode every time you open Formal Endpoint, configure it in `~/.formal/config.toml`.
```toml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
[transparent_proxy]
enable = true
enforce = true
fail = "open"
allow_quic = false
intercept_localhost = false
```
`enable` specifies whether the endpoint agent should try to enable Transparent Mode upon startup. If omitted, `enable` defaults to `false`.
`enforce` controls whether you can change Transparent Mode behavior with `formal transparent-proxy install`, `formal transparent-proxy enable`, and the tray toggle. If omitted, `enforce` defaults to `false`.
Privileged users can still disable the network extension without
`formal transparent-proxy enable` or `formal transparent-proxy disable`.
`fail` can be `open` or `closed`. If the endpoint agent errors, `open` preserves network connectivity. `closed` keeps enforcing that Transparent Mode is running. If omitted, `fail` defaults to `open`.
`allow_quic` controls whether Transparent Mode allows QUIC traffic (UDP/443). Blocking QUIC is required to intercept Claude Chat sessions in the Claude Desktop App. If omitted, `allow_quic` defaults to `false`.
`intercept_localhost` controls whether Transparent Mode intercepts IPv4 loopback traffic, such as local tunnels on `127.0.0.1`. If omitted, `intercept_localhost` defaults to `false`.
See [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode) for install and enable steps.
## MDM overrides (macOS)
Forced `.mobileconfig` keys override `~/.formal/config.toml`. On macOS, Forced keys in the `com.formal.desktop` domain take precedence over the file. They apply even when `config.toml` is absent.
Deploy the values as `Forced` (locked) keys in a `com.apple.ManagedClient.preferences` payload. Generate the profile from **Endpoint Rollout** in the Control Plane.
| Managed preference key | Type | `config.toml` setting |
| - | - | - |
| `checkForUpdates` | bool | `check_for_updates` |
| `hideSystrayIcon` | bool | `hide_systray_icon` |
| `transparentProxyEnable` | bool | `transparent_proxy.enable` |
| `transparentProxyEnforce` | bool | `transparent_proxy.enforce` |
| `transparentProxyFail` | string | `transparent_proxy.fail` (`open` or `closed`) |
| `transparentProxyAllowQuic` | bool | `transparent_proxy.allow_quic` |
| `transparentProxyInterceptLocalhost` | bool | `transparent_proxy.intercept_localhost` |
These `config.toml` keys have no MDM mapping: `postgres_db`, `launch_commands`, `auth_mode`, and `[oidc]`.
### Require a password to uninstall
Set the `uninstallPasswordHash` managed preference to protect `formal uninstall`. This key exists only in the `.mobileconfig` profile. It is not a `config.toml` setting.
When the key is present, the CLI prompts for the password. It refuses to remove Formal unless the password matches.
Enter an **Uninstall Password** on the Configuration Profile step in **Endpoint Rollout**. Formal embeds the SHA-256 hex digest in the profile. The plaintext password never leaves your browser.
This is a deterrent, not tamper protection. A user with `sudo` can still stop
processes and delete files manually.
## Next Steps
Install the app, log in, and connect to resources
Install the headless package on Debian or Ubuntu
Deploy Formal Endpoint with your MDM
# Formal Endpoint
Source: https://docs.formal.ai/docs/guides/client-apps/desktop-app
Streamline resource access with the Formal Endpoint and CLI
## Overview
The Formal Endpoint simplifies connecting to protected resources by automating authentication and connection management. It also includes a powerful CLI. Deploy it on [macOS](/docs/guides/client-apps/macos), [Windows](/docs/guides/client-apps/windows), [Linux](/docs/guides/client-apps/linux), or [Kubernetes](/docs/guides/client-apps/kubernetes-egress).
Configure startup behavior in [`~/.formal/config.toml`](/docs/guides/client-apps/configuration).
The Endpoint runs a background process, started with `formal agent`, that the CLI talks to. Endpoint guides call it "the agent". It is unrelated to [Formal Agent](/docs/guides/getting-started/formal-agent), the AI assistant in the Control Plane.
## Access
Download the Formal Endpoint from the [Control Plane](https://app.formal.ai). Click **Download Formal Endpoint** at the bottom of the sidebar, then choose your operating system. Linux packages are under **Linux**.
To roll the Endpoint out to a managed fleet, use [MDM Endpoint Rollout](/docs/guides/integrations/mdm#endpoint-rollout).
## Connecting to Resources
### Avoiding Credentials
After you log in, confirm your session with:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal auth whoami
```
When you run `formal connect` for a resource, the Formal Endpoint injects Formal credentials for you. You do not pass Formal usernames or passwords to the client.
* **SSH and Kubernetes:** Your `~/.ssh/config` and `~/.kube/config` are updated so tools use the right paths and identity.
* **Other technologies:** A localhost listener is started. Point your client at that address and port without supplying Formal credentials.
## Transparent Mode
Transparent Mode is supported on **macOS** and **Linux**. On Linux, enable
it in `~/.formal/config.toml`. See
[Install Formal Endpoint on Linux](/docs/guides/client-apps/linux).
With Transparent Mode, the Formal Endpoint can decide to intercept traffic
automatically. It then applies logging, policy enforcement, and optional
routing to Connectors.
The interception criteria for each TCP connection are based on
[network rules](/docs/guides/network-rules).
Once Transparent Mode is on, you do not change existing client applications.
**One-time setup**
1. Run:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal transparent-proxy install
```
2. Approve any system prompts so the Formal Endpoint **network extension** can run.
3. Start Transparent Mode:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal transparent-proxy enable
```
4. Verify that Transparent Mode is running:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal transparent-proxy status
```
**Connect**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal connect
```
Replace `` with the name from `formal ls`. Traffic to that resource’s hostname then goes through the Connector transparently.
Write [network rules](/docs/guides/network-rules) to select which flows Transparent Mode intercepts.
## Device Trust
The Formal Endpoint reports device information for policy enforcement:
* Hardware model and serial number
* OS version and security settings
* System Integrity Protection status
* Secure boot status
See [MDM Integration](/docs/guides/integrations/mdm) for device-based policies.
## Best Practices
The Formal Endpoint must be running for CLI commands to work. Enable "Launch at
startup" in preferences.
When connecting via `formal connect`, use tab completion to find resources
quickly.
`formal ls` provides an interactive way to explore and connect to resources
without memorizing names.
Always disconnect from resources when finished to free up local ports.
## Next Steps
Roll out the Endpoint with your MDM
Authenticate as a machine identity
Connect to resources from the terminal
Configure `~/.formal/config.toml`
# Formal Kubernetes Egress Agent
Source: https://docs.formal.ai/docs/guides/client-apps/kubernetes-egress
Deploy Formal Endpoint on Kubernetes to apply policies to application container traffic
## Overview
Deploy Formal Endpoint on Kubernetes to apply Formal to application
container traffic. On Kubernetes, Formal Endpoint runs as a native sidecar
outside the application container. It is not installed on the host.
Configure [policies](/docs/guides/policies/introduction) against that
traffic as you would on [macOS, Windows, or Linux](/docs/guides/client-apps/desktop-app).
```mermaid theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
flowchart LR
subgraph pod["Pod"]
app["Application container"]
sidecar["Formal Endpoint native sidecar"]
end
internet["Public internet"]
app --> sidecar --> internet
```
## Cluster requirements
Your cluster must meet these requirements before you run the sidecar.
| Requirement | Why it is required |
| - | - |
| **Kubernetes 1.29+** | Native sidecars use `restartPolicy: Always` on an init container. |
| **Sidecar `NET_ADMIN`** | The sidecar adds the `NET_ADMIN` Linux capability so it can intercept Pod egress. The cluster does not need a `NET_ADMIN` setting. |
| **Privileged Pod Security Standard** | The sidecar runs as root and adds `NET_ADMIN`. Restricted and Baseline [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/) deny those Pods. |
| **Outbound HTTPS to the Control Plane** | The sidecar must reach `https://api.joinformal.com`. |
| **Image pull access** | Nodes must pull the Formal Endpoint image from ECR or GAR. |
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
kubectl version
```
The server version must be 1.29 or later.
## Installation
Install Formal Endpoint as a native sidecar on your application Pod. The
recommended path is the
[Helm chart](https://github.com/formalco/helm-charts/tree/main/charts/kubernetes-egress).
It injects the sidecar onto labeled Pods via a
[MutatingAdmissionPolicy](https://kubernetes.io/docs/reference/access-authn-authz/mutating-admission-policy/).
If you already control the workload spec, you can
[add the sidecar to your spec](#add-the-sidecar-to-your-spec) instead.
### Add the Formal API Key Secret
The sidecar authenticates as a Formal machine user. Create an API key for that
user and store it as a Kubernetes Secret in each namespace that runs labeled
Pods.
The chart does not create this Secret.
Go to [Users](https://app.formal.ai/users) and click **Create User**.
Choose **Machine**. Give the user a name that matches the workload.
Go to [API Keys](https://app.formal.ai/api-keys) and click **Create API
Key**. Assign the key to that machine user.
Create `formal-kubernetes-egress` in every namespace that will run
labeled Pods. The sidecar reads `FORMAL_API_KEY` from the
`formal-api-key` key. To use a different Secret, set
`formalAPIKeySecret.name` and `formalAPIKeySecret.key`.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
kubectl create secret generic formal-kubernetes-egress \
--namespace agents \
--from-literal=formal-api-key=
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
kubectl get secret formal-kubernetes-egress --namespace agents
```
The Secret must exist before you create labeled Pods in that namespace.
## Deploy with Helm
The chart is published as `kubernetes-egress` in the
[Formal Helm Charts](https://github.com/formalco/helm-charts) repository.
The chart needs Kubernetes 1.36+ and cluster RBAC to manage
[MutatingAdmissionPolicy](https://kubernetes.io/docs/reference/kubernetes-api/admissionregistration/mutating-admission-policy-v1/)
and
[MutatingAdmissionPolicyBinding](https://kubernetes.io/docs/reference/kubernetes-api/admissionregistration/mutating-admission-policy-binding-v1/).
### Apply the inject label
The policy binding mutates Pods that have this label set to `true`.
```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal.ai/kubernetes-egress-inject: "true"
```
Put it on the Pod template (`spec.template.metadata.labels`) of your
Deployment, StatefulSet, DaemonSet, Job, or CronJob:
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
spec:
template:
metadata:
labels:
formal.ai/kubernetes-egress-inject: "true"
```
The policy mutates Pod CREATE only. Labeling an existing Pod does not inject
the sidecar.
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
kubectl get pods --namespace agents --show-labels
kubectl get pods -l formal.ai/kubernetes-egress-inject=true --all-namespaces
```
Expected output includes `formal.ai/kubernetes-egress-inject=true` on the
workload Pods.
To stop injecting, remove the label from the Pod template and roll the
workload. Existing Pods keep their sidecar until you replace them.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm repo add formal https://formalco.github.io/helm-charts
helm repo update
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm upgrade --install formal-kubernetes-egress formal/kubernetes-egress \
--namespace formal \
--create-namespace
```
The chart installs cluster-scoped admission objects only.
Apply the [inject label](#apply-the-inject-label) on the
workload Pod template. New Pods created with that label are injected.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
kubectl get mutatingadmissionpolicy formal-kubernetes-egress
kubectl get mutatingadmissionpolicybinding formal-kubernetes-egress
kubectl get pods -l formal.ai/kubernetes-egress-inject=true --namespace agents \
-o jsonpath='{range .items[*]}{.metadata.name}{" "}{.spec.initContainers[*].name}{"\n"}{end}'
```
New labeled Pods include a `formal-kubernetes-egress` init container.
Use `helm upgrade --install` for every apply.
### Image pull credentials
The default image is:
`654654333078.dkr.ecr.us-east-1.amazonaws.com/formalco-prod-kubernetes-egress:latest`
On AKS, pull from GAR with
[`azure-gar-cred`](https://github.com/formalco/helm-charts/tree/main/charts/azure-gar-cred).
Set `pullWithCredentials: true` and the GAR repository:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm upgrade --install formal-kubernetes-egress formal/kubernetes-egress \
--namespace formal \
--set pullWithCredentials=true \
--set sidecar.image.repository=us-docker.pkg.dev/formal-public-assets/formalco-prod-kubernetes-egress/formalco-prod-kubernetes-egress
```
Use `ecr-cred` when you pull Formal ECR images outside AWS.
Do not install both credential charts in the same namespace.
## Add the sidecar to your spec
If you already control the workload chart or manifests, add this native
sidecar to the Pod template instead of using Formal's Helm chart.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
spec:
initContainers:
- name: formal-kubernetes-egress
image: 654654333078.dkr.ecr.us-east-1.amazonaws.com/formalco-prod-kubernetes-egress:latest
imagePullPolicy: Always
restartPolicy: Always
env:
- name: FORMAL_API_KEY
valueFrom:
secretKeyRef:
name: formal-kubernetes-egress
key: formal-api-key
securityContext:
runAsUser: 0
runAsGroup: 0
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
add: ["NET_ADMIN"]
volumeMounts:
- name: formal-ca
mountPath: /var/lib/formal/ca
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
cpu: "2"
memory: 4Gi
volumes:
- name: formal-ca
emptyDir: {}
```
On each application container, mount `formal-ca` at `/formal-ca` and set
the [TLS trust](#tls-trust) environment variables.
## TLS trust
The sidecar intercepts TLS and presents Formal's local CA. Injection mounts
`/formal-ca/localca-cert.pem` on every application container and sets these
environment variables so common HTTP clients trust that CA.
| Variable | Clients |
| - | - |
| `SSL_CERT_FILE` | OpenSSL, curl, Python `ssl`, many CLIs |
| `NIX_SSL_CERT_FILE` | Nix |
| `NODE_EXTRA_CA_CERTS` | Node.js |
| `REQUESTS_CA_BUNDLE` | Python `requests`, `urllib3` |
| `PIP_CERT` | pip |
| `AWS_CA_BUNDLE` | AWS SDK, boto3 |
| `GIT_SSL_CAINFO` | git |
| `CARGO_HTTP_CAINFO` | cargo |
Runtimes that ignore these variables need their own trust configuration
pointed at `/formal-ca/localca-cert.pem`. That includes Java truststores, Go
binaries that embed a CA pool, and some Python clients that pin
[certifi](https://pypi.org/project/certifi/) (for example httpx).
## Troubleshooting
**Possible causes:**
* The Pod spec does not include the sidecar
* Pod is missing `formal.ai/kubernetes-egress-inject: "true"`
* The label is present with a different value
* The label is on the Deployment or Namespace, not the Pod
* MutatingAdmissionPolicy is not installed
**Solution:**
1. Confirm the Pod spec includes `formal-kubernetes-egress`, or confirm the Pod label
2. For controllers, confirm `spec.template.metadata.labels`
3. Confirm the admission objects exist if you use the injector
4. Create a new Pod. Labeling a running Pod does not inject
**Possible causes:**
* Secret `formal-kubernetes-egress` is missing
* Image pull failed
* Pod Security Standard drops `NET_ADMIN`
**Solution:**
1. Create the Secret in that namespace
2. Confirm image pull credentials
3. Use Privileged Pod Security, or exempt the namespace
**Cause:** The chart requires Kubernetes 1.36 or later.\
**Fix:** Add the sidecar to your spec, or upgrade the control plane
and reinstall the chart.
**Possible causes:**
* The client ignores the injected CA environment variables
* The client uses a bundled CA store (Java, certifi, a static Go binary)
**Solution:**
1. Point the client at `/formal-ca/localca-cert.pem`
2. Confirm the container has that file: `kubectl exec` and `ls /formal-ca`
3. Restart the Pod so it picks up the current admission mutation
## Next Steps
See how the Endpoint authenticates and routes traffic
Control intercepted egress with Rego policies
Search Endpoint logs for intercepted requests
Manage the identity the sidecar authenticates as
# Install Formal Endpoint on Linux
Source: https://docs.formal.ai/docs/guides/client-apps/linux
Install the headless Formal Endpoint package on Debian and Ubuntu hosts
## Overview
Install the headless Formal Endpoint package on Debian or Ubuntu. The package adds the `formal` CLI and a systemd user service that runs `formal agent --headless`. Enable Transparent Mode to intercept host traffic, including traffic from coding agents.
For Kubernetes, use the [Kubernetes egress sidecar](/docs/guides/client-apps/kubernetes-egress) instead of this package.
## Prerequisites
* A Debian or Ubuntu host with `sudo` and systemd
* A Formal API key from [API Keys](https://app.formal.ai/api-keys)
* Outbound HTTPS to `api.joinformal.com`
The package supports `amd64` and `arm64`.
## Install
In the [Control Plane](https://app.formal.ai), click **Download Formal
Endpoint** at the bottom of the sidebar. Open **Linux** and pick the
headless package for your architecture:
* **Ubuntu/Debian (x86, headless)** for `amd64`
* **Ubuntu/Debian (ARM, headless)** for `arm64`
Print the host architecture with `dpkg --print-architecture`. Copy the
`.deb` file to the host, for example with `scp`.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sudo apt-get update
sudo apt-get install --yes ./formal-desktop-headless_*.deb
```
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
command -v formal
# Expected: /usr/bin/formal
```
The Endpoint writes its local CA to `/var/lib/formal/ca`. Your user must be able to write that directory:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sudo chgrp "$(id -gn)" /var/lib/formal/ca
sudo chmod 0775 /var/lib/formal/ca
```
Create `~/.formal` and store the API key in `formal.env`. The user service loads that file on start.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
mkdir -p ~/.formal
chmod 0700 ~/.formal
printf 'FORMAL_API_KEY=\n' > ~/.formal/formal.env
chmod 0600 ~/.formal/formal.env
cat > ~/.formal/config.toml <<'EOF'
[transparent_proxy]
enable = true
EOF
```
Replace `` with a key from [API Keys](https://app.formal.ai/api-keys). See [Configuration](/docs/guides/client-apps/configuration) for other `config.toml` keys. To skip long-lived keys, use [OIDC](/docs/guides/client-apps/oidc#log-in-with-oidc).
Lingering lets systemd start the user service without an interactive login. This is required on servers and VMs.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sudo loginctl enable-linger "$(id -un)"
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
systemctl --user daemon-reload
systemctl --user enable --now formal.service
```
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
XDG_RUNTIME_DIR="/run/user/$(id -u)" \
systemctl --user is-active formal.service
# Expected: active
```
## Verify
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal auth whoami
formal transparent-proxy status
```
`formal auth whoami` should show the user that owns the API key. The Transparent Mode status should be enabled.
Write [network rules](/docs/guides/network-rules) to select which flows Transparent Mode intercepts. On Linux, match a hostname or `is_llm_hostname`. Linux has no process signing identity.
Read service logs with `journalctl --user -u formal.service`. Set `XDG_RUNTIME_DIR` first when your shell has no session bus.
## Headless Mode
The Formal Endpoint supports being launched in headless mode. This can be useful in server environments without a GUI or keyring support.
In this guide, "the agent" means the Endpoint's background process that `formal agent` starts. It is not the Control Plane's [Formal Agent](/docs/guides/getting-started/formal-agent) assistant.
To start the agent in headless mode, you can use the following command:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal agent --headless
```
Now in a separate terminal, you can run the Formal CLI as normal to connect to resources and get credentials:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal auth login
formal auth credentials
formal ls
formal connect [resource-name]
```
In headless mode, Formal credentials are only stored in the agent's memory. This means that you'll need to reauthenticate when restarting the agent (e.g., when the machine itself reboots).
To skip `formal auth login`, configure [OIDC](/docs/guides/client-apps/oidc#log-in-with-oidc) or start the agent with an [API key](/docs/guides/client-apps/oidc#log-in-with-an-api-key).
On Linux, the Debian package installs a `formal.service` user unit.
## Troubleshooting
Confirm lingering is enabled with `loginctl show-user "$(id -un)"`. Set `XDG_RUNTIME_DIR="/run/user/$(id -u)"` and read failures with `journalctl --user -u formal.service`. Then run `systemctl --user enable --now formal.service`.
**Error:** `WARNING: CLI cannot connect to the Formal app.`
The CLI talks to the user service over a local socket. Confirm `formal.service` is active, then retry `formal auth whoami`.
The Endpoint must write `/var/lib/formal/ca`. Re-run:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sudo chgrp "$(id -gn)" /var/lib/formal/ca
sudo chmod 0775 /var/lib/formal/ca
```
Then restart the service.
Confirm `~/.formal/config.toml` sets `transparent_proxy.enable = true`. Confirm `formal transparent-proxy status` is enabled. Confirm a [network rule](/docs/guides/network-rules) matches the destination. Without a matching rule, the Endpoint forwards the connection without inspecting it.
## Next Steps
Review authentication and connection options
Set config.toml keys for the Endpoint
Select which traffic Transparent Mode intercepts
Run the Endpoint inside Cursor and Devin agents
Install the Endpoint on Grok Bot computers
# Install Formal Endpoint on macOS
Source: https://docs.formal.ai/docs/guides/client-apps/macos
Download the Formal Endpoint for macOS and deploy it with MDM
## Overview
Download the Formal Endpoint for macOS from the Control Plane. For a single Mac, install the package locally. For a fleet, use MDM Endpoint Rollout.
## Download
In the [Control Plane](https://app.formal.ai), click **Download Formal Endpoint** at the bottom of the sidebar. Open **macOS** and download the package for your Mac.
Open the package and follow the installer. After install, the Formal icon appears in the menu bar.
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal auth login
formal ls
```
`formal ls` should list resources available to you.
For CLI setup and Transparent Mode, see the [Formal Endpoint overview](/docs/guides/client-apps/desktop-app).
## MDM Deployment
Deploy the Formal Endpoint to managed Macs through your MDM.
1. Go to [MDM](https://app.formal.ai/mdm-integrations) and click **Set Up Endpoint Rollout**.
2. Select your provider and download the deployment files.
3. Follow the setup instructions in your MDM console (Jamf, Intune, Kandji, Fleet, or Mosyle).
See [MDM Endpoint Rollout](/docs/guides/integrations/mdm#endpoint-rollout) for the full walkthrough.
Need help deploying through MDM? Contact Formal support.
# OIDC
Source: https://docs.formal.ai/docs/guides/client-apps/oidc
Log in to the Formal Endpoint with OIDC or an API key
## Log In with OIDC
Use OIDC when the Formal Endpoint should authenticate as a machine identity. This flow avoids long-lived Formal credentials on the host.
The Endpoint exchanges source OIDC tokens for short-lived Formal credentials. It refreshes those credentials while the source can provide valid tokens.
Create an [OIDC integration](/docs/guides/integrations/oidc) for the workload. Map the integration to a Formal machine user.
Configure a claim condition that restricts access to the intended workload. You can also configure an end-user email expression.
Copy the integration ID after creating the integration.
Add one token source to `~/.formal/config.toml`.
Use AWS on hosts with credentials from the standard AWS credential chain.
```toml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
auth_mode = "oidc"
[oidc]
provider = "aws"
integration_id = "integrationoidc_"
```
The Endpoint uses the standard AWS credential chain. Supported sources include environment variables, shared configuration, IRSA, EKS Pod Identity, and EC2 instance profiles.
If no region is set in the environment or shared config, the Endpoint uses EC2 instance metadata.
Use Azure on AKS with Workload Identity or on Azure compute with managed identity.
```toml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
auth_mode = "oidc"
[oidc]
provider = "azure"
integration_id = "integrationoidc_"
```
Set `AZURE_FEDERATED_TOKEN_FILE` for Workload Identity. Set `AZURE_CLIENT_ID` when using a user-assigned managed identity.
Azure uses the fixed Azure Resource Manager audience. Your integration must validate that audience and the expected Azure identity claims.
This token source does not support Azure DevOps Pipelines.
Use GCP on GKE, Compute Engine, or Cloud Run. You can also use Application Default Credentials for a service account.
```toml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
auth_mode = "oidc"
[oidc]
provider = "gcp"
integration_id = "integrationoidc_"
```
On Google Cloud, the Endpoint requests a Google-signed ID token from the metadata server. Elsewhere, it uses Application Default Credentials.
Supported credentials include service account keys, impersonated service accounts, and Workload Identity Federation with service account impersonation. User credentials from `gcloud auth application-default login` are not supported.
On GKE, annotate the Kubernetes ServiceAccount with `iam.gke.io/gcp-service-account`. Google only issues ID tokens for IAM service accounts.
Set the integration issuer to `https://accounts.google.com`. Restrict the claim condition to the expected service account:
```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
claims.email == "@.iam.gserviceaccount.com" &&
claims.email_verified == true
```
Use this source when your CI system provides an OIDC JWT.
```toml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
auth_mode = "oidc"
[oidc]
env = "GITLAB_OIDC_TOKEN"
```
Request `oidc.formal.ai/` as the token audience.
For providers with a fixed audience, also set the integration ID:
```toml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
auth_mode = "oidc"
[oidc]
env = "SPACELIFT_OIDC_TOKEN"
integration_id = "integrationoidc_"
```
The Endpoint reads the variable when it starts. Start it from the same environment:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
export GITLAB_OIDC_TOKEN=""
formal agent --headless
```
The Endpoint cannot renew a token supplied through an environment variable. Restart it with a new token before the current token expires.
Use this source when a JWT is written to disk and rotated in place, such as a projected Kubernetes ServiceAccount token.
```toml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
auth_mode = "oidc"
[oidc]
file = "/var/run/secrets/formal/token"
```
The Endpoint reads the file on every refresh, so it picks up rotated tokens without a restart. Set `integration_id` when the JWT audience is not `oidc.formal.ai/`.
On Kubernetes, project a ServiceAccount token with the Formal audience:
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
volumes:
- name: formal-token
projected:
sources:
- serviceAccountToken:
path: token
audience: oidc.formal.ai/
expirationSeconds: 3600
```
Mount the volume at `/var/run/secrets/formal` in the Endpoint container.
Set the integration issuer to the cluster's ServiceAccount issuer. Formal must reach its discovery document or JWKS over public HTTPS. Find the issuer with:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
kubectl get --raw /.well-known/openid-configuration | jq -r .issuer
```
The token `sub` is `system:serviceaccount::`. Restrict the claim condition to the expected workloads:
```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
claims.sub.startsWith("system:serviceaccount::")
```
Unset `FORMAL_API_KEY` because API key authentication takes precedence.
Restart the desktop app, or start a headless agent:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
unset FORMAL_API_KEY
formal agent --headless
```
Run these commands from another terminal:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal auth whoami
formal ls
```
`formal auth whoami` should show the integration's machine user. `formal ls` should show resources available to that identity.
## Log In with an API Key
Use an API key when the Formal Endpoint should skip the browser login flow. This works for human users and machine users.
Go to [API Keys](https://app.formal.ai/api-keys) and click **Create API Key**.
To authenticate as a machine user, assign the key to that machine user.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
FORMAL_API_KEY= formal agent
```
For a server without a GUI or keyring, add `--headless`:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
FORMAL_API_KEY= formal agent --headless
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal auth whoami
```
The output should show the human or machine user that owns the key.
# Install Formal Endpoint on Windows
Source: https://docs.formal.ai/docs/guides/client-apps/windows
Install the Formal Endpoint MSI on Windows, sign in, and connect to Resources
## Overview
The Formal Endpoint for Windows ships as an MSI installer. It installs the `formal` CLI and the Endpoint's background agent, and adds a **Formal** shortcut to the Start menu.
On Windows, the Endpoint connects you to Resources through local listeners with `formal connect`. [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode) and [network rules](/docs/guides/network-rules) are not available on Windows yet.
## Prerequisites
* 64-bit Windows on an x86-64 (amd64) processor. There is no Arm64 package.
* Administrator rights. The MSI installs for all users of the machine.
* Windows Hello set up with a fingerprint, face, or PIN, if your policies use the [MFA action](/docs/guides/policies/enforcement#mfa-action).
## Install
In the [Control Plane](https://app.formal.ai), click **Download Formal Endpoint** at the bottom of the sidebar, then choose **Windows**. You can also download the latest MSI directly:
```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
https://static-assets.formalcloud.net/desktop-app/windows/formal-desktop_latest_windows_amd64.msi
```
Open the MSI and follow the installer. To install silently, run this from an elevated command prompt:
```powershell theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
msiexec /i formal-desktop_latest_windows_amd64.msi /qn
```
The installer puts `formal.exe` under `Program Files\Formal` and adds that folder to the system `PATH`.
Open the Start menu and select **Formal**. The shortcut starts the Endpoint agent in the background.
Open a new terminal so it picks up the updated `PATH`, then sign in:
```powershell theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal auth login
```
A browser window opens for Formal sign-in.
## Verify
```powershell theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal auth whoami
formal ls
```
`formal auth whoami` shows your Formal user. `formal ls` lists the Resources available to you. Connect to one with `formal connect `. See the [CLI reference](/docs/guides/client-apps/cli).
## Upgrade
The Endpoint can't upgrade itself on Windows, and `formal upgrade` reports that it isn't supported. To move to a new version:
1. Uninstall **Formal** from the **Apps** section of Windows **Settings**.
2. Install the new MSI.
Uninstall the current version before you install a new one. Installing a new
MSI over an existing install registers a second copy of Formal, and
uninstalling either copy later leaves the other's files behind.
## Deploy to a Fleet
Deploy the MSI with your Windows device management tool, such as Microsoft Intune, as a standard Windows app. Use the silent install command above. The [MDM Endpoint Rollout](/docs/guides/integrations/mdm#endpoint-rollout) files in the Control Plane are for macOS.
## Files and Logs
| Item | Location |
| - | - |
| Configuration | `%USERPROFILE%\.formal\config.toml`. See [Configuration](/docs/guides/client-apps/configuration). |
| Logs | `%LOCALAPPDATA%\Formal\Logs` |
| Endpoint data | `%LOCALAPPDATA%\Formal` |
## Troubleshooting
**Cause:** The terminal started before the installer updated `PATH`. **Fix:** Open a new terminal. If the problem persists, sign out of Windows and back in.
**Cause:** The Endpoint agent isn't running. **Fix:** Start **Formal** from the Start menu, then retry the command.
**Cause:** Windows Hello isn't set up, or you're connected over Remote Desktop, where Windows Hello is unavailable. **Fix:** Set up Windows Hello on the device and sign in locally.
## Next Steps
Review authentication and connection options
Connect to Resources from the terminal
# Forms
Source: https://docs.formal.ai/docs/guides/configuration/forms
Create structured data collection forms for workflow triggers via Slack
## Overview
Forms enable structured data collection that can trigger [workflows](/docs/guides/configuration/workflows). A form defines a set of typed fields that users fill out via Slack. When submitted, the form data is available to workflow actions via CEL expressions.
Forms are useful for **Access requests**: Enable users to request access to perform specific actions on resources with time-bound parameters for policy suspensions.
## Form Structure
A form consists of:
* **Name**: A unique name within your organization
* **Description**: Optional description shown to users when filling out the form
* **Fields**: One or more typed fields for data collection
### Field Types
**Text and numeric inputs:**
* `string` — Plain text input
* `number` — Numeric input
* `email` — Email address input
* `url` — URL input
**Date and time inputs:**
* `date` — Date picker
* `time` — Time picker
* `timestamp` — Combined date and time picker
**Selection inputs** (require options configuration — see [below](#selection-field-options)):
* `select` — Single-select dropdown
* `multi_select` — Multi-select dropdown
* `checkbox` — Checkbox group
* `radio` — Radio button group
### Field IDs
Each field has an `id` that must match the pattern `field_[_a-zA-Z0-9]+`. This ID is used to reference field values in workflow actions via `trigger.form_submission.submission.field_`.
### Selection Field Options
The `select`, `multi_select`, `checkbox`, and `radio` field types all require an options configuration. All four types support **static options**. Only `select` and `multi_select` also support a **dynamic options source**. A field must have one or the other, not both.
#### Static Options
Provide a fixed list of options directly in the field configuration. Each option has a `label` (displayed to the user) and a `value` (submitted with the form).
#### Dynamic Options
Dynamic sources allow you to populate `select` and `multi_select` inputs by calling a `List` endpoint on Formal's API. This is useful when the set of options changes over time (e.g., listing resources, users, or datastores). `checkbox` and `radio` fields do not support dynamic sources and must use static options.
A dynamic source has the following configuration:
| Parameter | Description |
| - | - |
| **App** | The service name (e.g., `core.v1.ResourceService`) |
| **Command Name** | The method name without the `List` prefix (e.g., `Resources` for the `ListResources` method) |
| **Machine User ID** | The machine user used to authenticate options retrieval |
| **Input** | The JSON request body. Use `${{ query }}` to interpolate the user's search text into the request. You can also pass `searchFields` to control which fields are searched |
| **Transform** | A CEL expression evaluated against the response body (available via the `body` variable) when the response is a 200. Should return a list of objects with `value` and `text` fields |
The `searchFields` parameter in the input controls which fields the `search` term is matched against. For example, `"searchFields": ["name", "technology", "hostname"]` restricts matching to those three fields on the target resource.
## Filling Out Forms
### Slack Integration
Forms can be filled out via Slack using the "Fill out form" shortcut. A [Slack integration](/docs/guides/integrations/chats) needs to be linked to your Formal organization.
#### Slash Command
Type `/formal form` to open a modal where you can select and fill out a form.
#### Submission Flow
1. User opens the form shortcut in Slack
2. User selects a form from the searchable dropdown
3. User fills out the fields and submits
4. A confirmation message with the submitted values is sent to the user
5. Any workflows with a matching `form-submission` trigger are executed
### Formal Endpoint
Forms can also be filled out via the Formal Endpoint.
#### Submission Flow
1. User opens the Formal Endpoint
2. User runs `formal form fill` and selects a form from the shown list
3. User fills out the fields and submits
4. A confirmation message with the submitted values is shown to the user, along with a command to submit the same payload again without needing to re-select the form
5. Any workflows with a matching `form-submission` trigger are executed
You can also display forms and their fields using `formal form list -o [jsonl|yaml]` for programmatic use.
## Using Forms in Workflows
Forms connect to workflows via the `form-submission` trigger type. See the [workflows documentation](/docs/guides/configuration/workflows#form-submission) for trigger details.
Form data will not be saved in the Formal Control Plane unless there is at least one workflow with the corresponding `form-submission` trigger type.
### Example: Access Request with Approval
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
type: form-submission
name: access_request
args:
id: form_abc123
actions:
- type: ask-in-chat
name: ask_approval
args:
message: "${{ trigger.form_submission.submitter_email }} is requesting access to ${{ trigger.form_submission.submission.field_resource }}. Reason: ${{ trigger.form_submission.submission.field_reason }}. Approve?"
recipient_channel: "security-approvals"
integration: slack
```
### Referencing Form Data in CEL
Form submission data is available under `trigger.form_submission`:
| Expression | Description |
| - | - |
| `trigger.form_submission.form_id` | The ID of the submitted form |
| `trigger.form_submission.form_name` | The name of the submitted form |
| `trigger.form_submission.submitter_id` | Formal user ID of the submitter; only present when filled out via form fill |
| `trigger.form_submission.submitter_email` | Email of the user in Slack who submitted the form |
| `trigger.form_submission.submission.field_` | Value of a specific field by its ID |
## Terraform Example
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_form" "access_request" {
name = "access-request-form"
description = "Form for requesting database access"
field {
id = "field_reason"
name = "Reason"
type = "string"
}
field {
id = "field_resource"
name = "Resource"
type = "select"
config {
option {
label = "Production"
value = "production"
}
option {
label = "Staging"
value = "staging"
}
}
}
field {
id = "field_duration"
name = "Duration (hours)"
type = "number"
}
}
```
For a dynamic options source (e.g., fetching resources from Formal):
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_form" "dynamic_access_request" {
name = "dynamic-access-request"
description = "Access request with dynamic resource selection"
field {
id = "field_resource"
name = "Resource"
type = "select"
config {
options_source {
app = "core.v1.ResourceService"
machine_user_id = formal_user.machine.id
transform = "body.resources.map(r, {\"value\": r.id, \"text\": r.name})"
command {
name = "Resources"
}
input = {
search = "${{ query }}"
limit = "25"
searchFields = jsonencode(["name", "technology", "hostname"])
}
}
}
}
field {
id = "field_reason"
name = "Reason"
type = "string"
}
}
```
See the [formal\_form resource documentation](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/form) for the full schema reference.
## Next Steps
Learn about workflow triggers, actions, and chaining
Configure your Slack integration for forms
# Pulumi
Source: https://docs.formal.ai/docs/guides/configuration/pulumi
Manage Formal infrastructure as code with the Pulumi provider
> **Outcome:** After this guide, you can manage Formal resources using Pulumi infrastructure-as-code.\
> **Prerequisites:** Pulumi CLI, Formal API key, programming language environment (TypeScript, Python, Go, or C#).
## Overview
The [Formal Pulumi Provider](https://www.pulumi.com/registry/packages/formal/) enables you to manage Connectors, Resources, Policies, Users, and all other Formal objects as infrastructure-as-code using your preferred programming language.
## Installation
The Formal provider is available for multiple programming languages:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
npm install @formalco/pulumi
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
pip install pulumi-formal
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
go get github.com/formalco/pulumi-formal/sdk/go/formal
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
dotnet add package Formal.Pulumi
```
## Authentication
Create an API key in the [Control Plane](https://app.formal.ai/api-keys):
1. Navigate to [API Keys](https://app.formal.ai/api-keys)
2. Click **Create API Key**
3. Name the key (e.g., "pulumi-production")
4. Copy the key immediately
Set the key using Pulumi's configuration system:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
pulumi config set formal:apiKey --secret
```
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
pulumi config get formal:apiKey
# Expected:
```
Alternatively, set as an environment variable:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
export FORMAL_API_KEY=""
```
## Examples
### Full Production Stack
```typescript theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
import * as pulumi from "@pulumi/pulumi";
import * as formal from "@formalco/pulumi";
// Create a Space
const productionSpace = new formal.Space("production", {
name: "production"
});
// Create a Resource
const postgresResource = new formal.Resource("postgres", {
name: "production-postgres",
technology: "postgres",
hostname: "prod-db.us-east-1.rds.amazonaws.com",
port: 5432,
spaceId: productionSpace.id,
terminationProtection: true
});
// Create a Connector
const productionConnector = new formal.Connector("production", {
name: "production-connector",
spaceId: productionSpace.id
});
// Create a Connector Listener
const postgresListener = new formal.ConnectorListener("production_postgres", {
connectorId: productionConnector.id,
name: "production-postgres-listener",
port: 5432
});
// Add Listener Rule for Resource
const postgresRule = new formal.ConnectorListenerRule("postgres", {
connectorListenerId: postgresListener.id,
type: "resource",
rule: postgresResource.id
});
// Create Users
const aliceUser = new formal.User("alice", {
firstName: "Alice",
lastName: "Smith",
email: "alice@example.com",
type: "human"
});
const lookerApp = new formal.User("looker_app", {
name: "Looker Application",
type: "machine"
});
// Create Groups
const engineeringGroup = new formal.Group("engineering", {
name: "Engineering Team",
description: "Engineers with database access"
});
const aliceMembership = new formal.GroupUserLink("alice_eng", {
groupId: engineeringGroup.id,
userId: aliceUser.id
});
// Create a Policy
const maskPiiPolicy = new formal.Policy("mask_pii", {
name: "mask-pii-data",
description: "Mask PII fields for non-privileged users",
status: "active",
module: `package formal.v2
import future.keywords.if
import future.keywords.in
response := {
"action": "mask",
"type": "nullify",
"columns": pii_columns
} if {
not "pii_access" in input.user.groups
pii_columns := [col |
col := input.row[_]
col["data_label"] in ["email_address", "social_security_number", "phone_number"]
]
count(pii_columns) > 0
}`
});
// AWS Integration
const awsIntegration = new formal.IntegrationCloud("aws", {
name: "aws-production",
cloudRegion: "us-east-1",
aws: {
templateVersion: "latest",
enableRdsAutodiscovery: true,
allowS3Access: true,
s3BucketArn: "/*"
}
});
// Log Integration
const datadogIntegration = new formal.IntegrationLog("datadog", {
name: "datadog-logs",
datadog: {
accountId: "",
apiKey: "",
site: "datadoghq.com"
}
});
// Export outputs
export const connectorToken = productionConnector.apiKey;
export const connectorListenerPort = postgresListener.port;
export const resourceIds = {
postgres: postgresResource.id
};
```
```python theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
import pulumi
import pulumi_formal as formal
# Create a Space
production_space = formal.Space("production",
name="production"
)
# Create a Resource
postgres_resource = formal.Resource("postgres",
name="production-postgres",
technology="postgres",
hostname="prod-db.us-east-1.rds.amazonaws.com",
port=5432,
space_id=production_space.id,
termination_protection=True
)
# Create a Connector
production_connector = formal.Connector("production",
name="production-connector",
space_id=production_space.id
)
# Create a Connector Listener
postgres_listener = formal.ConnectorListener("production_postgres",
connector_id=production_connector.id,
name="production-postgres-listener",
port=5432
)
# Add Listener Rule for Resource
postgres_rule = formal.ConnectorListenerRule("postgres",
connector_listener_id=postgres_listener.id,
type="resource",
rule=postgres_resource.id
)
# Create Users
alice_user = formal.User("alice",
first_name="Alice",
last_name="Smith",
email="alice@example.com",
type="human"
)
looker_app = formal.User("looker_app",
name="Looker Application",
type="machine"
)
# Create Groups
engineering_group = formal.Group("engineering",
name="Engineering Team",
description="Engineers with database access"
)
alice_membership = formal.GroupUserLink("alice_eng",
group_id=engineering_group.id,
user_id=alice_user.id
)
# Create a Policy
mask_pii_policy = formal.Policy("mask_pii",
name="mask-pii-data",
description="Mask PII fields for non-privileged users",
status="active",
module="""package formal.v2
import future.keywords.if
import future.keywords.in
response := {
"action": "mask",
"type": "nullify",
"columns": pii_columns
} if {
not "pii_access" in input.user.groups
pii_columns := [col |
col := input.row[_]
col["data_label"] in ["email_address", "social_security_number", "phone_number"]
]
count(pii_columns) > 0
}"""
)
# AWS Integration
aws_integration = formal.IntegrationCloud("aws",
name="aws-production",
cloud_region="us-east-1",
aws=formal.IntegrationCloudAwsArgs(
template_version="latest",
enable_rds_autodiscovery=True,
allow_s3_access=True,
s3_bucket_arn="/*"
)
)
# Log Integration
datadog_integration = formal.IntegrationLog("datadog",
name="datadog-logs",
datadog=formal.IntegrationLogDatadogArgs(
account_id="",
api_key="",
site="datadoghq.com"
)
)
# Export outputs
pulumi.export("connector_token", production_connector.api_key)
pulumi.export("connector_listener_port", postgres_listener.port)
pulumi.export("resource_ids", {
"postgres": postgres_resource.id
})
```
```go theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package main
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
formal "github.com/formalco/pulumi-formal/sdk/go/formal"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create a Space
productionSpace, err := formal.NewSpace(ctx, "production", &formal.SpaceArgs{
Name: pulumi.String("production"),
})
if err != nil {
return err
}
// Create a Resource
postgresResource, err := formal.NewResource(ctx, "postgres", &formal.ResourceArgs{
Name: pulumi.String("production-postgres"),
Technology: pulumi.String("postgres"),
Hostname: pulumi.String("prod-db.us-east-1.rds.amazonaws.com"),
Port: pulumi.Int(5432),
SpaceId: productionSpace.ID(),
TerminationProtection: pulumi.Bool(true),
})
if err != nil {
return err
}
// Create a Connector
productionConnector, err := formal.NewConnector(ctx, "production", &formal.ConnectorArgs{
Name: pulumi.String("production-connector"),
SpaceId: productionSpace.ID(),
})
if err != nil {
return err
}
// Create a Connector Listener
postgresListener, err := formal.NewConnectorListener(ctx, "production_postgres", &formal.ConnectorListenerArgs{
ConnectorId: productionConnector.ID(),
Name: pulumi.String("production-postgres-listener"),
Port: pulumi.Int(5432),
})
if err != nil {
return err
}
// Add Listener Rule for Resource
_, err = formal.NewConnectorListenerRule(ctx, "postgres", &formal.ConnectorListenerRuleArgs{
ConnectorListenerId: postgresListener.ID(),
Type: pulumi.String("resource"),
Rule: postgresResource.ID(),
})
if err != nil {
return err
}
// Create Users
aliceUser, err := formal.NewUser(ctx, "alice", &formal.UserArgs{
FirstName: pulumi.String("Alice"),
LastName: pulumi.String("Smith"),
Email: pulumi.String("alice@example.com"),
Type: pulumi.String("human"),
})
if err != nil {
return err
}
_, err = formal.NewUser(ctx, "looker_app", &formal.UserArgs{
Name: pulumi.String("Looker Application"),
Type: pulumi.String("machine"),
})
if err != nil {
return err
}
// Create Groups
engineeringGroup, err := formal.NewGroup(ctx, "engineering", &formal.GroupArgs{
Name: pulumi.String("Engineering Team"),
Description: pulumi.String("Engineers with database access"),
})
if err != nil {
return err
}
_, err = formal.NewGroupUserLink(ctx, "alice_eng", &formal.GroupUserLinkArgs{
GroupId: engineeringGroup.ID(),
UserId: aliceUser.ID(),
})
if err != nil {
return err
}
// Create a Policy
_, err = formal.NewPolicy(ctx, "mask_pii", &formal.PolicyArgs{
Name: pulumi.String("mask-pii-data"),
Description: pulumi.String("Mask PII fields for non-privileged users"),
Status: pulumi.String("active"),
Module: pulumi.String(`package formal.v2
import future.keywords.if
import future.keywords.in
response := {
"action": "mask",
"type": "nullify",
"columns": pii_columns
} if {
not "pii_access" in input.user.groups
pii_columns := [col |
col := input.row[_]
col["data_label"] in ["email_address", "social_security_number", "phone_number"]
]
count(pii_columns) > 0
}`),
})
if err != nil {
return err
}
// AWS Integration
_, err = formal.NewIntegrationCloud(ctx, "aws", &formal.IntegrationCloudArgs{
Name: pulumi.String("aws-production"),
CloudRegion: pulumi.String("us-east-1"),
Aws: &formal.IntegrationCloudAwsArgs{
TemplateVersion: pulumi.String("latest"),
EnableRdsAutodiscovery: pulumi.Bool(true),
AllowS3Access: pulumi.Bool(true),
S3BucketArn: pulumi.String("/*"),
},
})
if err != nil {
return err
}
// Log Integration
_, err = formal.NewIntegrationLog(ctx, "datadog", &formal.IntegrationLogArgs{
Name: pulumi.String("datadog-logs"),
Datadog: &formal.IntegrationLogDatadogArgs{
AccountId: pulumi.String(""),
ApiKey: pulumi.String(""),
Site: pulumi.String("datadoghq.com"),
},
})
if err != nil {
return err
}
// Export outputs
ctx.Export("connectorToken", productionConnector.ApiKey)
ctx.Export("connectorListenerPort", postgresListener.Port)
ctx.Export("resourceIds", pulumi.StringMap{
"postgres": postgresResource.ID(),
})
return nil
})
}
```
```csharp theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
using System.Collections.Generic;
using Pulumi;
using Formal.Pulumi;
using Formal.Pulumi.Inputs;
return await Deployment.RunAsync(() =>
{
// Create a Space
var productionSpace = new Space("production", new SpaceArgs
{
Name = "production"
});
// Create a Resource (fully qualified because Pulumi.Resource also exists)
var postgresResource = new Formal.Pulumi.Resource("postgres", new Formal.Pulumi.ResourceArgs
{
Name = "production-postgres",
Technology = "postgres",
Hostname = "prod-db.us-east-1.rds.amazonaws.com",
Port = 5432,
SpaceId = productionSpace.Id,
TerminationProtection = true
});
// Create a Connector
var productionConnector = new Connector("production", new ConnectorArgs
{
Name = "production-connector",
SpaceId = productionSpace.Id
});
// Create a Connector Listener
var postgresListener = new ConnectorListener("production_postgres", new ConnectorListenerArgs
{
ConnectorId = productionConnector.Id,
Name = "production-postgres-listener",
Port = 5432
});
// Add Listener Rule for Resource
var postgresRule = new ConnectorListenerRule("postgres", new ConnectorListenerRuleArgs
{
ConnectorListenerId = postgresListener.Id,
Type = "resource",
Rule = postgresResource.Id
});
// Create Users
var aliceUser = new User("alice", new UserArgs
{
FirstName = "Alice",
LastName = "Smith",
Email = "alice@example.com",
Type = "human"
});
var lookerApp = new User("looker_app", new UserArgs
{
Name = "Looker Application",
Type = "machine"
});
// Create Groups
var engineeringGroup = new Group("engineering", new GroupArgs
{
Name = "Engineering Team",
Description = "Engineers with database access"
});
var aliceMembership = new GroupUserLink("alice_eng", new GroupUserLinkArgs
{
GroupId = engineeringGroup.Id,
UserId = aliceUser.Id
});
// Create a Policy
var maskPiiPolicy = new Policy("mask_pii", new PolicyArgs
{
Name = "mask-pii-data",
Description = "Mask PII fields for non-privileged users",
Status = "active",
Module = @"package formal.v2
import future.keywords.if
import future.keywords.in
response := {
""action"": ""mask"",
""type"": ""nullify"",
""columns"": pii_columns
} if {
not ""pii_access"" in input.user.groups
pii_columns := [col |
col := input.row[_]
col[""data_label""] in [""email_address"", ""social_security_number"", ""phone_number""]
]
count(pii_columns) > 0
}"
});
// AWS Integration
var awsIntegration = new IntegrationCloud("aws", new IntegrationCloudArgs
{
Name = "aws-production",
CloudRegion = "us-east-1",
Aws = new IntegrationCloudAwsArgs
{
TemplateVersion = "latest",
EnableRdsAutodiscovery = true,
AllowS3Access = true,
S3BucketArn = "/*"
}
});
// Log Integration
var datadogIntegration = new IntegrationLog("datadog", new IntegrationLogArgs
{
Name = "datadog-logs",
Datadog = new IntegrationLogDatadogArgs
{
AccountId = "",
ApiKey = "",
Site = "datadoghq.com"
}
});
// Export outputs
return new Dictionary
{
["connectorToken"] = productionConnector.ApiKey,
["connectorListenerPort"] = postgresListener.Port,
["resourceIds"] = new Dictionary
{
["postgres"] = postgresResource.Id
}
};
});
```
## Resource Documentation
Full documentation for all resources:
**Core Objects:**
* [Connector](https://www.pulumi.com/registry/packages/formal/api-docs/connector/)
* [Resource](https://www.pulumi.com/registry/packages/formal/api-docs/resource/)
* [User](https://www.pulumi.com/registry/packages/formal/api-docs/user/)
* [Group](https://www.pulumi.com/registry/packages/formal/api-docs/group/)
* [Policy](https://www.pulumi.com/registry/packages/formal/api-docs/policy/)
* [Space](https://www.pulumi.com/registry/packages/formal/api-docs/space/)
* [Satellite](https://www.pulumi.com/registry/packages/formal/api-docs/satellite/)
**Integrations:**
* [IntegrationCloud](https://www.pulumi.com/registry/packages/formal/api-docs/integrationcloud/)
* [IntegrationLog](https://www.pulumi.com/registry/packages/formal/api-docs/integrationlog/)
* [IntegrationBi](https://www.pulumi.com/registry/packages/formal/api-docs/integrationbi/)
## Example Repositories
Clone and customize for your needs:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
git clone https://github.com/formalco/pulumi-formal.git
```
## Best Practices
Pin provider versions to avoid unexpected changes:
**TypeScript:**
```json theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"dependencies": {
"@formalco/pulumi": "^1.2.7"
}
}
```
**Python:**
```txt theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
pulumi-formal>=1.2.7,<2.0.0
```
Use Pulumi Cloud or self-hosted backends for team collaboration:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
pulumi login https://api.pulumi.com
pulumi stack init production
```
Parameterize configurations for reusability:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
pulumi config set environment production
pulumi config set formal:apiKey --secret
```
Access in code:
```typescript theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
const config = new pulumi.Config();
const environment = config.require("environment");
```
Use separate stacks for prod/staging/dev:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
pulumi stack init production
pulumi stack init staging
pulumi stack init development
```
Protect production resources:
```typescript theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
const resource = new formal.Resource("prod_db", {
name: "production-postgres",
terminationProtection: true,
// ...
});
```
## Importing Existing Resources
Bring existing Formal objects under Pulumi management:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Import a Resource
pulumi import formal:index/resource:Resource postgres
# Import a Connector
pulumi import formal:index/connector:Connector main
# Import a User
pulumi import formal:index/user:User alice
```
Get object IDs from the Control Plane or API.
## Outputs
Export useful information:
```typescript theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
export const connectorToken = productionConnector.apiKey;
export const connectorListenerPort = postgresListener.port;
export const resourceIds = {
postgres: postgresResource.id
};
```
```python theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
pulumi.export("connector_token", production_connector.api_key)
pulumi.export("connector_listener_port", postgres_listener.port)
pulumi.export("resource_ids", {
"postgres": postgres_resource.id
})
```
```go theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
ctx.Export("connectorToken", productionConnector.ApiKey)
ctx.Export("connectorListenerPort", postgresListener.Port)
ctx.Export("resourceIds", pulumi.StringMap{
"postgres": postgresResource.ID(),
})
```
```csharp theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
return new Dictionary
{
["connectorToken"] = productionConnector.ApiKey,
["connectorListenerPort"] = postgresListener.Port,
["resourceIds"] = new Dictionary
{
["postgres"] = postgresResource.Id
}
};
```
## Troubleshooting
**Possible causes:**
* Invalid API key
* API key not set correctly
**Solution:**
1. Verify API key in [Control Plane](https://app.formal.ai/api-keys)
2. Check configuration: `pulumi config get formal:apiKey`
3. Ensure no whitespace in key
**Possible causes:**
* Package not installed
* Wrong package name
**Solution:**
1. Install correct package for your language
2. Verify import statements
3. Check package.json/requirements.txt
**Possible causes:**
* Invalid resource configuration
* Missing required fields
**Solution:**
1. Check [API documentation](https://www.pulumi.com/registry/packages/formal/)
2. Verify all required fields are set
3. Check resource naming conventions
## Next Steps
Complete Pulumi provider documentation
Deployment examples on GitHub
Manage state and collaborate
Alternative IaC solution
# SDKs
Source: https://docs.formal.ai/docs/guides/configuration/sdks
Programmatic access to Formal with the Go SDK
## Overview
Formal provides SDKs for programmatic access to the platform. Use SDKs to build custom tools, automation, and integrations.
For most infrastructure management tasks, use the [Terraform
provider](/docs/guides/configuration/terraform) instead of SDKs.
## Official SDKs
The official Formal SDK for Go is available:
* **Go**: [github.com/formalco/go-sdk](https://github.com/formalco/go-sdk)
### Features
* Create, read, update, delete Formal objects
* Query logs and sessions programmatically
* Manage policies dynamically
* Integrate Formal into CI/CD pipelines
* Build custom dashboards and reports
### Go Example
Install the SDK:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
go get github.com/formalco/go-sdk/v3@latest
```
List your Resources:
```go theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package main
import (
"context"
"fmt"
"log"
"os"
formal "github.com/formalco/go-sdk/v3"
corev1 "github.com/formalco/go-sdk/v3/core/v1"
)
func main() {
client, err := formal.New(formal.WithAPIKey(os.Getenv("FORMAL_API_KEY")))
if err != nil {
log.Fatal(err)
}
res, err := client.ResourceServiceClient.ListResources(context.Background(), &corev1.ListResourcesRequest{Limit: 50})
if err != nil {
log.Fatal(err)
}
for _, r := range res.Resources {
fmt.Println(r.Id, r.Name, r.Technology)
}
}
```
The client calls `https://api.formal.ai` by default.
## HTTP API
You can also call the API directly. It uses the
[Connect protocol](https://connectrpc.com/docs/protocol/), not REST. Every
method is a `POST` to `https://api.formal.ai/./` with a
JSON body.
### Authentication
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
export FORMAL_API_KEY=""
```
### Example Requests
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
curl -X POST "https://api.formal.ai/core.v1.ResourceService/ListResources" \
-H "X-API-KEY: $FORMAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit": 50}'
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
curl -X POST "https://api.formal.ai/core.v1.UserService/CreateUser" \
-H "X-API-KEY: $FORMAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "human",
"human": {
"first_name": "Alice",
"last_name": "Smith",
"email": "alice@example.com"
}
}'
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Logs for alice@example.com over the last 24 hours (Unix seconds)
curl -X POST "https://api.formal.ai/core.v1.LogsService/ListLogsV2" \
-H "X-API-KEY: $FORMAL_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"query\": \"user.formal.user.email:alice@example.com\",
\"start_timestamp\": \"$(( $(date +%s) - 86400 ))\",
\"end_timestamp\": \"$(date +%s)\",
\"limit\": 50
}"
```
The API requires HTTP/2. `curl` negotiates it automatically over HTTPS.
HTTP/1.1-only clients receive a `464` error.
## API Documentation
See the [API Reference](/docs/api/introduction) for complete endpoint documentation.
# Terraform
Source: https://docs.formal.ai/docs/guides/configuration/terraform
Manage Formal infrastructure as code with the Terraform provider
## Overview
The [Formal Terraform Provider](https://registry.terraform.io/providers/formalco/formal/latest) enables you to manage Connectors, Resources, Policies, Users, and all other Formal objects as infrastructure-as-code.
## Installation
Add the provider to your Terraform configuration:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
terraform {
required_providers {
formal = {
source = "formalco/formal"
version = "~> 4.32"
}
}
}
provider "formal" {
api_key = var.formal_api_key
}
```
## Authentication
Create an API key in the [Control Plane](https://app.formal.ai/api-keys):
1. Navigate to [API Keys](https://app.formal.ai/api-keys)
2. Click **Create API Key**
3. Name the key (e.g., "terraform-production")
4. Copy the key immediately
Set the key as an environment variable or Terraform variable:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
export FORMAL_API_KEY="your-api-key-here"
```
Or in Terraform:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
variable "formal_api_key" {
description = "Formal API key"
type = string
sensitive = true
}
```
## Examples
### Full Production Stack
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
terraform {
required_providers {
formal = {
source = "formalco/formal"
version = "~> 4.32"
}
aws = {
source = "hashicorp/aws"
version = "~> 6.17.0"
}
}
}
# Variables
variable "name" {
description = "Base name for resources"
type = string
default = "formal-production"
}
variable "datadog_application_key" {
description = "Datadog Application Key"
type = string
default = "datadog-account-id"
sensitive = true
}
variable "datadog_api_key" {
description = "Datadog API key"
type = string
sensitive = true
default = "datadog-api-key"
}
# Configure the Formal Provider
provider "formal" {
api_key = ""
retrieve_sensitive_values = false
}
# Configure AWS Provider
provider "aws" {
region = "us-east-1"
}
# Create a Space
resource "formal_space" "production" {
name = "production"
}
# Create a Resource
resource "formal_resource" "postgres" {
name = "production-postgres"
technology = "postgres"
hostname = "prod-db.us-east-1.rds.amazonaws.com"
port = 5432
space_id = formal_space.production.id
termination_protection = false
}
# Create a Connector
resource "formal_connector" "production" {
name = "production-connector"
space_id = formal_space.production.id
}
# Configure Connector Settings
resource "formal_connector_configuration" "production" {
connector_id = formal_connector.production.id
log_level = "info"
# metrics collector endpoint. If there is no collector at the endpoint specified, the
# connector will not send metrics
otel_endpoint_hostname = "localhost"
otel_endpoint_port = 4317
}
# Create Connector Listener
resource "formal_connector_listener" "production_postgres" {
connector_id = formal_connector.production.id
name = "production-postgres-listener"
port = 5432
}
# Add Listener Rule for Resource
resource "formal_connector_listener_rule" "postgres" {
connector_listener_id = formal_connector_listener.production_postgres.id
type = "resource"
rule = formal_resource.postgres.id
}
# Create Users
resource "formal_user" "alice" {
first_name = "Alice"
last_name = "Smith"
email = "alice@example.com"
type = "human"
}
resource "formal_user" "looker_app" {
name = "Looker Application"
type = "machine"
}
# Create Groups
resource "formal_group" "engineering" {
name = "Engineering Team"
description = "Engineers with database access"
}
resource "formal_group_user_link" "alice_eng" {
group_id = formal_group.engineering.id
user_id = formal_user.alice.id
}
# Create a Policy
resource "formal_policy" "mask_pii" {
name = "mask-pii-data"
description = "Mask PII fields for non-privileged users"
status = "active"
module = <<-EOT
package formal.v2
import future.keywords.if
import future.keywords.in
response := {
"action": "mask",
"type": "nullify",
"columns": pii_columns
} if {
not "pii_access" in input.user.groups
pii_columns := [col |
col := input.row[_]
col["data_label"] in ["email_address", "social_security_number", "phone_number"]
]
count(pii_columns) > 0
}
EOT
}
# AWS S3 Bucket for Logs
resource "aws_s3_bucket" "logs" {
bucket = "${var.name}-logs"
force_destroy = true
}
# AWS Integration
resource "formal_integration_cloud" "aws" {
name = "aws-production"
cloud_region = "us-east-1"
aws {
template_version = "latest"
enable_rds_autodiscovery = true
enable_eks_autodiscovery = true
allow_s3_access = true
s3_bucket_arn = "${aws_s3_bucket.logs.arn}/*"
}
}
# Log Integration
resource "formal_integration_log" "datadog" {
name = "datadog-logs"
datadog {
account_id = var.datadog_application_key
api_key = var.datadog_api_key
site = "datadoghq.com"
}
}
```
## Resource Documentation
Full documentation for all resources:
**Core Resources:**
* [formal\_connector](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/connector)
* [formal\_resource](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/resource)
* [formal\_user](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/user)
* [formal\_group](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/group)
* [formal\_policy](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/policy)
* [formal\_hook](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/hook)
* [formal\_space](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/space)
* [formal\_satellite](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/satellite)
* [formal\_workflow](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/workflow)
* [formal\_form](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/form)
**Integrations:**
* [formal\_integration\_cloud](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/integration_cloud)
* [formal\_integration\_log](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/integration_log)
* [formal\_integration\_bi](https://registry.terraform.io/providers/formalco/formal/latest/docs/resources/integration_bi)
**Data Sources:**
* [formal\_connector](https://registry.terraform.io/providers/formalco/formal/latest/docs/data-sources/connector) - Look up a connector by ID or name
* [formal\_group](https://registry.terraform.io/providers/formalco/formal/latest/docs/data-sources/group) - Look up a group by ID or name
* [formal\_resource](https://registry.terraform.io/providers/formalco/formal/latest/docs/data-sources/resource) - Look up a resource by ID or name
* [formal\_space](https://registry.terraform.io/providers/formalco/formal/latest/docs/data-sources/space) - Look up a space by ID or name
## Example Repositories
Formal provides complete Terraform examples:
* [AWS Connector Deployment](https://github.com/formalco/terraform-provider-formal/tree/main/examples/deployments/aws)
* [Policy Example](https://github.com/formalco/terraform-provider-formal/tree/main/examples/policies)
Clone and customize for your needs:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
git clone https://github.com/formalco/terraform-provider-formal.git
cd terraform-provider-formal/examples/deployments/aws/connector
```
## Best Practices
Pin provider versions to avoid unexpected changes:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal = {
source = "formalco/formal"
version = "~> 4.32.0" # Allow patches, not minor versions
}
```
Use remote state backends (S3, Terraform Cloud) for team collaboration:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "formal/production/terraform.tfstate"
region = "us-east-1"
}
}
```
Parameterize configurations for reusability:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
variable "environment" {
type = string
}
resource "formal_connector" "main" {
name = "${var.environment}-connector"
}
```
Use workspaces or separate state files for prod/staging/dev:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
terraform workspace new production
terraform workspace select production
```
Protect production resources:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_resource" "prod_db" {
name = "production-postgres"
termination_protection = true
# ...
}
```
## Importing Existing Resources
Bring existing Formal objects under Terraform management:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Import a Resource
terraform import formal_resource.postgres
# Import a Connector
terraform import formal_connector.main
# Import a User
terraform import formal_user.alice
```
Get object IDs from the Control Plane or API.
## Outputs
Export useful information:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
output "connector_token" {
description = "Connector API token for deployment"
value = formal_connector.production.api_key
sensitive = true
}
output "connector_listener_port" {
description = "Configured listener port"
value = formal_connector_listener.production_postgres.port
}
output "resource_ids" {
description = "Map of resource names to IDs"
value = {
(formal_space.production.name) = formal_space.production.id
(formal_resource.postgres.name) = formal_resource.postgres.id
(formal_connector.production.name) = formal_connector.production.id
(formal_connector_listener.production_postgres.name) = formal_connector_listener.production_postgres.id
(formal_group.engineering.name) = formal_group.engineering.id
}
}
```
## Next Steps
Complete Terraform provider documentation
Deployment examples on GitHub
# Workflows
Source: https://docs.formal.ai/docs/guides/configuration/workflows
Automate actions based on triggers using Formal workflows
## Overview
Workflows enable automation of actions based on triggers. A workflow consists of:
* **Trigger**: What starts the workflow (e.g., an API request, a new log event, or a new autodiscovered resource)
* **Actions**: What the workflow does when triggered (e.g., send a Slack message, call the Formal API)
Workflows are defined in YAML and can be managed via Terraform or the Formal API.
## Triggers vs Actions
| Component | Purpose | When it runs |
| - | - | - |
| **Trigger** | Defines what starts the workflow | Once, when the triggering event occurs |
| **Actions** | Defines what the workflow does | After the trigger fires, in sequence based on `depends_on` |
A workflow has exactly **one trigger** and **one or more actions**. Actions can be chained together using `depends_on` and conditionally executed using `if`.
## Workflow Structure
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "my-trigger" # Unique name for this trigger
type: "api-request" # Trigger type
args: # Type-specific arguments
allow: "user.email == 'admin@example.com'"
actions:
- name: "first-action" # Unique name for this action
type: "send-slack-message" # Action type
args: # Type-specific arguments
text: "Workflow triggered!"
recipient_email: "ops@example.com"
- name: "second-action"
type: "formal-app-command"
depends_on: # Wait for these actions to complete
- "first-action"
if: "actions.first_action.message_sent == true" # Conditional execution
args:
# ...
```
## `depends_on` - Action Chaining
The `depends_on` field controls the execution order of actions. Actions with empty `depends_on` run immediately after the trigger. Subsequent actions run after their dependencies complete.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
actions:
# Runs immediately after trigger
- name: "step-1"
type: "send-slack-message"
args:
text: "Starting workflow..."
recipient_email: "ops@example.com"
# Runs after step-1 completes
- name: "step-2"
type: "formal-app-command"
depends_on:
- "step-1"
args:
# ...
# Runs after step-2 completes
- name: "step-3"
type: "send-slack-message"
depends_on:
- "step-2"
args:
text: "Workflow complete!"
recipient_email: "ops@example.com"
```
## Referencing Previous Trigger and Action Outputs
Actions can reference previous triggers and actions outputs via the following syntax in [CEL expressions](https://cel.dev/):
* `trigger.`: reference any output field of the trigger
* `actions..`: reference any output field of an action by its name
Some arguments, such as `if` or the `api-request` `allow` argument, take CEL expressions by default. For arguments that take strings
instead, embed CEL expressions via the `${{}}` syntax.
## `if` - Conditional Execution
The `if` field is a CEL expression that determines whether a subsequent action should execute based on the previous actions or trigger.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
actions:
- name: "notify-on-success"
type: "send-slack-message"
depends_on:
- "create-resource"
if: "actions.create_resource.status_code == 200"
args:
text: "Resource created successfully!"
recipient_email: "admin@example.com"
- name: "notify-on-failure"
type: "send-slack-message"
depends_on:
- "create-resource"
if: "actions.create_resource.status_code != 200"
args:
text: "Failed to create resource: ${{ actions.create_resource.body.message }}"
recipient_email: "admin@example.com"
```
## Trigger Types
### `api-request`
Triggered when a user calls the `CreateWorkflowTrigger` API endpoint. Useful for manually triggered workflows or integrations.
**Args:**
| Arg | Type | Required | Description |
| - | - | - | - |
| `allow` | string | No | CEL expression to authorize the request. If omitted, all authenticated users can trigger. |
**Outputs (available in actions):**
| Output | Type | Description |
| - | - | - |
| `user` | object | The user who triggered the workflow |
| `payload` | object | Custom payload passed in the API request |
When the caller authenticates with an [OIDC integration](/docs/guides/integrations/oidc), `user.oidc` is also available:
| Output | Type | Description |
| - | - | - |
| `user.oidc.integration_id` | string | OIDC integration ID |
| `user.oidc.issuer` | string | Token issuer |
| `user.oidc.claims.*` | any | Verified IdP JWT claims (shape varies by IdP) |
| `user.oidc.expires_at` | string | Upstream JWT expiry |
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "manual-trigger"
type: "api-request"
args:
allow: "user.groups.exists(g, g == 'groupname')"
```
OIDC callers can reference verified claims directly instead of re-embedding them in `payload`:
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "background_agent_access_request"
type: "api-request"
args:
allow: "has(user.oidc)"
actions:
- name: "ask_end_user"
type: "ask-in-chat"
args:
integration: "slack"
recipient_email: "${{ trigger.user.oidc.claims.owner_email }}"
message: "Approve access for `${{ trigger.user.oidc.claims.owner_email }}`?"
```
**Triggering via API:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
curl -X POST -H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"workflow_id": "workflow_abc123", "payload": {"key": "value"}}' \
https://api.formal.ai/core.v1.WorkflowService/CreateWorkflowTrigger
```
***
### `new-log`
Triggered when a new log event matches a specified condition. Useful for reacting to specific database queries, policy violations, or access patterns.
**Args:**
| Arg | Type | Required | Description |
| - | - | - | - |
| `if` | string | Yes | CEL expression to filter logs. Only logs matching this condition trigger the workflow. |
| `unique_by` | object | No | Only trigger for logs whose key values are new. See [Unique logs](#unique-logs). |
**Outputs (available in actions):**
| Output | Type | Description |
| - | - | - |
| `log` | object | The full log event that matched the condition |
| `unique` | object | With `unique_by`, the key values that triggered this run, by key name |
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "blocked-query-alert"
type: "new-log"
args:
if: "log.event_type == 'session-login-failed' && log.triggered_policies.exists(p, p.status == 'active' && p.type == 'block')"
```
#### Unique logs
`unique_by` triggers the workflow only when a matching log's key values are new, for example the first time a user loads a skill, or at most once an hour per user and resource.
| Field | Type | Required | Description |
| - | - | - | - |
| `keys` | map | Yes | Up to five key names (lowercase letters, digits, and underscores) mapped to CEL expressions over `log`. An expression that returns a list triggers one run per new element. Without `keys`, the other fields are ignored and every matching log triggers. |
| `reset_on` | string | No | `trigger` (default) triggers again once `window` has passed since the key last triggered, however often it appears in between. `sighting` triggers again only after the key has gone unseen for the whole `window`. |
| `window` | string | No | A duration such as `1h` or `30d`, from `1m` to `3650d`. Omit it to trigger once per key, ever. |
A key that is missing from a log, null, or an empty list counts as null, so logs missing the same key share it. To ignore such logs instead, check for the field in `if`, as the example below does. To give a missing key its own value, default it in the key expression, for example `has(log.user.username) ? log.user.username : "unknown"`.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "first-skill-use"
type: "new-log"
args:
if: "has(log.llm) && has(log.llm.loaded_skills) && size(log.llm.loaded_skills) > 0"
unique_by:
keys:
user: log.user.username
skill: log.llm.loaded_skills.map(s, s.name)
reset_on: sighting
actions:
- name: "notify"
type: "send-slack-message"
args:
text: "${{ trigger.unique.user }} used the ${{ trigger.unique.skill }} skill for the first time."
recipient_channel: "ai-governance"
```
Keys are tracked from the moment the workflow is saved; logs from before then are not considered. Changing `keys` forgets every key seen so far, while changing `reset_on` or `window` keeps them. Encrypted key values differ on every log, so they are grouped as ``; key on unencrypted fields. A log whose keys expand to more than 100 combinations triggers one run without deduplication. Each workflow keeps at most one million keys, forgetting the least recently seen first.
***
### `new-autodiscovered-resource`
Triggered when a new resource is autodiscovered via cloud integration (AWS, GCP, etc.). Useful for automatically onboarding new databases.
**Args:** None
**Outputs (available in actions):**
| Output | Type | Description |
| - | - | - |
| `resource` | object | The autodiscovered resource object |
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "new-resource-discovered"
type: "new-autodiscovered-resource"
```
***
### `updated-autodiscovered-resource`
Triggered when an existing autodiscovered resource is updated.
**Args:** None
**Outputs (available in actions):**
| Output | Type | Description |
| - | - | - |
| `resource` | object | The updated autodiscovered resource object |
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "resource-updated"
type: "updated-autodiscovered-resource"
```
***
### `deleted-autodiscovered-resource`
Triggered when an autodiscovered resource is deleted because it no longer exists in the cloud provider.
**Args:** None
**Outputs (available in actions):**
| Output | Type | Description |
| - | - | - |
| `resource` | object | The deleted autodiscovered resource object |
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "resource-removed"
type: "deleted-autodiscovered-resource"
```
***
### `cron-schedule`
Triggered on a recurring schedule defined by a standard 5-field cron expression (`minute hour day-of-month month day-of-week`). All times are evaluated in **UTC**. Useful for periodic checks, scheduled reports, and recurring maintenance tasks.
**Args:**
| Arg | Type | Required | Description |
| - | - | - | - |
| `schedule` | string | Yes | 5-field cron expression (e.g., `"0 9 * * 1-5"` for weekdays at 9am UTC) |
| `recovery` | string | No | Controls behavior when scheduled triggers are missed during a Formal outage. One of `none` (default), `latest`, or `all`. See below. |
**Outputs (available in actions):**
| Output | Type | Description |
| - | - | - |
| `trigger.scheduled_time` | string | The time (UTC, RFC 3339) the cron was scheduled to run, always truncated to the minute. |
| `trigger.execution_time` | string | The actual time (UTC, RFC 3339) the trigger fired. Matches `scheduled_time` under normal operation. |
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "workday-check"
type: "cron-schedule"
args:
schedule: "0 8,17 * * 1-5" # 8am and 5pm UTC on weekdays
actions:
- name: "notify"
type: "send-slack-message"
args:
text: "Workday check ran at ${{ trigger.execution_time }} (scheduled for ${{ trigger.scheduled_time }})"
recipient_channel: "ops"
```
**Recovery policy (optional):** Under normal operation, cron triggers fire reliably every minute and `recovery` has no effect. It only matters in the unlikely event of a Formal platform outage where the workflow engine is temporarily unable to process scheduled triggers. When the platform recovers, any missed ticks are handled according to the `recovery` policy. If omitted, defaults to `none`. Missed triggers are capped at a 24-hour lookback window.
| Policy | Behavior |
| - | - |
| `none` | Skips all missed triggers and resumes from the next future tick on the schedule. |
| `latest` | Fires a single trigger for the most recent missed tick, then resumes normally. The `scheduled_time` output reflects the missed tick, while `execution_time` reflects the actual recovery time. |
| `all` | Fires every missed tick (up to 24 hours) in chronological order, each with its own `scheduled_time`, then resumes normally. |
Cron schedules are always evaluated in UTC. To schedule at a local time, convert manually (e.g., 9am US Eastern = `"0 14 * * *"` during EDT or `"0 13 * * *"` during EST).
***
### `form-submission`
Triggered when a user submits a [Formal form](/docs/guides/configuration/forms) via Slack. Useful for approval workflows, access requests, and other structured data collection scenarios.
**Args:**
| Arg | Type | Required | Description |
| - | - | - | - |
| `id` | string | Yes | The ID of the form to listen for submissions on |
**Outputs (available in actions via `trigger.form_submission`):**
| Output | Type | Description |
| - | - | - |
| `form_id` | string | The ID of the submitted form |
| `form_name` | string | The name of the submitted form |
| `submitter_id` | string | Formal user ID of the submitter; empty if a Slack submitter has no matching Formal user |
| `submitter_email` | string | Email of the user who submitted the form |
| `submission` | object | Map of field IDs to submitted values |
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
trigger:
name: "access-request"
type: "form-submission"
args:
id: "form_abc123"
```
**Referencing form values in actions:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
actions:
- name: "notify-approver"
type: "send-slack-message"
args:
text: "${{ trigger.form_submission.submitter_email }} submitted a request: ${{ trigger.form_submission.submission.field_reason }}"
recipient_channel: "approvals"
```
See the [Forms documentation](/docs/guides/configuration/forms) for details on creating forms and the full Slack integration.
## Action Types
### `send-slack-message`
Sends a direct message to a Slack user or channel. Requires a Slack integration to be configured.
**Args:**
| Arg | Type | Required | Description |
| - | - | - | - |
| `text` | string | Yes | Message text to send (supports Slack markdown) |
| `recipient_email` | string | One of | Email address of the Slack user (mutually exclusive with `recipient_channel`) |
| `recipient_channel` | string | One of | Slack channel name without `#`, or channel ID such as `C0123456789` (mutually exclusive with `recipient_email`) |
**Outputs:**
| Output | Type | Description |
| - | - | - |
| `recipient_email` | string | Email of the recipient (if email was used) |
| `recipient_channel` | string | Name of the channel (if channel was used) |
| `message_sent` | boolean | Whether the message was successfully sent |
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
actions:
# Send to a user by email
- name: "notify-admin"
type: "send-slack-message"
args:
text: "New resource discovered: ${{ trigger.resource.name }}"
recipient_email: "admin@example.com"
# Send to a channel
- name: "notify-channel"
type: "send-slack-message"
args:
text: "New resource discovered: ${{ trigger.resource.name }}"
recipient_channel: "security-alerts"
```
***
### `ask-in-chat`
Sends an interactive message with Yes/No buttons. The workflow pauses until the user responds.
**Args:**
| Arg | Type | Required | Description |
| - | - | - | - |
| `message` | string | Yes | Message text to display |
| `integration` | string | Yes | Must be `"slack"` |
| `recipient_email` | string | One of | Email of the Slack user (mutually exclusive with `recipient_channel`) |
| `recipient_channel` | string | One of | Slack channel name without `#`, or channel ID such as `C0123456789` (mutually exclusive with `recipient_email`) |
**Outputs:**
The user's response triggers a webhook that continues the workflow with `action-response`.
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
actions:
- name: "approve-access"
type: "ask-in-chat"
args:
message: "Approve access request from ${{ trigger.user.email }}?"
integration: "slack"
recipient_channel: "security-approvals"
```
***
### `formal-app-command`
Calls a [Formal API endpoint](/docs/api/introduction) using a machine user's credentials.
**Args:**
| Arg | Type | Required | Description |
| - | - | - | - |
| `app` | string | Yes | Service name (e.g., `Resource`, `User`, `Policy`) |
| `command.name` | string | Yes | Method name without prefix (e.g., `Resource` for `CreateResource`) |
| `command.type` | string | Yes | Method prefix (e.g., `Create`, `Update`, `Delete`, `Get`) |
| `input` | object | Yes | Request payload for the API call |
| `machine_user_id` | string | Yes | ID of the machine user to authenticate as |
**Outputs:**
| Output | Type | Description |
| - | - | - |
| `status_code` | int | HTTP status code of the response |
| `body` | object | Parsed JSON response body |
**Example:**
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
actions:
- name: "create-resource"
type: "formal-app-command"
args:
app: "Resource"
command:
name: "Resource"
type: "Create"
input:
name: "${{ trigger.resource.name }}"
technology: "${{ trigger.resource.technology }}"
hostname: "${{ trigger.resource.hostname }}"
port: "${{ trigger.resource.port }}"
machine_user_id: "user_abc123"
```
## Terraform Examples
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_workflow" "autopromote_prod_resources" {
name = "Autopromote Production Resources"
code = <<-YAML
trigger:
name: "resource_discovered"
type: "new-autodiscovered-resource"
actions:
- name: "promote_resource"
type: "formal-app-command"
depends_on: ["resource_discovered"]
if: trigger.resource.tags.Environment == "Production"
args:
app: "Resource"
command:
name: "Resource"
type: "Create"
input:
name: "${{ trigger.resource.name }}"
technology: "${{ trigger.resource.technology }}"
hostname: "${{ trigger.resource.hostname }}"
port: "${{ trigger.resource.port }}"
machine_user_id: "user_abc123"
YAML
}
```
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_workflow" "request_policy_suspension_from_api_request" {
name = "Request oneoff SQL queries in Slack from API requests"
code = <<-YAML
trigger:
type: api-request
name: request_query
args:
allow: true # write a condition on who can perform approval requests if applicable
actions:
- type: ask-in-chat
name: ask_approver
args:
recipient_email:
message: 'Do you want to allow `${{ trigger.payload.query }}` from ${{ trigger.user.email }}?'
integration: slack
- type: formal-app-command
name: suspend_policy
depends_on: [ask_approver]
if: actions.ask_approver.response == "yes"
args:
app: Policies
command:
name: PolicySuspension
type: Create
machine_user_id: "user_abc123"
input:
policy_id: "policy_1234"
identity_type: user
identity_id: ${{ trigger.user.id }}
input_condition: ${{ trigger.payload.query }}
oneoff: false
expiration_minutes: 60
YAML
}
```
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_workflow" "request_policy_suspension_from_form" {
name = "Request oneoff SQL queries in Slack from API requests"
# assume we have a form `form_abc123` with two fields:
# one field with id `field_resource` that should be the resource name
# one field with id `field_access_un` that should store the timestamp that the requester is requesting access until
code = <<-YAML
trigger:
type: form-submission
name: form_submission
args:
id: form_abc123
actions:
- type: ask-in-chat
name: ask_approval
args:
message: 'Approve access request to "${{trigger.form_submission.submission.field_resource }}" from ${{ trigger.form_submission.submitter_email }} until ?'
recipient_channel: some-slack-channel
integration: slack
- type: formal-app-command
name: get_user_id
depends_on: [ask_approval]
args:
app: User
command:
name: Users
type: List
machine_user_id: user_abc123
input:
limit: 1
filter:
field:
key: "db_username"
operator: "contains"
value:
value: ${{ trigger.form_submission.submitter_email }}
"@type": "type.googleapis.com/google.protobuf.StringValue"
- type: formal-app-command
name: suspend_policy
depends_on: [get_user_id]
if: actions.ask_approval.response == "yes" && (int(timestamp(trigger.form_submission.submission.field_access_un)) > int(now) + 60) && actions.get_user_id.status_code == 200 && size(actions.get_user_id.body.users) > 0
args:
app: Policies
command:
name: PolicySuspension
type: Create
machine_user_id: user_abc123
input:
policy_id: policy_abc123
identity_type: user
identity_id: ${{ actions.get_user_id.body.users[0].id }}
input_condition: 'input.resource.name == "${{ trigger.form_submission.submission.field_resource }}"'
oneoff: false
expiration_minutes: ${{(int(timestamp(trigger.form_submission.submission.field_access_un)) - int(now))/60}}
YAML
}
```
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_workflow" "daily_compliance_report" {
name = "Daily Compliance Report"
code = <<-YAML
trigger:
name: "daily-report"
type: "cron-schedule"
args:
schedule: "0 9 * * 1-5" # weekdays at 9am UTC
# recovery: latest # optional, defaults to none
actions:
- name: "send-report"
type: "send-slack-message"
args:
text: "Daily compliance report (scheduled: ${{ trigger.scheduled_time }}, ran: ${{ trigger.execution_time }})"
recipient_channel: "compliance"
YAML
}
```
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_workflow" "blocked_query_alert" {
name = "blocked-query-alert"
code = <<-YAML
trigger:
name: "blocked_query"
type: "new-log"
args:
if: "log.event_type == 'request' && log.triggered_policies.exists(p, p.status == 'active' && p.type == 'block')"
actions:
- name: "alert_security"
type: "send-slack-message"
args:
text: |
Query ${{ log.request.query.normalized }} blocked by policy!
User: ${{ trigger.log.user.email }}
Resource: ${{ trigger.log.resource.name }}
recipient_email: "security@example.com"
YAML
}
```
## Next Steps
Learn about Formal policies for access control
Full Terraform provider documentation
# Assessments
Source: https://docs.formal.ai/docs/guides/core-concepts/assessments
Configure assessments and use scores and labels in LLM response policies
Assessments provide scores and labels for LLM response policies. Formal scores tool calls for alignment with the user's intent and risk. It assigns labels to LLM responses based on conditions evaluated against the conversation.
## Configuration
Enable assessments on the [Assessment](https://app.formal.ai/ai-assessments) page in the Control Plane. Assessments are included in LLM logs.
Under **Risk and misalignment checks**, create conditions with a name, type, severity, and enabled status. Risk checks identify harmful consequences. Misalignment checks identify departures from the user's expressed or reasonably implied intent. You can edit, disable, or delete defaults, and add your own checks. Deleting a default does not recreate it when you enable assessments again.
**Verify:** Save a check and confirm its condition, type, severity, and status in the checks table.
On the same page, create labels for topics or behaviors. Each label has a condition that completes "The current conversation ..." and a probability threshold. Formal assigns the label when it is active and the assessed probability reaches the threshold.
For example, labels can identify a broad topic or a specific behavior:
| Example label | Condition |
| - | - |
| `engineering` | `is materially related to developing or maintaining software` |
| `customer-data-export` | `includes an attempt to export customer personal data` |
| `production-change` | `includes an attempt to change production infrastructure` |
## Policy Evaluation
LLM response policies can use the following fields in `input.assessment`:
| Field | Type | Description |
| - | - | - |
| `alignment` | **Number** | From `0` to `1`. Lower values mean less alignment with the user's intent. |
| `risk` | **Number** | From `0` to `1`. Higher values mean greater assessed risk. |
| `labels` | **\[]String** | Names of active labels assigned to the LLM response. |
Alignment and risk scores are available for LLM responses with tool calls. If assessment data is unavailable, conditions that reference `input.assessment` do not match.
A check contributes nothing when its assessed probability is `0.5` or lower, and its full configured severity when the probability is `0.8` or higher. Between those points, its contribution rises linearly from `0` to the severity.
| Severity | Numeric value |
| - | - |
| Low | `0.2` |
| Medium | `0.5` |
| High | `0.8` |
| Critical | `1` |
For each tool call, risk is the highest risk contribution. Alignment is `1` minus the highest misalignment contribution. With zero contributions, risk is `0` and alignment is `1`. Across a response, Formal uses the highest tool call risk and lowest alignment. Missing or invalid assessment answers make assessment data unavailable.
Repeated identical requests can reuse an assessment for up to 30 seconds. Changes to checks may take that long to appear for those requests.
With the example `engineering` label above, this policy blocks an LLM response when that label applies and a tool call risk score is at least `0.7`:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
import future.keywords.in
response := {
"action": "block",
"type": "block_with_formal_message",
"reason": "Engineering tool call exceeds the risk threshold"
} if {
input.resource.technology == "llm"
"engineering" in input.assessment.labels
input.assessment.risk >= 0.7
}
```
# Clustering
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/clustering
Enable shared state across multiple connector instances
For load balancing and redundancy, we recommend deploying multiple Connector instances to represent the same logical Connector. The Connector instances can operate entirely independently, but you may want to share their state across all instances to enable advanced features such as distributed rate limiting or BigQuery job ID caching. To do this, you will need to enable these instances to form a cluster.
## Configuration
### Discovery
To enable clustering, the Connectors need to be able to discover each other. Clustering is supported on AWS ECS and Kubernetes.
For AWS ECS deployments, configure IAM permissions to allow Connector instances to list and describe ECS tasks. This enables them to discover other instances in the same service:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Allow ECS tasks to discover peers for cluster formation
resource "aws_iam_policy" "ecs_task_permissions" {
name = "connector-ecs-task-permissions"
description = "Allow ECS tasks to discover peers for cluster formation"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = ["ecs:DescribeTasks", "ecs:ListTasks"],
Effect = "Allow",
Resource = "*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_permissions" {
role = aws_iam_role.ecs_task_role.name
policy_arn = aws_iam_policy.ecs_task_permissions.arn
}
```
See our [AWS Terraform example](https://github.com/formalco/terraform-provider-formal/tree/main/examples/deployments/aws/connector) for a complete configuration.
When deploying on Kubernetes using our Helm chart, clustering is automatically configured via RBAC permissions. The Helm chart grants the necessary permissions for Connector pods to discover each other and coordinate cluster formation.
### Network
Connectors communicate with each other over the network on port 7946.
Configure the security group to allow inter-instance communication on the cluster ports:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Allow internal cluster communication on port 7946
resource "aws_security_group" "connector" {
ingress {
description = "Cluster communication port TCP"
from_port = 7946
to_port = 7946
protocol = "tcp"
self = true # Allow from same security group
}
ingress {
description = "Cluster communication port UDP"
from_port = 7946
to_port = 7946
protocol = "udp"
self = true # Allow from same security group
}
# ...
}
```
See our [AWS Terraform example](https://github.com/formalco/terraform-provider-formal/tree/main/examples/deployments/aws/connector) for a complete configuration.
If your cluster uses network policies, configure them to allow communication between Connector pods on port 7946 (both TCP and UDP). Restrictive network policies may prevent cluster formation.
## Example
Here's an example policy that uses the cluster's shared state to enforce a rate limit on S3 bucket access:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
request := { "action": "block", "type": "block_with_formal_message" } if {
input.bucket.name == "sensitive-bucket"
input.bucket.access_count.last_minute > 5
}
```
This policy blocks access to `sensitive-bucket` if it has been accessed more than five times in the last minute by the user, across all Connector instances.
## How It Works
Instances discover each other via service discovery (e.g. ECS task listing, Kubernetes pod listing) and communicate over port 7946 (TCP and UDP) using a gossip protocol. Each node maintains a membership list and shared state such as rate limit counters and cache entries.
### Node Failure and Recovery
When a cluster member becomes unresponsive:
1. Other nodes detect the failure through missed gossip heartbeats
2. The node is marked as failed in the membership list
3. After 1 minute, the failed node is removed from the cluster state
4. Remaining healthy nodes continue operating with shared state preserved
When a new or recovered instance joins the cluster:
1. The instance discovers existing members via service discovery
2. It sends a join request to known peers
3. A peer sends recent state updates from its event buffer (up to 10,000)
4. The node can resume normal operation with up-to-date shared state
### Consistency Model
The cluster uses **eventual consistency** via gossip. State updates are broadcast to all nodes and ordered using a [Lamport Clock](https://en.wikipedia.org/wiki/Lamport_timestamp) for causal ordering. There is a brief propagation delay before all nodes have the same state. The Control Plane remains the source of truth for all configuration (policies, resources, users).
Rate limiting counters may have slight inaccuracies during the gossip
propagation window. For strict rate limiting, this means a user might get a
few extra requests through before the limit is enforced across all nodes.
### Network Partitions
If a network partition separates cluster members, each partition continues operating independently with its own local state; rate limiting and cache sharing only works within each partition. When the partition heals, nodes rejoin and reconcile state via event replay. Configuration and policies are not affected since each Connector independently receives updates from the Control Plane.
# Deployment
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/deployment
Infrastructure and network requirements for deploying Connectors
The Connector is available as a statically linked binary in a multi-architecture distroless Docker image.
### Container Image Registries
You can pull Connector images from either of these registries:
* AWS ECR: `654654333078.dkr.ecr..amazonaws.com/formalco-prod-connector:`
Available regions: `us-east-1`, `us-west-1`, `us-west-2`, `ap-southeast-1`, and `eu-west-1`.
* GCP Artifact Registry: `us-docker.pkg.dev/formal-public-assets/formalco-prod-connector/formalco-prod-connector:`
On AKS, use the GAR image with
[`azure-gar-cred`](https://github.com/formalco/helm-charts/tree/main/charts/azure-gar-cred).
Set `pullWithCredentials: true` in the Connector Helm values.
### Amazon ECR Pull-Through Cache
You can cache Formal images in your own Amazon ECR registry with an ECR pull-through cache rule.
Your clusters then pull from your registry, so they need no Formal image pull secret.
Formal must first allowlist your AWS account ID or organization ID for ECR pulls.
Contact Formal support to request access.
1. **Create an IAM role for the cache**
Amazon ECR assumes this role to pull from Formal's registry. Save this trust policy as `ptc-trust.json`:
```json theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "pullthroughcache.ecr.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
```
Save this permissions policy as `ptc-permissions.json`:
```json theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchImportUpstreamImage",
"ecr:GetImageCopyStatus",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "*"
}
]
}
```
Create the role:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
aws iam create-role \
--role-name formal-ecr-pull-through-cache \
--assume-role-policy-document file://ptc-trust.json
aws iam put-role-policy \
--role-name formal-ecr-pull-through-cache \
--policy-name formal-ecr-pull-through-cache \
--policy-document file://ptc-permissions.json
```
2. **Create the pull-through cache rule**
Run this in the AWS account and region of your registry.
Set `--upstream-registry-url` to a Formal ECR region listed above.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
aws ecr create-pull-through-cache-rule \
--ecr-repository-prefix formal \
--upstream-registry ecr \
--upstream-registry-url 654654333078.dkr.ecr.us-east-1.amazonaws.com \
--custom-role-arn arn:aws:iam:::role/formal-ecr-pull-through-cache
```
3. **Pull through your registry**
Prefix Formal repository names with `formal/` and use your registry host:
```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
.dkr.ecr..amazonaws.com/formal/formalco-prod-connector:
```
In the Connector Helm chart, set `image.repository` to this value and keep `pullWithCredentials: false`.
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
aws ecr get-login-password --region | docker login \
--username AWS --password-stdin .dkr.ecr..amazonaws.com
docker pull .dkr.ecr..amazonaws.com/formal/formalco-prod-connector:latest
aws ecr describe-images --region --repository-name formal/formalco-prod-connector
# Expected: the cached image appears in your registry
```
Amazon ECR checks Formal's registry for tag updates at most once every 24 hours.
Pin versioned tags instead of `latest`.
Keep cached repositories tag-mutable, or cached tags stop updating.
### Infrastructure Requirements
* **Operating System**: Linux environment
* **Architecture**: AMD64 or ARM64
* **Container Runtime**: Docker or compatible container runtime
### Network Requirements
The Connector requires:
* **Network access** to `api.joinformal.com` (Formal Control Plane)
* **Outbound access** to your protected resources
* **Inbound access** from clients on configured listener ports
#### AWS VPC Private Link Connectivity
For customers deploying on AWS, the Connector can connect to the Formal Control Plane using AWS VPC Private Link instead of traversing the public internet. This provides enhanced security and network isolation.
**Service Details:**
* **Service Name**: `com.amazonaws.vpce.eu-west-1.vpce-svc-01bfea09d5ec08d36`
* **Region**: `eu-west-1`
* **Endpoint Type**: Interface (services that use NLBs or GWLBs)
**Setup Steps:**
1. **Create VPC Endpoint**
Using AWS Console:
* Navigate to **VPC** → **Endpoints** → **Create Endpoint**
* Select **Other endpoint services**
* Enter service name: `com.amazonaws.vpce.eu-west-1.vpce-svc-01bfea09d5ec08d36`
* Click **Verify service**
* Select your VPC and subnets where the Connector is deployed
* Configure security groups to allow outbound HTTPS traffic (port 443) from the Connector
* **Important**: Check **Enable Private DNS Name**
* If your VPC is in a region other than `eu-west-1`, enable **Cross-region endpoint** and specify `eu-west-1` as the target region
* Review and create the endpoint
2. **Configuration Requirements**
* **Private DNS**: Must be enabled for proper DNS resolution of Control Plane endpoints
* **Cross-region Support**: Required if your VPC is in any region other than `eu-west-1`
* **Security Groups**: Must allow outbound HTTPS (port 443) from Connector to VPC endpoint
* **Network ACLs**: Ensure subnet ACLs permit traffic to/from the VPC endpoint
3. **Verify Connectivity**
Once the endpoint is in **Available** state, test from your Connector instance:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Verify DNS resolves to private IP
nslookup api.joinformal.com
# Test HTTPS connectivity
curl -v https://api.joinformal.com
```
The VPC endpoint uses Private DNS to automatically redirect `api.joinformal.com` traffic through the private connection. No configuration changes are needed on the Connector side.
For cross-region deployments (regions other than `eu-west-1`), you must enable the cross-region endpoint feature and specify `eu-west-1` as the target region, or the connection will fail.
### Resource Requirements
The Connector requires adequate resources to apply policies with minimal latency and maintain all necessary context in RAM (Control Plane data, queries metadata, responses data, log buffer, etc.).
| Spec | Minimum | Recommended |
| - | - | - |
| **CPU** | 1 core | 2 cores per node (2 nodes) |
| **RAM** | 2 GB | 4 GB per node (2 nodes) |
| **Disk** | 0 GB | 10 GB |
**CPU Throttling Warning**: If you don't allocate entire CPU cores to the Connector and rely on CPU throttling (e.g., CFS quotas on Linux, CPU limits in Kubernetes), you will most likely experience increased latency and timeouts during peak loads.
### Production Recommendations
For production deployments, we recommend:
* **High Availability**: Run at least 2 nodes behind a load balancer
* **Resource Allocation**: 2 CPU cores and 4 GB RAM per node
* **Load Distribution**: Distribute traffic across multiple Connector instances
* **Persistent Storage**: Mount a persistent volume to `/formal/logs` for the [log spool](/docs/guides/observability/log-configuration#logs-spool)
CPU, RAM and disk requirements vary based on usage patterns, query types, traffic volume, and enforced policies. Monitor your Connector with a [telemetry collector](/docs/guides/core-concepts/connectors/health.mdx#metrics) and scale resources as needed.
The Connector buffers up to 400 MB of logs in memory before [spooling to disk](/docs/guides/observability/log-configuration#logs-spool). A typical log entry is around 1 KB – at 100 requests per second, 10 GB of disk provides more than a day of outage buffer. The Connector works without disk (spooling is disabled), but logs may be lost during disruptions.
When deploying multiple instances, Connectors attempt automatically to form a cluster with shared state. It enables Connectors to coordinate rate limiting across all instances. See the [Clustering](/docs/guides/core-concepts/connectors/clustering) page for details.
### Recommended Deployment Options
Deploy as a Fargate service behind a Network Load Balancer with multi-AZ
availability
Deploy using [Formal Helm charts](https://github.com/formalco/helm-charts) on any Kubernetes cluster (EKS, GKE, AKS, on-premises).
Run as a standalone container (development/testing only)
See the [Quickstart guide](/docs/guides/getting-started/quickstart) for step-by-step deployment instructions.
## Environment Variables
The Connector is configured primarily through the Control Plane, but requires the following environment variable:
| Variable | Type | Description |
| - | - | - |
| `FORMAL_CONTROL_PLANE_API_KEY` | **String** | API token to authenticate with the Control Plane (obtained when creating the Connector) |
# FIPS
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/fips
Deploy the FIPS build of the Formal Connector and understand its cryptographic and feature restrictions
The FIPS Connector is a separate build of the Formal Connector for environments that require FIPS 140-3.
It uses the Go Cryptographic Module and limits the Connector to approved cryptography.
Using this image does not make an entire deployment FIPS compliant. You must
confirm that your operating environment and configuration meet your compliance
requirements.
## Pull the image
The FIPS image uses a separate `formalco-prod-connector-fips` repository.
Use the same release tag across either registry.
| Registry | Image |
| - | - |
| AWS ECR | `654654333078.dkr.ecr..amazonaws.com/formalco-prod-connector-fips:` |
| GCP Artifact Registry | `us-docker.pkg.dev/formal-public-assets/formalco-prod-connector-fips/formalco-prod-connector-fips:` |
ECR images are available in `us-east-1`, `us-west-1`, and `us-west-2`.
Both registries also publish a `latest` tag.
For example, pull a versioned image from GCP Artifact Registry:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
docker pull us-docker.pkg.dev/formal-public-assets/formalco-prod-connector-fips/formalco-prod-connector-fips:
```
Use versioned tags in production so deployments remain reproducible.
## Go Cryptographic Module
Formal does not maintain a separate validated cryptographic module.
The FIPS Connector builds against the Go Cryptographic Module `v1.26.0`, frozen from Go 1.26, in FIPS 140-3 mode.
In this mode, the Go module performs integrity checks and cryptographic self-tests.
It uses a NIST SP 800-90A deterministic random bit generator.
Go TLS also rejects protocol versions, cipher suites, signatures, and key exchanges that are not approved.
Required key consistency tests can make some key generation operations slower.
The image also directs AWS SDK calls to AWS FIPS service endpoints.
The Go project lists `v1.26.0` as pending CMVP review and covered by
[CAVP certificate A8028](https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program/details?validation=40638).
See the [Go FIPS 140-3 documentation](https://go.dev/doc/security/fips140)
for its current CMVP status, tested operating environments, and security policy.
## Restrictions
The FIPS build removes features that depend on code outside the Go Cryptographic Module.
It also rejects protocol options that require unapproved cryptography.
| Area | Restriction | Supported alternative |
| - | - | - |
| Policy hooks | Hooks are unavailable. | Express the required behavior in policy without hooks. |
| RDP | RDP proxying is unavailable. | Use a standard Connector build for RDP resources. |
| MySQL and MariaDB | Client and upstream connections must use TLS. Plaintext connections are rejected. | Configure TLS on both sides of the Connector. |
| MySQL and MariaDB authentication | `mysql_native_password` is unavailable. | Use `caching_sha2_password` over TLS. |
| PostgreSQL authentication | MD5 password authentication is unavailable. | Use SCRAM-SHA-256. |
| MongoDB authentication | SCRAM-SHA-1 is unavailable. | Use SCRAM-SHA-256. |
| TLS | Go rejects non-approved choices, including ChaCha20-Poly1305 cipher suites. | Use an approved TLS configuration, such as TLS 1.2 or 1.3 with AES-GCM. |
| S3 checksums | `Content-MD5` and SHA-1 checksums are unavailable. | Use SHA-256, CRC32, CRC32C, or CRC64NVME checksums. |
| SSH | SHA-1 `ssh-rsa` signatures are unavailable. An upstream host key must be pinned. | Use RSA-SHA2 or another approved signature with a pinned host key. |
| Resource TLS verification | `insecure-skip-verify` and `insecure-verify-ca-only` are unavailable. | Use full certificate and hostname verification. |
Review these restrictions before replacing a standard Connector image.
Existing resource or policy settings can prevent the FIPS Connector from starting or serving a connection.
# Health
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/health
How to gain observability into your Connector instances
Your Formal Connector deployment will require different resource requirements depending on the specifics of your setup. Factors like the number of active connections you expect to pass through the Connector at once, the policies you configure, the payload sizes of requests and responses, and more can affect the health of individual Connector instances.
There are two ways we help our customers get observability into their Connectors: **Logs** and **Metrics**.
## Logs
The Connector emits logs to stdout/stderr that can be captured in your log collector of choice.
For example, if deploying your [Connector on ECS Fargate using Terraform](https://github.com/formalco/terraform-provider-formal/blob/21a509320e912921700dda2269eae2629b5db49e/examples/deployments/aws/postgres/internal/postgres_proxy/ecs.tf#L69), use `logConfiguration` to route these logs to be sent to your observability tool of choice:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "aws_ecs_task_definition" "ecs_task" {
# ignoring other fields irrelevant to logs in this example
container_definitions = jsonencode([
{
name = var.name
image = var.container_image
essential = true
environment = [
{
name = "DD_VERSION"
value = "1.0.0"
},
{
name = "DD_ENV",
value = "prod"
},
{
name = "DD_SERVICE"
value = var.name
},
# specify other env vars here
],
logConfiguration = {
logDriver = "awsfirelens"
options = {
"Name" = "datadog",
"Host" = "http-intake.logs.datadoghq.eu",
"TLS" = "on",
"dd_source" = var.name,
"provider" = "ecs",
"dd_service" = var.name,
"apikey" = var.datadog_api_key
}
}
dependsOn = [
{ "containerName" : "log_router", "condition" : "START" },
{ "condition" = "HEALTHY", "containerName" = "datadog-agent" }
]
},
{
name = "log_router"
image = "public.ecr.aws/aws-observability/aws-for-fluent-bit:stable"
memoryReservation = 50,
firelensConfiguration = {
"type" = "fluentbit",
"options" = {
"enable-ecs-log-metadata" = "true"
}
},
},
{
name = "datadog-agent",
image = "public.ecr.aws/datadog/agent:latest",
portMappings = [
{
"containerPort" = 8126,
"hostPort" = 8126,
"protocol" = "tcp"
}
{
# We need this for metrics
"containerPort" = 4317,
"hostPort" = 4317,
"protocol" = "tcp"
}
],
environment = [{
"name" = "ECS_FARGATE",
"value" = "true"
},
{
"name" = "DD_APM_ENABLED",
"value" = "true"
},
{
"name" = "DD_LOGS_ENABLED",
"value" = "true"
},
{
"name" = "DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL",
"value" = "true"
},
{
"name" = "DD_APM_NON_LOCAL_TRAFFIC",
"value" = "true"
},
{
"name" = "DD_API_KEY",
"value" = var.datadog_api_key
},
{
"name" = "DD_SITE",
"value" = "datadoghq.eu"
}]
}
])
}
```
## Metrics
The Connector emits metrics over [OTLP/gRPC](https://opentelemetry.io/docs/specs/otlp/) to an OpenTelemetry Collector such as [the Datadog Agent](https://docs.datadoghq.com/opentelemetry/setup/agent/) to [127.0.0.1 port 4317](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc-default-port) by default. This endpoint can be customized via connector configuration settings. The Connector currently emits the following metrics:
* `formal.connector.instance.heartbeat`: A heartbeat gauge metric once every 30 seconds to verify Connector instance uptime.
* `formal.connector.memory.usage_percent`: A memory usage percentage gauge metric based on the connector's runtime memory allotment. Note that these percentages may look different than the host's memory usage if the memory allotted to the Connector is less than the overall host.
* `formal.connector.connections_opened`: A counter that is incremented every time the instance accepts a new TCP connection from a client.
* `formal.connector.connections_closed`: A counter that is incremented every time the instance closes a TCP connection from a client.
* `formal.connector.active_connections`: A gauge metric of the current number of open TCP client connections that the instance has.
* `formal.connector.control_plane.pings_received`: A counter metric that is incremented every time we receive a ping from the Control Plane.
* `formal.connector.resource_health_check`: A gauge metric of resource health checks.
### Deploying an OpenTelemetry Collector
By default the Connector sends metrics to an OpenTelemetry Collector at `127.0.0.1` port 4317, so a Collector on the same host works out of the box. For this setup, run the Datadog Agent as a container on that host (so it is reachable over 127.0.0.1) and set the `DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_GRPC_ENDPOINT=0.0.0.0:4317` environment variable.
You can also configure the Connector to send metrics to a different hostname and port using the Connector configuration settings, either from the Control Plane or with Terraform. In Terraform, use the `otel_endpoint_hostname` and `otel_endpoint_port` fields in your `formal_connector_configuration` resource to specify a custom OpenTelemetry endpoint. If nothing is listening on the configured endpoint, the Connector will refrain from sending metrics entirely.
**Note:** these metrics are not sent over TLS, so make sure the OpenTelemetry endpoint is in your trusted network.
## Datadog Dashboard
To visualize connector health in a single place, we provide a [template dashboard](/assets/json/connector_health_dashboard_datadog.json) for you to import into your Datadog instance.
* Download the [template JSON](/assets/json/connector_health_dashboard_datadog.json).
* Sign in to your [Datadog account](https://app.datadoghq.com/).
* Navigate to the [Dashboard List](https://app.datadoghq.com/dashboard/lists) page.
* Choose the **+ New Dashboard** button.
* Enter a dashboard name.
* Choose the **New Dashboard** button.
* In the new dashboard, choose the **Configure** button.
* Choose the **Import dashboard JSON** button.
* Upload the dashboard JSON file downloaded in the previous steps.
## Grafana Dashboard
If you use Grafana with Prometheus, we provide a [template dashboard](/assets/json/connector_health_dashboard_grafana.json) for you to import into your Grafana instance.
This dashboard expects metrics to be collected via an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) configured with a Prometheus exporter, which Prometheus then scrapes.
* Download the [template JSON](/assets/json/connector_health_dashboard_grafana.json).
* Sign in to your Grafana instance.
* Navigate to **Dashboards** in the side menu.
* Choose the **New** button, then **Import**.
* Upload the dashboard JSON file or paste its contents.
* Select your Prometheus data source when prompted.
* Choose **Import**.
# Introduction
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/introduction
The protocol-aware proxy that provides visibility and control over data access
## What is a Connector?
The Formal Connector is a protocol-aware reverse proxy that sits between your users and your protected resources. It provides real-time visibility, policy enforcement, and access control over all data flowing through your infrastructure.
The Connector is designed to be extremely easy to deploy and manage, thanks to two key features:
* **Smart Routing**: The Connector listens on a single port, automatically detects the wire protocol used by the client (PostgreSQL, MongoDB, HTTP, SSH, etc.), and parses this protocol in real-time
* **Zero Config**: The Connector automatically discovers resources in your cloud environment (Kubernetes clusters, RDS instances, EC2 instances, etc.) and connects to them without manual configuration
## How Connectors Work
All client connections flow through the Connector:
```mermaid theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sequenceDiagram
participant Client
participant Connector
participant Resource
Client->>Connector: Connect to database
Connector->>Connector: Authenticate identity
Connector->>Connector: Evaluate policies
Connector->>Resource: Forward request
Resource-->>Connector: Response
Connector->>Connector: Apply data masking
Connector-->>Client: Masked response
```
The Connector intercepts every request and response, allowing you to:
* ✅ Authenticate users and verify their identity
* ✅ Evaluate access policies in real-time
* ✅ Log all queries, commands, and API calls
* ✅ Mask and redact sensitive data automatically
* ✅ Terminate suspicious sessions immediately
## Monitoring and Health
The Connector exposes a health check endpoint on port 8080:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
curl http://connector-host:8080/health
```
For production monitoring, configure:
* **CloudWatch Logs** (AWS ECS)
* **kubectl logs** (Kubernetes)
* **Cloud Logging** (GCP)
## Configuration
1. Navigate to [Connectors](https://app.formal.ai/connectors)
2. Click **Create Connector**
3. Enter a name (and optional Space), then click **Create**
4. Open the Connector → **Listeners** → **New Listener**
5. Add a rule with **New Rule** (**Resource** or **Technology**)
6. Copy the Connector API token from Connector details
7. Deploy the container with the token
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_connector" "production" {
name = "production-connector"
}
resource "formal_connector_listener" "postgres" {
connector_id = formal_connector.production.id
name = "postgres-listener"
port = 5432
}
resource "formal_connector_listener_rule" "postgres" {
connector_listener_id = formal_connector_listener.postgres.id
type = "resource"
rule = formal_resource.postgres.id
}
output "connector_token" {
value = formal_connector.production.api_key
sensitive = true
}
```
## Next Steps
Define the databases, APIs, and infrastructure the Connector protects
Segment access with logical groupings
Control who can access what with policy-as-code
View active connections and session recordings
# Listeners and Rules
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/listeners
The protocol-aware proxy that provides visibility and control over data access
## Listeners and Rules
When you create a Connector, you define **listeners** that specify how clients connect to it.
### Listeners
A listener monitors a specific port for incoming client connections. You can define multiple listeners on different ports for different use cases.
Port 8080 is reserved for the Connector's health check endpoint and cannot be
used for listeners.
### Listener Rules
Each listener uses routing rules to determine where to forward requests. There
are two rule types:
Routes all connections to a **specific resource**.
Example: All traffic on port 5432 goes to the `production-postgres` database.
Routes connections to **all resources of a specific technology** (e.g., all
PostgreSQL databases). When multiple resources match, [smart
routing](/docs/guides/core-concepts/resources/smart-routing) automatically
selects the correct resource based on the connection string.
[Spaces](/docs/guides/core-concepts/spaces) limit which resources are accessible.
A single listener can have multiple rules, and you can combine different rule
types.
## Terraform Example
The following example demonstrates how to deploy a Connector linked to all PostgreSQL resources:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
terraform {
required_providers {
formal = {
source = "formalco/formal"
version = "~> 4.32"
}
}
}
variable "formal_api_key" {
type = string
sensitive = true
}
provider "formal" {
api_key = var.formal_api_key
}
resource "formal_connector" "main" {
name = "main"
}
resource "formal_connector_listener" "postgres_listener" {
connector_id = formal_connector.main.id
name = "postgres-listener"
port = 5432
}
resource "formal_connector_listener_rule" "postgres_rule" {
connector_listener_id = formal_connector_listener.postgres_listener.id
type = "technology"
rule = "postgres"
}
```
This configuration creates:
* A Connector named `main`
* A listener on port 5432, attached to the Connector through `connector_id`
* A technology rule that routes all PostgreSQL traffic to any PostgreSQL resource in your organization
Always set `connector_id` on `formal_connector_listener`. A listener without
it is not attached to any Connector.
# MCP Gateway
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/mcp-gateway
Route remote MCP servers through a Connector so any MCP client signs in with Formal
## Overview
With the MCP gateway, MCP clients reach remote MCP servers through a Connector instead of connecting to them directly.
The [Set up the MCP gateway](https://app.formal.ai/recipes/mcp-gateway)
recipe walks you through the listener, MCP servers, and network rule. See
[Recipes](/docs/guides/getting-started/recipes).
The gateway works with remote MCP servers that clients reach over HTTPS. It doesn't cover local servers that clients start as a subprocess over stdio.
You add servers such as Linear or Notion to the [Catalog](https://app.formal.ai/catalog), and each one gets its own address on the Connector:
```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
https://./
```
MCP clients connect to that address and sign in with Formal. The Connector adds the upstream credential, enforces policies, and logs every tool call.
Authentication happens on two sides, and you configure each one separately:
| Side | Between | Options |
| - | - | - |
| **Upstream** | Connector and MCP server | Linked accounts (OAuth), shared credentials, or pass through |
| **Downstream** | MCP client and Connector | Formal OAuth, or identity headers from the Formal Endpoint |
## Upstream authentication
When you add a server, you choose which credential Formal sends to it. The admin Catalog offers three options:
| Option | Credential sent upstream |
| - | - |
| **Linked accounts** | Each user's own OAuth token for the server |
| **Shared credentials** | A credential you provide, such as an API key |
| **Pass through** | Whatever the client sends |
### Linked accounts (OAuth)
Each user links their own account for the server once. Formal stores the token encrypted in its control plane and refreshes it when the server supports refresh. The Connector adds the token to each request after policies allow it. Users never see the token.
Users link accounts on the server's page in the Catalog. They can also link during OAuth sign-in from an MCP client.
Formal needs an OAuth client to sign users in to the server. Choose one under **OAuth app**:
Formal identifies itself with a [Client ID Metadata Document](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents) (CIMD). Its client ID is a URL where Formal hosts its name and redirect URI. You don't register anything with the server.
The server's authorization server must:
* Advertise `client_id_metadata_document_supported: true`.
* Support the authorization code flow with PKCE `S256`.
* Accept public clients, with token endpoint auth method `none`.
Linear, Notion, Sentry, Canva, Grafana Cloud, Fireflies, Exa, and Pylon met these requirements at the time of writing. To check another server, read its authorization server metadata:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
curl -s https://mcp.linear.app/.well-known/oauth-authorization-server \
| jq '{client_id_metadata_document_supported, code_challenge_methods_supported, token_endpoint_auth_methods_supported}'
# Expected: client_id_metadata_document_supported is true, and "none" is listed
```
Use this for servers without CIMD support, such as GitHub and Slack.
1. Register an OAuth app with the server's provider.
2. Copy the **Redirect URI** from the Catalog into the app's allowed redirect URIs.
3. Enter the app's **Client ID** and, if it has one, its **Client secret**.
Formal encrypts the secret and never shows it again. Leave the secret empty for a public client, which uses PKCE alone.
To set OAuth scopes, use **Permissions to request**. Leave it empty to get the server's default access.
If you change the scopes or the OAuth app, Formal unlinks every account. Users must link again.
If a user hasn't linked an account, the Connector rejects their requests with a pointer to the server's Catalog page.
### Shared credentials
Formal adds one credential that you provide, such as an API key or a service account token. Users never see it.
You store the credential as a [native user](/docs/guides/core-concepts/resources/native-users) on the server's resource, and Formal sends the default one. To send different credentials to different people, add [selection rules](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user) on the resource page.
Use shared credentials for servers that accept API keys, or when everyone should act as one account.
### Pass through
Clients send their own sign-in or API key, and Formal forwards it unchanged. Formal stores nothing.
With pass through, the client's own credential goes to the server, and nothing in the request identifies the Formal user. Formal can only attribute these requests when they come through the [Formal Endpoint](#identity-headers), which adds the user's identity. For that reason, pass-through servers have no Connector address in the Catalog.
## Downstream authentication
Clients identify the user to the Connector in one of two ways. Formal uses that identity for policies, logs, and choosing the upstream credential.
| Method | Used by | Available when |
| - | - | - |
| **Formal OAuth** | Any MCP client that supports CIMD | Upstream is linked accounts or shared credentials |
| **Identity headers** | The Formal Endpoint | Any upstream option |
### Formal OAuth
When upstream authentication isn't pass through, the Connector acts as an OAuth protected resource:
```mermaid theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sequenceDiagram
participant Client as MCP client
participant Connector as Connector
participant Formal as Formal (Catalog + OAuth)
participant Server as MCP server
Client->>Connector: MCP request (no token)
Connector-->>Client: 401 with protected resource metadata
Client->>Formal: OAuth sign-in and consent
Formal-->>Client: Access token for this server
Client->>Connector: MCP request with Formal token
Connector->>Server: Request with upstream credential
Server-->>Connector: Response
Connector-->>Client: Response
```
1. The Connector answers a request without a token with `401 Unauthorized`. Its `WWW-Authenticate` header points to `/.well-known/oauth-protected-resource`.
2. Formal is listed in that document as the authorization server.
3. The client starts an authorization code flow with Formal. It identifies itself with its own CIMD URL.
4. The user signs in to Formal and approves the client on a Catalog consent screen. If the server uses linked accounts, the user links their account here too.
5. Formal issues a short-lived access token bound to this server's address, plus a refresh token.
The Connector validates access tokens offline and removes them before forwarding the request upstream. Formal rotates refresh tokens on each use and keeps them in its control plane.
Formal's authorization server accepts only public clients that use PKCE `S256`. It doesn't support Dynamic Client Registration (DCR). Clients that only support DCR can't sign in this way. On a device, route them through the Endpoint with [identity headers](#identity-headers).
Users can review and revoke approved clients on the server's page in the Catalog. After a revoke, Formal stops refreshing that client's tokens, but access tokens already issued stay valid until they expire.
### Identity headers
The Connector also accepts a Formal identity in two headers:
* `X-Formal-User-Username`: the Formal username, such as `idp:formal:human:alice@example.com`
* `X-Formal-User-Password`: a Formal-signed token for that user
The [Formal Endpoint](/docs/guides/client-apps/desktop-app#transparent-mode) adds both headers for MCP clients on the device. Users keep the server's original URL, such as `https://mcp.linear.app/mcp`. They don't sign in again, because the Endpoint already knows who they are.
To route Endpoint traffic to the gateway, create a [network rule](/docs/guides/network-rules) that forwards MCP traffic from AI agents to the Connector:
```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"condition": {
"pre_tcp": process.is_agent,
"pre_tls": process.is_agent && resource.technology == "mcp",
"post_tls": true
},
"outputs": {
"forward_to_connector": true
}
}
```
The rule matches requests from [known agents](/docs/guides/network-rules#known-agent-identities) when the destination is an MCP server in Formal. Checking the destination in `pre_tls` keeps Formal from terminating TLS on the agents' other traffic. Servers you add later are covered without editing the rule. To cover other applications, widen the process conditions.
## Deploy the Connector
Before you add servers, set up the following on the Connector:
1. A listener that routes MCP traffic.
2. A hostname.
3. DNS and TLS that cover every subdomain of that hostname (i.e., `*.`), to enable [smart routing](/docs/guides/core-concepts/resources/smart-routing). A request to `linear.acme.connectors.joinformal.com` routes to the server named `linear`.
### 1. Add an MCP listener
Create a listener with a **Technology** rule set to **MCP**. Use port `443` so client URLs don't need a port.
1. Go to [Connectors](https://app.formal.ai/connectors) and open your Connector.
2. Add a listener on port `443`.
3. Add a rule with type **Technology** and technology **MCP**.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_connector_listener" "mcp" {
connector_id = formal_connector.main.id
name = "mcp-gateway"
port = 443
}
resource "formal_connector_listener_rule" "mcp" {
connector_listener_id = formal_connector_listener.mcp.id
type = "technology"
rule = "mcp"
}
```
With a technology rule, the Connector routes every MCP server it can see. To expose only some servers, use **Resource** rules instead. See [Listeners and Rules](/docs/guides/core-concepts/connectors/listeners).
A Connector in a [Space](/docs/guides/core-concepts/spaces) only routes servers in that Space. A Connector without a Space routes all of them.
### 2. Add a hostname with wildcard DNS and TLS
Clients reach each server at a subdomain of the Connector hostname, so both DNS and the TLS certificate must cover `*.`.
Use a hostname ending in `..connectors.joinformal.com`. Formal creates both the hostname and wildcard DNS records. It also issues a wildcard certificate.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_connector_hostname" "main" {
connector_id = formal_connector.main.id
hostname = "gateway..connectors.joinformal.com"
dns_record = ""
}
```
Use a hostname in a DNS zone you control. You must:
1. Create a DNS record for `*.` that points to your load balancer.
2. Upload a certificate whose subject alternative names include `*.`.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_connector_hostname" "main" {
connector_id = formal_connector.main.id
hostname = "mcp.example.com"
certificate = file("wildcard-mcp-example-com.pem")
private_key = file("wildcard-mcp-example-com-key.pem")
}
```
See [Hostname and TLS Configuration](/docs/guides/core-concepts/connectors/tls) for the full TLS options.
Claude and ChatGPT call MCP servers from Anthropic's and OpenAI's clouds. To use them, expose the Connector's listener to the internet.
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
HOST="gateway..connectors.joinformal.com"
# Any subdomain should resolve to your load balancer
dig +short "linear.$HOST"
# The certificate should list *.
openssl s_client -connect "linear.$HOST:443" -servername "linear.$HOST" /dev/null \
| openssl x509 -noout -ext subjectAltName
# Expected: DNS:*.gateway..connectors.joinformal.com
```
## Grant access to the Catalog
Users finish setup in the employee Catalog at [catalog.formal.ai](https://catalog.formal.ai). There, they approve MCP clients during OAuth sign-in and link their upstream accounts. In [Permissions](/docs/guides/core-concepts/permissions), you control these calls through the **Catalog** application.
If a permission uses `default allow := false`, add a rule for the Catalog to that same permission:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app
import future.keywords.if
import future.keywords.in
default allow := false
# ...your existing rules...
allow if {
input.app.name == "Catalog"
}
```
To limit the Catalog to one group, add a permission that denies everyone else:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app
import future.keywords.if
import future.keywords.in
default allow := true
allow := false if {
input.app.name == "Catalog"
not "mcp-users" in input.user.groups
}
```
Keep these constraints in mind:
* Only active human users can approve an MCP client. Machine users authenticate with [identity headers](#identity-headers) instead.
* Administrators who add servers need the **Mcp** and **Resource** applications. To edit **Access Controls** on a server, they also need **Policies**.
## Add an MCP server
1. Go to [Catalog](https://app.formal.ai/catalog) and click **Add MCP**.
2. Pick a template, or click **Add Custom** and enter the server's URL.
3. Enter a **Name**. Formal uses it as the server's subdomain on the Connector.
4. Under **Authentication**, choose how Formal authenticates to the server. See [Upstream authentication](#upstream-authentication).
5. Save the server.
Use a valid DNS label for the name: lowercase letters, digits, and hyphens, up to 63 characters. Formal can't give a server with any other name a Connector address.
You can also create the server with Terraform. Set the `mcp-endpoint` tag to the server's MCP path:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_resource" "linear" {
name = "linear"
technology = "mcp"
hostname = "mcp.linear.app"
port = 443
tags = {
"mcp-endpoint" = "/mcp"
}
}
```
Then set its authentication in the Control Plane:
1. Open the server in the [Catalog](https://app.formal.ai/catalog).
2. On the **Configuration** tab, under **Authentication**, choose an option for **How does Formal authenticate to this server?**
3. For **Linked accounts**, choose an **OAuth app**. For **Shared credentials**, click **Add credential**.
4. Click **Save authentication**.
**Verify:**
1. Open the server in the Catalog. On its **Configuration** tab, you'll see a **Connector address**, such as `https://linear.gateway..connectors.joinformal.com/mcp`. If the address is missing, the reason appears in its place.
2. If you chose linked accounts or shared credentials, confirm that Formal is listed as the authorization server:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
curl -s https://linear.gateway..connectors.joinformal.com/.well-known/oauth-protected-resource | jq .
# Expected: "resource" is the server's address, and "authorization_servers" lists Formal
```
## Connect MCP clients
Pick the URL based on where the client runs:
| Client runs | Uses | Example |
| - | - | - |
| In the vendor's cloud (Claude, ChatGPT) | The Connector address | `https://linear./mcp` |
| On a device with the Formal Endpoint | The server's original URL | `https://mcp.linear.app/mcp` |
| On a device without the Endpoint | The Connector address | `https://linear./mcp` |
Users can find the right URL and setup steps for over 40 clients on each server's page in the [Catalog](https://catalog.formal.ai). The steps account for whether their device runs the Endpoint.
In the examples below, the server is named `linear`, and the Connector hostname is `gateway.acme.connectors.joinformal.com`.
### Claude and ChatGPT
Claude and ChatGPT call MCP servers from Anthropic's and OpenAI's clouds, so they always use the Connector address. A workspace owner adds the server once for the organization. Each member then connects with their own Formal account.
The **Add to Claude** and **Add to ChatGPT** buttons appear under **Add to your organization's AI apps** in the server's **Authentication** section. You'll see them once the server uses linked accounts or shared credentials and has a Connector address.
To add the server for your organization:
1. In the [Catalog](https://app.formal.ai/catalog), open the server and go to the **Configuration** tab.
2. Under **Add to your organization's AI apps**, click **Add to Claude**. Claude opens its organization connector settings with the server's name and Connector address filled in.
3. As a Claude Owner, confirm the connector.
Each member then connects:
1. In Claude, open **Settings** → **Connectors**, find the server, and click **Connect**.
2. Sign in to Formal and approve Claude on the Catalog consent screen.
3. In a new conversation, enable the connector from the tools menu.
To add the server for your organization:
1. In the [Catalog](https://app.formal.ai/catalog), open the server and go to the **Configuration** tab.
2. Under **Add to your organization's AI apps**, click **Add to ChatGPT**. Formal copies the Connector address and opens your workspace's app settings in ChatGPT.
3. Choose **Create**, paste the address as the MCP server URL, and publish the app.
Each member then connects:
1. In ChatGPT, find the app under **Settings** → **Apps** and click **Connect**.
2. Sign in to Formal and approve ChatGPT on the Catalog consent screen.
3. In a new chat, enable the app from the tools picker.
If you restrict inbound traffic to the Connector, allow the IP ranges that Anthropic and OpenAI publish for outbound connector traffic.
### Claude Code
Use the server's original URL. The Endpoint routes it through the gateway and adds your identity.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
claude mcp add --transport http linear https://mcp.linear.app/mcp
```
If the server uses linked accounts, link your account in the Catalog first.
Use the Connector address:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
claude mcp add --transport http linear https://linear.gateway.acme.connectors.joinformal.com/mcp
```
Start Claude Code, run `/mcp`, and select `linear`. Sign in to Formal in your browser and approve Claude Code.
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
claude mcp get linear
# Expected: linear shows as connected
```
### Codex
Use the server's original URL:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
codex mcp add linear --url https://mcp.linear.app/mcp
```
If the server uses linked accounts, link your account in the Catalog first.
Use the Connector address, then sign in:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
codex mcp add linear --url https://linear.gateway.acme.connectors.joinformal.com/mcp
codex mcp login linear
```
Sign in to Formal in your browser and approve Codex.
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
codex mcp list
# Expected: linear is listed and enabled
```
Then ask Codex to list the server's tools. Each call appears in [Logs](https://app.formal.ai/logs) with your Formal identity.
## Governance
Every request through the gateway carries a Formal identity, so you can control who uses each server and which tools they can call.
### Restrict access and tools
1. Go to [Catalog](https://app.formal.ai/catalog) and open the server.
2. Open the **Access Controls** tab.
3. Under **Who can use this MCP?**, choose **Everyone**, **Specific users**, or **No one**. With **Specific users**, add users and groups.
4. Under **Which tools can they use?**, choose **All tools** or **Specific tools**. With **Specific tools**, select the allowed tools. Use the **Read-only** and **Destructive** filters to find tools by the hints each server publishes.
5. Optionally, edit **Message shown when access is denied**.
6. Click **Save changes**.
Formal saves these controls as a policy named `MCP access controls: `. The Connector blocks requests from anyone outside the allowed users and groups. It also blocks `tools/call` requests for tools outside the list. Clients may still list those tools, but calls to them fail with the block message.
The **Access Controls** tab shows only the policy it created. Other policies can also restrict the server.
### Write custom policies
For rules the tab doesn't cover, write a [policy](/docs/guides/policies/introduction) on MCP traffic. Policies can read each call's tool name and parameters. See [MCP resources](/docs/guides/core-concepts/resources/mcp#policy-evaluation) for an example and [MCP policy inputs](/docs/guides/policies/evaluation#mcp-resources) for every field.
### Review usage
Tool calls appear in [Logs](https://app.formal.ai/logs) with the user, server, and tool name. In the Catalog, a server's **Usage** tab breaks down its requests by tool and by user.
## Troubleshooting
**Possible causes:**
* The Connector has no hostname, or no listener routes MCP
* The Connector is in a different Space from the server
* The server name isn't a valid DNS label
**Fix:** Fix the cause listed in the Catalog, then reload the server.
**Possible causes:**
* The certificate covers only the bare hostname, not `*.`
* No wildcard DNS record exists
**Fix:** Re-run the `dig` and `openssl` checks in [Deploy the Connector](#deploy-the-connector).
**Possible causes:**
* The client doesn't support Client ID Metadata Documents
* The client's redirect URI isn't in its metadata document
**Fix:** Use the Formal Endpoint or identity headers for that client.
**Cause:** A permission denies the **Catalog** application for this user.
**Fix:** Allow `input.app.name == "Catalog"` in every permission that uses `default allow := false`. See [Grant access to the Catalog](#grant-access-to-the-catalog).
**Possible causes:**
* The server doesn't support CIMD with public clients
* A pre-registered app is missing Formal's redirect URI
**Fix:** Run the CIMD metadata check in [Linked accounts](#linked-accounts-oauth). If the server doesn't support CIMD, switch to **Your OAuth app**.
**Cause:** The server uses linked accounts, and the user hasn't linked one, or the link expired.
**Fix:** Link or re-link the account on the server's page in the Catalog.
## Next Steps
Write policies on MCP tool calls
Manage shared credentials
Route Endpoint traffic to the Connector
Control who can use the Catalog
# Operations
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/operations
Upgrades, health checks, and disaster recovery for Connectors
Once a Connector is deployed, keeping it running smoothly requires occasional maintenance. This guide covers day-to-day operational tasks: upgrades, health monitoring, and what to do when things go wrong.
## Upgrading the Connector
The Connector is distributed as a Docker image and follows [semantic versioning](https://semver.org/) (e.g., `2.21.1`). Find the latest version in the [Connector changelog](/docs/changelog/connector). Updates are applied by pulling the latest image and restarting the container; there is no in-place auto-update mechanism.
Configuration changes (policies, resources, listeners, users, etc.) are pushed from the Control Plane to the Connector in real time: they usually don't require a restart or an upgrade.
### Version Checking
Before upgrading, it's useful to know what version is currently running. The running version can be checked through:
* **OTLP Metrics**: the `service.version` attribute on all emitted metrics
* **Connector Logs**: the version is logged at startup
* **Control Plane**: the "Connected Instances" section on the Connector page shows the version
### Upgrade Procedure
1. Update the Connector image tag in your ECS task definition
2. Deploy the new task definition
3. ECS performs a rolling update: it starts new tasks with the new image, waits for them to pass health checks, then drains and stops old tasks
4. Verify the new version is running via [health metrics](/docs/guides/core-concepts/connectors/health)
1. Update the image tag in your Helm values or Deployment manifest
2. Apply the change (`helm upgrade` or `kubectl apply`)
3. Kubernetes performs a rolling update: new pods start, pass readiness checks, then old pods are terminated
4. Verify the new version via [health metrics](/docs/guides/core-concepts/connectors/health)
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Example: Helm values
image:
tag: "2.21.1" # Update to new version
```
1. Pull the new image
2. Stop the running container
3. Start a new container with the same configuration
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
export FORMAL_CONNECTOR_IMAGE="654654333078.dkr.ecr.us-east-1.amazonaws.com/formalco-prod-connector:2.21.1"
# Or use GCP Artifact Registry:
# export FORMAL_CONNECTOR_IMAGE="us-docker.pkg.dev/formal-public-assets/formalco-prod-connector/formalco-prod-connector:2.21.1"
docker pull "$FORMAL_CONNECTOR_IMAGE"
docker stop formal-connector
docker run -d --name formal-connector \
-e FORMAL_CONTROL_PLANE_API_KEY=your-api-token \
"$FORMAL_CONNECTOR_IMAGE"
```
Standalone Docker deployments have downtime during the restart. Use ECS or Kubernetes for zero-downtime upgrades.
## Lifecycle
The Connector is designed for zero-downtime deployments.
### Health Checks
The Connector exposes two HTTP health check endpoints on port `8080`:
| Endpoint | Purpose | Success | Failure |
| - | - | - | - |
| `GET /health` | Liveness probe: is the process running? | `200 OK` | No response |
| `GET /ready` | Readiness probe: is initialization complete? | `200 OK` | `503 Service Unavailable` |
The readiness endpoint returns `503` until the Connector has connected to the Control Plane, loaded its configuration, and started all listeners.
The [Quickstart](/docs/guides/getting-started/quickstart) deployment options come with probes already configured to make the best use of these health check endpoints.
### Graceful Shutdown
When the Connector receives a SIGTERM or SIGINT signal (e.g., during a rolling update or manual stop), it performs a graceful shutdown:
1. Stops accepting new connections on all listeners
2. Leaves the cluster gracefully, notifying other Connector instances
3. Stops the telemetry exporter
4. Closes the cache and cleans up resources
5. Shuts down the health check server
Use multiple Connector instances behind a load balancer to ensure clients can reconnect to a healthy instance during rolling updates.
## Disaster Recovery
The Connector is designed to be mostly **stateless**, which makes disaster recovery straightforward. All configuration (policies, resources, users, etc.) is stored in the Control Plane and pushed to the Connector on startup and in real time, so any lost instance can simply be replaced.
The only local data that can be lost is:
| Data | Location | Impact |
| - | - | - |
| **Log spool** | `/formal/logs` | Buffered logs are lost if the disk isn't persistent |
| **Cluster state** | In-memory | Rate limit counters and cache reset; rebuilds via gossip on rejoin |
When running multiple instances with [clustering](/docs/guides/core-concepts/connectors/clustering) enabled, cluster state is shared and rebuilds automatically on rejoin.
### Recovery Scenarios
**Scenario**: A Connector instance stops unexpectedly.
**Impact**: Clients connected to the failed instance are disconnected.
**Recovery**:
1. The orchestrator (ECS/Kubernetes) automatically starts a replacement instance
2. The new instance connects to the Control Plane and loads configuration
3. Clients reconnect via the load balancer to a healthy instance
4. If [clustering](/docs/guides/core-concepts/connectors/clustering) is enabled, the new instance rejoins the cluster and receives state replay
**Data loss**: In-memory state (rate limit counters, cache) is lost unless clustering is enabled. Buffered logs on persistent disk are recovered on restart.
**Scenario**: All Connector instances stop unexpectedly.
**Impact**: All client connections are dropped.
**Recovery**:
1. The orchestrator starts new instances
2. Each instance connects to the Control Plane and loads full configuration
3. Instances discover each other and form a new cluster
4. Clients reconnect via the load balancer
**Data loss**: All in-memory state (rate limit counters, cache) is lost. Buffered logs on disk spool are recovered on restart.
**Scenario**: The Formal Control Plane becomes unavailable.
**Impact**: The Connector continues operating with its last known configuration. Configuration updates are not received, logs are spooled to disk, and new instances cannot start.
Check the public [Formal status page](https://status.formal.ai) for current incidents and Control Plane availability.
**Recovery**:
* The Connector automatically reconnects when the Control Plane is restored
* Pending configuration updates are received
* Buffered logs are flushed
**Data loss**: None, if a persistent disk is available for the log spool.
**Scenario**: Network issues prevent some Connector instances from communicating with each other.
**Impact**: If clustering is enabled, each partition continues operating independently with its own local state; rate limiting and cache sharing only works within each partition.
**Recovery**:
* Nodes rejoin when the partition heals
* State is reconciled via event replay
* Configuration and policies are not affected
**Data loss**: None.
**Scenario**: Network issues prevent the Connector from reaching the Control Plane.
**Impact**: The Connector continues operating with its last known configuration. Configuration updates are not received, logs are spooled to disk, and new instances cannot start.
**Recovery**:
* The Connector automatically reconnects when network access is restored
* Pending configuration updates are received
* Buffered logs are flushed
**Data loss**: None, if a persistent disk is available for the log spool.
### Best Practices for Resilience
Deploy at least 2 Connector instances behind a load balancer for high availability. Distribute across availability zones for fault tolerance.
Set up probes to ensure traffic is only routed to healthy instances. See [Health Checks](#health-checks) for details.
Mount a persistent volume to `/formal/logs` to buffer logs during outages. A typical log entry is \~1 KB — at 100 requests/sec, 10 GB provides over a day of buffer. See [Log Spool](/docs/guides/observability/log-configuration#logs-spool) for details.
Set up alerts on [Connector metrics](/docs/guides/core-concepts/connectors/health): heartbeat gaps, memory usage spikes, connection count anomalies, and missed Control Plane pings.
Always use rolling update strategies (the default in ECS and Kubernetes) to avoid downtime during upgrades. Ensure readiness probes are configured so traffic isn't routed to instances that haven't finished starting.
# Hostname and TLS Configuration
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/tls
How to setup hostnames and TLS for your Connectors
The Formal Connector supports encrypted communication using TLS. You can ensure that communications between clients and the Connector are encrypted and ensure that communications between the Connector and Resources are encrypted.
```mermaid theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sequenceDiagram
participant Client as Client
participant Connector as Connector
participant Resource as Resource
Note over Client, Connector: TLS Encrypted Connection
Client->>Connector: Request
Note over Connector, Resource: TLS Encrypted Connection
Connector->>Resource: Request
Resource-->>Connector: Response
Connector-->>Client: Response
```
## TLS between clients and the Connector
The hostname you configure will be used to reach and use your Connector. It can be managed by Formal or by yourself.
* **Formal-Managed**: The hostname is a subdomain of a domain that belongs to Formal. Formal will create and renew the TLS certificate of your Connector.
* **Customer-Managed**: The hostname is in a DNS zone you control. You are responsible for configuring your DNS records and managing your TLS certificates.
Formal recommends using Formal-managed hostnames for simplified TLS certificate management and automatic renewal.
### Formal-Managed
Formal will automatically generate and renew the TLS certificate for your Connector if the hostname ends with \
`.[NAME_OF_YOUR_ORG].connectors.joinformal.com`.
Using our Terraform provider, you can create such a Connector hostname and specify where it should point to:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_connector_hostname" "main" {
connector_id = formal_connector.main.id
hostname = var.connector_hostname # e.g. "postgres..connectors.joinformal.com"
dns_record = var.dns_record # CNAME record value to point to
}
```
The DNS record you want to set here is the hostname of the load balancer you have deployed to target your Connector.
When creating the Connector hostname with the web interface, just select "Formal-Managed" and fill the form.
### Customer-Managed
For self-managed hostname and TLS, you must provide your TLS certificate to the Connector. You can do so via our APIs, Terraform, or the Control Plane:
## TLS between the Connector and Resources
You can configure TLS settings between the Connector and resources on an individual resource level via our APIs, Terraform, or the Control Plane:
TLS settings for connections between the Connector and Resources are configured per-resource either via the UI or Terraform. The TLS configuration can be set to one of the following options: `disable`, `insecure-skip-verify`, `insecure-verify-ca-only` (verifies certificate chain but not hostname), or `verify-full`.
# Token Encryption
Source: https://docs.formal.ai/docs/guides/core-concepts/connectors/token_encryption
Encrypt HTTP tokens with a customer KMS key on the Connector
# Overview
Token encryption lets HTTP policy `encrypt` and `decrypt` actions seal sensitive values (for example OAuth tokens) when they leave the Formal Connector. Sealed values use the `formalsealed:v1:` envelope format.
The Connector wraps a per-process AES-256 data key with your cloud KMS key encryption key (KEK). This is separate from [log encryption keys](/docs/guides/integrations/encryption_keys), which use asymmetric JWE for log fields.
> **Outcome:** You can register a KMS KEK on a Connector and use policy encrypt/decrypt actions.\
> **Prerequisites:** A Connector, and a KMS key the Connector role can use.
# Configuration via Terraform
Use the `formal_connector_token_encryption_key` resource. One key per Connector.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "aws_kms_key" "formal_token_kek" {
description = "Formal connector token encryption KEK"
key_usage = "ENCRYPT_DECRYPT"
}
resource "formal_connector_token_encryption_key" "example" {
connector_id = formal_connector.example.id
key_provider = "aws-kms"
key_id = aws_kms_key.formal_token_kek.arn
}
```
Grant the Connector task or pod role `kms:Encrypt` and `kms:Decrypt` on the CMK.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
provider "google" {
project = "my-project"
}
resource "google_kms_crypto_key" "formal_token_kek" {
name = "formal-token-kek"
key_ring = "formal-keyring"
purpose = "ENCRYPT_DECRYPT"
version_template {
algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION"
}
}
resource "formal_connector_token_encryption_key" "example" {
connector_id = formal_connector.example.id
key_provider = "gcp-kms"
key_id = google_kms_crypto_key.formal_token_kek.id
}
```
Use a symmetric `ENCRYPT_DECRYPT` CryptoKey. Grant the Connector workload identity Cloud KMS CryptoKey Encrypter/Decrypter on that key.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "azurerm_key_vault_key" "formal_token_kek" {
name = "formal-token-kek"
key_vault_id = "formal-key-vault-id"
key_type = "RSA"
key_size = 2048
key_opts = [
"wrapKey",
"unwrapKey",
]
}
resource "formal_connector_token_encryption_key" "example" {
connector_id = formal_connector.example.id
key_provider = "azure-key-vault"
key_id = azurerm_key_vault_key.formal_token_kek.versionless_id
}
```
Use an RSA key that supports wrap/unwrap (`RSA-OAEP-256`). Grant the Connector managed identity `keys/wrapKey` and `keys/unwrapKey` on that key. Add an access policy or RBAC role for the Connector identity in addition to the Terraform runner policy above.
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
terraform apply
```
Then enable an HTTP policy with `encrypt` / `decrypt` targets and confirm sealed values use the `formalsealed:v1:` prefix.
# IAM requirements
| Provider | Connector permissions |
| - | - |
| AWS KMS | `kms:Encrypt`, `kms:Decrypt` on the CMK |
| GCP KMS | Cloud KMS CryptoKey Encrypter/Decrypter on the CryptoKey |
| Azure Key Vault | `keys/wrapKey`, `keys/unwrapKey` on the key |
# Rotating the KEK
Changing `key_id` (or `key_provider`) on a Connector is a **breaking** change for already-sealed values. Downstream clients that hold `formalsealed:v1:` tokens produced under the previous KEK will not be able to decrypt them after the Connector switches to the new KEK. Plan for client re-authentication (or re-issuance of sealed tokens) when you rotate the KEK.
# Next Steps
Seal Notion MCP OAuth tokens on the Endpoint or Connector
Write encrypt and decrypt actions
Encrypt sensitive fields in logs
# Data Inventory
Source: https://docs.formal.ai/docs/guides/core-concepts/data-inventory
Discover, classify, and label your data with built-in and custom data labels and tags
> **Outcome:** Learn how data labels are assigned, locked, and automatically classified during discovery.
## Overview
Formal automatically discovers and catalogs databases, schemas, tables, and columns across your infrastructure. The data inventory gives you visibility into what data exists, where it lives, and how sensitive it is.
The inventory is populated by the [Data Discovery Satellite](/docs/guides/core-concepts/satellites#data-discovery-satellite), which scans your resources on a schedule. During each scan, columns can be automatically classified with **data labels** that identify sensitive data types.
## List Available Labels
Formal has two places to see available data labels.
* **Label picker:** Open **Data Inventory** and select a column label. The picker shows every label available for manual assignment.
* **Labels:** Open **Data Inventory** and click **Labels** to list and manage [built-in](#built-in-labels) and custom labels.
You can also list labels with the [ListDataLabels API](/api-reference/corev1inventoryservice/listdatalabels).
## Discovery
The Data Discovery Satellite connects to your resources and catalogs their structure:
* **Databases** and **schemas**
* **Tables** and **views**
* **Columns** with data types
Configure discovery schedules per resource with options for frequency (every 6, 12, 18, or 24 hours, or custom cron) and deletion policy for removed schemas.
See [Satellites](/docs/guides/core-concepts/satellites#data-discovery-satellite) for deployment and configuration.
## Data Labels
Data labels identify the type of sensitive data in a column or field (e.g., `email_address`, `social_security_number`, `phone_number`). Use labels in policies to enforce masking, filtering, and access control based on data sensitivity.
Formal ships with built-in labels and supports custom labels with automatic matchers. During discovery, the Data Discovery Satellite applies those matchers to assign labels without manual work.
### Built-in Labels
Formal provides these labels as **Prompt** labels. Use the identifier in policies. Built-in labels appear in **Labels**, so you can edit or delete them like custom labels.
#### Personal and Contact Data
| Identifier | Default prompt |
| - | - |
| `name` | Person Name |
| `date_of_birth` | Date of Birth |
| `date_of_death` | Date of Death |
| `age` | Age |
| `email_address` | Email Address |
| `phone_number` | Phone Number |
| `fax_number` | Fax Number |
#### Location
| Identifier | Default prompt |
| - | - |
| `street_address` | Street Address |
| `city` | City |
| `county` | County |
| `precinct` | Precinct |
| `zip_code` | ZIP Code |
| `geocode` | Geocode |
#### Government Identifiers
| Identifier | Default prompt |
| - | - |
| `social_security_number` | Social Security Number |
| `driver_identification_number` | Driver Identification Number |
| `passport_number` | Passport Number |
| `individual_taxpayer_identification_number` | Individual Taxpayer Identification Number |
| `government_id_number` | Government ID Number |
| `personal_id_number` | Personal ID Number |
#### Healthcare
| Identifier | Default prompt |
| - | - |
| `medical_record_number` | Medical Record Number |
| `health_plan_beneficiary_id` | Health Plan Beneficiary ID |
| `subscriber_id` | Subscriber ID |
| `medical_claim_number` | Medical Claim Number |
| `medical_policy_number` | Medical Policy Number |
| `medical_license_number` | Medical License Number |
| `health_insurance_policy_number` | Health Insurance Policy Number |
| `health_insurance_provider` | Health Insurance Provider |
| `admission_date` | Admission Date |
| `discharge_date` | Discharge Date |
#### Financial
| Identifier | Default prompt |
| - | - |
| `account_number` | Account Number |
| `bank_account_number` | Bank Account Number |
| `bank_routing_number` | Bank Routing Number |
| `credit_card_number` | Credit/Debit Card Number |
| `credit_card_cvv` | Credit Card CVV |
| `international_bank_account_number` | International Bank Account Number |
#### Employment
| Identifier | Default prompt |
| - | - |
| `job_title` | Job Title |
| `department` | Department |
| `company_name` | Company Name |
| `employee_identification_number` | Employee Identification Number |
| `individual_compensation` | Individual Compensation |
| `professional_license_number` | Professional License Number |
| `certificate_license_number` | Certificate License Number |
| `certificate_number` | Certificate Number |
#### Vehicles and Devices
| Identifier | Default prompt |
| - | - |
| `license_plate_number` | License Plate Number |
| `vehicle_serial_number` | Vehicle Serial Number |
| `vehicle_model` | Vehicle Model |
| `device_identifier` | Device Identifier |
| `device_serial_number` | Device Serial Number |
#### Network
| Identifier | Default prompt |
| - | - |
| `url` | URL |
| `ip_address` | IP Address |
| `mac_address` | MAC Address |
#### Credentials and Attachments
| Identifier | Default prompt |
| - | - |
| `username` | Username |
| `password` | Password |
| `sensitive_attachment_name` | Sensitive Attachment Name |
### Custom Labels
Custom labels are labels your organization creates for its own data types. They appear in **Labels** and in the label picker.
### Inventory Path
Every object in the inventory has a **path** that identifies where it lives. Paths are dot-separated strings built from database hierarchy:
| Object type | Path format | Example |
| - | - | - |
| Database | `database` | `app` |
| Schema | `database.schema` | `app.public` |
| Table | `database.schema.table` | `app.public.customers` |
| Column | `database.schema.table.column` | `app.public.customers.email` |
The path does not include the resource name. Formal stores the resource separately on each inventory object.
Regex label matchers run against the column path only. They do not inspect row values.
### When Labels Are Classified
Formal assigns labels in three ways:
**During scheduled discovery.** When the Data Discovery Satellite lists columns for a table, it runs automatic classifiers before writing results to the inventory. Discovery schedules are configured per resource (every 6, 12, 18, or 24 hours, or a custom cron).
**Manually in the Control Plane.** In **Data Inventory**, select a column and choose a label from the dropdown.
**Through the API or Terraform.** Use the inventory APIs or `formal_inventory_object_data_label_link` to set labels programmatically.
Automatic classification only runs during discovery scans. On each scan, discovery re-runs matchers and can assign or update labels on **unlocked** columns. **Locked** columns keep their current label.
Saving a matcher edit in **Labels** does not change any column labels by itself. The updated matcher takes effect on the next discovery scan—for unlocked columns only. Lock a column if you want to keep its label even after you change matchers.
### Label Locking
Locking is a per-column setting. It controls whether **scheduled discovery scans** can change that column's label.
Locking only matters once a column already has a label. It prevents discovery from **overwriting** that label on future scans. Unlabeled columns get labels on the next scan regardless of lock state.
| | Unlocked | Locked |
| - | - | - |
| Column already has a label | Discovery can update the label on the next scan if matchers produce a different result | Discovery keeps the current label; scans do not overwrite it |
| You change the label manually or via Terraform | Allowed | Allowed |
Lock a column when you have set the label yourself and do not want discovery to change it on future scans.
1. Open **Data Inventory** and select a column.
2. Turn on **Lock Label** in the Classification section.
Filter locked columns in the inventory toolbar with the **Locked** filter.
### Automatic Classification
Create custom labels from **Data Inventory** → **Labels**. Each label uses a **matcher** that tells discovery how to classify columns.
Discovery evaluates matchers in two stages:
1. **Regex matchers** run first against the column inventory path.
2. **Prompt matchers** run on columns that regex did not match.
#### Regex Labels
Regex labels match column paths with a regular expression. They do not read row values.
| Setting | Value |
| - | - |
| Matcher type | **Regex** |
| Pattern | A regular expression tested against `database.schema.table.column` |
Example: the pattern `(^|\.)email$` matches `app.public.customers.email` and `warehouse.staging.users.contact_email`.
Regex labels work with only the Data Discovery Satellite deployed. No AI Satellite link is required.
#### Prompt Labels
Prompt labels use natural language to classify columns from metadata and sampled data.
| Setting | Value |
| - | - |
| Matcher type | **Prompt** |
| Prompt | A description of when the label applies, for example "Columns containing personal email addresses" |
For each unmatched column, discovery samples up to 10 row values from the table. The classifier receives the column name, data type, and those samples. It does not scan every row in the table.
Prompt labels require:
* A deployed [Data Discovery Satellite](/docs/guides/core-concepts/satellites#data-discovery-satellite)
* At least one prompt label in **Labels**
* An LLM backend for classification (see below)
* A native user with `SELECT` permission on the tables being scanned. Discovery uses this credential to read sample rows.
You do **not** need an [AI Satellite](/docs/guides/core-concepts/satellites#ai-satellite) for prompt labels. Choose one backend:
**AWS Bedrock (no AI Satellite).** Set this environment variable on the Data Discovery Satellite:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
DATA_DISCOVERY_LABELING_USE_BEDROCK=true
```
Discovery then calls Amazon Bedrock directly using the container's AWS credentials (for example, an ECS task role). Grant the role `bedrock:InvokeModel` access to the Claude Haiku inference profile in your account and region. See [Prompt Classification Backends](/docs/guides/core-concepts/satellites#prompt-classification-backends) for a full deployment example.
**AI Satellite.** Deploy and link an AI Satellite to the Data Discovery Satellite. Use this option when you want classification to run in your own infrastructure instead of calling Bedrock.
If sampling fails (for example, missing `SELECT` permission), regex labels still apply. Prompt labels are skipped for that table.
### Manual Label Management
Use **Labels** in the Control Plane to create, edit, and delete custom labels. You can also manage labels with the [ListDataLabels API](/api-reference/corev1inventoryservice/listdatalabels) or the Terraform `formal_data_label` resource.
### Use Labels in Policies
Reference labels in policies to enforce data-sensitive access control. Labels are available via `col["data_label"]`.
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import rego.v1
default response := {"action": "allow"}
# Mask any column labeled as email_address
response := {"action": "mask_value"} if {
col := input.row[_]
col["data_label"] == "email_address"
}
```
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import rego.v1
default response := {"action": "allow"}
# Mask multiple sensitive label types
response := {"action": "mask_value"} if {
col := input.row[_]
col["data_label"] in ["email_address", "social_security_number", "phone_number"]
}
```
See [Policy Examples](/docs/guides/policies/examples) for more patterns.
## Tags
Tags are custom tags you define for organizing inventory objects. Unlike data labels, tags are not used for automatic classification by the AI Satellite.
Use tags to categorize data by team, environment, compliance scope, or any other dimension relevant to your organization. Tags are always custom.
Tags are also available in policy inputs. You can use them to write policies that target specific columns or files based on your own categories.
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import rego.v1
default response := {"action": "allow"}
response := {"action": "mask_value"} if {
col := input.row[_]
"finance" in col["tags"]
}
```
## Next Steps
Set up AI and Data Discovery Satellites for automatic classification
Use labels to enforce masking and access control
See real-world policies using data labels
Manage labels and inventory as code
# Identities
Source: https://docs.formal.ai/docs/guides/core-concepts/identities
Manage users and groups
## Overview
This page covers how to create and manage identities in Formal.
There are two types of identities in Formal:
* **Users**: Individual human or machine identities
* **Groups**: Collections of users for streamlined permission management
**Native Users are not identities**: Native users are a separate concept from identities.
While identities (users and groups) represent who is accessing resources through Formal, native users hold
the credentials Connectors use to authenticate to the underlying resources (databases, servers, etc.).
To learn more about native users, refer to [our Native Users documentation](/docs/guides/core-concepts/resources/native-users).
## Users
Users represent identities that connect to resources through Formal Connectors. Every query, command, or API call can be attributed to a specific user for auditing and policy enforcement.
### User Types
**Human Users** are for employees who need direct access to protected resources.
They can be invited manually but often organizations provision those users via SCIM or SSO (Formal supports JIT provisioning via SSO).
They can then retrieve their username and access token from the Control Plane to connect through Connectors.
**Use cases:**
* Engineers querying production databases
* Analysts accessing data warehouses
* DevOps teams managing infrastructure via SSH
**Machine Users** are for applications, BI tools, and automated systems that
connect to resources. Unlike human users, machine users don't receive email
invitations and can have longer-lived credentials.
**Use cases:**
* Internal applications querying databases
* BI tools like Looker, Metabase, Tableau
* CI/CD pipelines accessing infrastructure
* ETL jobs moving data
* AI code agents
### Creating Users
Go to [Users](https://app.formal.ai/users) in the Control Plane
Select **Create User**Choose **Human** or **Machine**
* **Name**: Full name or application name - **Email**: For human users
(receives credentials) - **Temporary**: (Optional) Set expiration time for
temporary access - **Groups**: (Optional) Add to groups
Temporary users are automatically deleted after the specified time—perfect for
contractors or time-limited access grants.
### Access Tokens
Formal decouples credentials from underlying resources, enabling each user to have their own set of short-lived, rotating credentials.
#### Default Validity Period
**7 days** - Access tokens automatically expire and must be refreshed.
#### Configuring Token Validity
To adjust the validity period for your organization, contact Formal support. Organization administrators are authorized to make this request.
### Accessing Formal Credentials
1. Navigate to [the Control Plane](https://app.formal.ai)
2. In the sidebar footer, click **Access Token**
3. Copy your credentials (username and access token)
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal auth credentials
```
This displays your Formal username and
access token for connecting through Connectors.
#### Passing Formal Credentials
Connect to resources using your Formal credentials:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# PostgreSQL example
psql -h connector.joinformal.com -p 5432 \
-U "idp:formal:human:alice@example.com" \
-d production_db
# Password:
```
#### Using Hashed Access Tokens
Some database clients have limitations with password length. For example, the standard `mysql` and `mariadb` CLI clients don't support passwords longer than 80 characters when using the `-p` (interactive password) option. Since Formal access tokens typically exceed this limit, you'll need to use hashed tokens instead.
**What are hashed tokens?**
Hashed tokens are shortened versions of your full access tokens that are compatible with clients that have password length restrictions. They maintain the same security properties while working within these constraints.
**How to obtain a hashed token:**
1. Navigate to [the Control Plane](https://app.formal.ai)
2. In the sidebar footer, click **Access Token**
3. Click **Show Hashed Token** to view your hashed token
4. Copy the hashed token for use with restricted clients
**When to use hashed tokens:**
* MySQL/MariaDB CLI clients with the `-p` flag
* Other database clients that limit password length
* Legacy applications with password field restrictions
Hashed tokens have the same access level as your full access token. Treat them with the same security precautions and never share them.
### Using End User Identities
There may be times when a machine identity interacting with the Connector is itself making requests on behalf of *end users*. For example, you may
want to control access to a Retool dashboard. Various technologies support the notion of *end user identities* where the machine user can map a request
to a separate Formal user identity. For more information on how to specify the end user, refer to the resource page for that particular technology.
End user identities do not require the machine identity to have access to the end user identity's access token. As a result,
when looking at end user identities, the machine identity needs to be trusted when writing policies against end users.
**External IDs** solve this by mapping end-user identities from external systems to Formal users:
Map the user's ID in your BI tool (e.g., Looker user ID 12345) to their
Formal user
Update your application to propagate the end-user ID when connecting through
Formal. Formal provides SDKs to simplify this:
* [formal-sqlcommenter for Python/Django](https://pypi.org/project/formal-sqlcommenter/)
* Built-in integrations for Metabase, Looker, and more
Check [Logs](/docs/guides/observability/logs) to confirm queries are
attributed to the correct end user
This enables accurate auditing and per-user policy enforcement even when using shared application credentials.
## Groups
Groups allow you to apply policies and native user assignments to multiple users simultaneously, streamlining permission management.
Write policies once for a group instead of per-user
Ensure all group members have the same permissions
Assign database credentials at the group level
Users can belong to multiple groups
### Creating Groups
Go to [Groups](https://app.formal.ai/groups) in the Control Plane
Click **Create Group**
Choose a descriptive name (e.g., "Engineering Team", "Analytics", "External
Contractors")
Select users to add to the group
### Managing Group Membership
Click on a group in the Groups list
Click **+ Add Member** to add users, or remove existing members from the
group details page
A user can be part of multiple groups. Their effective permissions are the
union of all group policies plus any user-specific policies.
## Best Practices
Create groups that match your organizational structure (teams, departments,
roles). This makes policy management scalable as your organization grows.
Set expiration dates for external contractors or time-limited access needs.
Formal automatically cleans up expired users.
Ensure end-user accountability even when using shared application credentials.
This is critical for compliance and security auditing.
Never share human user credentials with applications. Create dedicated machine
users for each application or service.
Prefer group-based native user assignments over individual assignments. This
reduces configuration complexity.
## Configuration
Manage users and groups through the Control Plane at:
* [Users](https://app.formal.ai/users)
* [Groups](https://app.formal.ai/groups)
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Create users
resource "formal_user" "alice" {
name = "Alice Smith"
email = "alice@example.com"
type = "human"
}
resource "formal_user" "bi_tool" {
name = "Looker Application"
type = "machine"
}
# Create groups
resource "formal_group" "engineering" {
name = "Engineering Team"
}
# Add users to groups
resource "formal_group_user_link" "alice_eng" {
group_id = formal_group.engineering.id
user_id = formal_user.alice.id
}
```
## Understanding Identities in Logs
Individual logs will specify which identity was used. If a formal identity was used,
the resulting `user` field will have `user.type` equal to `formal`. In addition,
`user.formal` will be populated with the identity. `user.formal.type` will specify whether the identity will be `human` or `machine`.
If a Formal identity is not specified, `user.formal` will be empty and `user.type` will be `native`.
## Next Steps
Map Formal users to resource credentials
Control access based on users and groups
Enable end-user attribution for BI tools
See what users are accessing in real-time
# Custom Resources
Source: https://docs.formal.ai/docs/guides/core-concepts/kubernetes-operator/custom-resources
Reference for the Formal Kubernetes Operator CRDs
## FormalResource
Registers an in-cluster service as a Formal resource.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
apiVersion: joinformal.com/v1
kind: FormalResource
metadata:
name: orders-database
spec:
technology: postgres
hostname: postgres.default.svc.cluster.local
port: 5432
```
### Spec
| Field | Type | Required | Description |
| - | - | - | - |
| `technology` | string | Yes | Protocol type (`postgres`, `mysql`, `mongodb`, `http`, etc.) |
| `hostname` | string | Yes | In-cluster hostname of the backend service |
| `port` | int | Yes | Port of the backend service |
| `spaceId` | string | No | ID of the [Space](/docs/guides/core-concepts/spaces) to assign the resource to |
| `tls` | object | No | TLS configuration for the backend connection |
### TLS Configuration
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
spec:
tls:
mode: verify-full
minVersion: "TLSv1.2"
caSecretRef:
name: my-ca-secret
key: ca.crt
```
| Field | Type | Required | Description |
| - | - | - | - |
| `tls.mode` | string | Yes | `disable`, `insecure-skip-verify`, `insecure-verify-ca-only` (verifies the chain but not the hostname), or `verify-full` |
| `tls.minVersion` | string | No | Minimum TLS version: `TLSv1.2` or `TLSv1.3` |
| `tls.caSecretRef.name` | string | No | Kubernetes Secret containing the CA certificate |
| `tls.caSecretRef.key` | string | No | Key within the Secret |
`tls.mode` is required whenever you set `tls`.
***
## FormalListener
Creates a listener on an existing Formal connector with routing rules.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
apiVersion: joinformal.com/v1
kind: FormalListener
metadata:
name: postgres
spec:
connectorId: connector_abc123
name: postgres
port: 5432
rules:
- type: technology
technology: postgres
```
### Spec
| Field | Type | Required | Description |
| - | - | - | - |
| `connectorId` | string | Yes | ID of the Formal connector |
| `name` | string | Yes | Display name of the listener |
| `port` | int | Yes | Port the listener binds to on the connector |
| `rules` | list | No | Routing rules for the listener |
### Rules
Each rule routes traffic to one or more resources:
| Field | Type | Required | Description |
| - | - | - | - |
| `type` | string | Yes | `resource` or `technology` |
| `resourceRef` | string | No | Name of a FormalResource in the same namespace (for `resource` rules) |
| `technology` | string | No | Technology name (for `technology` rules) |
When using `resource` rules, the operator resolves the `resourceRef` to the Formal resource ID automatically.
The connector itself is not managed by the operator. Create the connector via
[Terraform](/docs/guides/configuration/terraform) or the [Control Plane](https://app.formal.ai/connectors), then reference its ID in
FormalListener resources.
***
## FormalNativeUser
Creates a legacy Native User on a Formal Resource. Credentials come from a
Kubernetes Secret.
This CRD supports only legacy Native Users. See
[Legacy Native Users](/docs/guides/core-concepts/resources/native-users-legacy)
for the selection and identity-link model used here.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
apiVersion: joinformal.com/v1
kind: FormalNativeUser
metadata:
name: orders-database-default
spec:
resourceRef: orders-database
type: basic
useAsDefault: true
secretRef:
name: postgres-credentials
keys:
username: username
password: password
```
### Spec
| Field | Type | Required | Description |
| - | - | - | - |
| `resourceRef` | string | Yes | Name of a FormalResource in the same namespace |
| `type` | string | Yes | See [supported types](#supported-types) below |
| `useAsDefault` | bool | No | Whether this is the default native user for the resource |
| `identityLinks` | list | No | See [identity links](#identity-links) below |
The operator watches referenced Secrets. When a Secret is updated, the
operator automatically propagates the new credentials to Formal.
### Supported Types
#### `basic`
Username and password credentials, sourced from a Kubernetes Secret.
| Field | Type | Required | Description |
| - | - | - | - |
| `secretRef.name` | string | Yes | Name of the Kubernetes Secret |
| `secretRef.keys.username` | string | Yes | Key in the Secret containing the username |
| `secretRef.keys.password` | string | Yes | Key in the Secret containing the password |
#### `iam`
Cloud IAM authentication.
| Field | Type | Required | Description |
| - | - | - | - |
| `iam.cloudProvider` | string | Yes | Cloud provider (`aws` or `gcp`) |
| `iam.username` | string | Yes | IAM username for authentication |
| `iam.role` | string | No | IAM role ARN or identifier (if omitted, uses the environment's ambient IAM credentials) |
#### `kubernetes`
Kubernetes authentication via kubeconfig.
| Field | Type | Required | Description |
| - | - | - | - |
| `kubernetes.kubeconfigEnv` | string | Yes | Environment variable name containing kubeconfig |
#### `ssh_key`
SSH key authentication, with the private key sourced from a Kubernetes Secret.
| Field | Type | Required | Description |
| - | - | - | - |
| `sshKey.username` | string | Yes | SSH username |
| `sshKey.secretRef.name` | string | Yes | Secret containing the private key |
| `sshKey.secretRef.key` | string | Yes | Key within the Secret |
#### `snowflake_key`
Snowflake key-pair authentication, with the private key sourced from a Kubernetes Secret.
| Field | Type | Required | Description |
| - | - | - | - |
| `snowflakeKey.username` | string | Yes | Snowflake username |
| `snowflakeKey.secretRef.name` | string | Yes | Secret containing the private key |
| `snowflakeKey.secretRef.key` | string | Yes | Key within the Secret |
#### `http_basic`
HTTP Basic authentication, with credentials sourced from a Kubernetes Secret.
| Field | Type | Required | Description |
| - | - | - | - |
| `httpBasic.header` | string | Yes | HTTP header name (e.g., `Authorization`) |
| `httpBasic.secretRef.name` | string | Yes | Name of the Kubernetes Secret |
| `httpBasic.secretRef.keys.username` | string | Yes | Key in the Secret containing the username |
| `httpBasic.secretRef.keys.password` | string | Yes | Key in the Secret containing the password |
#### `http_bearer`
HTTP Bearer token authentication, with the token sourced from a Kubernetes Secret.
| Field | Type | Required | Description |
| - | - | - | - |
| `httpBearer.header` | string | Yes | HTTP header name (e.g., `Authorization`) |
| `httpBearer.secretRef.name` | string | Yes | Secret containing the token |
| `httpBearer.secretRef.key` | string | Yes | Key within the Secret |
#### `http_api_key`
HTTP API key authentication, with the value sourced from a Kubernetes Secret.
| Field | Type | Required | Description |
| - | - | - | - |
| `httpApiKey.location` | string | Yes | `header` or `query_parameter` |
| `httpApiKey.key` | string | Yes | Header or query parameter name |
| `httpApiKey.secretRef.name` | string | Yes | Secret containing the API key value |
| `httpApiKey.secretRef.key` | string | Yes | Key within the Secret |
### Identity Links
`identityLinks` binds Formal identities to the native user. Each entry is resolved by name against the Formal control plane:
| Field | Type | Required | Description |
| - | - | - | - |
| `type` | string | Yes | `user`, `group`, or `resource_hostname` |
| `name` | string | Yes | Name of the identity to link (see resolution rules below) |
| `terminationProtection` | bool | No | If `true`, the link cannot be deleted |
Resolution rules per `type`:
* `group`: matched against `Group.name`
* `user`: matched against `User.db_username`
* `resource_hostname`: matched against `ResourceHostname.name` within the parent resource
Links not present in `identityLinks` on reconcile are removed, so the spec is the source of truth.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
spec:
resourceRef: orders-database
type: basic
secretRef:
name: postgres-credentials
keys:
username: username
password: password
identityLinks:
- type: group
name: db-readers
- type: group
name: orders-admins
terminationProtection: true
```
***
## FormalPolicy
Creates and manages a Formal policy from Rego code.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
apiVersion: joinformal.com/v1
kind: FormalPolicy
metadata:
name: data-eng-postgres-read
spec:
description: Read-only Postgres access for the data-eng team
status: active
terminationProtection: false
code: |
package formal.v2
import future.keywords.if
import future.keywords.in
session := {
"action": "block",
"type": "block_with_formal_message",
"reason": "Access restricted to the data-eng team"
} if {
input.resource.technology == "postgres"
input.resource.tags["team"] == "data-eng"
not "data-eng" in input.user.groups
}
request := {
"action": "block",
"type": "block_with_formal_message",
"reason": "Read-only access for the data-eng team"
} if {
input.resource.tags["team"] == "data-eng"
input.query.statement_type != "SELECT"
}
```
### Spec
| Field | Type | Required | Description |
| - | - | - | - |
| `description` | string | Yes | Description shown for the policy |
| `status` | string | Yes | Policy mode: `draft`, `dry-run`, or `active` |
| `code` | string | Yes | Policy logic written in Rego |
| `terminationProtection` | bool | No | Prevents policy deletion when `true`; defaults to `false` |
The operator uses `metadata.name` as the Formal policy name.
It synchronizes spec changes and stores the Formal policy ID in `status.policyId`.
If the policy is deleted in Formal, the operator recreates it during reconciliation.
Deleting the custom resource also deletes the policy from Formal.
Disable `terminationProtection` before deleting a protected policy.
**Verify:**
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
kubectl get formalpolicy data-eng-postgres-read \
-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}{"\n"}'
# Expected: True
```
# Deployment
Source: https://docs.formal.ai/docs/guides/core-concepts/kubernetes-operator/deployment
Deploy the Formal Kubernetes Operator using Helm
The operator is distributed as a Helm chart. Its image is available from ECR and GAR.
## Installation
Add the Formal Helm repository:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm repo add formal https://formalco.github.io/helm-charts
```
Install the operator with an API key:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm install formal-kubernetes-operator formal/kubernetes-operator \
--set formalAPIKey=
```
The chart stores `formalAPIKey` in a Secret, so the key is also in the Helm release. To keep the key out of Helm, create the Secret yourself and reference it with `formalAPIKeySecret`. The chart does not create or change that Secret, so External Secrets or another controller can manage it.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
kubectl create secret generic formal-operator-api-key \
--namespace formal \
--from-literal=api-key=
helm install formal-kubernetes-operator formal/kubernetes-operator \
--namespace formal \
--set formalAPIKeySecret.name=formal-operator-api-key \
--set formalAPIKeySecret.key=api-key
```
Or authenticate with OIDC. Create an [OIDC integration](/docs/guides/integrations/oidc) first.
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm install formal-kubernetes-operator formal/kubernetes-operator \
--set oidc.integration_id=integrationoidc_ \
--set-json 'oidc.aws={}'
```
This will:
1. Install the CRDs (FormalResource, FormalListener, FormalNativeUser, FormalPolicy)
2. Create a ServiceAccount with the necessary RBAC permissions
3. Deploy the operator
Configure exactly one auth method: `formalAPIKey`, `formalAPIKeySecret`, or `oidc`.
## Configuration
| Parameter | Default | Description |
| - | - | - |
| `formalAPIKey` | `""` | Formal API key. The chart stores it in a Secret. Conflicts with `formalAPIKeySecret` and `oidc`. |
| `formalAPIKeySecret.name` | `""` | Existing Secret that holds the API key. Requires `formalAPIKeySecret.key`. Conflicts with `formalAPIKey` and `oidc`. |
| `formalAPIKeySecret.key` | `""` | Key in that Secret. Requires `formalAPIKeySecret.name`. |
| `oidc.integration_id` | `""` | Formal OIDC integration ID. Required with `oidc.aws` or `oidc.azure`. |
| `oidc.aws` | unset | Empty object enables AWS STS. Mutually exclusive with `oidc.azure`. |
| `oidc.azure` | unset | Empty object enables Azure Entra. Mutually exclusive with `oidc.aws`. |
| `serviceAccount.annotations` | `{}` | ServiceAccount annotations (IRSA, Pod Identity, or AKS Workload Identity). |
| `image.repository` | ECR | ECR or GAR image repository |
| `image.tag` | `""` | Docker image tag. Empty uses the operator version pinned by the chart. |
| `image.pullPolicy` | `Always` | Image pull policy |
| `replicaCount` | `1` | Number of operator replicas |
| `pullWithCredentials` | `false` | Use the image pull secret created by a credential helper chart |
| `resources` | See below | CPU/memory requests and limits |
| `metricsPort` | `8080` | Metrics bind port. Written to the operator ConfigMap. |
| `reconcileInterval` | `""` | Reconcile period (for example `5m`). Empty uses `10m`. |
Default resource requests and limits:
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
```
## Upgrading
Each chart release pins a compatible operator image. `helm upgrade` does not update CRDs, so apply them from the new chart version first:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm repo update formal
helm pull formal/kubernetes-operator --untar
kubectl apply --server-side -f kubernetes-operator/crds/
helm upgrade formal-kubernetes-operator formal/kubernetes-operator --reset-then-reuse-values
```
## Image pull credentials
### Azure AKS
On AKS, install `azure-gar-cred` before the operator.
First complete the [AKS identity setup](https://github.com/formalco/helm-charts/tree/main/charts/azure-gar-cred).
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm install formal-azure-gar-cred formal/azure-gar-cred \
--namespace formal \
--create-namespace \
--values azure-gar-cred-values.yaml
helm install formal-kubernetes-operator formal/kubernetes-operator \
--namespace formal \
--set oidc.integration_id=integrationoidc_ \
--set-json 'oidc.azure={}' \
--set pullWithCredentials=true \
--set image.repository=us-docker.pkg.dev/formal-public-assets/formalco-prod-kubernetes-operator/formalco-prod-kubernetes-operator
```
### ECR outside AWS
Install `ecr-cred` before the operator when the cluster lacks native ECR access.
Set `pullWithCredentials=true` on the operator chart.
Do not install `ecr-cred` and `azure-gar-cred` in the same namespace.
## Configuration file
The chart mounts a ConfigMap at `/etc/formal/config.yaml` and sets `FORMAL_CONFIG`. The file holds non-secret settings:
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
metricsAddr: ":8080"
reconcileInterval: 10m
oidc:
integration_id: integrationoidc_...
aws: {}
```
`FORMAL_API_KEY` comes from the chart Secret or from the Secret in `formalAPIKeySecret`. Helm adds a `checksum/config` annotation so ConfigMap changes roll the pod.
**Verify:** The pod has `FORMAL_CONFIG=/etc/formal/config.yaml` and the mounted file matches your values.
## Authentication
Use an API key or OIDC. Do not set both.
### AWS OIDC
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
oidc:
integration_id: integrationoidc_01h...
aws: {}
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm install formal-kubernetes-operator formal/kubernetes-operator \
--set oidc.integration_id=integrationoidc_ \
--set-json 'oidc.aws={}' \
--set serviceAccount.annotations."eks\\.amazonaws.com/role-arn"=arn:aws:iam:::role/
```
Configure AWS credentials through IRSA, EKS Pod Identity, or the standard AWS environment variables.
**Verify:** The operator pod starts and reconciles without `FORMAL_API_KEY`.
### Azure OIDC
Use the `azure` token source on AKS with Workload Identity or on Azure compute with managed identity. Set the integration ID to the ID of your Formal OIDC integration.
```yaml theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
oidc:
integration_id: integrationoidc_01h...
azure: {}
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
helm install formal-kubernetes-operator formal/kubernetes-operator \
--set oidc.integration_id=integrationoidc_ \
--set-json 'oidc.azure={}' \
--set serviceAccount.annotations."azure\\.workload\\.identity/client-id"=
```
Enabling `oidc.azure` labels the pod with `azure.workload.identity/use=true`.
This token source does not support Azure DevOps Pipelines.
**Verify:** The operator pod starts and reconciles without `FORMAL_API_KEY`.
# Introduction
Source: https://docs.formal.ai/docs/guides/core-concepts/kubernetes-operator/introduction
Automatically register in-cluster services as Formal resources
## What is the Kubernetes Operator?
The Formal Kubernetes Operator watches for custom resources in your cluster and automatically manages the corresponding Formal resources, listeners, native users, and policies via the Formal API. This removes the need to manually configure resources via Terraform or the Control Plane when your services already live in Kubernetes.
`FormalNativeUser` creates
[legacy Native Users](/docs/guides/core-concepts/resources/native-users-legacy).
The operator introduces four Custom Resource Definitions (CRDs):
* **FormalResource**: Declares a backend service (e.g., a PostgreSQL database) that should be registered as a Formal resource
* **FormalListener**: Declares a listener on an existing Formal connector with routing rules that point to resources
* **FormalNativeUser**: Declares a legacy Native User, with credentials sourced from a Kubernetes Secret
* **FormalPolicy**: Declares a Formal policy from Rego code
When you create, update, or delete these custom resources, the operator reconciles the desired state with the Formal Control Plane. Finalizers ensure that Formal-side resources are cleaned up when Kubernetes resources are deleted.
## How It Works
```mermaid theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sequenceDiagram
participant K8s as Kubernetes
participant Op as Operator
participant CP as Formal Control Plane
K8s->>Op: FormalResource created
Op->>CP: Create resource
CP-->>Op: Resource ID
Op->>K8s: Update status
K8s->>Op: FormalListener created
Op->>CP: Create listener + rules
CP-->>Op: Listener ID
Op->>K8s: Update status
K8s->>Op: FormalResource deleted
Op->>CP: Delete resource
Op->>K8s: Remove finalizer
```
The operator continuously watches for changes and keeps Formal in sync with your cluster state. Secret updates (e.g., rotated database credentials) are automatically propagated to the corresponding native users in Formal.
## Prerequisites
* A Kubernetes cluster (v1.28+)
* A Formal API key, or an [OIDC integration](/docs/guides/integrations/oidc)
* An existing Formal connector (the operator manages listeners and resources, not connectors themselves)
# Permissions
Source: https://docs.formal.ai/docs/guides/core-concepts/permissions
How to configure user permissions for Formal applications (Control Plane)
This page covers **Control Plane permissions** - policies that control access
to Formal's web interface and APIs. For policies that control data access
through Connectors (like database query policies), see the
[Policies](/docs/guides/policies/introduction) section.
## Introduction
Formal's permission system is built on the [Open Policy Agent (OPA)](https://www.openpolicyagent.org/), allowing you to restrict user access to specific applications through Rego policies. By defining granular permissions, you can ensure users have access only to the applications necessary for their roles and responsibilities. These permissions are enforced against our [API endpoints](/docs/api/introduction).
## Default permissions
Upon account creation, Formal operates under an allow-by-default model. Meaning, by default, if no permission blocks an endpoint for a given user, then the endpoint is allowed.
## Permission Model
Unlike other software products, Formal does not have the traditional concept of Roles. Instead, users can leverage Permissions to create role based access control (RBAC) using your organization's Groups (which can be SCIM provisioned from your IDP into Formal).
## User experience when blocked
The Formal APIs will return HTTP 403 error status codes if a user is blocked on a particular endpoint. Users who are interacting with the Formal APIs through the Control Plane will experience "Forbidden" toasts or dialogs.
## List of inputs
You can block or allow requests based on the following user and application inputs:
### User inputs
* `input.user.id`
* `input.user.name`
* `input.user.email`
* `input.user.groups`
* `input.user.ip_address`
* `input.user.type` (i.e. `human` or `machine`)
* `input.user.aws_principal_arn` — full AWS principal ARN ([AWS SigV4 authentication](/docs/api/aws-sigv4-authentication) machine auth only)
* `input.user.aws_account_id` — AWS account ID ([AWS SigV4 authentication](/docs/api/aws-sigv4-authentication) machine auth only)
`input.user.aws_principal_arn` and `input.user.aws_account_id` are populated
only for requests authenticated with [AWS SigV4](/docs/api/aws-sigv4-authentication).
API key and SSO requests leave these fields empty.
### Application inputs
* `input.app.name`
* `input.app.command.name`
* `input.app.command.type`
The permissions system also supports blocking or allowing requests based on the request payload for any API call based on input arguments (`input.args`).
#### List of command types
Here is a table listing all command types that can be used. If the API call contains the operation, then it is categorized as the corresponding type.
| Type | Operations |
| - | - |
| read | `Get`, `List` |
| create | `Create` |
| update | `Update` |
| delete | `Delete` |
| login | `Login` |
#### List of applications
API endpoints are segmented into **Applications.**
Here is a table listing all applications that can be configured for access permissions:
These applications have [public API endpoints](/api-reference) accessible via API keys or the Control Plane.
| Name | Description |
| - | - |
| [AIAssessment](/api-reference/corev1aiassessmentservice) | Enable AI assessments and manage the labels they assign. |
| [Approval](/api-reference/corev1approvalservice) | Manage access requests and related analytics. |
| [ChangeAuthorization](/api-reference/corev1changeauthorizationservice) | Manage public signing keys used to cryptographically sign and verify API changes. |
| [Connector](/api-reference/corev1connectorservice) | Manage Connector proxy instances and listeners. |
| [Graph](/api-reference/corev1graphservice) | View Formal's data graph. |
| [Group](/api-reference/corev1groupservice) | Manage user groups for role-based access control. |
| [Hook](/api-reference/corev1hookservice) | Manage webhooks and event delivery settings. |
| [Insights](/api-reference/corev1insightsservice) | View security findings and suggested next steps. |
| [IntegrationAIProvider](/api-reference/corev1integrationaiproviderservice) | Manage AI provider integrations (Anthropic, OpenAI, Cursor). |
| [IntegrationBI](/api-reference/corev1integrationbiservice) | Manage BI tool integrations (Metabase, Omni, Fivetran). |
| [IntegrationCloud](/api-reference/corev1integrationcloudservice) | Manage cloud provider integrations (AWS, GCP, Azure). |
| [IntegrationMDM](/api-reference/corev1integrationmdmservice) | Manage Mobile Device Management integrations. |
| [IntegrationOIDC](/api-reference/corev1integrationoidcservice) | Manage OIDC trust configurations for control-plane authentication. |
| [IntegrationsLog](/api-reference/corev1integrationslogservice) | Manage log forwarding (Splunk, Datadog, S3, GCS, Azure Blob). |
| [Inventory](/api-reference/corev1inventoryservice) | Manage discovered schemas, tables, and columns. |
| [Logs](/api-reference/corev1logsservice) | View and manage audit logs and retention settings. |
| [Mcp](/api-reference/corev1mcpservice) | Manage MCP servers and list observed servers that are not yet managed. |
| [NativeUser](/api-reference/corev1nativeuserservice) | Manage Native Users, the credentials Connectors use for Resources. |
| [Permissions](/api-reference/corev1permissionsservice) | Manage OPA permissions for this API. |
| [Policies](/api-reference/corev1policiesservice) | Manage data access policies (masking, blocking, alerts). |
| [PolicyDataLoader](/api-reference/corev1policydataloaderservice) | Manage external data loaders for policy context. |
| [Resource](/api-reference/corev1resourceservice) | Manage datastores and APIs proxied by Formal. |
| [Satellite](/api-reference/corev1satelliteservice) | Manage Satellite deployments for data discovery. |
| [ScenarioMonitoring](/api-reference/corev1scenariomonitoringservice) | Manage real-time alerts and monitoring rules. |
| [Search](/api-reference/corev1searchservice) | Search across resources, connectors, policies, and more. |
| [Sessions](/api-reference/corev1sessionsservice) | View and replay user session recordings. |
| [Skill](/api-reference/corev1skillservice) | Manage AI agent skills and list observed skills that are not yet managed. |
| [Space](/api-reference/corev1spaceservice) | Manage Spaces for organizing resources by team or project. |
| [User](/api-reference/corev1userservice) | Manage users, invitations, and account settings. |
| [Workflow](/api-reference/corev1workflowservice) | Manage automated workflows across Formal. |
These applications are only accessible through the Control Plane and do not have public API documentation.
For Control Plane only applications, we recommend restricting access at the application level rather than by individual command types, since the available commands are not publicly documented.
| Name | Description |
| - | - |
| Access | View and manage personal access tokens. |
| Agent | AI-powered conversational assistant for Formal. |
| AgentObservability | View AI usage, policy actions, and session activity. |
| Catalog | Browse managed MCPs, personal activity, and submit access requests from Catalog. |
| DSPM | Manage DSPM jobs and tasks. |
| Dashboard | View dashboard home page statistics. |
| Desktop | Approve personal Endpoint logins. |
| Developer | Manage developer API keys. |
| DirectorySync | Configure SCIM directory sync with identity providers. |
| Reports | Create and manage security reports and widgets. |
| Slack | Configure Slack notifications and alerts. |
| Sso | Configure SAML Single Sign-On. |
| Version | View latest connector and satellite versions. |
## Example: Single Application
Below is an example of a Rego policy that grants access to the Sessions application exclusively for users in the admin group.
```Rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app
import future.keywords.in
import future.keywords.if
default allow = false
allow := true if {
input.app.name == "Sessions"
"admin" in input.user.groups
}
```
This policy sets the default access to false, meaning no access is granted unless specified by a rule. The allow rule checks if the application requested is Sessions and if the user belongs to the admin group. If both conditions are met, access is granted.
## Example: Granular Access Control
```Rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app
import future.keywords.in
import future.keywords.if
default allow = false
# Allow read access to everyone
allow if {
input.app.command.type == "read"
}
# Allow everyone to get their own Credentials
allow if {
input.app.name == "Access"
}
# Allow full access if the user is a member of the security team
allow if {
"security-admins" in input.user.groups
}
# Allow engineers to access relevant applications
allow if {
input.app.name in ["Desktop"]
"engineers" in input.user.groups
}
```
This multi-rule policy demonstrates how to combine different access levels:
* **Broad read access**: Everyone can view most data using `read` command types.
* **Self-service access**: Everyone can manage their own credentials in the **Access** app.
* **Administrative access**: Users in `security-admins` have unrestricted access to all apps.
* **Team-specific access**: Users in the `engineers` group can access developer-focused tools.
Since `default allow = false` is set, any request that does not match at least one of these `allow` rules is blocked. Formal returns a 403 Forbidden error for these requests.
## Example: Argument-based Access Control
```Rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app
import future.keywords.if
default allow = true
allow := false if {
input.app.command.name == "UpdatePolicy"
input.args.id == "policy_01kg6rhpe7em4rr2v0mcwm0mp4"
not "security-admins" in input.user.groups
}
```
This policy blocks updates to the policy with the ID `policy_01kg6rhpe7em4rr2v0mcwm0mp4` unless the user is in the `security-admins` group.
## Example: Restrict Access to Logs
Viewing Connector, Desktop, Control Plane, and workflow logs all use the **Logs** application. There is no separate permission per log source.
```Rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app
import future.keywords.in
import future.keywords.if
default allow = true
allow := false if {
input.app.name == "Logs"
not "security-admins" in input.user.groups
}
```
Only users in `security-admins` can open [Logs](https://app.formal.ai/logs) or call Logs APIs. For Control Plane audit fields and more permission patterns, see [Control Plane Logs](/docs/guides/observability/control-plane-logs#permissions-for-viewing-logs).
## Example: AWS SigV4 machine access
Use `input.user.aws_principal_arn` and `input.user.aws_account_id` to scope
[AWS SigV4-authenticated](/docs/api/aws-sigv4-authentication) machine users by
AWS principal or account.
```Rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app
import future.keywords.if
default allow = false
allow if {
input.user.type == "machine"
input.user.aws_account_id == "123456789012"
startswith(input.user.aws_principal_arn, "arn:aws:iam::123456789012:role/formal-")
input.app.name == "Resource"
input.app.command.type == "read"
}
```
This policy allows read access to the **Resource** application only for machine
users authenticated from AWS account `123456789012` whose principal ARN starts
with `arn:aws:iam::123456789012:role/formal-`.
# AWS
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/aws
How to access AWS APIs through the Formal Connector
## Overview
An AWS resource lets users access AWS APIs through the Formal Connector without
giving them credentials for the upstream IAM role. Users continue to send
requests to standard AWS service endpoints, such as
`ec2.us-west-2.amazonaws.com`. The Formal Endpoint routes those requests to the
Connector, where Formal evaluates policy and records the activity. The Connector
then re-signs allowed requests with the IAM identity selected by a Native User.
That Native User can select an assumed role or the Connector's ambient AWS
identity.
The AWS CLI and SDKs require an access key ID and secret access key to sign
requests. However, these client-side credentials can be placeholders. They only
produce the initial signature, which the Connector replaces before the request
reaches AWS.
Set up this request path in five parts. Create the AWS resource, configure the
Connector's AWS identity, and configure a Native User. Then make the resource
reachable through a Connector listener and route traffic through the Formal
Endpoint.
The Connector requires an authenticated Formal identity for this request path.
It rejects requests that do not resolve an authenticated Formal identity.
## Create an AWS resource
The resource hostname determines which AWS endpoints it covers. Set it to
`amazonaws.com` with port `443` to cover standard service endpoints across all
commercial AWS regions:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_resource" "aws" {
name = "aws"
technology = "aws"
hostname = "amazonaws.com"
port = 443
}
```
To limit access to one region, use a narrower suffix such as
`us-east-1.amazonaws.com`. Update the Formal Endpoint network rule to match that
suffix. AWS partitions with other domain suffixes require separate resources
and network rules.
## Configure the Connector's AWS identity
Before configuring a Native User, give the Connector an AWS region and ambient
AWS identity. The Connector loads both through the
[AWS SDK for Go default configuration](https://docs.aws.amazon.com/sdk-for-go/v2/developer-guide/configure-gosdk.html).
Set `AWS_REGION` in the Connector's deployment environment. This is the default
region for AWS SDK operations:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
AWS_REGION=""
```
On AWS, configure the ambient identity with an ECS task role, an EC2 instance
profile, or an EKS IAM role for service accounts (IRSA). The SDK loads these
credentials automatically.
For other deployments, use environment variables or mount a shared AWS
credentials file:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
AWS_ACCESS_KEY_ID=""
AWS_SECRET_ACCESS_KEY=""
```
Restart or redeploy the Connector after changing its environment.
The Connector requires credentials that AWS recognizes. Do not use the
client-side placeholder credentials configured later in this guide.
Choose **AWS IAM** to use this ambient identity. Choose **AWS IAM Role** to
assume another role.
## Configure the upstream AWS identity
Every signed request must resolve a Native User. Add one after creating the AWS
resource. The Control Plane opens the **Add Native User** wizard automatically:
1. Select an AWS IAM authentication method:
* **AWS IAM** uses the Connector's ambient AWS identity.
* **AWS IAM Role** assumes the IAM role you specify.
2. Enter a unique **Label**, such as `aws-role`.
3. Enter a descriptive **Username**. Generic AWS API Resources do not use it
when signing requests.
4. For **AWS IAM Role**, enter the role ARN.
5. Create the Native User.
6. Configure
[**Default Native User**](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user)
with the Native User's ID.
If you skipped the wizard, open the Resource's **Authentication** tab and click
**Add User**.
See [Select a default Native User](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user)
for selection examples.
Resources using legacy Native Users store `iam` or a role ARN in the secret
field. See
[legacy IAM authentication](/docs/guides/core-concepts/resources/native-users-legacy#use-legacy-iam-authentication).
In Terraform, use the `aws_iam` block for the Connector's ambient identity:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_native_user_v3" "aws_ambient" {
resource_id = formal_resource.aws.id
label = "aws-ambient"
aws_iam {
username = "aws-ambient"
}
}
```
Use `aws_iam_role` to assume a role:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_native_user_v3" "aws_role" {
resource_id = formal_resource.aws.id
label = "aws-role"
aws_iam_role {
username = "aws-role"
role = "arn:aws:iam::123456789012:role/formal-aws-access"
}
}
```
To select between the two Native Users:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_resource_native_user_selection" "aws" {
resource_id = formal_resource.aws.id
cel = <<-CEL
"aws-admins" in user.groups
? "${formal_native_user_v3.aws_role.id}"
: "${formal_native_user_v3.aws_ambient.id}"
CEL
}
```
The selection above uses the assumed role for members of `aws-admins` and the
Connector's ambient identity for everyone else.
For an assumed role, both sides of the role assumption must allow it. The
Connector's ambient identity needs permission to call `sts:AssumeRole`. The
upstream role's trust policy must also trust that identity.
The AWS identity used to sign the request defines the maximum permissions
available through the resource. The resolved Native User explicitly selects an
assumed role or the Connector's ambient identity. Formal policies can narrow
that access further.
Create additional Native Users for different AWS access levels. Use the
Resource's [default Native User selection](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user)
to choose one for each Formal identity.
The Connector does not use ambient AWS credentials as a fallback. A signed
request fails when no Native User resolves, its configuration is invalid, or
its role cannot be assumed.
## Make the resource reachable through a Connector
The Formal Endpoint needs a Connector and listener port to which it can forward
the intercepted request. A network rule alone does not create this route.
In the Control Plane:
1. Go to **Infrastructure** → **Connectors** and open the Connector that should
proxy the AWS resource.
2. Open **Listeners**, select **New Listener**, enter a **Name** and available
**Port**, then create the listener. Port `8080` is reserved for Connector
health checks.
3. Open the listener and select **New Rule**.
4. Set the rule type to **Resource**, select the AWS resource, and create the
rule.
5. Confirm the AWS resource appears under the Connector's **Resources** tab
(**Reachable Resources**).
See [Listeners and Rules](/docs/guides/core-concepts/connectors/listeners) for
more background.
The equivalent Terraform configuration is:
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_connector_listener" "aws" {
connector_id = formal_connector.main.id
name = "aws-listener"
port = 4027
}
resource "formal_connector_listener_rule" "aws" {
connector_listener_id = formal_connector_listener.aws.id
type = "resource"
rule = formal_resource.aws.id
}
```
The port can be any available Connector listener port other than `8080`.
## Route traffic through the Formal Endpoint
The final part of the request path is a Formal Endpoint network rule. Network
rules require Transparent Mode to be installed and enabled. Complete the
[Transparent Mode setup](/docs/guides/client-apps/desktop-app#transparent-mode)
before creating the rule.
One AWS resource can cover many service hostnames. The rule must preserve each
request's hostname so the Connector knows which AWS service to contact.
For a resource scoped to `amazonaws.com`, create a rule that:
* Matches `amazonaws.com` and hosts ending in `.amazonaws.com`.
* Selects the AWS resource.
* Enables **Forward to Connector**.
**Forward to Connector** must be enabled. Without it, the Formal Endpoint treats
`amazonaws.com` as the upstream host instead of forwarding the original AWS
service hostname to the Connector.
The equivalent CEL configuration is:
```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"condition": {
"pre_tls": hostname == "amazonaws.com" || hostname.endsWith(".amazonaws.com"),
"post_tls": hostname == "amazonaws.com" || hostname.endsWith(".amazonaws.com")
},
"outputs": {
"resource_name": "",
"forward_to_connector": true
}
}
```
Replace `` with the name of the AWS resource you created.
Create the rule under **Infrastructure** → **Network Rules**, and set its status
to **Active**. New rules default to **Draft** and do not route traffic until
activated.
### Configure the AWS CLI
With the resource, Native User, Connector listener, and network rule in place,
create a dedicated AWS CLI profile. Give the profile placeholder credentials
and configure it to trust the Formal Endpoint's CA certificate:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
aws configure set aws_access_key_id FORMALPLACEHOLDER --profile formal-aws
aws configure set aws_secret_access_key formal-placeholder-secret --profile formal-aws
aws configure set region us-east-1 --profile formal-aws
aws configure set ca_bundle "$HOME/.formal/ca/localca-cert.pem" --profile formal-aws
```
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
aws configure set aws_access_key_id FORMALPLACEHOLDER --profile formal-aws
aws configure set aws_secret_access_key formal-placeholder-secret --profile formal-aws
aws configure set region us-east-1 --profile formal-aws
aws configure set ca_bundle /var/lib/formal/ca/localca-cert.pem --profile formal-aws
```
Change `us-east-1` to the region you normally use. Keep the placeholder
credentials as shown: the Formal Endpoint authenticates your Formal identity,
while the Connector supplies the AWS identity used upstream.
Do not configure an `endpoint_url` or pass `--endpoint-url` to individual
commands. The AWS CLI must use standard service endpoints. This allows the
Formal Endpoint network rule to preserve and route each service hostname.
Verify the connection with AWS STS:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
aws sts get-caller-identity --profile formal-aws
```
The returned ARN should identify the IAM identity selected by the resolved Native
User. You can now use the `formal-aws` profile with other AWS CLI commands.
Formal will route, evaluate, and record each request in the same way.
### Troubleshoot `InvalidClientTokenId`
If AWS returns `InvalidClientTokenId` while using the placeholder credentials,
first verify the Formal route rather than replacing the placeholders:
1. Confirm Transparent Mode is enabled:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal transparent-proxy enable
formal transparent-proxy status
```
2. Confirm the network rule is **Active**, selects the AWS resource, and has
**Forward to Connector** enabled.
3. Confirm you are logged in to the Formal Endpoint with `formal auth login`.
4. Confirm the Resource's
[default Native User selection](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user)
selects a Native User for your Formal identity.
5. Confirm the resource appears under the Connector's **Reachable Resources**.
After adding a listener rule, allow up to one minute for the Formal Endpoint's
reachability data to refresh. Restart the Formal Endpoint if the mapping still
does not update.
6. On macOS, inspect `~/Library/Logs/Formal/main.log`. The message
`catchall: failed to resolve connector forwarding target` means the Formal
Endpoint cannot find a Connector listener that reaches the selected resource.
When Transparent Mode is configured to fail open, an unresolved Connector
route can pass the placeholder-signed request to AWS. AWS then reports a
credential error even though the underlying problem is Formal routing.
# BigQuery
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/bigquery
How to connect to a BigQuery Resource using the Formal Connector
## Overview
The Formal Connector enables secure access to BigQuery. When domain-wide delegation is enabled, it can impersonate users in your organization, ensuring proper access control and audit logging of all BigQuery operations.
### Differences with other technologies
The connection to BigQuery differs from other technologies in two important ways:
* **No Native Users**: Unlike Postgres or HTTP, Formal native users are not supported for BigQuery currently. Instead, the connector uses [Google ADC](https://cloud.google.com/docs/authentication/application-default-credentials) to connect to BigQuery.
* **Automatic Impersonation**: When domain-wide delegation is configured, the Connector will automatically impersonate detected end users for BigQuery requests.
## Setup
The steps outlined here provide a general overview of the process. For a
complete working example that you can deploy as-is in your GKE cluster, or use
as a starting point to integrate our Connector in your existing deployment
pipeline, please refer to our [Terraform example on
GitHub](https://github.com/formalco/terraform-provider-formal/tree/main/examples/deployments/gcp/gke-bigquery-connector).
### 1. Configure Domain-wide Delegation
Before creating a BigQuery resource in Formal, you'll need a service account with domain-wide delegation enabled in your Google Workspace. This step must be done by an admin user of your Google organization.
Start by creating a service account in your Google Cloud Console if you don't have one already, then enable domain-wide delegation:
* Go to your [Google Workspace Admin Console](https://admin.google.com)
* Navigate to **Security** > **Access and data control** > **API Controls** > **Manage domain-wide delegation**.
* Click **Add New**:
* **Client ID**: Your service account's client ID
* **OAuth Scopes**:
* `https://www.googleapis.com/auth/bigquery`
* `https://www.googleapis.com/auth/cloud-platform`
* Click **Authorize**
### 2. Create a BigQuery Resource
To create a BigQuery Resource in Formal, follow these steps:
1. Navigate to the Resources page.
2. Click on the **Create Resource** button.
3. Fill in the required fields:
* **Technology**: Select `BigQuery` from the dropdown
* **Resource Friendly Name**: Enter a name of your choice to identify this resource
* **Hostname**: Enter `bigquery.googleapis.com`
* **Port**: Enter `443` if your Connector has [TLS certificates](/docs/guides/core-concepts/connectors/tls) enabled, anything else otherwise (e.g. `7777`)
4. Click **Create** to complete the resource creation.
### 3. Connect to BigQuery
The last step before querying BigQuery is to deploy your Formal Connector using your service account credentials. The connector uses [Google ADC](https://cloud.google.com/docs/authentication/application-default-credentials) to detect the service account credentials and connect to BigQuery.
Once your Formal Connector is set up, you can connect to BigQuery through the Formal Connector using the `bq` command line tool:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
bq --api ${PROTOCOL}://${HOSTNAME}:${PORT} query "SELECT 1"
```
Use `https` or `http` for `${PROTOCOL}` depending on whether your connector
has been configured for TLS or not. Replace `${HOSTNAME}` with your actual
Formal Connector hostname and `${PORT}` with your actual listener port.
Here's another example of how to connect to BigQuery through the Formal Connector using Python:
```python theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
from google.cloud import bigquery # requires google-cloud-bigquery
PROTOCOL = "..." # "http" or "https", depending on your connector setup
HOSTNAME = "..." # Your connector hostname
PORT = "..." # Your connector listener port
client = bigquery.Client(
client_options={"api_endpoint": f"{PROTOCOL}://{HOSTNAME}:{PORT}"}
)
query = "SELECT word FROM `bigquery-public-data.samples.shakespeare` LIMIT 10"
for row in client.query(query):
print(row.word)
```
## Policy Evaluation
Formal supports the following policy evaluation stages for BigQuery:
* **Session**: Evaluate and enforce policies at connection time
* **Request**: Evaluate and enforce policies before query execution
* **Response**: Evaluate and enforce policies after data retrieval
## Monitoring and Logging
The Connector provides detailed logs of all BigQuery operations, including:
* Query execution
* Data access patterns
* User identity
* Evaluated / triggered policies
These logs can be accessed through the Control Plane for audit and compliance purposes.
## Troubleshooting
Common issues include:
* TLS errors: Google SDK is strict when using a TLS certificate, and will fail if it isn't signed by a trusted certificate authority.
* Service account credentials: The Formal Connector doesn't use the correct service account credentials
* Permissions: The service account does not have the necessary permissions, especially domain-wide delegation
If you feel stuck, please reach out to us on Slack!
# ClickHouse
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/clickhouse
How to connect to a ClickHouse Resource using the Formal Connector
Formal supports ClickHouse over HTTP and HTTPS. Configure clients and drivers
to use the HTTP protocol rather than ClickHouse's native TCP protocol.
## Requirements
### Native User Permissions
Create ClickHouse native users with the upstream grants that their mapped Formal
users need. For example, map analysts to a ClickHouse native user with `SELECT`
access. Map data engineers to one with the required write or DDL grants. Formal
does not need an administrative ClickHouse user to proxy queries.
See [Select a default Native User](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user)
for selection examples.
## Connect to ClickHouse
1. Go to [Resources](https://app.formal.ai/resources) and click **Create Resource**.
2. Set **Technology** to **ClickHouse**.
3. Enter a Resource name and the upstream ClickHouse HTTP hostname and port.
4. Configure Resource TLS when the upstream endpoint uses HTTPS.
5. Create a **Password** native user. Set its username and password to the
credentials of the upstream ClickHouse user.
6. Configure a
[default Native User](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user).
**Verify:** Run the `curl` query under [Direct connection
examples](#direct-connection-examples). A successful Resource returns the
selected database and native ClickHouse username.
## Authentication
Choose one of these authentication methods:
* **Formal Endpoint:** Route requests through Transparent Mode as the
signed-in Formal user.
* **Direct with a Formal identity:** Send a Formal username and access token to
the Connector. Formal connects upstream through the Native User selected by
the Resource's [default Native User selection](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user).
* **Direct with native credentials:** Forward an end user's native ClickHouse
credentials directly to ClickHouse.
### Route traffic through the Formal Endpoint
Formal Endpoint Transparent Mode supports ClickHouse HTTP traffic.
Configure a network rule that selects the ClickHouse Resource and forwards
traffic to the Connector. Clients keep using the Resource's real hostname. The
Formal Endpoint automatically applies the signed-in user's Formal credentials.
See [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode)
for setup and platform requirements.
### Connect directly with a Formal identity
Clients can send a Formal username and access token using either ClickHouse
authentication headers or HTTP Basic authentication:
* `X-ClickHouse-User` and `X-ClickHouse-Key`
* HTTP Basic authentication
## Direct connection examples
These examples connect directly to the Connector with a Formal identity.
### curl
Get your Formal username and access token from the [Control Plane](https://app.formal.ai), then send a query in the request body:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
export FORMAL_CONNECTOR_URL="https://:"
export FORMAL_USERNAME=""
export FORMAL_ACCESS_TOKEN=""
curl --fail-with-body \
--header "X-ClickHouse-User: ${FORMAL_USERNAME}" \
--header "X-ClickHouse-Key: ${FORMAL_ACCESS_TOKEN}" \
--data-binary "SELECT currentDatabase(), currentUser()" \
"${FORMAL_CONNECTOR_URL}/?database=default"
# Expected: default
```
### Python with ClickHouse Connect
[`clickhouse-connect`](https://clickhouse.com/docs/integrations/python) uses the
ClickHouse HTTP interface:
```python theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
import os
import clickhouse_connect
client = clickhouse_connect.get_client(
host=os.environ["FORMAL_CONNECTOR_HOSTNAME"],
port=int(os.environ["FORMAL_CONNECTOR_PORT"]),
username=os.environ["FORMAL_USERNAME"],
password=os.environ["FORMAL_ACCESS_TOKEN"],
database="default",
secure=True,
)
print(client.query("SELECT 1").result_set[0][0])
```
Set `secure=False` only when the client-to-Connector listener intentionally uses
plain HTTP.
**Verify:** The script prints `1` after the Connector authenticates the user and
ClickHouse executes the query.
## Smart Routing
The Connector supports [Smart Routing](/docs/guides/core-concepts/resources/smart-routing)
for ClickHouse.
Configure a technology listener to route one port to multiple HTTP-based
resources. Prefix the Connector hostname with the Resource name:
```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
RESOURCE_NAME.CONNECTOR_HOSTNAME
```
For example, `analytics-ch.connector.example.com` selects the ClickHouse
Resource named `analytics-ch`. Configure wildcard DNS and a wildcard TLS
certificate when using hostname-based routing.
## ClickHouse compatibility
ClickHouse Connect and the official Go driver work when configured for HTTP.
Formal supports common query workflows, named sessions, query parameters,
settings, and compression.
## Policy evaluation
Formal evaluates ClickHouse traffic at all three policy stages:
| Stage | When it runs | Supported controls |
| - | - | - |
| **Session** | When access begins | Allow or block the request based on identity, Resource, native user, named session, or database |
| **Request** | Before the query is sent to ClickHouse | Allow, block, rewrite, or decrypt using HTTP, query, and ClickHouse request context |
| **Response** | After ClickHouse responds | Allow, block, rewrite headers or the full body, and encrypt supported HTTP targets |
For common identity, device, Resource, Connector, and Space inputs, see [Policy
Evaluation](/docs/guides/policies/evaluation). The inputs below describe the
ClickHouse request and response.
### Inputs by stage
| Input | Session | Request | Response | Semantics |
| - | :-: | :-: | :-: | - |
| `input.db_name` | ✓ | ✓ | When known | Database used for the query |
| `input.clickhouse.session_id` | ✓ | ✓ | ✓ | Named ClickHouse HTTP session; empty for a stateless request |
| `input.sql_query` | | ✓ | | SQL statement and general statement classification |
| `input.table_paths` | | ✓ | | Table paths identified for the request, when available |
| `input.clickhouse.endpoint` | | ✓ | ✓ | Type of ClickHouse request or custom-handler path |
| `input.clickhouse.url_settings` | | ✓ | ✓ | Settings supplied as URL parameters by this request |
| `input.clickhouse.url_filters` | | ✓ | ✓ | `filter` URL parameters supplied by this request, in order |
| `input.clickhouse.request_parameters` | | ✓ | ✓ | `param_*` bindings supplied with the request |
| `input.clickhouse.query_id` | | ✓ | ✓ | Query ID used for the request |
| `input.clickhouse.quota_key` | | ✓ | ✓ | Quota key used for the request |
| `input.clickhouse.session_check` | | ✓ | ✓ | Whether ClickHouse must require the named session to already exist |
| `input.clickhouse.format` | | | ✓ | Output format reported by ClickHouse for the response |
| `input.http` | | ✓ | ✓ | Request method, path, and query, with headers and body for the current stage |
ClickHouse exposes `input.http` at request and response policy. Request policy
receives request transport details. Response policy combines the request method,
path, and query with the response headers and body.
### Database input
`input.db_name` identifies the database used for a query. It may be empty for a
request that does not target a database.
### SQL and table inputs
At request policy, `input.sql_query` provides:
| Field | Semantics |
| - | - |
| `query` | SQL context available to policy |
| `statement_type` | General classification such as `SELECT`, `INSERT`, `UPDATE`, or `DELETE` |
| `command_type` | More specific classification when available |
| `incomplete` | Whether the query context may omit details |
`input.sql_query` may be incomplete. Check `input.sql_query.incomplete` before
relying on the full statement. For `INSERT` statements, `input.sql_query` never
includes `VALUES`.
`input.table_paths` contains table paths when Formal can identify them. The list
may be incomplete. Keep ClickHouse permissions as the primary table-level
control.
### Request-local ClickHouse inputs
`input.clickhouse.url_settings` contains only settings supplied in the current
request URL. It does not include profile defaults, named-session settings, or
SQL `SETTINGS` clauses. An absent setting does not describe its effective value.
`input.clickhouse.url_filters` contains each `filter` URL parameter in request
order. It does not include filters from profiles, sessions, paths, or server
configuration.
Likewise, `input.clickhouse.request_parameters` contains only bindings supplied
by the current request. It does not include bindings retained by a named
session.
## Restrict the selected database
The following request policy allows query requests only when the selected
database is `analytics` or `reporting`:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
allowed_databases := {"analytics", "reporting"}
request := {
"action": "block",
"type": "block_with_formal_message",
"message": "The selected database is not approved for this identity",
} if {
input.resource.technology == "clickhouse"
input.clickhouse.endpoint == "query"
not allowed_databases[input.db_name]
}
```
`input.db_name` is the explicit database, the named session's current database,
or the authenticated user's default. It does not identify every database named
inside the SQL statement. ClickHouse permissions must restrict cross-database
access through qualified table names.
Combine `input.db_name` with identity, group, or device inputs for more specific
selected-database rules.
## Require read-only queries
ClickHouse enforces read-only access when clients set `readonly=1`. The
following request policy requires this setting for every query:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
request := {
"action": "block",
"type": "block_with_formal_message",
"message": "ClickHouse read-only mode is required",
} if {
input.resource.technology == "clickhouse"
input.clickhouse.endpoint == "query"
object.get(input.clickhouse.url_settings, "readonly", "") != "1"
}
```
Clients can supply this setting in the connection configuration or as
`readonly=1` in the request URL.
## Govern native users
Use separate upstream ClickHouse users for each privilege level. Select among
them with the Resource's
[default Native User selection](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user).
Then enforce the result in session policy. This example prevents users from
explicitly requesting the `admin` label:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
session := {
"action": "block",
"type": "block_with_formal_message",
"message": "The admin ClickHouse user cannot be selected directly",
} if {
input.resource.technology == "clickhouse"
input.native_user == "admin"
input.native_user_assignment == "user-requested"
}
```
## Response policies
ClickHouse response policies use `input.http` to inspect the response body and
headers. They can block a response, rewrite headers, replace the full body, or
encrypt supported HTTP targets. These controls also apply to compressed
responses.
Formal does not currently expose ClickHouse response columns or cells to
policy, so ClickHouse response rules should not reference `input.row`,
`input.columns`, or column-aware mask and filter actions. If a policy produces
an active structured mask action for a ClickHouse response, Formal fails closed
rather than returning data without the requested protection.
## Application and end-user attribution
ClickHouse clients and BI tools can attach Formal attribution parameters to a
request:
| Parameter | Meaning |
| - | - |
| `SQL_formal_application_name` | Client or application name |
| `SQL_formal_application_id` | Stable application identifier |
| `SQL_formal_application_env` | Application environment |
| `SQL_formal_enduser` | End-user identity behind a shared application |
These values are available to policies and Formal audit logs. They can also
appear in ClickHouse's `system.query_log`.
## Troubleshooting
* **The client disconnects before running a query:** confirm that it uses the
HTTP protocol. For `clickhouse-go`, set `Protocol: clickhouse.HTTP`.
* **No Native User is resolved:** configure a
[default Native User selection](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user)
or request a label explicitly.
* **Smart Routing selects the wrong Resource:** verify that the first hostname
label exactly matches the Resource name and that wildcard DNS reaches the
Connector.
## Next steps
Build identity-aware access controls
Inspect ClickHouse query activity
# Dial Configuration
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/dial
How the Connector reaches a Resource's upstream: direct TCP or Google Cloud SQL
Dial configuration controls how the Connector establishes the upstream connection for a Resource. It is optional. When absent, the Connector dials direct TCP using the Resource's `hostname` and `port`. This is the default and matches the behavior of every Resource before this feature.
```mermaid theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
sequenceDiagram
participant Client as Client
participant Connector as Connector
participant Upstream as Upstream
Client->>Connector: Connect to hostname:port
alt dial_method = tcp (default)
Connector->>Upstream: TCP dial to hostname:port
else dial_method = gcp_cloudsql
Connector->>Upstream: Google Cloud SQL dial to project:region:instance
Note over Connector, Upstream: Cloud SQL handles mTLS end-to-end
end
Upstream-->>Connector: Response
Connector-->>Client: Response
```
## Dial methods
### `tcp` (default)
The Connector dials `hostname:port` directly. This fits self-managed databases, Amazon RDS and Aurora, and any other endpoint reachable over plain TCP from the Connector.
### `gcp_cloudsql`
The Connector dials Google Cloud SQL natively, similarly to what Google's `cloud-sql-proxy` does. Ephemeral mTLS certificates are minted and rotated automatically, and the connection is encrypted end-to-end by Cloud SQL.
`gcp_cloudsql` requires `dial_target` to be the Cloud SQL instance connection name in `project:region:instance` form. Supported only for `postgres`, `mysql`, and `mariadb` Resources.
Connector prerequisites:
* The Connector runs in the Cloud SQL instance's VPC, or in a peered / PSC-connected VPC. The dial targets the instance's private IP.
* Application Default Credentials configured: the `GOOGLE_APPLICATION_CREDENTIALS` environment variable points at a service account JSON key, or the Connector runs on a GCP workload with a bound identity.
* The credentials grant `roles/cloudsql.client` on the project that owns the Cloud SQL instance.
When `dial_method` is not `tcp`, the Resource's [TLS configuration](/docs/guides/core-concepts/resources/tls) is ignored.
## Configure dial configuration
Navigate to [Resources](https://app.formal.ai/resources) and open the target Resource.
Select the **Connectivity** tab. The **Dial Configuration** section is where the dial method and target are set.
Selecting **TCP** keeps the default. Selecting **Google Cloud SQL** routes upstream connections natively.
Enter the Cloud SQL instance connection name in `project:region:instance` form (for example `my-project:us-central1:pg-prod`), then click **Save**.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# Direct TCP dial (matches the default, usually omitted).
resource "formal_resource_dial_configuration" "tcp" {
resource_id = formal_resource.my_postgres.id
dial_method = "tcp"
}
# Native Cloud SQL dial.
resource "formal_resource_dial_configuration" "cloudsql" {
resource_id = formal_resource.my_postgres.id
dial_method = "gcp_cloudsql"
dial_target = "my-project:us-central1:pg-prod"
}
```
A Resource has at most one dial configuration. Deleting it restores the default `tcp` behavior.
## Verify
After applying the configuration, run the standard connectivity check:
1. From the Resource Details page, click **Test Connection** (or **Test Connectors** for a specific Connector).
2. The check should report success for the TCP, TLS, Authentication, and Data Access stages.
See [Testing Resource Connectivity](/docs/guides/core-concepts/resources/introduction#testing-resource-connectivity) for what each stage validates.
For Cloud SQL, the first dial against a new instance adds roughly one second of latency while the ephemeral certificate is minted. Subsequent dials within the hour reuse the cached certificate.
# DynamoDB
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/dynamodb
How to connect to a DynamoDB Resource using the Formal Connector
## Requirements
Before deploying using the Formal Connector with DynamoDB, ensure that you have created a DynamoDB Resource on Formal. This is a prerequisite for the deployment process.
## Deployment
The Formal Connector can be deployed using the [Formal Terraform Provider](https://registry.terraform.io/providers/formalco/formal/latest/docs). This method allows for easy integration into your existing Terraform workflows and infrastructure as code practices.
## Networking
To ensure seamless connectivity between your environment and the Formal Resources, certain ports must be accessible. Specifically, for DynamoDB access through the Connector, port 443 must be assigned and allowed through your security groups. A single Connector instance can listen on multiple ports simultaneously for various resources, providing flexibility in your deployment architecture.
## Set up your connection details
To connect to your DynamoDB database through the Connector, follow these steps:
1. Grab Formal username and access token on [Control Plane](https://app.formal.ai/) and set them as AWS credentials:
* `AWS_ACCESS_KEY_ID`: Formal Username
* `AWS_SECRET_ACCESS_KEY`: Formal Access Token
## Connecting to DynamoDB
Ensure that you have completed the setup of your connection details and credentials as described above. Then, connect to DynamoDB using your preferred client. Authentication will rely on the credentials obtained from the Control Plane. All data activity is logged to the log location configured in your Formal installation.
## Code Samples for connecting through the Connector
The following code samples demonstrate how to connect to DynamoDB through the Formal Connector using NodeJS, Python, and Java. These examples showcase the configuration needed to route traffic through the Connector and utilize a custom certificate bundle for secure connections.
```javascript NodeJS theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
import { DynamoDBClient, ListTablesCommand } from '@aws-sdk/client-dynamodb';
import { NodeHttpHandler } from '@aws-sdk/node-http-handler';
import { HttpsProxyAgent } from 'hpagent';
import fs from 'fs';
import util from 'util';
const certificate_bundle = '/path/to_certificate_bundle.pem'
const connector_endpoint = 'example-connector-endpoint.com'
const connector_port = 443
var region = 'us-east-2'
// load the certificate bundle file
const certs = [
fs.readFileSync(certificate_bundle)
];
// create a proxy agent pointing to the Connector that uses the custom certificate bundle
const agent = new HttpsProxyAgent({
proxy: util.format('http://%s:%d', connector_endpoint, connector_port),
ca: certs
});
(async () => {
const client = new DynamoDBClient({
region: region,
requestHandler: new NodeHttpHandler({
httpAgent: agent,
httpsAgent: agent
}),
});
const command = new ListTablesCommand({});
try {
console.log("Running List Tables command through the Connector")
const results = await client.send(command);
console.log(results.TableNames.join("\n"));
} catch (err) {
console.error(err);
}
})();
```
```python Python theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
import boto3
from botocore.config import Config
# reference AWS documentation:
# https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html
def connector_example():
certificate_bundle = '/path_to_certificate_bundle.pem'
connector_endpoint = 'example-connector-endpoint.com'
connector_port = 443 # port selected when binding the dynamodb repo to the Connector
region = 'us-east-2'
dynamodb_client = boto3.client('dynamodb',
aws_access_key_id="YOUR_AWS_ACCESS_KEY_ID",
aws_secret_access_key="YOUR_AWS_SECRET_ACCESS_KEY",
region_name=region,
verify=certificate_bundle,
config=Config(
proxies={
"http": f"http://{connector_endpoint}:{connector_port}",
"https": f"http://{connector_endpoint}:{connector_port}",
}),
)
print('Running List Tables command through the Connector')
result = dynamodb_client.list_tables()
print(result)
if __name__ == "__main__":
connector_example()
```
```java Java theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.TableCollection;
import com.amazonaws.services.dynamodbv2.model.ListTablesResult;
public class ExampleConnectorConn {
static String certificate_bundle = "/path_to_certificate_bundle.pem";
static String connector_endpoint = "example-connector-endpoint.com";
static Integer connector_port = 443; // port selected when binding the dynamodb repo to the Connector
static String region = "us-east-2";
public static void main(String[] args) throws Exception {
BasicAWSCredentials basic = new BasicAWSCredentials("YOUR_AWS_ACCESS_KEY_ID", "YOUR_AWS_SECRET_ACCESS_KEY");
AWSCredentialsProvider credentials = new AWSStaticCredentialsProvider(basic);
// Connector configuration
ClientConfiguration config = new ClientConfiguration();
config.setProxyHost(connector_endpoint);
config.setProxyPort(connector_port);
config.setProxyProtocol(Protocol.HTTP);
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.
standard().
withCredentials(credentials).
withRegion(region).
withClientConfiguration(config).build();
DynamoDB dynamoDB = new DynamoDB(client);
System.out.println("Running List Tables command through the connector");
TableCollection result = dynamoDB.listTables();
for (Table a: result) {
System.out.println(a.getTableName());
}
}
}
```
## Policy Evaluation
The Connector does not evaluate Formal policies for DynamoDB resources. It authenticates and logs each request, then forwards it to DynamoDB.
To enforce policies on DynamoDB API calls, access DynamoDB through an [AWS resource](/docs/guides/core-concepts/resources/aws) instead.
## Applications
Applications leveraging the AWS SDK can be modified to directly communicate with the Connector by following the examples provided. This allows for seamless integration of Formal's security and compliance features into your existing DynamoDB workflows.
## Note on other AWS SDKs
AWS offers SDKs for various programming languages. While this document provides examples for NodeJS, Python, and Java, the principles apply across all SDKs. If you require examples for a language not covered here, please refer to the [AWS official documentation](https://aws.amazon.com/developer/tools/) or contact Formal support for assistance.
# GCP
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/gcp
How to proxy Google Cloud APIs through the Formal Connector with GCP Resources
## Overview
A GCP Resource represents a Google Cloud API, such as Cloud Storage at `storage.googleapis.com`. The Connector proxies it with its HTTP engine. It adds the Google API service, method, and custom action to policy input and logs.
A GCP Resource is different from a
[GCP Cloud Account](/docs/guides/integrations/clouds/gcp), which lets Formal
discover Resources and write logs in your Google Cloud project.
## Create a GCP Resource
Go to [Resources](https://app.formal.ai/resources) and click **Create Resource**. Set **Technology** to **GCP**. Set **Hostname** to the API hostname, such as `storage.googleapis.com`. The **Port** defaults to `443`.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_resource" "gcs_api" {
name = "gcs-api"
technology = "gcp"
hostname = "storage.googleapis.com"
port = 443
}
```
Create one Resource for each Google API hostname you want to govern.
## Authentication
By default, clients send their own Google credentials, such as an OAuth access token in the `Authorization` header. The Connector forwards them unchanged.
To inject a credential instead, add a [Native User](/docs/guides/core-concepts/resources/native-users) with one of the HTTP credential types: **HTTP Bearer**, **HTTP Basic**, **API Key (Header)**, or **API Key (Query)**. See [HTTP authentication](/docs/guides/core-concepts/resources/http#authentication).
The Control Plane also lists **GCP IAM** for GCP Resources. Connectors don't
support that credential type for this technology yet, and requests that
select it fail.
## Connect to a GCP Resource
With [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode), the Formal Endpoint recognizes Google API traffic. Add a [network rule](/docs/guides/network-rules) that forwards traffic for your Formal Resources to the Connector:
```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"condition": {
"pre_tcp": true,
"pre_tls": resource.name != "",
"post_tls": true
},
"outputs": {
"forward_to_connector": true,
"resource_name": resource.name
}
}
```
Clients keep calling `*.googleapis.com`. They must trust the Endpoint's certificate authority, so tools that ship their own CA bundle need it added to their configuration.
Send requests to `https://.`, as for [HTTP Resources](/docs/guides/core-concepts/resources/http#multiple-resources). This needs wildcard DNS and a wildcard TLS certificate for the Connector hostname. Point your client's API endpoint for that service at this address.
**Verify:** Make a request, then open [Logs](https://app.formal.ai/logs) and filter on `resource.technology:gcp`.
## Policy Evaluation
GCP Resources support the same stages and actions as [HTTP Resources](/docs/guides/core-concepts/resources/http#policy-evaluation). Policies also receive `input.gcp.api`:
| Field | Description |
| - | - |
| `input.gcp.api.host` | Host the client sent the request to |
| `input.gcp.api.service` | Google API service, such as `storage` for `storage.googleapis.com` |
| `input.gcp.api.method` | HTTP method |
| `input.gcp.api.custom_action` | Custom method from the last path segment, such as `setIamPolicy` in `/v1/projects/my-project:setIamPolicy` |
Logs record the same values under `request.http.gcp.api`.
`host` and `service` come from the request's `Host` header. When a client
calls the Connector hostname, or the Endpoint forwards the request to the
Connector, `host` is the Connector address and `service` is empty. In those
cases, match on `input.http.hostname` or `input.resource.name`, which hold
the Resource's upstream hostname and name.
### Example: Block IAM Policy Changes
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
request := {
"action": "block",
"type": "block_with_custom_message",
"message": "Change IAM policies through Terraform"
} if {
input.resource.technology == "gcp"
input.gcp.api.custom_action == "setIamPolicy"
}
```
## Next Steps
Learn how HTTP-based Resources work
Choose which traffic the Endpoint intercepts
# gRPC
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/grpc
How to connect to a gRPC Resource using the Formal Connector
## Requirements
### Networking
Certain ports must be accessible to connect to Formal Resources. You can listen on multiple ports simultaneously for various Resources.
gRPC is a high-performance, open-source Remote Procedure Call (RPC) framework that uses HTTP/2 for transport, Protocol Buffers as the interface description language, and provides features like authentication, bidirectional streaming, and flow control. The Formal Connector enables you to proxy and monitor gRPC traffic to your APIs while enforcing policies and logging all interactions.
The connector runs a healthcheck server on port 8080 to let clients determine if the proxy is operational. As a result, you cannot create a listener on port 8080 to connect to a gRPC resource.
### Resource health check
Formal periodically checks that the Connector can open a TCP connection to the gRPC server. When [TLS](/docs/guides/core-concepts/resources/tls) is enabled for the Resource, the check also completes a TLS handshake. It does not send gRPC requests.
### Native Users
The Connector can inject upstream credentials with Native Users. gRPC Resources accept the HTTP credential types, such as **HTTP Bearer** and **API Key (Header)**. Header credentials reach the server as gRPC metadata.
Configure them with the same Terraform blocks as [HTTP Native Users](/docs/guides/core-concepts/resources/http#native-users).
## Connect to a gRPC Resource
To connect to a gRPC resource with a Formal identity, set the following metadata in your gRPC calls:
* `x-formal-user-username`: The username of the user making the request
* `x-formal-user-password`: The user's authentication credentials
### Connecting using the gRPC CLI
To connect using `grpc_cli`, execute the following command:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
grpc_cli call CONNECTOR_HOSTNAME:PORT GRPC_METHOD_NAME --metadata "x-formal-user-username:FORMAL_USERNAME:x-formal-user-password:FORMAL_ACCESS_TOKEN"
```
Make sure to replace `CONNECTOR_HOSTNAME`, `PORT`, `GRPC_METHOD_NAME`, `FORMAL_USERNAME`, and `FORMAL_ACCESS_TOKEN` with the right values.
## Smart Routing
The Connector features Smart Routing for gRPC, allowing the linkage of an
unlimited number of Resources over the same port.
Prefix the Connector hostname with the resource name:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
grpc_cli call RESOURCE_NAME.CONNECTOR_HOSTNAME:PORT GRPC_METHOD_NAME \
--metadata "x-formal-user-username:FORMAL_USERNAME:x-formal-user-password:FORMAL_ACCESS_TOKEN"
```
Replace `RESOURCE_NAME`, `CONNECTOR_HOSTNAME`, `PORT`, `GRPC_METHOD_NAME`,
`FORMAL_USERNAME`, and `FORMAL_ACCESS_TOKEN` with the right values.
If your gRPC client dials one address and sends a separate authority or server
name, set that value to `RESOURCE_NAME.CONNECTOR_HOSTNAME`.
Make sure your DNS entries are properly configured to handle the subdomain
routing, i.e. that `*.CONNECTOR_HOSTNAME` targets your Connector. Use a
wildcard TLS certificate that covers every resource subdomain.
## Policy Evaluation
Formal supports the following policy evaluation stages for gRPC:
* **Session**: Evaluate and enforce policies at connection time
* **Request**: Evaluate and enforce policies before request execution
## Policies and Controls
You can apply Formal policies to gRPC traffic at the **session** and **request** stages. The gRPC method name is available as `input.grpc.method_name`.
# HTTP
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/http
How to connect to an HTTP Resource using the Formal Connector
The connector runs a healthcheck server on port 8080 to let clients determine if the proxy is operational. As a result, you cannot create a listener on port 8080 to connect to an HTTP resource.
## Connect to an HTTP Resource
To connect to an HTTP Resource, follow these steps:
1. **Adjust the Hostname**: In your API request, modify the hostname to point to the Connector. Replace the hostname with the http resource name you have specified in the resources section of the Control Plane or Terraform. If the hostname of your connector is `formalcloud.net`, and your http resource name is `stripe`, you can replace the request URL with the following:
* Old Way:
`https://api.stripe.com/v1/customers`
* New Way (Using the Connector):
`https://stripe.formalcloud.net/v1/customers`
2. **Tool of Your Preference**: Use your preferred API tool to send requests through the Connector. This can be tools like Postman, cURL, or any other HTTP client that allows you to specify the API endpoint. In order to see the user who made the request, you can set `X-Formal-User-Username` and `X-Formal-User-Password` in the request headers.
## Payload Encryption
The Connector encrypts both response and request payloads before they're sent to the Formal Control Plane. This ensures that Formal won't have access to any transmitted data. Create a [log configuration](/docs/guides/observability/log-configuration) to enable payload encryption.
### Decrypting the logs in Formal UI
1. First, deploy the AWS Lambda function using the code found [here](https://github.com/formalco/decryptor).
2. Then, navigate to a specific HTTP Log and hit the `Decrypt Response Body` or `Decrypt Request Body` to input the URL of the Lambda function directly in the UI.
You can watch a demo of this feature [here](https://www.loom.com/share/520863bc10fb40e88b6d0a697175a983?sid=db5c0266-dc25-4bd6-9e1e-d4571009d90e).
## HTTP Payload Size Limitation
To ensure efficient and streamlined logging while preventing potential issues related to large data volumes, our system provides configurable size limitations for both HTTP request and response payloads. If the size of a payload exceeds the configured limit, it will not be included in the logs. Create a [log configuration](/docs/guides/observability/log-configuration) to enable limits on payload sizes.
## Multiple Resources
The Connector supports connecting multiple downstream APIs to the same Connector.
For example, using a Connector with the hostname `test-http-proxy.formalcloud.net`, you can query:
```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
- `stripe.test-http-proxy.formalcloud.net`
- `openai.test-http-proxy.formalcloud.net`
```
Which will then redirect traffic to the following resources:
```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
- api.stripe.com
- api.openai.com
```
Configure your DNS entries with the subdomains.
Create HTTP resources with names matching the subdomains used in the hostname of the Connector. In the example provided, the names of the resources should be `openai` and `stripe`.
Link multiple resources to the same port.
The TLS Certificate must be a wildcard certificate and should cover every subdomain.
## Policy Evaluation
Formal supports the following policy evaluation stages for HTTP:
* **Session**: Evaluate and enforce policies at connection time. Actions: `allow`, `block`.
* **Request**: Evaluate and enforce policies before request execution. Actions: `allow`, `block`, `rewrite`, `decrypt`.
* **Response**: Evaluate and enforce policies after data retrieval. Actions: `allow`, `filter`, `mask`, `rewrite`, `encrypt`.
Use `encrypt` and `decrypt` to seal secrets such as OAuth tokens on the Connector. See [Token Encryption](/docs/guides/core-concepts/connectors/token_encryption) and [Encrypt & Decrypt](/docs/guides/policies/enforcement#encrypt--decrypt-actions). For an MCP worked example, see [Encrypt MCP OAuth Tokens](/docs/guides/example-use-cases/encrypt-mcp-oauth-tokens).
### Header Rewrites
The `rewrite` action at the request stage adds, replaces, or removes HTTP headers before the request reaches the upstream service. Common use cases include injecting authentication tokens, enforcing organization-level API restrictions, and stripping internal headers. For Anthropic tenant restrictions, see [Restrict Anthropic Sign-Ins to One Org](/docs/guides/example-use-cases/restrict-anthropic-sign-ins).
Header names in `input.http.headers` are normalized to lowercase. The rewrite applies canonical HTTP casing before forwarding (e.g., `x-custom-header` becomes `X-Custom-Header`).
#### Add a header
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
rewritten_headers := object.union(input.http.headers, {
"x-org-id": ["org-123"],
})
request := {
"action": "rewrite",
"headers": rewritten_headers,
} if {
input.resource.technology == "http"
}
```
Header values must be arrays of strings. `object.union` merges the new headers into the existing `input.http.headers`; keys in the second object overwrite keys in the first.
#### Replace an existing header
The same `object.union` pattern overwrites a header value:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
rewritten_headers := object.union(input.http.headers, {
"authorization": ["Bearer "],
})
request := {
"action": "rewrite",
"headers": rewritten_headers,
} if {
input.resource.technology == "http"
}
```
#### Remove a header
Setting a header to an empty array removes it before the request is forwarded:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
rewritten_headers := object.union(input.http.headers, {
"x-debug": [],
})
request := {
"action": "rewrite",
"headers": rewritten_headers,
} if {
input.resource.technology == "http"
}
```
Formal logs both received and sent headers for each request. Rewrite behavior is visible in the [Logs](/docs/guides/observability/logs) page.
### Body Rewrites
The `rewrite` action can replace an entire HTTP request or response body.
At each stage, `input.http` describes the message being evaluated.
Each rewrite action supports one body field:
| Field | Policy input | Behavior |
| - | - | - |
| `body` | `input.http.body` | Writes the string exactly. |
| `json` | `input.http.json` | Serializes a JSON value. |
| `post_form` | `input.http.post_form` | Rewrites HTML form fields. |
Formal rejects actions that set more than one of `body`, `json`, or `post_form`.
#### Text bodies
The `body` field replaces plain text, XML, or another body represented as a string:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
request := {
"action": "rewrite",
"body": "replacement text",
} if {
input.resource.technology == "http"
input.http.path == "/messages"
}
```
An empty string clears the body.
#### JSON bodies
The request or response must contain valid JSON and use a JSON content type.
Formal then exposes the parsed value as `input.http.json`.
`object.union` replaces selected fields while preserving the remaining object:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
rewritten_json := object.union(input.http.json, {
"password": "redacted",
})
request := {
"action": "rewrite",
"json": rewritten_json,
} if {
input.resource.technology == "http"
input.http.path == "/users"
}
```
Rego's
[`json.patch`](https://www.openpolicyagent.org/docs/policy-reference/builtins/object#builtin-json-patch)
built-in supports nested changes and array operations.
It accepts standard [JSON Patch](https://jsonpatch.com/) operations.
The following patch replaces a nested value, removes a field, and appends an array item:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
patched_json := json.patch(input.http.json, [
{
"op": "replace",
"path": "/profile/email",
"value": "redacted",
},
{
"op": "remove",
"path": "/internal_note",
},
{
"op": "add",
"path": "/labels/-",
"value": "reviewed",
},
])
request := {
"action": "rewrite",
"json": patched_json,
} if {
input.resource.technology == "http"
input.http.path == "/users"
}
```
Patch paths use slash-separated keys, such as `/profile/email`.
All operations apply atomically.
If any operation fails, `json.patch` is undefined and this rewrite does not apply.
The Rego documentation describes
[JSON object syntax and access](https://www.openpolicyagent.org/docs/policy-language/#objects).
Its [object built-ins](https://www.openpolicyagent.org/docs/policy-reference/builtins/object)
include `object.get`, `object.remove`, `object.union`, and related functions.
If the body or content type does not qualify, `input.http.json` is absent.
The raw string remains available as `input.http.body`.
#### HTML form bodies
Formal exposes parsed HTML form fields as `input.http.post_form`.
Each field contains an array because a form can repeat keys.
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
rewritten_form := object.union(input.http.post_form, {
"password": ["redacted"],
})
request := {
"action": "rewrite",
"post_form": rewritten_form,
} if {
input.resource.technology == "http"
input.http.path == "/login"
}
```
Repeated values use string arrays, such as `{"scope": ["read", "write"]}`.
#### Response bodies
A `response` rule supports the same fields.
At this stage, `input.http.body`, `input.http.json`, and `input.http.post_form` describe the upstream response.
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
response := {
"action": "rewrite",
"json": object.union(input.http.json, {
"internal_note": "redacted",
}),
} if {
input.resource.technology == "http"
input.http.path == "/accounts"
}
```
Formal logs the original body as received and the rewritten body as sent.
The [Logs](/docs/guides/observability/logs) page displays both values.
## Authentication
The Connector supports forwarding HTTP authentication headers transparently from HTTP clients. In addition, the Connector supports adding HTTP authentication headers that
the HTTP client does not have access to using Native Users.
### Native Users
The connector can inject credentials for the upstream Resource using Native
Users.
Choose one of these typed credential methods:
* **HTTP Basic** injects a username and password into a named header.
* **HTTP Bearer** injects a bearer token into a named header.
* **API Key (Header)** injects a key into a named request header.
* **API Key (Query)** injects a key into a named query parameter.
#### Terraform
Use the Terraform block that matches the credential method.
##### HTTP Basic
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_native_user_v3" "http_basic" {
resource_id = formal_resource.api.id
label = "basic-auth"
http_basic {
header = "Authorization"
username = "api_user"
password {
environment_variable = "HTTP_BASIC_PASSWORD"
}
}
}
```
##### HTTP Bearer
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_native_user_v3" "http_bearer" {
resource_id = formal_resource.api.id
label = "bearer"
http_bearer {
header = "Authorization"
token {
environment_variable = "HTTP_BEARER_TOKEN"
}
}
}
```
##### API Key (Header)
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_native_user_v3" "http_api_key_header" {
resource_id = formal_resource.api.id
label = "header-api-key"
http_api_key_header {
key = "X-Api-Key"
value {
environment_variable = "HTTP_API_KEY_HEADER"
}
}
}
```
##### API Key (Query)
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_native_user_v3" "http_api_key_query" {
resource_id = formal_resource.api.id
label = "query-api-key"
http_api_key_query {
key = "api_key"
value {
environment_variable = "HTTP_API_KEY_QUERY"
}
}
}
```
Each secret can be stored in the Formal control plane, read from a Connector
environment variable, or returned by a connection-time hook.
See [Select a default Native User](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user)
for selection examples.
If an HTTP Resource has no Native Users configured, the Connector forwards
requests upstream without injecting credentials.
Resources that still use JSON documents in the password field use the
[legacy Native User format](/docs/guides/core-concepts/resources/native-users-legacy#configure-legacy-http-credentials).
To seal client-held OAuth tokens so they leave the Connector as `formalsealed:v1:` envelopes, configure [Token Encryption](/docs/guides/core-concepts/connectors/token_encryption) and use the HTTP `encrypt` and `decrypt` policy actions.
# Introduction
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/introduction
Define the databases, APIs, and infrastructure that Formal protects
## What is a Resource?
A Resource is any system you want to protect with Formal—databases, APIs, Kubernetes clusters, SSH servers, and more. Resources define the endpoints that Connectors can route traffic to and enforce policies on.
## Supported Resource Types
Formal supports a wide range of technologies. [View all supported technologies](/docs/guides/core-concepts/resources/postgres).
Including RDS, Aurora, Supabase, AlloyDB
Including RDS, Aurora
MariaDB servers
Amazon Redshift clusters
Self-hosted and Atlas
Cloud data warehouse
Google Cloud data warehouse
OLAP database
AWS service APIs
NoSQL database
Object storage
Google Cloud APIs
REST APIs and web services
Socket Registry Firewall
High-performance RPC framework
Web applications and portals
Linux servers and bastions
Windows remote desktops
K8s clusters (kubectl exec)
## Creating a Resource
Go to [Resources](https://app.formal.ai/resources) in the Control Plane
Select the **Create Resource** button
* **Technology**: The technology (PostgreSQL, MongoDB, etc.)
* **Resource Friendly Name**: A friendly identifier
* **Hostname**: Database hostname or IP address
* **Port**: Connection port (e.g., 5432 for PostgreSQL)
* **Space**: (Optional) Logical grouping for access control
* Set **Termination Protection** to prevent accidental deletion
* Click **Create**. For technologies that support native users, the
Control Plane opens the **Add Native User** wizard next (you can skip and
configure later on the **Authentication** tab)
* After create, configure **TLS**, **Hostnames**, **Aliases**, and
**Tags** from the resource details page as needed
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_resource" "production_postgres" {
name = "production-postgres"
technology = "postgres"
hostname = "prod-db.us-east-1.rds.amazonaws.com"
port = 5432
space_id = formal_space.production.id
# Optional: Termination protection
termination_protection = true
# Optional: Tags
tags = {
environment = "production"
team = "backend"
}
# Optional: Aliases
aliases = ["prod-postgres", "backend-postgres"]
}
```
Your resource doesn't need to be publicly accessible. The Connector reaches it
from within your VPC.
## Testing Resource Connectivity
You can test resource connectivity directly from the Control Plane to verify that your resources are accessible and properly configured.
### Test Connection from Resource Details
1. Navigate to [Resources](https://app.formal.ai/resources) in the Control Plane
2. Click on the resource you want to test
3. In the **Resource Details** page, click the **Test Connection** button in the top-right corner
4. The system will attempt to connect to your resource and display the results
### Test Specific Connectors
1. From the Resource Details page, click **Test Connectors**
2. Select the connector you want to test from the list
3. Click **Test** to initiate a health check for that specific connector
4. Monitor the test status - you'll see "Testing..." with a spinner during the test
5. Review the results to identify any connectivity issues
### Configure Health Check Database
For database resources, you can specify which database to use for health checks:
1. In the Resource Details page, open the **Discovery** tab
2. Under **Database Health Check**, enter the **Database Name** (e.g., "main", "postgres", "mysql")
3. Click **Save Health Check**
4. This database will be used for all subsequent health checks
If you leave this field empty, MySQL defaults to `mysql` and Postgres defaults to `postgres`.
Formal connects to that database and runs `SELECT 1`.
The native user must have permission to connect to the selected database.
### Understanding Test Results
#### Successful Connection Test
When all tests pass, you'll see a green "3/3 tests passed" indicator and a multi-stage validation flow:
1. **TCP Connection** - Verifies network connectivity to the resource
2. **TLS Check** - Protocol-specific TLS handshake validation (when applicable)
3. **Configuration Check** - Validates health check settings
4. **Authentication** - Validates user credentials (shows the authenticated user)
5. **Data Access** - Confirms database permissions for the user
The test results will show:
* Connector name and instance information
* Each validation stage with green checkmarks
* Specific user details for authentication and data access
* Resource identification
### Health Check Stages Explained
Each health check performs multiple validation stages to ensure complete connectivity:
**TCP Check**: Basic network connectivity verification
* Verifies the connector can reach the resource endpoint
* Tests basic network connectivity
**TLS Check**: TLS/SSL handshake verification
* Only applicable to TLS-enabled resources
* Validates certificate and encryption setup
**Configuration Check**: Configuration validation
* Verifies health check configuration is valid
* Checks database name and other settings
**Authentication Check**: Authentication verification
* Tests authentication using configured native users
* Includes AWS IAM authentication for RDS resources
**Data Access Check**: Data access verification
* Runs `SELECT 1` against the configured health check database
* Confirms the connector can interact with the resource
#### Failed Connection Tests
When tests fail, you'll see error indicators and specific failure messages:
* **Timeout**: Resource could not be reached - check network configuration and firewall rules such as a security group in a cloud environment
* **Authentication Failed**: Verify credentials and native user configuration
* **Database Not Found**: Ensure the specified database exists and is accessible
### Updating Resources
Changes to resources (hostname, port, space, etc.) take effect immediately for new connections. Active sessions continue using old settings until they reconnect.
### Deleting Resources
Resources with **Termination Protection** enabled cannot be deleted until
protection is disabled.
Before deleting a resource:
1. Check for active sessions
2. Remove it from any Connector listener rules
3. Disable termination protection if enabled
4. Confirm deletion
## Spaces and Resources
Resources can be assigned to a [Space](/docs/guides/core-concepts/spaces) to limit which Connectors can access them:
* **Resource with a Space**: Only Connectors in the same Space can access it
* **Resource without a Space**: Any Connector can access it
This allows you to create network segmentation and isolation patterns that match your infrastructure.
# Kubernetes
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/kubernetes
How to connect to a Kubernetes Resource using the Formal Connector
Formal streamlines access and control for any Kubernetes clusters by providing:
* Single Sign-On (SSO) integration with providers like Okta
* Granular role-based access controls with MFA support
* Session recording for compliance
* Automatic cluster discovery (for EKS)
## Requirements
To connect to a Kubernetes cluster through the Connector, you need the following:
* The Formal Endpoint running and authenticated. You can find more information on how to achieve this in [the documentation of the Formal Endpoint](/docs/guides/client-apps/desktop-app).
* At least one Kubernetes cluster linked to the Formal Connector as a Resource, with its Native User set. For AWS EKS clusters, set the Resource name to the cluster name.
The Formal Endpoint will automatically update your local kubeconfig file and change the current context.
## Configuration
### Resource
When creating a Kubernetes resource, use the Kubernetes API endpoint URL with port 443 as the target port.
The Connector uses the resource name to identify which cluster to target when a user tries to access a cluster.
How the Connector finds the upstream cluster depends on the Native User:
* **AWS IAM** and **AWS IAM Role** look up the EKS cluster by the Resource name.
* **Kubeconfig** and **Kubeconfig Path** select the kubeconfig context whose cluster `server` URL matches the Resource hostname and port. Context names are ignored, and exactly one context must match.
* **Azure IAM** and **GCP IAM** connect to the Resource hostname and port directly.
For AWS EKS clusters, the Kubernetes Resource name **must match exactly** the
EKS cluster name.
### Native Users
The Connector supports the following Native User credential types for Kubernetes:
1. **AWS IAM** uses the Connector's ambient AWS identity.
2. **AWS IAM Role** assumes the specified AWS role ARN.
3. **Azure IAM** uses the Connector's ambient Microsoft Entra identity.
4. **GCP IAM** uses the Connector's ambient Google identity.
5. **Kubeconfig Path** reads a kubeconfig file from the Connector.
6. **Kubeconfig** uses an inline kubeconfig YAML document.
#### Terraform
For Kubernetes-specific credentials, configure one of the following Native
Users. Kubeconfig paths and documents can be literal values or references to
environment variables available to the Connector.
##### Kubeconfig Path
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_native_user_v3" "kubeconfig_path" {
resource_id = formal_resource.cluster.id
label = "kubeconfig-path"
kubernetes_path {
kubeconfig_path {
literal = "/etc/formal/kubeconfig"
}
}
}
```
##### Kubeconfig
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_native_user_v3" "kubeconfig_inline" {
resource_id = formal_resource.cluster.id
label = "kubeconfig-inline"
kubernetes_inline {
kubeconfig {
environment_variable = "KUBECONFIG_YAML"
}
}
}
```
See
[Select a default Native User](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user)
for selection examples.
#### AWS IAM permissions
When using **AWS IAM** or **AWS IAM Role**, the Connector needs the following AWS
permissions to fetch the upstream cluster configuration:
```json theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["eks:DescribeCluster", "sts:GetCallerIdentity"],
"Resource": "*"
}
]
}
```
If the Connector is deployed in an AWS EKS cluster, grant these permissions to the Connector pod's IAM role with [EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html):
1. Create an IAM role trusted by `pods.eks.amazonaws.com`
2. Attach the AWS IAM policy above to that role
3. Associate the role with the Connector service account via EKS Pod Identity
4. Use that service account on the Connector pod
These steps are detailed in the [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-association.html) and demonstrated in our [example Terraform deployment](https://github.com/formalco/terraform-provider-formal/tree/main/examples/deployments/aws/eks-kubernetes-connector).
#### Upstream Kubernetes permissions
When connected to the target Kubernetes cluster, the Connector inherits the permissions defined by its token's username and groups (e.g. the matching *ClusterRoleBinding* and *ClusterRole*).
If the credentials have limited permissions, the Formal app will show the access logs correctly but users will see errors when attempting `kubectl` commands. For example:
```console theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
$ kubectl get pods
E0526 10:28:09.839098 25809 memcache.go:265] couldn't get current server API group list: the server has asked for the
client to provide credentials
E0526 10:28:10.715348 25809 memcache.go:265] couldn't get current server API group list: the server has asked for the
client to provide credentials
E0526 10:28:11.805740 25809 memcache.go:265] couldn't get current server API group list: the server has asked for the
client to provide credentials
E0526 10:28:12.912758 25809 memcache.go:265] couldn't get current server API group list: the server has asked for the
client to provide credentials
E0526 10:28:13.788519 25809 memcache.go:265] couldn't get current server API group list: the server has asked for the
client to provide credentials
error: You must be logged in to the server (the server has asked for the client to provide credentials)
```
On the opposite, if the permissions are set correctly, users will be able to run `kubectl` commands according to the Connector identity on the Kubernetes cluster. For example, for a *ClusterRole* that allows reading pods but not nodes:
```console theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
$ kubectl get pods
No resources found in default namespace.
$ kubectl get nodes
Error from server (Forbidden): nodes is forbidden: User "arn:aws:sts::123456789012:assumed-role/formal-connector-20240101000000000000000001/1234567890123456789" cannot list resource "nodes" in API group "" at the cluster scope
```
As seen in the last error of the previous example, the Connector identity is used for all commands. If you want the end user identity to propagate, you should use [impersonation](#impersonation) (recommended) or synchronize end users and native users.
#### Upstream Kubernetes permissions (AWS EKS)
If your target Kubernetes resource is an AWS EKS cluster, you need to:
* Allow your Connector role to connect to the EKS cluster with an [Access Entry Configuration](https://docs.aws.amazon.com/eks/latest/userguide/access-entries.html)
* Define its permissions with an [Access Entry Policy Association](https://docs.aws.amazon.com/eks/latest/userguide/access-policies.html) that links your previously created Pod Identity IAM role with the cluster access policy of your choice (e.g. `arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy`).
#### Example Terraform deployment
For a complete example of deploying the Formal Connector in an EKS cluster that controls access to that cluster, see our [example Terraform configuration](https://github.com/formalco/terraform-provider-formal/tree/main/examples/deployments/aws/eks-kubernetes-connector). This example demonstrates:
* Deploying the Formal Connector on AWS EKS
* Setting up AWS IAM roles and policies for EKS API access
* Configuring the Kubernetes cluster access permissions for the Connector
## Impersonation
User impersonation provides two key benefits:
1. **Improved audit logs**: Actions in Kubernetes logs are attributed to individual users rather than showing all Formal-mediated actions under a single service account.
2. **Granular access control**: Users' permissions are restricted to match what they would have when connecting directly to the cluster.
For more information about user impersonation, refer to the [Kubernetes documentation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation).
### Kubernetes RBAC
To allow user impersonation, the target Kubernetes cluster must have a specific RBAC configuration and allow the `impersonate` verb.
While the `cluster-admin` role includes impersonation permissions by default, custom roles require explicit configuration. Here is an example *ClusterRole* and *ClusterRoleBinding* that will allow a Connector to impersonate end users:
```yaml ClusterRole theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
name: impersonate
rules:
- apiGroups:
- ""
resources:
- "*"
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- "*"
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- "*"
verbs:
- get
- list
- watch
- apiGroups: # Ability to impersonate and review your own access
- ""
resources:
- users
- groups
verbs:
- impersonate
- apiGroups:
- "authorization.k8s.io"
resources:
- selfsubjectaccessreviews
verbs:
- create
```
```yaml ClusterRoleBinding theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: formal
subjects:
- kind: Group
name: formal-connector # Name of the Connector group
apiGroup: rbac.authorization.k8s.io
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: impersonate # Name of the ClusterRole you want the Connector to use
```
### Formal policy
The Connector decides whether to impersonate users based on policies.
This policy will grant the calling user access to resources defined by the groups in `input.user.groups` rather than the access level defined by the native user.
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
session := { "action": "impersonate", "user": input.user.username, "groups": input.user.groups } if {
input.datastore.technology == "kubernetes"
}
```
When using impersonation, the Formal Connector adds two headers to the request made:
* `Impersonate-User`: The user specified in the policy under user.
* `Impersonate-Group`: The list of groups in the policy under groups. If using RBAC, it should match the groups used in your Kubernetes cluster.
## Policy Evaluation
Formal supports the following policy evaluation stages for Kubernetes:
* **Session**: Evaluate and enforce policies on every Kubernetes API request. `input.kubernetes` describes that request.
## Kubernetes Input
The Connector populates the `input.kubernetes` object for policy evaluation. See the full reference in [Policy Evaluation](/docs/guides/policies/evaluation#kubernetes-resources).
| Field | Description | Example |
| - | - | - |
| `group` | API group (empty for core resources) | `apps` |
| `group_version` | API version | `v1` |
| `namespace` | Namespace (empty for cluster-scoped resources) | `default` |
| `kind` | Resource kind | `pods` |
| `name` | Resource name (empty for list/collection operations) | `my-pod` |
| `subresource` | Subresource | `exec` |
| `verb` | API verb, or lower-cased HTTP method for non-resource paths | `get`, `list`, `create` |
## Other policies
Additionally, you can apply access restrictions to Kubernetes Resources, similar to any other Resource. This includes the implementation of two additional filters, among others:
1. **Kind**: allow or block access to a specific Kubernetes Kind (e.g. secret)
2. **Namespace**: allow or block access to a specific Kubernetes Namespace
### MFA for `kubectl exec`
This policy requires users to use MFA to run `kubectl exec`.
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
session := { "action": "mfa" } if {
input.kubernetes.subresource == "exec"
}
```
### Block `kube-*` namespaces
The following policy blocks every query to the Connector for namespaces that start with `kube-`.
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
session := { "action": "block", "type": "block_with_formal_message" } if {
startswith(input.kubernetes.namespace, "kube-")
}
```
## Recordings
The Formal Connector records `kubectl exec` and `kubectl attach` sessions. Other requests, including `kubectl port-forward`, are logged but not recorded. The session recordings can be accessed and reviewed in the [Sessions application](/docs/guides/observability/sessions).
# LLM
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/llm
How Formal proxies LLM API traffic through the Formal Endpoint or a Connector
## Overview
LLM resources let Formal inspect and control traffic to LLM provider APIs, such as `api.anthropic.com` or `chatgpt.com`. Formal can proxy this traffic in two places:
* **Formal Endpoint**: [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode) intercepts LLM traffic on **macOS** and **Linux**. The Endpoint evaluates policies and forwards requests to the provider.
* **Connector**: A Connector in your infrastructure evaluates policies and forwards requests to the provider. Traffic reaches it from the Formal Endpoint or directly from your applications.
```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
your app → Formal Endpoint → LLM provider API
your app → Formal Endpoint (optional) → Connector → LLM provider API
```
## Creating LLM Resources
You must create an LLM resource in Formal for each LLM API you want to intercept. The resource tells Formal which upstream hostname and port to forward traffic to.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_resource" "anthropic" {
name = "anthropic"
technology = "llm"
hostname = "api.anthropic.com"
port = 443
}
resource "formal_resource" "chatgpt" {
name = "chatgpt"
technology = "llm"
hostname = "chatgpt.com"
port = 443
}
```
## Connecting
### Through the Formal Endpoint
LLM resources do not support `formal connect`. Instead, enable [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode) and write a [network rule](/docs/guides/network-rules) that matches LLM traffic. This rule matches known agents on macOS and Linux, and known LLM provider hostnames on Linux:
```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"condition": {
"pre_tcp": process.is_agent,
"pre_tls": process.is_agent || (is_llm_hostname && os == "linux"),
"post_tls": technology == "llm"
}
}
```
To have a Connector evaluate this traffic instead of the Endpoint, set `forward_to_connector` in the rule's [outputs](/docs/guides/network-rules#rule-structure). The LLM resource must be reachable through a Connector listener.
### Through a Connector
Applications in your infrastructure can send LLM traffic to a Connector directly. Link the LLM resource to a Connector listener, then replace the provider hostname with the Connector hostname. When the listener serves several resources, prefix the hostname with the resource name, as described in [Smart Routing](/docs/guides/core-concepts/resources/smart-routing#http):
* **Before:** `https://api.anthropic.com/v1/messages`
* **After:** `https://anthropic./v1/messages`
As with [HTTP resources](/docs/guides/core-concepts/resources/http), set the `X-Formal-User-Username` and `X-Formal-User-Password` headers to associate requests with a Formal identity.
## Observability
### Log source
Logs appear with `source: desktop` when the Formal Endpoint evaluates the traffic, and with `source: connector` when a Connector does. Filter for them in the [Logs page](https://app.formal.ai/logs) with `source:desktop` or `source:connector`.
To encrypt or strip these logs, create a log configuration with **Source** set to **Endpoint** or **Connector**. See [Logs Configuration](/docs/guides/observability/log-configuration#source).
### What is captured
Formal captures the full content of each LLM request and response, including:
* **Model and provider** — the model name extracted from the request body
* **Messages** — the full prompt and completion text
* **Tool calls** — any tool/function calls the model makes, including their names, arguments, and results
* **Token usage** — prompt tokens, completion tokens, and total tokens
* **Streaming responses** — individual chunks are reassembled into the complete response before logging
### Session replay
Full session replay is available in the [Sessions page](https://app.formal.ai/sessions). The session replay shows every request sent and response received, including all tool call exchanges.
## Policies
You can enforce policies on LLM sessions at the `session`, `request`, and `response` stages. Use `session` rules to block a connection before it reaches the upstream provider, `request` rules to inspect or rewrite request headers and bodies, and `response` rules to inspect tool calls, the model, the provider, and token usage.
Request-stage rewrite policies can:
* Add, replace, or remove **headers** before Formal forwards the request
* Replace the **body** (for example with `regex.replace` on `input.http.body`) to redact sensitive prompt text such as SSNs without blocking the session
See [LLM policy inputs](/docs/guides/policies/evaluation#llm-resources) for the full list of available fields and examples, including [redacting SSNs from request messages](/docs/guides/policies/evaluation#example-redact-ssns-from-request-messages). To allow only your Anthropic organization, see [Restrict Anthropic Sign-Ins to One Org](/docs/guides/example-use-cases/restrict-anthropic-sign-ins).
# MariaDB
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/mariadb
How to connect to a MariaDB Resource using the Formal Connector
## Overview
MariaDB has its own technology, `mariadb`, in Formal. The Connector proxies it with the same engine and SQL dialect as [MySQL](/docs/guides/core-concepts/resources/mysql), so the MySQL guide applies to MariaDB too.
Choose `mariadb` rather than `mysql` for MariaDB servers. Policies and logs then report `mariadb` as the Resource technology, which lets you scope policies to MariaDB alone.
## Create a MariaDB Resource
Go to [Resources](https://app.formal.ai/resources) and click **Create Resource**. Set **Technology** to **MariaDB**, then enter the **Hostname**. The **Port** defaults to `3306`.
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_resource" "orders" {
name = "orders-mariadb"
technology = "mariadb"
hostname = "orders.example.internal"
port = 3306
}
```
## Requirements
### Networking
You may assign any listener port except 8080 (the Connector's health check port). Make sure your security groups allow the traffic.
### Database Health Check
Formal periodically connects and runs `SELECT 1`. If you don't configure a database for health checks, Formal uses `mysql`. See [Configure Health Check Database](/docs/guides/core-concepts/resources/introduction#configure-health-check-database).
### Native Users
MariaDB Resources require a [Native User](/docs/guides/core-concepts/resources/native-users). The Control Plane offers **Password**, **AWS IAM**, **AWS IAM Role**, and **GCP IAM** credentials. Grant the Native User the permissions end users need upstream. Granting `SELECT` on `information_schema` is recommended.
## Connect to MariaDB
Use the `mariadb` or `mysql` client:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
mariadb -h CONNECTOR_HOSTNAME -P PORT -D DATABASE_NAME -u FORMAL_USERNAME --password=PASSWORD
```
Replace `CONNECTOR_HOSTNAME`, `PORT`, `DATABASE_NAME`, `FORMAL_USERNAME`, and `PASSWORD` with the right values.
The `mariadb` and `mysql` clients don't accept passwords longer than 80
characters with the interactive `-p` option. Formal access tokens are longer,
so use [hashed tokens](/docs/guides/core-concepts/identities#using-hashed-access-tokens).
**Verify:**
```sql theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
SELECT CURRENT_USER();
```
The result is the Native User's database user.
### Smart Routing
Several MariaDB Resources can share one listener port. Add the Resource name after the database name:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
mariadb -h CONNECTOR_HOSTNAME -P 3306 -D DATABASE_NAME@RESOURCE_NAME -u FORMAL_USERNAME
```
## Policy Evaluation
Formal evaluates policies at the **session**, **request**, and **response** stages, as for MySQL. To target MariaDB only:
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
import future.keywords.in
request := {
"action": "block",
"type": "block_with_custom_message",
"message": "Schema changes on MariaDB require a change ticket"
} if {
input.resource.technology == "mariadb"
input.sql_query.statement_type in {"CREATE", "ALTER", "DROP"}
}
```
## Next Steps
Read the full MySQL guide
Configure upstream credentials
# MCP
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/mcp
How to proxy MCP traffic through the Formal Connector
## Overview
MCP (Model Context Protocol) resources let you intercept and control HTTP traffic
originating from AI agents. Formal sits in the path to enforce policies and log all activity.
This page is about proxying MCP traffic through a Connector. Formal's
hosted MCP is [Connect Formal MCP](/docs/api/mcp-server).
## Connecting via the Formal Endpoint (Transparent Mode)
Transparent Mode is supported on **macOS** and **Linux**. On macOS, install
the Formal network extension (`formal transparent-proxy install`) before use.
On Linux, enable it in `~/.formal/config.toml`. See
[Install Formal Endpoint on Linux](/docs/guides/client-apps/linux).
Use the Formal Endpoint's [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode) to intercept outbound traffic without reconfiguring the MCP client itself.
MCP resources do not support `formal connect`. Instead, write a [network rule](/docs/guides/network-rules) that matches MCP traffic. This rule intercepts MCP traffic from known agents:
```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
"condition": {
"pre_tcp": process.is_agent,
"pre_tls": process.is_agent,
"post_tls": technology == "mcp"
}
}
```
### Creating an MCP Resource
```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_resource" "github_mcp" {
name = "github-mcp"
technology = "mcp"
hostname = "api.githubcopilot.com"
port = 443
}
```
## Connecting without the Formal Endpoint
If your MCP client runs in a cloud environment, route its traffic through a
Connector by updating the upstream hostname.
Replace the target API hostname with the Connector hostname (and the resource subdomain if using smart routing):
* **Before:** `https://api.githubcopilot.com/mcp`
* **After:** `https:///mcp`
Like with the HTTP technology, set `X-Formal-User-Username` and `X-Formal-User-Password` headers to associate
requests with a Formal identity.
To let any MCP client sign in with Formal OAuth through the Connector, see [MCP Gateway](/docs/guides/core-concepts/connectors/mcp-gateway).
## Policy Evaluation
Formal supports the following policy evaluation stages for MCP:
* **Session**: Evaluate and enforce policies at connection time
* **Request**: Evaluate and enforce policies before the request is forwarded (`allow`, `block`, `rewrite`, `decrypt`)
* **Response**: Evaluate and enforce policies after the response is received (`allow`, `encrypt`)
At the request and response stages, `input.mcp` is populated when the
request is an MCP tool call. See [MCP policy inputs](/docs/guides/policies/evaluation#mcp-resources)
for the full list of available fields.
### Example: Block a specific tool
```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2
import future.keywords.if
default request := {"action": "allow"}
request := {"action": "block", "type": "block_with_formal_message"} if {
input.mcp.tool_name == "list_pull_requests"
}
```
## Authentication
The Connector forwards authentication headers from the MCP client transparently.
You can also inject credentials using [Native Users](/docs/guides/core-concepts/resources/native-users)
so the MCP client does not need to hold secrets directly.
Use [Default Native User](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user)
to select credentials for each Formal identity.
To seal OAuth access tokens so the client stores `formalsealed:v1:` envelopes instead of plaintext, see [Encrypt MCP OAuth Tokens](/docs/guides/example-use-cases/encrypt-mcp-oauth-tokens) (Notion MCP worked example).
# MongoDB
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/mongodb
How to connect to a MongoDB Resource using the Formal Connector
## Requirements
### Networking
Certain ports must be accessible to connect to Formal Resources. You can listen on multiple ports simultaneously for various Resources.
For accessing MongoDB, you may assign any port except 8080 (Connector's health check port). Ensure your security groups are configured to allow traffic on the designated port.
### Database health check
Formal periodically assesses the health of the database. We test the connection to the `admin` database (`authSource` and `defaultauthdb` [are not specified by default in our generated URI](https://www.mongodb.com/docs/manual/reference/connection-string/)).
## SRV connection strings
MongoDB clusters can be published as a single DNS name whose SRV records point to the actual hostnames of the cluster's servers. That DNS name is what `mongodb+srv://` connection strings contain, such as the ones MongoDB Atlas provides.
Set the Resource's `hostname` to it, without the scheme (e.g. `cluster0.abcde.mongodb.net`). The Connector notices the SRV records, looks them up, and connects to the server accepting writes, so nothing needs changing when the cluster fails over.
The Resource's `port` is ignored for these Resources, because each SRV record carries the port of the server it points to.
## Connect to MongoDB
When a session-level policy denies by default, some clients such as `mongosh` cannot establish a connection, because they open unauthenticated connections before authentication.
To connect under such a policy, use the [Formal Endpoint](/docs/guides/client-apps/desktop-app), which is not affected by this.
### mongosh
To connect using `mongosh`, execute the following command:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
mongosh --host CONNECTOR_HOSTNAME:PORT -u 'idp:formal:human:john@joinformal.com'
```
You can also specify a MongoDB URI directly:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
mongosh 'mongodb://CONNECTOR_HOSTNAME:PORT/' -u 'FORMAL_USERNAME'
```
Make sure to replace `CONNECTOR_HOSTNAME`, `PORT`, `DATABASE_NAME`, and
`FORMAL_USERNAME` with the right values. To map Formal identities to upstream
credentials, see
[Select a default Native User](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user).
You can access your Formal Credentials in the [Control Plane](https://app.formal.ai).
You are free to use any preferred client.
## Smart Routing
The Connector features Smart Routing for MongoDB, allowing the linkage of an unlimited number of Resources over the same port (e.g. 27017).
Set the connection string's `appName` option to `formal_resource_name=RESOURCE_NAME`. The Connector reads the resource name from `appName`, not from the database name:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
mongosh 'mongodb://CONNECTOR_HOSTNAME:PORT/?appName=formal_resource_name=RESOURCE_NAME'
```
## Data Discovery
The [Data Discovery Satellite](/docs/guides/core-concepts/satellites#data-discovery-satellite) catalogs MongoDB into the [data inventory](/docs/guides/core-concepts/data-inventory). Databases are listed as databases, collections as tables, and MongoDB views as views. MongoDB has no schema layer, so paths have the `DATABASE.COLLECTION.FIELD` shape, matching the paths policies target.
MongoDB does not keep a catalog of the fields a collection holds, so discovery reads the first 500 documents of each collection and reports the union of the fields it finds:
* Top-level fields become columns, with the BSON type MongoDB's `$type` operator reports. A field whose type differs between documents is reported as `mixed`.
* Fields inside embedded documents become sub-columns. Array indexes are left out of the path, so a field of the documents inside an array will be reported once, at `DATABASE.COLLECTION.ARRAY.FIELD`.
* An array of scalars is a single column: its values are sampled at the array's own path, which is also where policies mask them.
Fields that only appear in documents beyond the first 500 of a collection are not discovered.
The native user configured for discovery needs `listDatabases` on the cluster, plus `listCollections` and `find` on every database to catalog. The built-in `readAnyDatabase` role covers all three. Without `listDatabases`, MongoDB returns only the databases the native user holds privileges on, and discovery catalogs those.
The `admin`, `config`, and `local` databases are listed but not descended into.
## Policy Evaluation
Formal supports the following policy evaluation stages for MongoDB:
* **Session**: Evaluate and enforce policies at connection time
* **Request**: Evaluate and enforce policies on a command before it reaches the database, through `input.mongodb` and `input.table_paths`
* **Response**: Evaluate and enforce policies after data retrieval
# MySQL
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/mysql
How to connect to a MySQL Resource using the Formal Connector
Google Cloud SQL MySQL can be reached natively via [Dial Configuration](/docs/guides/core-concepts/resources/dial), without a `cloud-sql-proxy` sidecar.
For MariaDB servers, create a Resource with the `mariadb` technology. See
[MariaDB](/docs/guides/core-concepts/resources/mariadb).
## Requirements
### Networking
Certain ports must be accessible to connect to Formal Resources. You can listen on multiple ports simultaneously for various Resources.
For accessing MySQL, you may assign any port except 8080 (Connector's health check port). Ensure your security groups are configured to allow traffic on the designated port.
### Database Health Check
Formal periodically assesses the health of the database. The database used for health checks can be configured in the Resource Details page (see [Database Health Check Configuration](/docs/guides/core-concepts/resources/introduction#configure-health-check-database)). If not configured, the system defaults to the `mysql` database.
Formal connects to the configured database and runs `SELECT 1`.
### Native User Permissions
The native user should match the permissions end users need to have upstream. For read-only access, grant only `SELECT` on the application schemas. For read and write access, also grant the write permissions those users need.
The native user must have permission to connect to the database configured for health checks. No special privileges are required for the health check itself. Formal does not require extra MySQL privileges just to proxy user queries.
Granting `SELECT` on `information_schema` is recommended so the Connector can leverage the schema inventory in some policy edge cases.
See [Native Users](/docs/guides/core-concepts/resources/native-users) for least-privilege guidance.
## Connect to MySQL
Once the Connector is started and able to reach a MySQL Resource, you can connect to it using any MySQL-compatible client.
MySQL listeners take longer to start than other technologies because they
generate a large RSA keypair at initialization. This key is used by the
`caching_sha2_password` authentication plugin to encrypt credentials when
clients connect without TLS. This can take up to a minute on modern hardware.
For example, to connect using the `mysql` CLI client, execute the following command:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
mysql -h CONNECTOR_HOSTNAME -P PORT -D DATABASE_NAME -u FORMAL_USERNAME --password=PASSWORD
```
Make sure to replace `CONNECTOR_HOSTNAME`, `PORT`, `DATABASE_NAME`, `PASSWORD`,
and `FORMAL_USERNAME` with the right values. To map Formal identities to
upstream credentials, see
[Select a default Native User](/docs/guides/core-concepts/resources/native-users#select-a-default-native-user).
You can access your Formal Credentials in the [Control Plane](https://app.formal.ai).
The standard `mysql` and `mariadb` CLI clients don't support passwords longer
than 80 characters when using the `-p` (interactive password) option. Since
Formal access tokens exceed this limit, you must use [hashed
tokens](/docs/guides/core-concepts/identities#using-hashed-access-tokens)
instead.
## Smart Routing
The Connector features Smart Routing for MySQL, allowing the linkage of an unlimited number of Resources over the same port (e.g. 3306).
Provide the name of the resource following the database name:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
mysql -h CONNECTOR_HOSTNAME -P 3306 -D DATABASE_NAME@RESOURCE_NAME -u 'idp:formal:human:john@joinformal.com'
```
It is possible to attach multiple hostnames to a single resource.
To specify the hostname of the resource you wish to connect to, you can use the following format:
```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
mysql -h CONNECTOR_HOSTNAME -P 3306 -D DATABASE_NAME@RESOURCE_NAME@RESOURCE_HOSTNAME_NAME -u 'idp:formal:human:john@joinformal.com'
```
Replace `CONNECTOR_HOSTNAME`, `DATABASE_NAME`, `RESOURCE_NAME`, and `RESOURCE_HOSTNAME_NAME` with the right values.
## Policy Evaluation
Formal supports the following policy evaluation stages for MySQL:
* **Session**: Evaluate and enforce policies at connection time
* **Request**: Evaluate and enforce policies before query execution
* **Response**: Evaluate and enforce policies after data retrieval
# Native Users
Source: https://docs.formal.ai/docs/guides/core-concepts/resources/native-users
Configure the credentials that Connectors use for upstream resources
## Overview
Native Users define how a Connector authenticates to an upstream Resource.
Formal identities never need to know or send these upstream credentials.
This guide documents Native Users supported by Connector 2.17.0 and later.
If your Resource still uses the legacy model, see
[Legacy Native Users](/docs/guides/core-concepts/resources/native-users-legacy).
If you would like to migrate to new Native Users, see the [migration guide](/docs/guides/core-concepts/resources/native-users-legacy#migrate-to-new-native-users).
gRPC Resources accept the HTTP credential types. Configure them with Terraform
or the API.
## How Native Users work
Each Native User has three important properties:
* A **label** identifies the Native User within its Resource.
* A **credential type** defines how the Connector authenticates upstream.
* A **credential source** supplies static values or runs a hook at connection time.
You can configure a default Native User selection for each Formal identity.
The selection returns a Native User ID.
A user can instead request a Native User by appending `@