Do you use GitHub Copilot CLI in a secure dockerised environment?


GitHub Copilot CLI is incredibly powerful, but giving AI deep access to your terminal and file system can be concerning. When you use features like --allow-all-tools - which approves all actions - Copilot can execute commands on your behalf, which means one wrong suggestion could have serious consequences.

Running Copilot CLI in a secure Docker container provides the best of both worlds: powerful AI assistance with strict security boundaries that limit the "blast radius" of any potential mistakes.

The problem with unrestricted access

When running Copilot CLI directly on your host machine:

Copilot has access to:

  • Your entire file system
  • Your SSH keys in ~/.ssh/
  • All your repositories
  • Your environment variables and secrets
  • System-wide configurations

Bad example - Copilot running with full system access creates unnecessary risk - a single mistake like rm -rf ~ could be catastrophic

The solution: Docker-based isolation

By running Copilot CLI inside a Docker container, you create a secure sandbox where:

  • Copilot can only see your current project directory
  • Your home directory, SSH keys, and other projects are completely inaccessible
  • You can safely use --allow-all-tools with confidence (automatic approval)
  • The worst-case scenario is limited to the current project

Understanding the safety net

If Copilot runs a dangerous command like rm -rf .:

❌ Without Docker:

  • Deletes everything in current directory and subdirectories, depending on the folder you ran copilot in, could be catastrophic
  • No way to easily recover lost files and folders

✅ With Docker:

  • Only deletes files in the mounted /work directory which is mapped to your current project folder
  • Your other projects and system files are safe
  • If setup with git, it is easily recoverable

Note: The container shares your host's network, so it can access local resources and services. This is intentional for development workflows but means it's not a fully firewalled environment.

Understanding the two modes

Before diving into the setup, it's important to understand the two approaches available. You can install both side-by-side with different command names to give yourself options.

Safe Mode (Recommended) - Always asks for confirmation before executing commands. Use this for general development work where you want control over what gets executed.

YOLO Mode (Auto-Approve) - Automatically approves all tool usage without confirmation. Convenient for trusted workflows but use with caution as it can execute commands without prompting.

Both modes include security checks for proper GitHub token scopes and warn about overly privileged tokens. The YOLO mode adds the --allow-all-tools flag which bypasses execution confirmation.

Implementation: The copilot_here setup

The complete solution is available at https://github.com/GordonBeeming/copilot_here.

Note: The functions below provide cross-platform support for Linux/macOS and Windows. For the latest version, always check the GitHub repository.

This mode asks for confirmation before executing any commands, giving you full control.

For Linux/macOS (Bash/Zsh):

Add the following function to your shell profile (e.g., ~/.zshrc, ~/.bashrc):

copilot_here() {
  # --- SECURITY CHECK ---
  if ! gh auth status 2>/dev/null | grep "Token scopes:" | grep -q "'copilot'"; then
    echo "❌ Error: Your gh token is missing the required 'copilot' scope."
    echo "Please run 'gh auth refresh -h github.com -s copilot' to add it."
    return 1
  fi

  if gh auth status 2>/dev/null | grep "Token scopes:" | grep -q -E "'(admin:|manage_|write:public_key|delete_repo|(write|delete)_packages)'"; then
    echo "⚠️  Warning: Your GitHub token has highly privileged scopes (e.g., admin:org, admin:enterprise)."
    printf "Are you sure you want to proceed with this token? [y/N]: "
    read confirmation
    local lower_confirmation
    lower_confirmation=$(echo "$confirmation" | tr '[:upper:]' '[:lower:]')
    if [[ "$lower_confirmation" != "y" && "$lower_confirmation" != "yes" ]]; then
      echo "Operation cancelled by user."
      return 1
    fi
  fi
  # --- END SECURITY CHECK ---

  local image_name="ghcr.io/gordonbeeming/copilot_here:latest"

  printf "Checking for the latest version of copilot_here... "
  (docker pull "$image_name" > /dev/null 2>&1) &
  local pull_pid=$!
  local spin='|/-\'

  local i=0
  while ps -p $pull_pid > /dev/null; do
    i=$(( (i+1) % 4 ))
    printf "%s\b" "${spin:$i:1}"
    sleep 0.1
  done

  wait $pull_pid
  local pull_status=$?

  if [ $pull_status -eq 0 ]; then
    echo "✅"
  else
    echo "❌"
    echo "Error: Failed to pull the Docker image. Please check your Docker setup and network."
    return 1
  fi

  local copilot_config_path="$HOME/.config/copilot-cli-docker"
  mkdir -p "$copilot_config_path"

  local token=$(gh auth token 2>/dev/null)
  if [ -z "$token" ]; then
    echo "⚠️  Could not retrieve token using 'gh auth token'. Please ensure you are logged in."
  fi

  local docker_args=(
    --rm -it
    -v "$(pwd)":/work
    -v "$copilot_config_path":/home/appuser/.copilot
    -e PUID=$(id -u)
    -e PGID=$(id -g)
    -e GITHUB_TOKEN="$token"
    "$image_name"
  )

  if [ $# -eq 0 ]; then
    docker run "${docker_args[@]}" copilot --banner
  else
    docker run "${docker_args[@]}" copilot -p "$*"
  fi
}

Then reload your shell (e.g., source ~/.zshrc).

For Windows (PowerShell):

Save the following as copilot_here.ps1 in a location of your choice (e.g., C:\Users\YourName\Documents\PowerShell\):

function Copilot-Here {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromRemainingArguments=$true)]
        [string[]]$Prompt
    )

    # --- SECURITY CHECK ---
    Write-Host "Verifying GitHub CLI authentication..."
    $authStatus = gh auth status 2>$null
    if (-not ($authStatus | Select-String -Quiet "'copilot'")) {
        Write-Host "❌ Error: Your gh token is missing the required 'copilot' scope." -ForegroundColor Red
        Write-Host "Please run 'gh auth refresh -h github.com -s copilot' to add it."
        return
    }

    $privilegedScopesPattern = "'(admin:|manage_|write:public_key|delete_repo|(write|delete)_packages)'"
    if ($authStatus | Select-String -Quiet $privilegedScopesPattern) {
        Write-Host "⚠️  Warning: Your GitHub token has highly privileged scopes." -ForegroundColor Yellow
        $confirmation = Read-Host "Are you sure you want to proceed with this token? [y/N]"
        if ($confirmation.ToLower() -ne 'y' -and $confirmation.ToLower() -ne 'yes') {
            Write-Host "Operation cancelled by user."
            return
        }
    }
    Write-Host "✅ Security checks passed."
    # --- END SECURITY CHECK ---

    $imageName = "ghcr.io/gordonbeeming/copilot_here:latest"

    Write-Host -NoNewline "Checking for the latest version of copilot_here... "
    $pullJob = Start-Job -ScriptBlock { param($img) docker pull $img } -ArgumentList $imageName
    $spinner = '|', '/', '-', '\'
    $i = 0
    while ($pullJob.State -eq 'Running') {
        Write-Host -NoNewline "$($spinner[$i])`b"
        $i = ($i + 1) % 4
        Start-Sleep -Milliseconds 100
    }

    Wait-Job $pullJob | Out-Null
    $pullOutput = Receive-Job $pullJob

    if ($pullJob.State -eq 'Completed') {
        Write-Host "✅"
    } else {
        Write-Host "❌" -ForegroundColor Red
        Write-Host "Error: Failed to pull the Docker image." -ForegroundColor Red
        if (-not [string]::IsNullOrEmpty($pullOutput)) {
            Write-Host "Docker output:`n$pullOutput"
        }
        Remove-Job $pullJob
        return
    }
    Remove-Job $pullJob

    $copilotConfigPath = Join-Path $env:USERPROFILE ".config\copilot-cli-docker"
    if (-not (Test-Path $copilotConfigPath)) {
        New-Item -Path $copilotConfigPath -ItemType Directory -Force | Out-Null
    }

    $token = gh auth token 2>$null
    if ([string]::IsNullOrEmpty($token)) {
        Write-Host "⚠️  Could not retrieve token using 'gh auth token'." -ForegroundColor Yellow
    }

    $dockerBaseArgs = @(
        "--rm", "-it",
        "-v", "$((Get-Location).Path):/work",
        "-v", "$($copilotConfigPath):/home/appuser/.copilot",
        "-e", "GITHUB_TOKEN=$token",
        $imageName
    )

    $copilotCommand = @("copilot")
    if ($Prompt.Length -eq 0) {
        $copilotCommand += "--banner"
    } else {
        $copilotCommand += "-p", ($Prompt -join ' ')
    }

    $finalDockerArgs = $dockerBaseArgs + $copilotCommand
    docker run $finalDockerArgs
}

Set-Alias -Name copilot_here -Value Copilot-Here

Then add it to your PowerShell profile. Open your profile for editing:

notepad $PROFILE

Add this line (adjust the path to where you saved the file):

. C:\Users\YourName\Documents\PowerShell\copilot_here.ps1

Reload your PowerShell profile:

. $PROFILE

Usage:

# Linux/macOS and Windows (same commands!)
copilot_here "clean and reinstall dependencies"
> Copilot suggests: rm -rf node_modules package-lock.json && npm install
Execute this command? [y/N]: y
✅ Executed safely in /work directory only

Good example - Safe mode asks for confirmation before executing commands on both platforms

Option 2: YOLO Mode (Auto-Approve)

This mode automatically approves all tool usage. Use with caution!

For Linux/macOS (Bash/Zsh):

Add this function alongside the safe version with a different name like copilot_yolo:

copilot_yolo() {
  # --- SECURITY CHECK ---
  if ! gh auth status 2>/dev/null | grep "Token scopes:" | grep -q "'copilot'"; then
    echo "❌ Error: Your gh token is missing the required 'copilot' scope."
    echo "Please run 'gh auth refresh -h github.com -s copilot' to add it."
    return 1
  fi

  if gh auth status 2>/dev/null | grep "Token scopes:" | grep -q -E "'(admin:|manage_|write:public_key|delete_repo|(write|delete)_packages)'"; then
    echo "⚠️  Warning: Your GitHub token has highly privileged scopes (e.g., admin:org, admin:enterprise)."
    printf "Are you sure you want to proceed with this token? [y/N]: "
    read confirmation
    local lower_confirmation
    lower_confirmation=$(echo "$confirmation" | tr '[:upper:]' '[:lower:]')
    if [[ "$lower_confirmation" != "y" && "$lower_confirmation" != "yes" ]]; then
      echo "Operation cancelled by user."
      return 1
    fi
  fi
  # --- END SECURITY CHECK ---

  local image_name="ghcr.io/gordonbeeming/copilot_here:latest"

  printf "Checking for the latest version of copilot_here... "
  (docker pull "$image_name" > /dev/null 2>&1) &
  local pull_pid=$!
  local spin='|/-\'

  local i=0
  while ps -p $pull_pid > /dev/null; do
    i=$(( (i+1) % 4 ))
    printf "%s\b" "${spin:$i:1}"
    sleep 0.1
  done

  wait $pull_pid
  local pull_status=$?

  if [ $pull_status -eq 0 ]; then
    echo "✅"
  else
    echo "❌"
    echo "Error: Failed to pull the Docker image. Please check your Docker setup and network."
    return 1
  fi

  local copilot_config_path="$HOME/.config/copilot-cli-docker"
  mkdir -p "$copilot_config_path"

  local token=$(gh auth token 2>/dev/null)
  if [ -z "$token" ]; then
    echo "⚠️  Could not retrieve token using 'gh auth token'. Please ensure you are logged in."
  fi

  local docker_args=(
    --rm -it
    -v "$(pwd)":/work
    -v "$copilot_config_path":/home/appuser/.copilot
    -e PUID=$(id -u)
    -e PGID=$(id -g)
    -e GITHUB_TOKEN="$token"
    "$image_name"
  )

  if [ $# -eq 0 ]; then
    docker run "${docker_args[@]}" copilot --banner --allow-all-tools
  else
    docker run "${docker_args[@]}" copilot -p "$*" --allow-all-tools
  fi
}

Then reload your shell (e.g., source ~/.zshrc).

For Windows (PowerShell):

Save the following as copilot_yolo.ps1 (or add to your existing file):

function Copilot-Yolo {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromRemainingArguments=$true)]
        [string[]]$Prompt
    )

    # --- SECURITY CHECK ---
    Write-Host "Verifying GitHub CLI authentication..."
    $authStatus = gh auth status 2>$null
    if (-not ($authStatus | Select-String -Quiet "'copilot'")) {
        Write-Host "❌ Error: Your gh token is missing the required 'copilot' scope." -ForegroundColor Red
        Write-Host "Please run 'gh auth refresh -h github.com -s copilot' to add it."
        return
    }

    $privilegedScopesPattern = "'(admin:|manage_|write:public_key|delete_repo|(write|delete)_packages)'"
    if ($authStatus | Select-String -Quiet $privilegedScopesPattern) {
        Write-Host "⚠️  Warning: Your GitHub token has highly privileged scopes." -ForegroundColor Yellow
        $confirmation = Read-Host "Are you sure you want to proceed with this token? [y/N]"
        if ($confirmation.ToLower() -ne 'y' -and $confirmation.ToLower() -ne 'yes') {
            Write-Host "Operation cancelled by user."
            return
        }
    }
    Write-Host "✅ Security checks passed."
    # --- END SECURITY CHECK ---

    $imageName = "ghcr.io/gordonbeeming/copilot_here:latest"

    Write-Host -NoNewline "Checking for the latest version of copilot_here... "
    $pullJob = Start-Job -ScriptBlock { param($img) docker pull $img } -ArgumentList $imageName
    $spinner = '|', '/', '-', '\'
    $i = 0
    while ($pullJob.State -eq 'Running') {
        Write-Host -NoNewline "$($spinner[$i])`b"
        $i = ($i + 1) % 4
        Start-Sleep -Milliseconds 100
    }

    Wait-Job $pullJob | Out-Null
    $pullOutput = Receive-Job $pullJob

    if ($pullJob.State -eq 'Completed') {
        Write-Host "✅"
    } else {
        Write-Host "❌" -ForegroundColor Red
        Write-Host "Error: Failed to pull the Docker image." -ForegroundColor Red
        if (-not [string]::IsNullOrEmpty($pullOutput)) {
            Write-Host "Docker output:`n$pullOutput"
        }
        Remove-Job $pullJob
        return
    }
    Remove-Job $pullJob

    $copilotConfigPath = Join-Path $env:USERPROFILE ".config\copilot-cli-docker"
    if (-not (Test-Path $copilotConfigPath)) {
        New-Item -Path $copilotConfigPath -ItemType Directory -Force | Out-Null
    }

    $token = gh auth token 2>$null
    if ([string]::IsNullOrEmpty($token)) {
        Write-Host "⚠️  Could not retrieve token using 'gh auth token'." -ForegroundColor Yellow
    }

    $dockerBaseArgs = @(
        "--rm", "-it",
        "-v", "$((Get-Location).Path):/work",
        "-v", "$($copilotConfigPath):/home/appuser/.copilot",
        "-e", "GITHUB_TOKEN=$token",
        $imageName
    )

    $copilotCommand = @("copilot")
    if ($Prompt.Length -eq 0) {
        $copilotCommand += "--banner", "--allow-all-tools"
    } else {
        $copilotCommand += "-p", ($Prompt -join ' '), "--allow-all-tools"
    }

    $finalDockerArgs = $dockerBaseArgs + $copilotCommand
    docker run $finalDockerArgs
}

Set-Alias -Name copilot_yolo -Value Copilot-Yolo

Add it to your PowerShell profile (same process as Option 1) and reload.

Usage:

# Linux/macOS and Windows (same commands!)
copilot_yolo "clean and reinstall dependencies"
> Copilot suggests: rm -rf node_modules package-lock.json && npm install
✅ Auto-executed in /work directory only

Good example - YOLO mode executes commands without asking for approval on both platforms

Tip: Install both functions so you can choose based on the situation. Use copilot_here by default and copilot_yolo only in trusted projects.

How it works

Security features

  1. File System Isolation

    • Only mounts your current project directory to /work
    • Your home directory, SSH keys, and other projects are completely hidden
    • Configuration stored in isolated ~/.config/copilot-cli-docker
  2. Token Scope Validation

    • Checks that your GitHub token has the required copilot scope
    • Warns if your token has dangerous elevated permissions
    • Requires explicit confirmation for high-privilege tokens
  3. User Permission Mapping (Linux/macOS)

    • Uses PUID and PGID to match your user ID inside the container
    • Files created by Copilot have correct ownership on your host
    • Windows Docker Desktop handles permissions automatically
  4. Network Access

    • Container shares host network for development workflows
    • Can access local services and APIs (by design)
    • Not a fully firewalled environment

✅ Benefits

This approach provides:

  • Security: Strict isolation limits damage potential
  • Confidence: Use powerful features like --allow-all-tools safely
  • Portability: Same setup works across all machines
  • Cross-platform: Works on Linux, macOS, and Windows
  • Auto-authentication: Seamlessly uses your existing gh login
  • Cognitive ease: Feel safe letting AI execute commands
  • Flexibility: Choose safe or YOLO mode based on context

Learn more

For detailed implementation, troubleshooting, and the complete source code:

Conclusion

Security and convenience don't have to be mutually exclusive. By running Copilot CLI in a Docker sandbox, you get powerful AI assistance with strict boundaries that protect your broader system. This setup works identically across Linux, macOS, and Windows, allowing you to embrace features like --allow-all-tools with confidence, knowing the worst-case scenario is limited to your current project.


Gordon Beeming
Lewis Toh
We open source.Loving SSW Rules? Star us on GitHub. Star
Stand by... we're migrating this site to TinaCMS