GitHub Copilot CLI security gapsWhat it sends. What it reads. What you can do.
The Copilot IDE extension and the CLI are different attack surfaces. The CLI runs as your shell user, reads your full environment, and sends transcript data to GitHub. Most security guidance for Copilot covers the IDE, not the terminal.
-
01
Risk
Ambient env + transcript
CLI reads your shell env, uploads session content
OAuth token · full shell user -
02
Control
content_exclusion + scoping
org policy blocks file types, env stripped at launch
enterprise audit log -
03
Result
Org-level visibility
scoped blast radius, admin-auditable usage
GitHub audit_log API
TL;DR· the answer, in twenty seconds
What: gh copilot suggest and gh copilot explain run as your shell user with access to your full environment. They send command context and explanations to GitHub's Copilot API, which means any secret visible to your shell is adjacent to transcript data leaving the machine.
Fix: Launch the CLI from a stripped environment (env -i PATH=$PATH HOME=$HOME gh copilot ...), set content_exclusion rules in your GitHub org's Copilot policy, and confirm audit log forwarding is active before any team uses it on production infrastructure work.
Lesson: A CLI AI tool inherits whatever trust level the invoking shell has. Treat it like any other process that talks to an external API: scope its environment, log its calls, and don't assume the IDE safety model applies.
GitHub Copilot in VS Code has been studied, red-teamed, and policy-documented for two years. The CLI extension is newer, gets less attention, and behaves differently in ways that matter for security.
gh copilot suggest takes a natural-language task description and proposes a shell command. gh copilot explain takes a command and describes what it does. Both ship context to GitHub's API. Both run under whatever identity executed gh auth login. The newer agent surface, available in GitHub's early-access channel since late 2025, can issue shell commands directly when run with appropriate flags.
That makes the CLI a different category of tool from the IDE extension, even though it carries the same brand name. An IDE extension sees file content. The CLI runs as you.
What to know in 60 seconds
gh copilotauthenticates via OAuth, not a PAT. The token scope covers your account, not just a repository.- Command context you provide (the task description, the command you're asking about) goes to GitHub's Copilot API endpoint. It is subject to GitHub's data handling, not your machine's disk encryption.
- GitHub Enterprise Cloud customers get audit log events for Copilot API calls via the
audit_logstreaming API. GitHub Team and Free accounts do not. - The
--no-executeflag (added in v1.0.3 ofgh-copilot) stops the CLI from running the suggested command without confirmation. It is not the default. - Content exclusion rules configured in your org's Copilot settings apply to IDE completions. Their behavior against CLI transcript content is documented inconsistently. Test before trusting.
The OAuth token model and what it means
When you run gh auth login and authenticate Copilot, you receive a token with copilot scope. The CLI uses that token for every API call. The token is stored in the system keychain on macOS and in a plaintext credential file on Linux unless you configure a credential helper.
# See what is currently stored
gh auth status
# Scope line shows what the token can reach
# GitHub CLI v2.x output:
# Logged in to github.com as yourname (keychain)
# Token scopes: 'copilot', 'gist', 'read:org', 'repo', 'workflow'
The repo scope in that list means the token can read and write private repositories. It is not scoped to a single org or project. If the token leaks, the blast radius is your full GitHub identity. GitGuardian's 2026 State of Secrets Sprawl reports that AI-service tokens are up 81% year-over-year in public scans. Copilot tokens surface in shell history files, log aggregators, and CI debug output.
Separate concern: gh copilot stores no persistent memory of prior sessions on disk, but the OAuth token itself sits on disk between invocations. Check where:
# macOS
security find-generic-password -s "gh:github.com" 2>/dev/null | grep -i acct
# Linux (gh uses ~/.config/gh/hosts.yml if no helper configured)
cat ~/.config/gh/hosts.yml
What transcript data GitHub receives
Each gh copilot suggest call sends a payload to https://api.githubcopilot.com. The payload includes your task description verbatim and, when the agent surface is active, recent shell output. GitHub's documentation for the CLI specifies that Copilot for CLI follows the same usage policies as Copilot in the IDE, but the data GitHub's servers receive is different: you are sending shell context, not source code completions.
For enterprise accounts, content exclusion rules let admins block specific file patterns and repository paths from appearing in Copilot completions. The rules work reliably for IDE use cases. For CLI task descriptions typed in a terminal, exclusion behavior depends on what the CLI sends in the request body, which changed between CLI versions. As of the v1.0.5 release (January 2026), task descriptions you type manually are not filtered by content exclusion rules because they are not file-backed content. Only shell output piped into gh copilot explain triggers the file-path matching logic.
This distinction matters if your team uses gh copilot explain on command output that includes secrets. A command like:
aws secretsmanager get-secret-value --secret-id prod/db | gh copilot explain
sends the secret value to the Copilot API. Content exclusion rules will not stop it. The file-path filters have no file to match against.
Shell history and ambient secrets
The CLI runs in your shell. It sees whatever your shell sees. If AWS_SECRET_ACCESS_KEY, STRIPE_KEY, or DATABASE_URL are exported in your environment, they are present in the process environment table for every gh copilot invocation.
The CLI does not deliberately read those values. But three risks remain.
First, the agent surface (the --agent flag in early access) generates and executes commands in your shell. A prompt-injected response could include a command that reads an env var and exfiltrates it. Second, shell history. Every task description you type to gh copilot suggest sits in ~/.zsh_history or ~/.bash_history alongside the secrets-laden commands you ran before it. Third, a future version of the CLI or the Copilot API server could change what context gets included in requests, as has happened with every major coding agent tool since 2024.
The conservative approach: launch gh copilot from a stripped environment.
# Strip the environment, add back only what gh needs
env -i \
PATH="$PATH" \
HOME="$HOME" \
TERM="$TERM" \
GH_TOKEN="$(gh auth token)" \
gh copilot suggest "list all S3 buckets in the us-east-1 region"
This keeps your AWS_* credentials, database passwords, and API keys out of the process environment. gh needs GH_TOKEN or access to the system keychain. Nothing else.
For teams, wrap this in a shell function:
# Add to ~/.zshrc or team-shared dotfile
copilot-safe() {
env -i \
PATH="$PATH" \
HOME="$HOME" \
TERM="${TERM:-xterm-256color}" \
GH_TOKEN="$(gh auth token 2>/dev/null)" \
gh copilot "$@"
}
If you're running hasp for other agent tools, the Copilot token fits the same model. Bind the token in the hasp vault instead of in ~/.config/gh/hosts.yml, and the copilot-safe wrapper becomes a one-liner: hasp run -- gh copilot. The token never touches the shell environment, which closes the shell-history capture path entirely.
Prompt injection via shell output
gh copilot explain takes a command as input and explains what it does. The natural workflow is to pipe command output into it. That is where prompt injection lands.
# Naive use
man curl | gh copilot explain
cat Makefile | gh copilot explain
git log --oneline -20 | gh copilot explain
In each case, the content being explained comes from an external source. man pages are static. Makefiles are yours. Git log messages are not: a contributor or a supply-chain attack can put arbitrary text into a commit message, and that text goes directly to the model as part of the explanation prompt.
Check Point's research on command-injection bugs in coding agent tools (published February 2026, covering CVE-2025-59536 in Claude Code) describes the same attack surface: model input derived from repository content produces unexpected model-directed actions. For gh copilot explain, the output is text, not code execution. For gh copilot suggest --agent, the output can be a command that runs.
Practical rule: do not pipe content from repositories you did not write into gh copilot explain. If you do, treat the output as advisory and review the suggested command before running it regardless of --no-execute status.
The --no-execute flag and what it actually protects
gh copilot suggest prompts you to confirm before running a command. The --no-execute flag makes it print the command only, without the confirmation prompt that a miskey could bypass.
gh copilot suggest --no-execute "find all files modified in the last 24 hours"
The flag prevents accidental execution from a prompt confirmation. It does not prevent a social-engineering attack where the displayed command looks safe but includes an additional clause. A shell pipeline can be visually truncated in narrow terminals. Always expand the full command before copying it to your prompt:
# Pipe to less to see the full command without truncation
gh copilot suggest --no-execute "compress the logs directory" | less
--no-execute is not a security boundary. It is a UX guardrail.
Audit log access for org admins
GitHub Enterprise Cloud streams Copilot audit events to the audit log. Events include copilot.chat_api_request entries that record the actor, timestamp, org, and which Copilot feature was invoked. They do not include the content of the request.
To confirm audit events are reaching your SIEM:
# GitHub CLI with admin:org scope
gh api \
-H "Accept: application/vnd.github+json" \
"/orgs/YOUR_ORG/audit-log?phrase=action:copilot&per_page=10" \
| jq '.[].action'
Expected output includes copilot.chat_api_request events for CLI usage.
If your org forwards audit logs to Splunk, Datadog, or a similar platform, set an alert on copilot.chat_api_request events outside business hours or from unexpected geographic locations. An OAuth token stolen from a developer machine and used from an unfamiliar IP will appear here.
For enterprises that use SAML SSO with Copilot, also confirm that Copilot access is revoked when a user's SAML session expires. Copilot tokens do not inherit SAML session timeout by default.
What gets missed in most Copilot security guidance
Most security writing on GitHub Copilot focuses on IP leakage from code completions and GDPR implications of training data. Neither applies to the CLI in the way it applies to the IDE.
The real risks for the CLI are process-level: what the process inherits, what it sends over the network, and who can issue commands to it indirectly through the content it processes. These are the same risks as any network-calling CLI tool. The fact that the model is doing natural-language reasoning does not change the process model.
The early-access agent surface raises the stakes. A CLI tool that explains commands is a research aid. A CLI tool that executes commands it generates is an automation agent. Security controls appropriate for the former (read the output, decide) are not sufficient for the latter (the agent decides for you). GitHub's documentation for the agent surface explicitly warns about this. Most teams deploying it anyway have not updated their threat model.
A checklist you can paste into a PR
## gh copilot security checklist
- [ ] `gh auth status` confirms token stored in system keychain (macOS) or credential helper (Linux)
- [ ] Token scopes reviewed; repo scope justified or removed via fine-grained PAT if possible
- [ ] Team uses copilot-safe() wrapper or equivalent env-stripped invocation for sensitive contexts
- [ ] `--no-execute` enforced in team runbook for any automated or scripted use
- [ ] Content exclusion rules configured in GitHub org Copilot settings and verified against CLI use
- [ ] `git log` and Makefile content not piped to `gh copilot explain` in production runbooks
- [ ] Audit log streaming confirmed active; alert on off-hours copilot.chat_api_request events
- [ ] SAML SSO session expiry verified to revoke Copilot token access
- [ ] Agent surface (--agent flag) behind explicit team approval gate before use in production contexts
- [ ] Shell history reviewed for task descriptions that include secret values
What this means for your stack
Any CLI tool that authenticates under your shell user identity and calls external APIs inherits your full trust level. Scoping that trust is mechanical: strip the environment, audit the calls, apply the least-privilege token the tool will accept. The Copilot CLI is not unusual in this respect. The gap is that most teams applied that reasoning to their deployment pipelines years ago and have not yet applied it to AI tooling running from developer laptops.
The next layer up is runtime brokering: secrets leave the shell entirely, agents receive references instead of values, and each grant gets logged to an append-only audit trail. hasp is one working implementation. curl -fsSL https://gethasp.com/install.sh | sh, hasp setup, connect a project, hand the next session a reference instead of a key. Source-available (FCL-1.0), local-first, macOS and Linux, no account.
The principle holds regardless of which tool you use. A CLI running as your shell user is only as safe as the environment you hand it.
Sources· cited above, in one place
Stop handing the agent your real keys.
hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.
- Local, encrypted vault — no account, no cloud, no telemetry by default.
- Brokered run — agent gets a reference, the child process gets the value.
- Pre-commit + pre-push hooks catch managed values before they ship.
- Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.