> ## 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.

# Protect Git Tokens in Command Lines

> Block coding agents from putting Git tokens on a command line, and encrypt process command lines in Formal Endpoint logs

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

An agent can authenticate Git by putting a token in the remote URL:

```bash theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
git clone https://x-access-token:<token>@github.com/acme/app.git
```

GitHub documents this form for [GitHub App installation tokens](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation), and GitLab documents `https://oauth2:<token>@` for [OAuth tokens](https://docs.gitlab.com/api/oauth2/). The token is then part of the command line of `git` and of the `git-remote-https` process it starts to open the connection.

When a [network rule](/docs/guides/network-rules) intercepts a connection, the Formal Endpoint records the command line of the connecting process and its ancestors in the session's device info. Formal truncates long command lines but does not look for credentials in them, so the token is stored as-is. Formal redacts the `Authorization` header Git sends on each request, but not the command line.

You may want to handle this in two places:

* **Logs:** a [log schema](/docs/guides/observability/log-schemas) encrypts the command lines on the Endpoint, before the log leaves the device.
* **Agents:** a `response` <G anchor="policy">policy</G> blocks agent tool calls whose arguments contain a token or a URL with credentials, so the command never runs.

## GitHub token formats

GitHub [prefixes each token type](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats):

| Prefix | Token |
| - | - |
| `ghp_` | Personal access token (classic) |
| `github_pat_` | Fine-grained personal access token |
| `gho_` | OAuth app access token |
| `ghu_` | GitHub App user access token |
| `ghs_` | GitHub App installation access token |
| `ghr_` | Refresh token |

Agents commonly find a token in the environment: the GitHub CLI reads [`GH_TOKEN` and `GITHUB_TOKEN`](https://cli.github.com/manual/gh_help_environment), and GitHub Actions creates a [`GITHUB_TOKEN`](https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication) installation token for each job. When the agent writes `$GITHUB_TOKEN` into the URL, the shell expands it, so the real token is in the command line but not in the agent's tool call.

## Prerequisites

* Formal Endpoint with Transparent Mode enabled
* An asymmetric [encryption key](/docs/guides/integrations/encryption_keys) registered in Formal
* Permission to create log schemas, network rules, and policies in Formal

## Encrypt tokens in logs

Command lines are only logged for connections a network rule intercepts, such as the rule in [Block Agent Git Pushes](/docs/guides/example-use-cases/block-agent-git-pushes#create-the-network-rule). Tool calls are logged for LLM traffic a network rule intercepts.

This schema encrypts the command lines of the connecting process and its ancestors, and the tool call arguments in LLM logs, in every Endpoint log:

```hcl theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
resource "formal_log_schema" "encrypt_git_tokens" {
  name              = "encrypt-git-tokens"
  encryption_key_id = formal_encryption_key.logs_key.id
  scope_cel         = "log.source == \"endpoint\""

  path {
    name    = "log.session.device.connection_process.process.cmdline"
    encrypt = true
  }

  path {
    name    = "log.session.device.connection_process.ancestors.cmdline"
    encrypt = true
  }

  path {
    name    = "log.llm.tool_calls.input"
    encrypt = true
  }

  path {
    name    = "log.llm.response_messages.content_parts.input"
    encrypt = true
  }

  path {
    name    = "log.llm.events.data"
    encrypt = true
  }
}
```

Process names, code signing details, and tool names stay readable. To remove the values instead, replace `encrypt = true` with `drop = true`.

<Warning>
  Log schemas do not apply to retained policy inputs. With
  [Policy Evaluation Input Retention](/docs/guides/observability/logs#policy-evaluation-input-retention)
  enabled, Formal stores each input in full, including the command lines.
</Warning>

## Block tokens in agent tool calls

This policy needs a network rule that intercepts LLM traffic, such as the one in [LLM resources](/docs/guides/core-concepts/resources/llm#through-the-formal-endpoint).

The policy checks every string in each tool call's arguments for a GitHub token prefix or a URL with credentials. The URL pattern also matches `x-access-token:$GITHUB_TOKEN@`, which the token prefixes cannot see.

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

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

credential_patterns := [
  `\b(gh[opsur]_|github_pat_)[A-Za-z0-9_]{20,}`,
  `https?://[^\s/@:]+:[^\s/@]+@`,
]

passes_credential(call) if {
  walk(call.input, [_, value])
  is_string(value)
  some pattern in credential_patterns
  regex.match(pattern, value)
}

response := {
  "action": "block",
  "type": "block_with_formal_message",
  "reason": "Agents may not put credentials in commands",
} if {
  input.resource.technology == "llm"
  some call in input.llm.tool_calls
  passes_credential(call)
}
```

The patterns match common formats, not every secret. A token passed through a credential helper, a script file, or a variable outside a URL does not match.

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

## Verify

Prompt Claude Code:

```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
Run: git clone https://x-access-token:ghs_FAKEFAKEFAKEFAKEFAKEFAKE@github.com/acme/app.git
```

The agent should get a Formal block message. The request appears in [Logs](https://app.formal.ai/logs) with the policy under triggered policies.

In the LLM log for that response, `tool_calls` `input` should be encrypted. For a Git command that a network rule intercepts, `cmdline` under `device.connection_process` should be encrypted in the session log.

## Next steps

<CardGroup cols={2}>
  <Card title="Log Schemas" icon="file-lines" href="/docs/guides/observability/log-schemas">
    Drop, encrypt, or truncate log fields
  </Card>

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

  <Card title="Policy Evaluation" icon="gavel" href="/docs/guides/policies/evaluation#llm-resources">
    Explore LLM policy inputs
  </Card>

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.