> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/openshell/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/openshell/_mcp/server.

# Grant GitHub Push Access to a Sandboxed Agent

> Launch an agent with a GitHub provider, diagnose a denied push, and grant repository-scoped write access.

This tutorial shows how provider access and sandbox policy work together. You
attach a GitHub provider that contributes read-only GitHub access, observe a
denied push, and add a user policy that permits writes to one repository.

The built-in [default policy](/how-it-works/policies/default-policy) does not grant network
access. Imported provider profiles contribute the endpoints and executable
paths needed by their providers.

## Prerequisites

You need:

* A working OpenShell installation and active gateway.
* An OCI image you built with Claude Code, `git`, and the GitHub CLI installed.
* An Anthropic API key.
* A GitHub fine-grained personal access token with read and write access to the
  target repository's contents.
* A scratch GitHub repository that you can push to.

Use two host terminals. Terminal 1 runs the agent. Terminal 2 inspects denials
and updates policy.

## Import the Provider Profiles

Review the [Claude Code profile](https://github.com/NVIDIA/OpenShell/blob/main/providers/claude-code.yaml)
and [GitHub profile](https://github.com/NVIDIA/OpenShell/blob/main/providers/github.yaml).
Confirm that their `binaries` paths match the executables in your image, then
import them directly. If a path or endpoint grant needs to change, edit a local
copy and import it with `-f` instead.

```shell
openshell profile import --url https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/claude-code.yaml --global
openshell profile import --url https://raw.githubusercontent.com/NVIDIA/OpenShell/main/providers/github.yaml --global
```

The gateway contains no built-in profiles. Import each profile once per gateway
at the scope where users need it.

## Create the Providers

Create provider instances from credentials in your host environment. Passing a
credential on the same command line exports it only to that command:

```shell
ANTHROPIC_API_KEY=<your-anthropic-key> \
  openshell provider create --name my-claude --type claude-code --from-existing

GITHUB_TOKEN=<your-github-token> \
  openshell provider create --name my-github --type github --from-existing
```

OpenShell stores the credentials through the configured credential driver. The
agent receives opaque placeholders; the sandbox proxy resolves them only for
endpoints allowed by the corresponding profile.

## Start the Agent

In terminal 1, start Claude Code from your image and attach both providers:

```shell
openshell sandbox create \
  --name github-demo \
  --from registry.example.com/your-org/claude-agent:latest \
  --provider my-claude \
  --provider my-github \
  -- claude
```

To use an existing sandbox, attach the providers from a host terminal, then
start a new agent process so it receives the provider environment variables:

```shell
openshell sandbox provider attach <sandbox-name> my-claude
openshell sandbox provider attach <sandbox-name> my-github
openshell sandbox connect <sandbox-name>
```

## Attempt a Push

Ask the agent to create a file and push it to your scratch repository. Replace
`<org>` and `<repo>` with the repository owner and name:

**`Prompt`**

```md title="Prompt" wordWrap showLineNumbers={false}
Create `hello_world.py`, commit it, and push it to
`https://github.com/<org>/<repo>.git`. Use the GitHub credentials already
available in the environment. Do not ask me to paste a token.
```

The push fails. The GitHub profile permits clone and fetch operations but does
not permit `git-receive-pack`, the Git Smart HTTP operation used for a push.
The credential remains attached and endpoint-scoped; changing the token would
not grant the missing network authority.

## Inspect the Denial

In terminal 2, inspect recent sandbox logs:

```shell
openshell logs github-demo --since 5m --source sandbox
```

You should see a denial for a request resembling this one:

```text
[1775014140.412] [sandbox] [OCSF ] [ocsf] HTTP:POST [MED] DENIED POST http://github.com:443/<org>/<repo>.git/git-receive-pack [policy:_provider_my_github engine:l7] [reason:L7_REQUEST deny POST github.com:443/<org>/<repo>.git/git-receive-pack reason=POST /<org>/<repo>.git/git-receive-pack not permitted by policy]
```

`_provider_my_github` is the rule that OpenShell composes from the attached
`my-github` provider. Policy events are INFO-level log records, so do not filter
them out with `--level warn`.

You can also run `openshell term` to inspect policy decisions in the terminal
dashboard.

## Create a Repository-Scoped Policy

`policy set` replaces the complete base policy, so start from the sandbox's
current one. In terminal 2, print it:

```shell
openshell policy get github-demo --base
```

The command prints revision details followed by the policy. Save only the policy
YAML as `github-push.yaml`. Keep its filesystem, Landlock, and process settings
unchanged, because OpenShell rejects removed filesystem paths and changed
Landlock or process settings on a running sandbox.

Add these entries under `network_policies`, creating the section if it is
missing. Replace `<org>` and `<repo>`, and adjust `binaries` to match your
image:

```yaml
network_policies:
  github_repository_push:
    name: github-repository-push
    endpoints:
      - host: github.com
        port: 443
        protocol: rest
        enforcement: enforce
        rules:
          - allow:
              method: GET
              path: "/<org>/<repo>.git/info/refs*"
          - allow:
              method: POST
              path: "/<org>/<repo>.git/git-upload-pack"
          - allow:
              method: POST
              path: "/<org>/<repo>.git/git-receive-pack"
    binaries:
      - path: /usr/bin/git
      - path: /usr/local/bin/git

  github_repository_api:
    name: github-repository-api
    endpoints:
      - host: api.github.com
        port: 443
        protocol: rest
        enforcement: enforce
        rules:
          - allow:
              method: "*"
              path: "/repos/<org>/<repo>/**"
    binaries:
      - path: /usr/bin/gh
      - path: /usr/local/bin/gh
```

The first entry grants Git Smart HTTP operations only for the selected
repository. The second lets `gh` use REST operations scoped to the same
repository. The attached GitHub provider continues to supply credential
placement and its broader read-only rules.

## Apply the Policy

Apply the policy and wait for the new revision to load:

```shell
openshell policy set github-demo --policy github-push.yaml --wait
```

Network policy changes hot-reload without recreating the sandbox. Provider
rules are composed with this user-authored base policy.

## Retry and Verify

Ask the agent to retry the push. Then confirm that the proxy allowed the scoped
request:

```shell
openshell logs github-demo --since 5m
openshell policy get github-demo --full
```

The push succeeds for the selected repository. Requests to push to another
repository remain denied.

## Clean Up

Delete the sandbox when you are finished:

```shell
openshell sandbox delete github-demo
```

## Next Steps

* Review [Profiles](/how-it-works/providers/profiles) for endpoint-scoped credential placement.
* Review [Manage Sandbox Policies](/how-it-works/policies/manage-policies) for incremental policy updates and policy history.
* Review the [Policy Schema](/how-it-works/policies/schema) for REST, GraphQL, and other protocol rules.