I Changed One HTTP Header in GitHub Copilot. It Stopped Charging Me.

· Lighthouse AI ·

6 min read Original article ↗

Hello, Rafael here - every week I cover interesting challenges and developments that I’ve come across recently through the lens of an engineer building AI systems.

Previously, I showed how I intercepted GitHub Copilot’s network traffic in order to understand how it works. Some interesting takeaways emerged from that exercise, but the most surprising finding was still yet to come. This is part 2 of that saga.

I put GitHub Copilot Behind a MITM Proxy. Here's What I found.

Back when I was analysing GitHub Copilot traffic, I noticed something intriguing. Some of the model requests were not mine. As in, they were not triggered by any of my messages in GitHub Copilot chat.

In addition to the LLM requests used to fulfil your prompts, Copilot also fires LLM requests on its own, for at least two reasons. The first is to come up with titles for your conversation. The second is to summarise the conversation up until a specific point.

I started to analyse those requests and responses, starting with headers. Immediately, one of them jumped out: X-initiator. For all LLM requests initiated by Copilot, this header is populated with agent.

I initially hypothesised that X-initiator was present in all LLM requests, and later confirmed that it was. For requests fired by Copilot, X-initiator is set to agent. For requests fired by me, X-initiator is set to user. It is simply a way to flag to Copilot Models API if a request was originated by Copilot itself or by the user.

This whole network traffic analysis rabbit hole started around billing; I was trying to investigate and understand how and why my quota was being exhausted so quickly.

So after learning about this X-initiator header, it was only natural that my paranoid self’s follow up question would be: am I being charged by LLM requests that I didn’t initiate?

My security-conscious self had a different question: could an attacker spoof this header and get free LLM calls?

There was a simple way to answer both questions.

Responses from Copilot’s Models API include updated quota-consumption data. This data is included in three distinct response headers:

  1. x-quota-snapshot_premium-interactions: remaining premium-interaction quota; relevant to billing.

  2. x-usage-ratelimit-session: remaining session-level allowance; apparently related to rate limiting or anti-abuse.

  3. x-usage-ratelimit-weekly: remaining weekly allowance; apparently related to rate limiting or anti-abuse.

Looking at those headers on agent-initiated requests gave me the answer. My quota did not change after multiple agent-initiated LLM requests.

So no, I was not being charged by requests that I didn’t initiate, fortunately.

But this reinforced my curiosity around the second question.

There were different ways to test Copilot’s behavior and answer this question. I already had my network-inspection stack set up with mitmproxy, so I decided to stick with it.

Besides passive traffic inspection, mitmproxy also allows you to inject any header or payload data you want into HTTP requests that are proxied through it. The easiest way to do this is through mitmproxy’s Python API.

The addon below rewrites the X-initiator header from user to agent for /v1/messages requests sent to api.individual.githubcopilot.com through mitmproxy.

The second step was to start mitmweb and pass the header rewriter addon as an argument with the command below.

A more instrumented version that parses all five x-quota-snapshot-* and x-usage-ratelimit-* response headers and logs per-request consumption deltas can be found here.

I performed 10 test requests total over a ~5-minute window against my own (paid) Copilot subscription. Here are the results.

The conclusion: by faking the data around who initiated the LLM request, GitHub Copilot didn’t charge it. This meant bad actors could basically get free tokens.

Caveat: although there appeared to be no restriction on which model could be used for agent-initiated requests, responses to spoofed requests were smaller. My hypothesis is that a backend-controlled max_new_tokens limit was applied.

The Copilot API was making a billing decision based on a parameter supplied by the client. In other words, the bug came down to one of the oldest rules in web security: never trust the client.

As of May 2026 (when I did this analysis), GitHub’s published Copilot premium-request overage rate was $0.04 per additional premium request beyond the monthly entitlement. Each bypassed user-initiated call could therefore avoid quota consumption that might otherwise have resulted in overage charges.

On a Pro plan (300 premium requests/month), a user applying the bypass to every eligible request could avoid consuming that premium-request allowance and, after the allowance would otherwise have been exhausted, avoid the corresponding overage charges.

GitHub Copilot’s billing documentation states that user-initiated premium-model calls count against the user’s premium-request allowance. My testing showed that this distinction was not being enforced entirely server-side. The issue could also have affected GitHub’s internal usage telemetry: analytics, billing reconciliation, or capacity-planning systems relying on the same classification signal could have undercounted actual user-initiated traffic.

We spend a lot of time thinking about novel AI-specific security problems: prompt injection, model extraction, tool abuse.

But AI systems are still software systems. Authentication, authorization, billing and abuse prevention are ultimately built on the same security principles as everything else.

X-initiator looked like an unremarkable piece of request metadata. But that single client-controlled value appeared to influence whether a model request counted against a user’s quota. Changing it was enough to turn a billing mechanism into a trust-boundary problem.

Sometimes the interesting vulnerability isn’t in the model at all. It’s in an HTTP header.

I submitted a report detailing the vulnerability to GitHub’s Bug Bounty Program through HackerOne.

  • 23/05/2026: Submitted HackerOne report

  • 24/05/2026: GitHub acknowledged the issue, closed the report as duplicate

  • 31/05/2026: I reached out asking for an ETA for the vulnerability to be fixed, no response

  • 21/06/2026: Reached out again, no response

  • 17/07/2026: Reached out again, no response

  • 05/08/2026: Retested the vulnerability and was no longer able to reproduce it.

Note: this is shared for educational and transparency purposes only. As of August 2026, I was no longer able to reproduce the vulnerability, which suggests it may have been fixed. GitHub did not confirm the fix to me.

  1. Follow the steps in this previous article to install and configure mitmproxy

  2. Note current x-quota-snapshot-premium_interactions value from any recent /v1/messages response

  3. Baseline run: with no header modification, send N chat messages through Copilot with claude-sonnet-4.6 selected. Observe that x-quota-snapshot-premium_interactions.rem decreases by approximately 1.0 per user-initiated message (Sonnet multiplier = 1.0×).

  4. Spoof run: enable the attached mitmproxy addon (spoof.py), which rewrites the x-initiator request header from user to agent on /v1/messages requests only. Send N identical chat messages.

  5. Observe that x-quota-snapshot-premium_interactions.rem does NOT decrement for the spoofed requests. Responses are still returned to the client (though smaller in size — see open question above).

  6. Confirm by reloading https://github.com/settings/billing - the spoofed requests do not appear in premium request analytics.