Skip to main content

Overview

Formal policies can enforce different actions at three evaluation stages: session, request, and response. Each stage has specific actions available based on when the policy is evaluated.

Evaluation Stages

Session

When: Connection establishment and, for SSH and Kubernetes exec, while the session runs Actions: allow, block, mfa, quarantine, suspend, impersonate Use for: Authentication, connection-level access control, reacting to session monitors

Request

When: Before query reaches resource Actions: allow, block, rewrite, decrypt Use for: Query validation, SQL rewriting, blocking writes, decrypting secrets for the upstream

Response

When: After data returns from resource Actions: allow, block, filter, mask, encrypt, rewrite Use for: Data masking, PII redaction, filtering results, encrypting secrets before they leave

Common Action Parameters

All actions support these parameters:

Block Action

Deny access and terminate the connection or query.

Parameters

block_with_formal_message returns Formal’s default block text. That message includes the policy ID plus the session ID and request ID when they are available, so users can share those identifiers with their security team.

Example

Suggest approved MCP alternatives

Use mcp_suggest_alternatives to block an unapproved MCP server and tell the AI agent what to do next. The block message becomes an advisory written for the agent. It lists the MCP resources you have registered in Formal, excluding the blocked one. The advisory tells the agent to ask the user to choose one of these options:
  • Switch to one of the approved MCP servers.
  • Request the blocked server. The agent looks for a form about MCP requests with formal form list and submits it with formal form fill.
  • Do nothing for now.
Formal ignores message when it builds the advisory. If the advisory cannot be built, Formal returns message, or a short default text when message is empty. This policy blocks tool calls to MCP servers that have no Formal resource, such as servers the Formal Endpoint intercepts on a laptop. It is the policy that the Block unknown MCPs control on the Guardrails page generates.
Create a form with fields named name, url, and reason so agents can file requests for blocked MCP servers. Requests then reach your approval workflow.

Allow Action

Explicitly permit an operation. Use in combination with default deny policies.

Example

Rewrite Action

Modify a SQL query before it reaches the resource, or modify an HTTP request or response in transit.

Parameters

HTTP-based resources include HTTP, LLM, and MCP resources.
body, json, and post_form each replace the whole body. Set at most one of them in a decision. Formal rejects a decision that sets more than one and does not apply it.
When several active policies rewrite the same message, Formal applies all of their header rewrites. If more than one sets a body, only one of them takes effect. Scope policies so that body rewrites don’t overlap.

Example

Example: Rewrite LLM request headers

For the full walkthrough, see Restrict Anthropic Sign-Ins to One Org.

Example: Redact SSNs from LLM request bodies

String-replace sensitive values in the outbound LLM body so coding agents can continue without sending SSNs upstream. Prefer body rewrites over blocking the session when the goal is redaction.

Filter Action

Remove rows from the result set based on conditions.

Example

Mask Action

Redact or obfuscate sensitive data in responses.

Parameters

With type safety on, Formal keeps typed columns valid, such as numbers, booleans, dates, JSON, and UUIDs. nullify, redact, the hash subtypes, and reduce_decimal_precision keep their output when the column’s type can hold it. Every other mask on a typed column returns the fallback:
  • fallback_to_null returns NULL.
  • fallback_to_default returns a placeholder such as 0, {}, or an all-zero UUID. Boolean columns get NULL instead.
Text columns are masked normally.
Formal turns on type safety only when typesafe_fallback is set. A typesafe key on its own has no effect.

Masking Types

type and sub_type do different jobs. type only sets the privacy level. sub_type picks the function that transforms the value. When you omit sub_type, Formal uses type as the subtype. That shortcut works for nullify, hash.with_salt, hash.no_salt, and redact.constant_characters. fake and redact.partial always need an explicit sub_type.
Masking fails silently when a name is wrong. Formal does not validate mask names when you save a policy, and it logs no error at query time.
  • An unknown sub_type leaves the value unmasked.
  • An unknown type has privacy level 0, so Formal drops the mask.
  • type: "fake" or type: "redact.partial" without a sub_type leaves the value unmasked.
Copy names from the tables on this page. Run the policy in dry-run and check query results before you activate it.

Masking Subtypes

Subtypes that end in _with_fake replace the value with a random fake value of the same kind. Pair them with type: "fake". Pair the partial subtypes, such as the email and character-level ones, with type: "redact.partial".
  • redact.constant_characters: Replace the value with redact
  • redact: Replace the value with redact. With type safety on, keeps redact when the column’s type can hold it, such as -1 in an integer column
  • replace_characters_with: Replace every character with redact. With "redact": "*", secret becomes ******
  • redact.first_n_characters: Replace the first characters_count characters. With "redact": "*" and a count of 3, hello becomes ***lo
  • redact.last_n_characters: Replace the last characters_count characters. With "redact": "*" and a count of 3, hello becomes he***
  • mask_everything_except_last: Replace every character except the last one. With "redact": "*", sensitive becomes ********e. This subtype ignores characters_count
  • nullify: Return NULL
  • hash.no_salt: Hash the value. The same input always gives the same output, so joins still work
  • hash.with_salt: Hash the value with a random salt. The output changes on every read
  • reduce_decimal_precision: Truncate to precision digits after the decimal point. With a precision of 2, 123.456 becomes 123.45. Non-numeric values are unchanged
  • email_mask_username: jane@example.com becomes ****@example.com
  • email_mask_domain_name: jane@example.com becomes jane@*******.com. The masked domain is always *******.com
  • email_mask_while_preserving: jane@example.com becomes j***@e******.com
  • email_mask_with_fake: Random fake email address
The first three subtypes replace every character with * when the value isn’t an email address.
  • person_full_name_mask_with_fake
  • person_first_name_mask_with_fake
  • person_last_name_mask_with_fake
  • person_gender_mask_with_fake
  • person_ssn_mask_with_fake
  • phone_mask_with_fake
  • postal_address_mask_with_fake
  • city_mask_with_fake
  • state_mask_with_fake
  • country_mask_with_fake
  • zip_mask_with_fake
  • location_mask_except_state_country: Keep only the US state and the country from a comma-separated address. 1 Main St, Springfield, IL, USA becomes IL, USA
  • payment_credit_card_number_mask_with_fake
  • payment_credit_card_cvv_mask_with_fake
  • payment_credit_card_exp_mask_with_fake
  • payment_credit_card_type_mask_with_fake
  • payment_currency_with_fake
  • payment_ach_routing_with_fake
  • payment_ach_account_with_fake
  • payment_bitcoin_address_with_fake
  • payment_bitcoin_private_key_with_fake
  • network_url_mask_with_fake
  • network_domain_mask_with_fake
  • network_ipv4_mask_with_fake
  • network_ipv6_mask_with_fake
  • network_mac_mask_with_fake
  • company_name_with_fake
Character-level subtypes repeat redact once per masked character. If redact is empty, those characters are removed instead of replaced. Always set redact, for example to "*".

Examples

Mask email usernames:
Replace PII with fake data:
Redact with constant value:

Encrypt & Decrypt Actions

Encrypt and decrypt sensitive HTTP values in-line, so plaintext secrets never reach places that should not see them. Formal encrypts secrets on the way out and decrypts them on the way back in, so tokens stay protected end to end.
  • The encrypt action on the Formal Endpoint is only available on macOS.
  • The encrypt action on the Connector requires configuring a cloud KMS key for token encryption. See Token Encryption for more information.
  • decrypt runs at the request stage. It decrypts Formal-encrypted values before the upstream receives them. If a value fails to decrypt, the request is forwarded unchanged.
  • encrypt runs at the response stage. It encrypts values before they leave the proxy. If encryption fails, the response is blocked.

Parameters

Each target is an object:

Example: Encrypt tokens in a response

Example: Decrypt a request header for the upstream

For an end-to-end Notion MCP walkthrough (Endpoint Secure Enclave or Connector KMS), see Encrypt MCP OAuth Tokens.

MFA Action

The MFA action requires device-owner verification before proceeding with the connection. When a policy returns the mfa action, the Formal Endpoint prompts for device-owner verification before the connection proceeds. On macOS this is Touch ID or the device passcode. On Windows this is Windows Hello (fingerprint, facial recognition, or PIN). When a Connector evaluates the policy, it raises the challenge in the Control Plane and waits. The user’s Formal Endpoint picks up the challenge and shows the prompt. The user needs the Endpoint running to answer it.

Parameters

If verification fails or times out, Formal blocks the connection with MFA authentication failed or timed out. Please try again.
MFA on Windows requires Windows Hello. It’s the strongest form of device-owner verification available. A device with no Windows Hello method configured, or accessed over a remote desktop session where Windows Hello is unavailable, fails the challenge closed and is denied access.

Example

Quarantine Action

Stop a user who shows high-risk behavior. quarantine is a session-stage action for SSH and Kubernetes exec sessions. It is usually paired with session monitors, which re-evaluate session policies while the session runs. What happens depends on when the rule matches:
  • While a session runs: Formal ends every active session of the user and blocks the user. Blocked users can’t open SSH, Kubernetes, or RDP sessions until an admin unblocks them.
  • When an SSH session starts: Formal refuses that session. The user is not blocked.

Parameters

To unblock a user, go to Users, open the user, and click Unblock User. Blocked users show a Blocked badge. You can also block a user manually with Block User.

Example

Suspend Action

End one session without blocking the user. suspend is a session-stage action for SSH and Kubernetes exec sessions.
  • While a session runs: Formal ends that session. The user can reconnect.
  • When an SSH session starts: Formal refuses that session.

Parameters

Example

Impersonate Action

Run Kubernetes API requests as the end user instead of the Connector’s identity. impersonate is a session-stage action for Kubernetes resources. Formal adds the Impersonate-User and Impersonate-Group headers to each request it forwards.

Parameters

Example

The cluster must allow the Connector to impersonate users and groups. See Kubernetes impersonation for the RBAC setup.

Rule Conflicts and Precedence

When multiple policies apply to the same query, Formal resolves conflicts using least privilege:

Example of Conflict Resolution

Use included_connectors or included_resources to avoid unintended policy conflicts.

Scoping Policies with Connectors, Resources, Spaces, and Technologies

To prevent policy conflicts when using the default keyword, limit which Connectors, Resources, Spaces, or technologies a policy applies to.

Include/Exclude Connectors

Use included_connectors to apply a policy only to specific connectors:
Use excluded_connectors to exclude specific connectors:
A single policy cannot use both included_connectors and excluded_connectors. Choose one or the other.

Include/Exclude Resources

Use included_resources to apply a policy only to specific resources:
Use excluded_resources to exclude specific resources:
A single policy cannot use both included_resources and excluded_resources. Choose one or the other.

Include/Exclude Spaces

Scoping by Space covers every Connector and Resource the Space contains. Use included_spaces to apply a policy only to specific Spaces:
Use excluded_spaces to exclude specific Spaces:
A single policy cannot use both included_spaces and excluded_spaces. Choose one or the other.
Connectors that belong to no Space are never in scope for included_spaces, and are always in scope for excluded_spaces.

Include/Exclude Technologies

Use included_technologies or excluded_technologies to scope a policy by the Resource’s technology identifier, such as postgres, ssh, or llm:
A single policy cannot use both included_technologies and excluded_technologies. Choose one or the other.

Policy Stage Configuration

Policy evaluation can also be turned off per stage for a Resource or a Connector, without editing policies. See Policy Stage Configuration.