September 19, 2026
How I Found an Unauthenticated Grafana Loki Endpoint That Leaked 234 Employee Identities ($1,200…
Every other endpoint on the API returned 401. Except one proxy path. That single route bypassed the auth middleware entirely, exposing a…

By anshh.bohara
4 min read
Every other endpoint on the API returned 401. Except one proxy path. That single route bypassed the auth middleware entirely, exposing a logging system full of employee PII and accepting writes that could forge events as any employee.
The Target
The target was a large IT services and consulting company in Southeast Asia, a subsidiary of a major telecom group. They built an internal AI chatbot platform for their employees. The frontend was a React SPA that talked to an API middleware hosted on Azure Container Apps.
How I Found the Entry Point
The frontend serves an env-config.js file that contains the API base URL. This is standard for SPAs -- the config is needed by the client, so it's public by design. But it gives you the exact middleware hostname to probe.
Hitting the root of that middleware returns a service descriptor:
{"service": "[REDACTED] API Middleware", "version": "1.0.0", "docs": "/docs"}{"service": "[REDACTED] API Middleware", "version": "1.0.0", "docs": "/docs"}I started probing paths beyond the documented API. Business endpoints like /workflows/user/get correctly returned 401 Missing authorization header. But /grafanaloki/ didn't. It was an open proxy to their internal Grafana Loki logging instance.
What Loki Exposes
Grafana Loki is a log aggregation system. It stores structured log entries organized by labels and detected fields. The API lets you query labels, field values, and the logs themselves.
First, I confirmed unauthenticated access:
GET /grafanaloki/loki/api/v1/labelsGET /grafanaloki/loki/api/v1/labelsResponse:
{"status": "success", "data": ["app", "env", "event_type", "job", "level", "service_name"]}{"status": "success", "data": ["app", "env", "event_type", "job", "level", "service_name"]}Then I started pulling detected field values. This is where it got interesting.
Extracting Employee PII
The AI chatbot frontend logs every user interaction to Loki with structured JSON events. Each event includes the user's email, Azure AD user ID, department, location, and country.
The detected_field values API returns all unique values for a given field across the queried time range:
GET /grafanaloki/loki/api/v1/detected_field/email/values?query={app="[REDACTED]",event_type="AgentResponse"}GET /grafanaloki/loki/api/v1/detected_field/email/values?query={app="[REDACTED]",event_type="AgentResponse"}Returns corporate email addresses from recent logs.
The Loki instance retained 30 days of data. By iterating daily time windows across the retention period, I collected:
-
234 unique corporate email addresses across 5 regional office domains
-
229 Azure AD Object IDs (UUIDs correlatable to Microsoft 365 profiles via Microsoft Graph)
-
67 department assignments including security-sensitive ones like cybersecurity, government IT, legal, and defence application teams
-
1,285 unique user sessions
The extraction was straightforward Python using the requests library, iterating day-by-day over the 30-day retention window and collecting unique email values from the detected_field API. Each day's query returned a subset, and the union across all days gave 234 unique addresses.
Internal AI Architecture Disclosure
The agentName field revealed the company's internal AI deployment:
GET /grafanaloki/loki/api/v1/detected_field/agentName/values?query={app="[REDACTED]",event_type="AgentResponse"}GET /grafanaloki/loki/api/v1/detected_field/agentName/values?query={app="[REDACTED]",event_type="AgentResponse"}Response:
{"values": ["[PolicyAgent]", "[LLM-Provider-1]", "[DocGenerator]", "[PerformanceAgent]", "[LLM-Provider-2]", "[TechAgent]", "[MainChatbot]"]}{"values": ["[PolicyAgent]", "[LLM-Provider-1]", "[DocGenerator]", "[PerformanceAgent]", "[LLM-Provider-2]", "[TechAgent]", "[MainChatbot]"]}Seven specialized internal AI agents. The LLM providers in use. Internal tool names. Combined with per-employee routing data, you could profile which employees use which internal tools.
The index stats confirmed the scale: 722,924 total log entries across 888 streams, 273 MB of telemetry data across the 30-day retention window.
The Write Access: Forging Events as Real Employees
Loki's push API was also unauthenticated. This turned a read-only PII leak into something more dangerous.
The attack: read a real employee's email and Azure AD user ID from the logs, then inject fabricated events attributed to that specific person.
POST /grafanaloki/loki/api/v1/push
Content-Type: application/json
{
"streams": [{
"stream": {
"app": "[REDACTED]",
"env": "prod",
"event_type": "MessageSent"
},
"values": [
["[NANOSECOND_TIMESTAMP]",
"{\"event\":\"MessageSent\",
\"userId\":\"[REAL_USERID_FROM_LOGS]\",
\"email\":\"[REAL_EMAIL_FROM_LOGS]\",
\"department\":\"[REAL_DEPT]\",
\"location\":\"Singapore\",
\"country\":\"SG\",
\"agentTarget\":\"[MainChatbot]\",
\"messageLength\":42}"]
]
}]
}POST /grafanaloki/loki/api/v1/push
Content-Type: application/json
{
"streams": [{
"stream": {
"app": "[REDACTED]",
"env": "prod",
"event_type": "MessageSent"
},
"values": [
["[NANOSECOND_TIMESTAMP]",
"{\"event\":\"MessageSent\",
\"userId\":\"[REAL_USERID_FROM_LOGS]\",
\"email\":\"[REAL_EMAIL_FROM_LOGS]\",
\"department\":\"[REAL_DEPT]\",
\"location\":\"Singapore\",
\"country\":\"SG\",
\"agentTarget\":\"[MainChatbot]\",
\"messageLength\":42}"]
]
}]
}HTTP 204. The forged event uses the same stream labels and JSON field schema as legitimate events. Any monitoring dashboard, SIEM, or audit query would include the forged entry alongside the employee's real usage. There's no field or metadata that distinguishes attacker-injected entries from application-generated ones.
This enables framing specific employees with fabricated AI usage patterns, corrupting audit trails for compliance or investigation purposes, and injecting noise to mask real attack indicators in SIEM/monitoring dashboards.
Negative Controls
1. Auth is enforced elsewhere. GET /workflows/user/get returns 401 Missing authorization header. The Loki bypass is specific to the /grafanaloki/ proxy path, not a blanket auth failure.
2. Random paths return 404. POST /randompath123/loki/api/v1/push returns 404 Not Found. The /grafanaloki/ route is a real proxied service, not a path-matching artifact.
3. Write is real. After injection, GET /grafanaloki/loki/api/v1/label/job/values includes the injected label. Data persists in the production Loki cluster.
How This Happened
Looking at the architecture: the API middleware is a Python/FastAPI service that adds Bearer token validation to all business routes. The /grafanaloki/ path is a reverse proxy rule that forwards requests to the internal Loki instance. The proxy was likely added for internal debugging and wasn't wrapped with the same auth middleware as the business endpoints.
The source map for the frontend (publicly accessible) confirmed the tracking implementation. The tracking service source code showed that every user interaction with the AI chatbot writes userId, email, department, location, and country to Loki. The developers probably didn't consider that the logging endpoint itself could be exposed.
Outcome
Reported through a bug bounty platform. Awarded $1,200 bounty. Classified as High severity (CVSS 8.2).
Lessons
1. Probe infrastructure paths, not just business API endpoints. When you find an API middleware, don't stop at the documented routes. Try standard infrastructure paths: /grafana/, /prometheus/, /kibana/, /loki/, /metrics/, /health/, /debug/, /actuator/. These are often proxied through the same middleware but miss the auth layer.
2. Logging systems are PII goldmines. Developers log user identifiers for debugging and analytics without thinking about who can access those logs. Email addresses, user IDs, department assignments, IP addresses — all of it ends up in logging systems that may have weaker access controls than the application itself.
3. Read + Write = identity forgery. A read-only PII leak is bad. But when you can also write to the same system, the attacker can use the harvested identities to forge events. Always check if the write path is also open. For Loki, that's /loki/api/v1/push. For Elasticsearch, it's POST /_doc. For Prometheus Pushgateway, it's POST /metrics/job/.
4. env-config.js is recon gold. SPA configuration files contain the service URLs that the frontend talks to. They're loaded by the browser, so they're public by design. But they often point to internal infrastructure that the developer didn't intend to be directly accessed. Treat them as a map of the backend surface.