- September 26, 2026
- 9 min read
Fulfilling a GDPR/CCPA Erasure Request for PII data stored on-premises with Cyera's Privacy Agent
I’ve been interested in data security and cyera’s AI security platform and built a demo to solve a use case and thought of sharing with community incase anyone else is interested. You can find everything I’m covering in this post in this github repo
Mock Scenario
Meridian Grid Co. is a regulated utility. A field contractor, Jordan Alvarez, submits a right-to-erasure request under GDPR/CCPA. Their PII — badge ID, substation access list, phone number — lives in an on-premises PostgreSQL HR/contractor database. That database isn't cloud-native, so agentless SaaS-only privacy tools can't reach it, and nobody wants to open an inbound hole in the firewall just to let a vendor's scanner in.
This is exactly the deployment model Cyera built its Privacy Agent for: a lightweight container the customer runs inside their own environment — Kubernetes, ECS, Cloud Run, anywhere they already run workloads. It makes only outbound connections to Cyera's platform, polls for pending privacy tasks, executes them against internal systems through a connector, and reports results back. Zero inbound exposure, no agent-initiated connection from the outside — which is the detail that actually matters to a security-conscious critical-infrastructure customer.
I didn't have a live Cyera account or registry credentials, so I couldn't pull their actual rm-agent image or talk to their real backend. Rather than fake it with screenshots, I built a functionally equivalent system that implements the same documented API contract and actually runs on Kubernetes — this post walks through it end to end.
Why this use case
- It's a real, currently-shipping Cyera product — not their core DSPM, which is agentless SaaS scanning, a different and much larger surface than something you can stand up in a demo.
- It's genuinely deployed the way this kind of question gets asked: on Kubernetes, inside the customer's own environment, matching Cyera's actual documented architecture for this component.
- It plays to real, defensible domain expertise: regulated, security-conscious environments where "no inbound exposure" isn't a nice-to-have, it's the reason a customer picks this deployment model over a SaaS-only competitor at all.
- It shows the product portfolio goes beyond DSPM into actual privacy operations (DSAR fulfillment)
What's real vs. what's a stand-in
Here's the mapping:
agentcontainer → the real Cyera Privacy Agent (contairium.privacy.cyera.io/rm-agent) — same responsibilities: authenticate with an API key, poll for tasks, execute, report back.control-planecontainer → the Cyera platform itself. In production this doesn't run in the customer's cluster at all — it's Cyera's SaaS backend that the agent polls over the internet.connectorcontainer → a Custom API Connection — the thin adapter pattern Cyera documents for exposing an internal system to the agent.- Postgres + seed data → Meridian Grid Co.'s legacy on-prem contractor/HR database.
- A k8s
Secretholding the API key → a stand-in for what production wires through a real credentials manager (Vault, AWS Secrets Manager) via Cyera'sCredentialsManagerProvider.
Everything in agent/app.py and control-plane/app.py mirrors the polling contract described in Cyera's public request-manager-agent docs (GET /v1/agent/tasks, POST /v1/agent/tasks/<id>/complete, Bearer-token auth) and the Custom API Connection spec — so what you're seeing below is the real architecture, running against a stand-in backend instead of production credentials.
Architecture
+---------------------------+
(real world: | Cyera Platform (SaaS) |
outside the | - privacy team submits |
cluster) ---------| DSARs here |
| - agent polls this |<-- in this demo, stood in
+---------------------------+ by control-plane/ pod
^ |
outbound | | tasks + results
| v
+--------------------------------------------------+
| Customer's Kubernetes cluster (cyera-demo ns) |
| |
| +----------------------+ |
| | cyera-privacy-agent |------+ |
| +----------------------+ | |
| v |
| +--------------+ |
| | connector | |
| | (Custom API | |
| | Connection) | |
| +------+-------+ |
| v |
| +--------------+ |
| | postgres | |
| | (meridian_hr)| |
| +--------------+ |
+--------------------------------------------------+Standing it up on EKS
The cluster is provisioned with Terraform, then everything else goes on with plain kubectl apply. The images for all three components (agent, control-plane, connector) get built and pushed first:
cd cyera-demo
# 1. Build and push the three demo container images
make build
# 2. Provision an EKS cluster with Terraform
make deploy-cluster
make update-kubeconfig
# 3. Deploy the namespace, postgres, connector, control-plane, and agent
make deploy-workloadsThe Postgres pod is seeded on startup with three contractors, including Jordan Alvarez, in a contractor_pii table carrying exactly the fields you'd expect a utility to hold on field staff: badge ID, substation access list, cert expiry, and a phone number and SSN last-4 for good measure. That table is the legacy on-prem system this whole exercise exists to reach.
The agent contract
The part worth reading closely is the agent's poll loop, because it's the whole reason this deployment model is defensible for a security team. It never accepts a connection — it only ever opens one:
def poll_for_tasks():
resp = requests.get(f"{CONTROL_PLANE_URL}/v1/agent/tasks", headers=HEADERS, timeout=10)
resp.raise_for_status()
return resp.json().get("tasks", [])
def execute_task(task):
identifier = task["identifier"]
request_type = task["request_type"]
if request_type == "erasure":
r = requests.post(f"{CONNECTOR_URL}/redact", json={"identifier": identifier}, timeout=10)
r.raise_for_status()
redacted = r.json()["redacted_records"]
return {"action": "erasure", "records_redacted": redacted}
def main_loop():
while True:
for task in poll_for_tasks():
result = execute_task(task)
report_completion(task["id"], result)
time.sleep(POLL_INTERVAL)The agent never touches Postgres directly. It calls the connector's documented /redact contract — the seam a customer's own engineering team builds once, for their own internal systems, following Cyera's Custom API Connection spec:
@app.post("/redact")
def redact():
email = request.get_json(force=True).get("identifier")
conn = get_conn()
cur = conn.cursor()
cur.execute(
"""UPDATE contractor_pii
SET full_name='[REDACTED]', phone='[REDACTED]', badge_id='[REDACTED]',
substation_access='[REDACTED]', ssn_last4='[REDACTED]'
WHERE email = %s""",
(email,),
)
affected = cur.rowcount
conn.commit()
return jsonify(redacted_records=affected), 200Running the erasure request end to end
With everything deployed, run-demo.sh scripts walks through the whole flow. First, the BEFORE state — Jordan Alvarez's full PII data sitting in the internal DB, completely invisible to any tool that can't reach inside this cluster:
$ curl -s http://localhost:8082/debug/records | python3 -m json.tool
[
{
"id": 1,
"full_name": "Jordan Alvarez",
"email": "jordan.alvarez@fieldpartner.com",
"phone": "555-0142",
"badge_id": "BADGE-7841",
"substation_access": "Substation-14, Substation-22",
"ssn_last4": "4821"
},
...
]Then the DSAR itself. This curl is standing in for the moment a privacy team clicks "New Request" in the real Cyera UI:
$ curl -s -X POST http://localhost:8081/admin/dsr \
-H "Content-Type: application/json" \
-d '{"identifier": "jordan.alvarez@fieldpartner.com", "request_type": "erasure"}'
{
"id": "7e2c9f1a-...",
"identifier": "jordan.alvarez@fieldpartner.com",
"request_type": "erasure",
"status": "pending",
"created_at": "2026-09-26T21:40:03.112Z",
"result": null
}The task sits at pending until the agent's next poll cycle — nothing pushed it there. That's the point worth pausing on: it's a pull model, which is exactly why the agent needs no inbound network access at all. A few seconds later, status flips to completed, and the AFTER state shows the redaction landed:
$ curl -s http://localhost:8082/debug/records | python3 -m json.tool
[
{
"id": 1,
"full_name": "[REDACTED]",
"email": "jordan.alvarez@fieldpartner.com",
"phone": "[REDACTED]",
"badge_id": "[REDACTED]",
"substation_access": "[REDACTED]",
"ssn_last4": "[REDACTED]"
},
...
]The record itself is retained, not deleted — that's deliberate. The row still exists for audit purposes, it just no longer exposes the contractor's personal data. In production, this same task would show up as completed in the Cyera platform UI with a full audit trail, which is the accountability piece GDPR Article 17 actually asks for.
Summary
- This simulates the integration contract, not Cyera's actual code — worth saying plainly rather than letting it be assumed.
- The real agent's polling behavior, error handling, retry/backoff, and credentials-manager integrations are almost certainly more sophisticated than what's modeled here in a few hundred lines of Python.
- Natural next steps if I extend it: add the
access(data export) request type, show idempotent retry behavior when a poll cycle fails mid-task, and handle multiple internal systems needing different connectors for the same DSAR.
The interesting part of this demo isn't the Flask apps — it's the shape of the contract. Any privacy platform that promises to reach on-premises systems without an inbound hole in your firewall has to land on roughly this design: outbound-only polling, a thin per-system connector, and a control plane that never initiates a connection into your network. Once you see that constraint, the rest of the architecture falls out of it.
Related Posts
RAM GOPINATHAN
September 17, 2026
Onboarding Edge Devices to FlightCTL with FIDO Device Onboarding
How I wired FDO's owner service, a bootc image layering go-fdo-client on the FlightCTL agent base, and two Ansible playbooks together so edge devices enroll into FlightCTL with zero manual steps.
RAM GOPINATHAN
September 14, 2026
Provisioning Devices and Onboarding to FlightCTL
Building a bootc image with the FlightCTL agent, baking it into an AMI, and provisioning EC2 test devices that inject their enrollment credentials via cloud-init and show up as pending enrollments in FlightCTL.
RAM GOPINATHAN
September 12, 2026
Deploying FlightCTL on EKS: Edge Fleet Management with Keycloak OIDC
A step-by-step walkthrough of standing up FlightCTL on Amazon EKS with Keycloak OIDC auth, ALB ingress, and a dedicated NLB for mTLS device enrollment.