> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Block Agent Git Pushes

> Block coding agents from pushing to sensitive GitHub repositories over both SSH and HTTPS with Formal Endpoint Transparent Mode

export const G = ({term, anchor, children}) => {
  const href = anchor ? `/docs/glossary/index#${anchor}` : `/docs/glossary/index`;
  return <a href={href} className="glossary-link" style={{
    textDecoration: "underline",
    textDecorationLine: "underline",
    textDecorationColor: "#6b7280",
    textDecorationThickness: "1px",
    textUnderlineOffset: "2px",
    color: "inherit",
    transition: "text-decoration-color 0.2s ease",
    borderBottom: "none"
  }} onMouseEnter={e => e.target.style.textDecorationColor = "#fff"} onMouseLeave={e => e.target.style.textDecorationColor = "#6b7280"}>
  {children || term}
</a>;
};

## Overview

Coding agents run `git push` as easily as they run tests. Teams may want pushes to sensitive repositories to come from people, not agents.

Git talks to GitHub over SSH or HTTPS. A rule that covers only one transport is bypassed by switching the remote URL. GitHub also serves SSH on port 443 at `ssh.github.com`, for networks that block port 22.

The Formal Endpoint intercepts all three in [Transparent Mode](/docs/guides/client-apps/desktop-app#transparent-mode). One network rule selects git traffic from agents. One <G anchor="policy">policy</G> blocks pushes:

* **SSH:** a push runs `git-receive-pack` on the server. A `session` rule reads it from `input.ssh.command`.
* **HTTPS:** a push sends `POST` to `<repository>/git-receive-pack`. A `request` rule reads it from `input.http`.

Clones and fetches use `git-upload-pack` and keep working. Pushes from a user's own terminal skip the rule.

## Prerequisites

* Formal Endpoint on macOS with Transparent Mode enabled
* Permission to create network rules and policies in Formal

## Create the network rule

The Endpoint passes a connection through untouched unless a network rule matches it. This rule matches GitHub over SSH, SSH on port 443, and HTTPS, for connections from a known agent or its children.

<Tabs>
  <Tab title="Control Plane">
    1. Navigate to [Network Rules](https://app.formal.ai/network-rules)
    2. Create a rule named `github-agent-git`
    3. Paste the CEL below
    4. Leave **Forward to Connector** unset
    5. Save the rule and set it to **Active**
  </Tab>

  <Tab title="Terraform">
    ```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
    resource "formal_network_rule" "github_agent_git" {
      name        = "github-agent-git"
      description = "Intercept git traffic to GitHub from coding agents"
      status      = "active"

      cel_expression = <<-EOT
        {
          "condition": {
            "pre_tcp": connection_process.has_agent_ancestor,
            "pre_tls": hostname in ["github.com", "ssh.github.com"],
            "post_tls": technology in ["http", "ssh"]
          },
          "outputs": {}
        }
      EOT
    }
    ```
  </Tab>
</Tabs>

```cel theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
{
  "condition": {
    "pre_tcp": connection_process.has_agent_ancestor,
    "pre_tls": hostname in ["github.com", "ssh.github.com"],
    "post_tls": technology in ["http", "ssh"]
  }
}
```

`connection_process.has_agent_ancestor` is true for a known agent and for every process it spawns, such as `git` and `ssh`. It relies on code-signed process identity, which only macOS has.

<Note>
  Interception happens on the Endpoint. This rule does not need a Formal
  <G anchor="resource">resource</G>, Connector listener, or **Forward to
  Connector**.
</Note>

## Create the policy

List the repositories to protect in `repositories`. The match is a substring, so repositories whose name starts with one of them, such as `acme/infrastructure-docs`, are also blocked.

```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.v2

import future.keywords.if
import future.keywords.in

hostnames := {"github.com", "ssh.github.com"}

repositories := {
  "acme/infrastructure",
  "acme/payments",
}

block := {
  "action": "block",
  "type": "block_with_formal_message",
}

session := block if {
  input.resource.hostname in hostnames
  contains(input.ssh.command, "git-receive-pack")
  some repository in repositories
  contains(input.ssh.command, repository)
}

request := block if {
  input.http.hostname in hostnames
  input.http.method == "POST"
  contains(input.http.path, "git-receive-pack")
  some repository in repositories
  contains(input.http.path, repository)
}
```

Create the policy in **Draft** or **Dry-run** first. Set it to **Active** after reviewing the matching sessions and requests.

See [SSH resources](/docs/guides/policies/evaluation#ssh-resources) and the [HTTP object](/docs/guides/policies/evaluation#http-object) for the fields each rule can read.

<Tip>
  As an alternative approach, an MFA action would pause the agent on the push
  and enforce the user's confirmation on their device:

  ```rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
  block := {
    "action": "mfa",
    "message": "confirm the push to a sensitive repository",
  }
  ```
</Tip>

## Verify agent pushes are blocked

Prompt Claude Code to clone a protected repository, make a change, and push it:

```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
Clone acme/infrastructure, add a line to the README, commit it, and push.
```

The clone should succeed. The push should fail with a Formal block message. The same push from a user's own terminal should go through.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The HTTPS push fails with a certificate error">
    Git does not trust the Formal CA. Run:

    ```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
    sudo env HOME="${HOME}" formal ca trust
    ```

    Git builds that use their own CA bundle ignore the system store. Point
    `http.sslCAInfo` at a bundle that includes the Formal CA.
  </Accordion>

  <Accordion title="The SSH push fails before the policy runs">
    See [SSH troubleshooting](/docs/guides/core-concepts/resources/ssh#troubleshooting)
    for key and host key errors.
  </Accordion>

  <Accordion title="The agent push goes through">
    Confirm the policy is **Active** and Transparent Mode is enabled. Confirm
    the `github-agent-git` network rule is **Active**. Without a matching rule
    the Endpoint forwards the connection without inspecting it. The rule only
    matches known agents, so a push from an unrecognized tool is not
    intercepted.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Network Rules" icon="filter" href="/docs/guides/network-rules">
    Select which traffic Transparent Mode intercepts
  </Card>

  <Card title="SSH" icon="terminal" href="/docs/guides/core-concepts/resources/ssh#transparent-mode">
    Review how the Endpoint handles SSH
  </Card>

  <Card title="Policy Evaluation" icon="gavel" href="/docs/guides/policies/evaluation">
    Explore SSH and HTTP policy inputs
  </Card>

  <Card title="Endpoint Logs" icon="chart-line" href="/docs/guides/observability/logs">
    Review sessions and policy decisions
  </Card>
</CardGroup>
