I Changed One HTTP Header in GitHub Copilot. It Stopped Charging Me.
How Copilot’s internal user-vs-agent distinction turned into a classic client-trust bug
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.
Some orphaned LLM requests
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.
Am I being scammed?
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.
Are agent initiated requests billed?
Responses from Copilot’s Models API include updated quota-consumption data. This data is included in three distinct response headers:
x-quota-snapshot_premium-interactions: remaining premium-interaction quota; relevant to billing.x-usage-ratelimit-session: remaining session-level allowance; apparently related to rate limiting or anti-abuse.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.
Can X-initiator spoofing unlock free LLM requests?
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.
from mitmproxy import http
class InitiatorSpoof:
def request(self, flow: http.HTTPFlow) -> None:
if flow.request.pretty_host != "api.individual.githubcopilot.com":
return
if flow.request.path != "/v1/messages": # Claude models endpoint
return
if flow.request.headers.get("x-initiator") == "user":
flow.request.headers["x-initiator"] = "agent"
addons = [InitiatorSpoof()]The second step was to start mitmweb and pass the header rewriter addon as an argument with the command below.
mitmweb -s spoof.pyA 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.
Spoofed requests: billing analysis
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 root cause
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.
Potential revenue impact
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.
Integrity impact
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.
Never trust the client
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.
Appendix A: Responsible Disclosure
I submitted a report detailing the vulnerability to GitHub’s Bug Bounty Program through HackerOne.
Timeline
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.
Appendix B: Steps used to calculate billing impact
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.
Follow the steps in this previous article to install and configure mitmproxy
Note current
x-quota-snapshot-premium_interactionsvalue from any recent/v1/messagesresponseBaseline run: with no header modification, send N chat messages through Copilot with
claude-sonnet-4.6selected. Observe thatx-quota-snapshot-premium_interactions.remdecreases by approximately 1.0 per user-initiated message (Sonnet multiplier = 1.0×).Spoof run: enable the attached mitmproxy addon (
spoof.py), which rewrites thex-initiatorrequest header fromusertoagenton/v1/messagesrequests only. Send N identical chat messages.Observe that
x-quota-snapshot-premium_interactions.remdoes NOT decrement for the spoofed requests. Responses are still returned to the client (though smaller in size — see open question above).Confirm by reloading https://github.com/settings/billing - the spoofed requests do not appear in premium request analytics.




