Key Points
- Mitiga Research discovered a method to exfiltrate sensitive data from a highly restricted GCP environment using a misconfigured Identity-Aware Proxy (IAP).
- In line with responsible disclosure practices, we reported the issue to Google VRP. In early June 2025, Google updated the public documentation but did not change the underlying behavior or system architecture.
- Even in environments with no outbound connectivity and strict VPC Service Controls, an attacker with the right IAM role can leak data via preflight
OPTIONS
requests. - By deploying an App Engine with a crafted
app.yaml
, an attacker can embed sensitive data within the configuration and retrieve it externally, bypassing traditional network egress mechanisms. - This research demonstrates how even minor misconfigurations in security services like IAP can be exploited for data exfiltration, especially in cloud setups where it’s not always clear what’s trusted and what’s not.
Introduction
When people talk about “highly restricted” cloud environments, they usually mean environments with no public IPs, no outbound internet, and strict VPC Service Controls locking everything down.
On paper, these environments seem pretty bulletproof: no way in, no way out. But in this blog, I’m going to show how a small, overlooked configuration in Google Cloud’s Identity-Aware Proxy (IAP) can quietly punch a hole through that wall.
By abusing how IAP handles Cross-Origin Resource Sharing (CORS) preflight requests, an attacker can leak data from a locked-down VM to the outside world. No socket connections, no egress, just a cool use of browser behavior and a crafted header. The goal is to show how this behavior works and why it’s important to understand the interactions between Identity Access Management (IAM), IAP, and CORS.
Defining App Engine, IAP, and CORS
What is an App Engine?
App Engine is a platform in Google Cloud that lets you run your code without having to manage servers. You just write your app, deploy it, and Google takes care of everything else, like scaling, load balancing, and updates. It’s perfect for web apps and APIs, especially if you want something that “just works” without needing to deal with the backend infrastructure.
Since App Engine hides most of the infrastructure, it's common for teams to hand out roles/appengine.deployer
just to let someone deploy new versions for the app. But that same role also lets them control the HTTP headers served to external users of the app. So now your front-end dev who just needed to tweak some CSS can also exfiltrate data to the outside world. More on that later.
What is Identity-Aware Proxy (IAP)?
IAP is a Google Cloud service that adds an extra layer of security by controlling who can access your applications. It authenticates users through Google Cloud Identity and enforces access based on their IAM roles using role-based access control (RBAC).
When a resource is protected by IAP, only authenticated users with the appropriate IAM roles can access it, and only through the proxy. IAP can be used to protect various Google Cloud services, including App Engine, Compute Engine, Cloud Run, Google Kubernetes Engine (GKE), and Cloud Functions.
This blog focuses on how the attack works with App Engine, but the technique isn't limited to that, as other IAP-protected services such as Cloud Run are vulnerable, too.

To understand how IAP protects applications, let’s first compare what happens when a request is made to a service with and without IAP enabled:
Without IAP
- User sends HTTPS request directly to the App Engine instance.
- No authentication or authorization is enforced by default unless implemented by the app itself.
- The request reaches the app server without any identity verification by Google Cloud.

With IAP
- User sends HTTPS request and the request hits IAP.
- IAP checks:
- If authenticated:
- Request is forwarded to authorization (3)
- If not authenticated:
- Redirect to Google sign-in
- User authenticates via
OAuth 2.0
- Google retrieves user identity (email, ID)
- If authenticated:
- IAP evaluates IAM policy for the resource
- If authorized:
- Request is forwarded to App Engine
- App Engine handles the request as usual
- If not authorized:
- 403 Forbidden returned
- If authorized:

What is Cross-Origin Resource Sharing (CORS)?
CORS is a security feature implemented by web browsers to control how web pages can request resources from a different domain than the one that served the web page. This is crucial because, by default, browsers enforce the “Same-Origin Policy," which restricts web pages from making requests to a different domain, protocol, or port to prevent malicious behavior.
However, modern web applications often need to request resources from different origins, such as APIs, fonts, or third-party services. CORS provides a way for servers to specify who can access their resources and how using specific HTTP headers.
When a browser makes a cross-origin HTTP request initiated from JavaScript, it first sends an HTTP request with an Origin
header to the target server. The server can then respond with appropriate CORS headers, such as Access-Control-Allow-Origin
, to indicate whether the request is allowed.
For certain types of requests, like those using custom headers or methods other than GET
or POST
, the browser sends a preflight request using the OPTIONS method. A preflight request is an automatic HTTP OPTIONS request sent by the browser to ask the server for permission before sending the actual request. If the server approves, it responds with the necessary headers, and the browser proceeds with the actual request. This mechanism ensures that only authorized domains can access resources, enhancing security while allowing necessary cross-origin communication.
Additional Reading:
- How CORS Works Behind the Scenes: A Deep Dive
- If CORS is just a header, why don’t attackers just ignore it?
The Attack Flow
Where the Idea for IAP Abuse Came From
Now that we’ve covered all the background, let’s get to the point. This idea started while I was experimenting with how IAP works and protects internal resources in GCP, mainly App Engine and VM instances. At some point during testing, I started looking into how IAP handles CORS.
By default, IAP blocks unauthenticated OPTIONS
requests, which are sent by browsers as “preflight” checks before cross-origin calls. So, if a browser from another origin tries to send one of these to a protected app, IAP just rejects it. That makes sense.
However, when CORS is enabled in IAP, it actually allows these preflight requests through, so the application can respond with CORS headers like Access-Control-Allow-Origin
. IAP must allow unauthenticated CORS preflight (OPTIONS
) requests to pass through because browsers do not include credentials in preflight requests by design, and IAP will otherwise return a 401 Unauthorized
, blocking the request. Since CORS requires the preflight to reach the backend to return headers like Access-Control-Allow-Origin
and Access-Control-Allow-Methods, blocking it prevents the browser from sending the actual request. It doesn’t give you access to the actual app or any protected data, but it does return a response.
It made me wonder: Could this behavior be used as an exfiltration channel? Could someone inside the environment trigger a request that leaks information through those CORS headers without ever needing to send a packet to the internet?
Testing the Theory
Using an internal virtual machine with no outbound internet access to simulate an internal attacker, I deployed an App Engine protected by IAP. The app.yaml
file included a secret, encoded in Base64, placed in the Access-Control-Allow-Origin
header. My goal was to access that secret externally, without authentication. Since IAP is supposed to protect this App Engine, it shouldn't work.

I kept the default IAP CORS setting on the App Engine disabled.

Representing the external attacker, I used a VM outside the organization with no access to internal GCP resources, no credentials, and no authentication session. I tried to access the App Engine, and, as expected, was redirected to Google authentication.

Even when I send an HTTP OPTIONS
request instead of GET
, I still get an error because the CORS setting is disabled, so authentication is required for HTTP OPTIONS
requests.

I also tried to access an image that I knew was hosted on another website and required the App Engine to use CORS to access it. Since the CORS setting in IAP wasn’t enabled, I received a 401
error with nothing useful in the response.

So, I enabled the CORS setting in IAP.

When sending a GET
request, we still get redirected to Google authentication.

However, sending an OPTIONS
request instead of GET
now returns 200 OK
. Exactly what we expected.

But I still didn’t receive any sensitive information from the App Engine. Finally, I tried accessing the image mentioned earlier using an OPTIONS
request, and voilà!

I was able to see the Access-Control-Allow-Origin
value, which is controlled by the App Engine admin. In this case, I injected a dummy Base64 string.
What You Need for This to Work
Now that we’ve walked through the attack, let’s break down the exact conditions that make it possible. This includes the required IAM roles, network access, and specific IAP configurations that an attacker needs to abuse this vector:
- IAP protected App Engine with CORS setting enabled
- An internal VM that can access *.
googleapis.com
(for the App Engine deployment) - An Identity with the Role
roles/appengine.deployer
Putting It All Together

An attacker with access to a GCP VM that has no outbound internet connectivity and no access to other internal resources can still exfiltrate data. They can:
- Encode a secret they want to exfiltrate as Base64 string.
- Enumerate App Engine versions by executing:
gcloud app versions describe
. - Createsa new
app.yaml
file and sets theAccess-Control-Allow-Origin
value to the Base64 string. - Deploy the new App Engine file by executing
gcloud app deploy.
- At this point, the exfiltrated string is exposed and can be retrieved externally via a preflight
OPTIONS
request.
- At this point, the exfiltrated string is exposed and can be retrieved externally via a preflight
- Send
OPTIONS
preflight request (from somewhere outside the organization).: - Execute
curl -X OPTIONS -I https://domain.example.com/image.jpg
- Extract the exfiltrated string from the header
access-control-allow-origin
of the HTTP response, and decodes it.
Limitations
The exfiltration channel is bounded by two platform limits. First, any single HTTP response-header field on App Engine must not exceed 8 KB; going larger triggers a 502 (“upstream sent too big header”
) and the data never leaves the project. Second, every call to gcloud app deploy
consumes both Cloud Build minutes and counts toward the 10,000-deploys-per-day cap per application. As a result, large files require chunking and rate limiting to avoid quota exhaustion.
Discovery and Disclosure Timeline
The timeline for this discovery and disclosure to Google is as follows:
- March 31, 2025: Initial vulnerability report submitted to Google Cloud VRP, describing a data exfiltration technique using IAP CORS misconfiguration.
- April 1, 2025: Google acknowledges receipt, escalates the report to the Cloud VRP queue, and requests coordinated disclosure.
- April 2, 2025: Google successfully reproduces the issue and files a bug with the product team and confirms the issue is under review.
- June 4, 2025: Google resolves the issue by updating the IAP documentation to clarify that preflight
OPTIONS
requests are unauthenticated and unaudited by IAP. The underlying service behavior and architecture remain unchanged, but the documentation update helps clarify and mitigate potential misuse of this mechanism.
Impact of this Exfiltration Method
An attacker can run a script on a private Google Cloud Platform (GCP) VM with no public IP and no internet access and still leak internal secrets, as long as the VM's identity has the right permissions and the CORS setting is enabled. That could include access tokens from users who connected to the VM, data from buckets the identity can reach, and more.
It’s fairly simple: if the VM has permissions to deploy an App Engine, an attacker can deploy any code they want and access it over the internet.
But it is not that simple when IAP is enabled.
IAP is supposed to act as an identity firewall, only allowing authenticated users to access the app. So, even if the attacker deploys a malicious app, they can’t actually access it from the outside without authentication and authorization.
The problem begins when a security engineer enables CORS in the IAP settings, often without realizing that this can open the door to data exfiltration due to the way CORS works.
App Engine is not the only victim
The same access_settings.cors_settings.allow_http_options
boolean exists for Cloud Run services and for IAP-protected external HTTP(S) load-balancer backends. If it is set to true there, unauthenticated OPTIONS
traffic is forwarded in exactly the same way. So, this attack isn’t limited to App Engine. An attacker can also exploit other services that are protected by IAP
Final anecdote to finish
Even without a fully misconfigured app, just knowing that allow_http_options=true
can let an attacker do some reconnaissance. For instance, the attacker can send an OPTIONS
request to various endpoints of the IAP-protected API (without credentials). If the endpoint exists and IAP is allowing OPTIONS
through, the backend might respond with something (allowed methods, or a 200 vs 404). This can tell the attacker which URLs are valid on the app, even though they can’t access the actual data. As long as the app is properly configured and secured by IAP it isn’t a major risk, but it is still an information leak.
Automating the Exfiltration Channel
Because ChatGPT exists, you don’t need me to generate these scripts for you. You can do it yourself. But here’s the pseudocode of what they do.
The first (upload_via_appengine
) runs from inside a compromised GCP VM with the right permissions and encodes arbitrary data into chunks embedded in App Engine CORS headers.
The second (download_from_cors
) runs externally and collects those chunks by issuing unauthenticated CORS preflight requests.
Together, they turn an IAP-protected App Engine into a reliable exfiltration mechanism, without requiring outbound internet or traditional data transfer methods.
upload_via_appengine (Internal Attacker - Authenticated Shell)
- Encodes a file to Base64
- Splits it to 400-character chunks
- For each chunk:
- Modifies app.yaml and sets the allowed CORS domain to the “chunk”
- Deploys App Engine
- Waits 5 seconds
download_from_cors (External Attacker - Unauthenticated Shell)
- Uses
curl -I -X OPTIONS
to read CORS header - Collects all chunks
- Decodes and writes the file
Using scripts like these, I managed to exfiltrate a 10MB file from a locked-down environment, at a blazing speed (😂) of 770kb per day.
Security Recommendations
- Lock down IAP custom setting: Leave
access_settings.cors_settings.allow_http_options
at its defaultfalse
to stop unauthenticated CORS preflight traffic. - Continuous monitoring & detection: Export Cloud Audit Logs for IAP and create log-based detections for spikes in app engine deploy operations.
- Build-time guard-rails: Add a static policy scan in CI (e.g., Open Policy Agent) to reject
app.yaml
files that setAccess-Control-Allow-Origin
to untrusted domains or encode unexpected strings. - Restrict deploy permissions to trusted identities: Limit
roles/appengine.deployer
to approved CI systems or service accounts only, preventing unauthorized users from deploying App Engine versions.
Summary
Even in highly locked-down GCP environments, without public IPs, outbound internet, or open firewalls, misconfigurations can open subtle yet powerful backdoors. In this research, we demonstrated how enabling CORS on an IAP-protected App Engine can be abused by an attacker with the right IAM roles to exfiltrate sensitive data using only CORS preflight OPTIONS requests.
By embedding data into the Access-Control-Allow-Origin
header of an App Engine deployment and retrieving it externally via unauthenticated CORS requests, an attacker can turn a private environment into a stealthy exfiltration channel. This bypasses traditional egress controls and leverages Google-managed infrastructure as the exfiltration bridge.
This vector underscores the importance of least privilege, careful IAM role assignment, and a deep understanding of how features like IAP and CORS interact. Small configuration choices in the cloud can quietly open the door to serious risks.
Interested in more real-world cloud attack research?
Check out other findings from Mitiga Labs, including ransomware detection, stealthy SaaS abuse, and more.