Skip to content

Copilot exfiltration protection is too agressive and block legitimate spec content. #4065

Description

@laurentb-roy

Describe the bug

I have a spec file that contains the following line

- Rover's `supergraph.yaml` supports Router-style variable expansion (e.g. `Authorization: "Bearer ${env.AUTH_TOKEN}"`), so the token can be injected via an environment variable at compose time without hand-editing the config file per run.

The security feature redact the Bearer ${env.AUTH_TOKEN}, to ****** causing agent to get confused.

That's really not ideal. It prevent reading completely valid specification. As you can see, no actual secret are being exfiltrated.

I understand why the feature exists, clearly the PII protection should be on by default but it would be nice to be able to configure the level of redaction. Certain type of task demand that we edit Authorization Header code, it is part of programming. The current security settings make this impossible.

That's quite blocking for my current work

Affected version

GitHub Copilot CLI 1.0.69

Steps to reproduce the behavior

Use plan mode or similar to create a markdown file containing specification on how to construct an Authorization Header
In a fresh session, ask the agent to review the file and see if there is ambiguities

The agent will complain about the ******. Sometime he will be smart about it and use xxd to read the ****** , meaning the secrets aren't really protected.

Expected behavior

With a precise, not on by default workaround, the agent can be made to see the full specification so he can review the file and work on it.

Additional context

No response

Activity

  1. added theissue type on Jul 8, 2026
  2. pegle commented on Sep 11, 2026

    @pegle

    Same redactor, but a failure mode that goes beyond confusing the agent: it silently produces a corrupt file on disk.

    Minimal repro — no secrets, no external service, no account needed

    python3 -c "
    h='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
    p='eyJpc3MiOiJmYWtlIiwic3ViIjoidGVzdCIsImlhdCI6MTIzNDU2Nzg5MH0'
    s='ZmFrZXNpZ25hdHVyZV9ub3RfcmVhbF9hdF9hbGxfMTIzNDU2Nzg5MA'
    print('A url  :', 'https://example.invalid/binary?token='+h+'.'+p+'.'+s)
    print('B plain:', 'FAKE-not-a-real-secret-12345')
    print('C uuid :', 'd1e4dcb6-2eb1-4b45-8151-c40c01db8654')
    "

    What the model actually receives (CLI 1.0.84-4, macOS):

    A url  : https://example.invalid/binary?token=******
    B plain: FAKE-not-a-real-secret-12345
    C uuid : d1e4dcb6-2eb1-4b45-8151-c40c01db8654
    

    So the matcher keys on the JWT shape, not on the parameter name and not on entropy: A is masked, while B (same token= parameter) and C (a UUID of the kind that appears in the very same URLs) pass through untouched. The replacement is fixed-width, so length is not preserved either.

    Where this bites in practice

    Atlassian's own Rovo MCP server v2 exposes downloadJiraIssueAttachment. It returns a short-lived signed URL plus a ready-to-run command, and its own instructions say "Bytes never pass through the LLM" — the token is explicitly meant to be used by the agent, not leaked by it:

    {"downloadUrl": "https://api.media.atlassian.com/file/<uuid>/binary?token=******",
     "downloadCommand": "curl --location '...?token=******' --output './screenshot.png'"}

    That token is a JWT, so it gets redacted. The agent then builds the command from what it can see and gets HTTP 401. Note the UUID in the same URL survives, which is what made the matching rule visible.

    The part that makes this more than an annoyance: the generated command has no --fail, so curl writes the 401 JSON body into the target file. The result is a screenshot.png that exists, is non-zero, and passes any [ -s "$f" ] check — while containing 143 bytes of JSON. An agent that does not sniff the file type will go on to "analyse the screenshot". I ran into exactly this while migrating an internal bug-triage workflow: the attachment step reported success, and only a file check revealed there was no image anywhere.

    So the practical outcome here is not a blocked task but a wrong one, which is harder to notice than the original report in this issue.

    The upload direction of the same tool family is already filed on the Atlassian side as atlassian/atlassian-mcp-server#233, reported against VS Code Copilot 1.135.0. Same mechanism, different Copilot surface — so this is not CLI-specific, and it affects a first-party integration rather than an exotic setup.

    Suggested direction

    I don't think "let me switch redaction off" is the right ask, and --secret-env-vars only widens the masking rather than narrowing it. But there is an option that keeps the security property completely intact:

    Substitute at execution time. Keep masking the value in everything the model sees, and have the CLI swap the real value back in when the model executes a command string that originated from that same tool result. The model still never sees the secret — which is the entire point of the feature — while the ephemeral token reaches the process that actually needs it.

    Failing that, a per-MCP-server allowlist would at least make first-party integrations usable.

    It may also be worth separating the risk classes. A single-use, short-lived, already-scoped token that a tool returns for immediate local execution is a different exposure from a long-lived credential sitting in a config file, and today both are treated identically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions