July 14, 2026
How I Found a Critical Log Exposure in Workpay’s Infrastructure — Hardcoded Credentials Leading to…
Introduction

By Himxch
5 min read
Introduction
During a routine recon session on Workpay's public-facing assets, I stumbled across a subdomain that turned out to be one of the more impactful findings I've reported this year. What started as a curious exposed API documentation page led me to a live Grafana Loki log aggregation instance — with hardcoded credentials sitting in a JavaScript bundle, accessible to anyone who knew where to look.
This is a writeup of that finding: how I discovered it, what I confirmed, and how Workpay responded. The vulnerability has since been fixed and Workpay has added me to their Hall of Fame.
Target
Workpay (workpay.co.ke / myworkpay.com) is a payroll platform serving employers and employees across Africa.
Step 1 — Recon Starts With an Exposed API Docs Page
While enumerating subdomains under the myworkpay.com and workpay.co.ke domains, I discovered:
https://assay-api.myworkpay.com/docshttps://assay-api.myworkpay.com/docsThis returned a fully rendered Scribe-generated API documentation page — no authentication required to view it. The docs exposed the complete API surface of Workpay's "Assay" performance management module: 150+ endpoints covering authentication, employee data, BSC appraisals, OKR management, performance reviews, roles, and company settings.
Two things immediately stood out:
Internal dev hostname leaked in the base URL. The docs declared Base URL: https://assay-api-dev.workpay.co.ke — a previously undisclosed internal hostname pointing at what appeared to be a dev/staging backend.
OpenAPI and Postman specs also publicly accessible at /docs.openapi and /docs.postman — useful for deeper enumeration without touching the live API.
At this point I had a full map of the application's API surface and a new internal subdomain to investigate.
Step 2 — Discovering the Loki Instance
Continuing subdomain enumeration on workpay.co.ke, I found:
https://loki.workpay.co.kehttps://loki.workpay.co.keThe service returned a 401 Unauthorized with no banner — consistent with Grafana Loki's basic auth challenge. This was interesting: Loki is a log aggregation backend, not something that should be publicly accessible.
The next question was: are credentials for this instance floating around somewhere?
Step 3 — Hardcoded Credentials in a JavaScript Bundle
A targeted search through Workpay's frontend JavaScript bundles (standard recon step — looking for API keys, tokens, and config values) turned up hardcoded Basic authentication credentials intended for the Loki instance.
VITE_LOKI_BASIC_USERNAME=
VITE_LOKI_BASUC_PASSWORD=
The credentials were embedded in plaintext inside a publicly served JS file — no obfuscation, no environment variable abstraction. Anyone performing JS secret analysis as part of standard recon would find them.
Step 4 — Confirming Authentication
Using the discovered credentials, I sent a GET request to the Loki labels endpoint:
GET /loki/loki/api/v1/labels
Host: loki.workpay.co.ke
Authorization: Basic [redacted]GET /loki/loki/api/v1/labels
Host: loki.workpay.co.ke
Authorization: Basic [redacted]Response: HTTP/2 200 OK
{
"status": "success",
"data": ["app", "env", "service_name"]
}{
"status": "success",
"data": ["app", "env", "service_name"]
}
Authentication succeeded. The Loki instance was live, accessible from the internet, and responding with real data. The label keys (app, env, service_name) confirmed this was a production log aggregation system, not a test instance.
I also confirmed the write endpoint was reachable (returning 422 Unprocessable Entity for an empty stream body — confirming the API was fully functional):
Step 5 — What the Logs Contained
Querying the log streams with the discovered credentials revealed that the Loki instance was aggregating production application logs from Workpay's live environment.
The log entries contained highly sensitive data, including:
- Full user PII: names, email addresses, phone numbers, dates of birth, gender, marital status, postal addresses, and avatar URLs for Workpay platform users
- Active JWT bearer tokens with admin-level API access, valid for 7+ days — sufficient for full account takeover without any further exploitation
- Internal API route structure: controller paths, file paths, server names, and internal hostnames (
localhost,169.254.169.126— an AWS EC2 link-local metadata address) - Third-party service credentials embedded in log entries
- Authentication flow details including token issuance timestamps, session metadata, and user role information
The severity here comes from the combination: credentials in a JS bundle → unauthenticated access to a production log aggregator → live PII and active session tokens for real users.
Step 6 — Stopping and Reporting
Once I confirmed authenticated access and observed the nature of the log data, I stopped all further interaction with the endpoint. I did not attempt to use any extracted tokens, did not download or retain user data, and did not probe further API endpoints.
I disclosed the finding to Workpay's security team at security@myworkpay.com, including:
- The subdomain and the vulnerable endpoint
- A description of the credential exposure vector (JS bundle)
- Confirmation of authenticated access with a single PoC request/response
- A description of the data types visible in logs (without including extracted PII or tokens in the report itself)
- Recommended remediation steps
Response and Fix
Workpay's security team acknowledged the report promptly. The vulnerability was triaged as High severity — classified as "Information disclosure leading to reading of sensitive information."
The fix was confirmed and the finding was marked Fixed.
Workpay added me to their public Hall of Fame.
Root Cause
Two separate failures combined to make this critical:
1. Hardcoded credentials in frontend JavaScript. Service credentials (in this case, for an internal infrastructure component) should never appear in client-side code. Secrets belong in environment variables, secret managers (AWS Secrets Manager, HashiCorp Vault, etc.), and should never be bundled into assets served to unauthenticated users.
2. Production log aggregator exposed to the internet. Loki (and similar systems — Elasticsearch, Kibana, Grafana) should never be publicly routable. These services should sit inside a private network/VPC, accessible only via VPN or internal tooling. Even with authentication in place, the attack surface for credential stuffing, brute force, or future credential exposure is eliminated entirely if the service isn't internet-facing.
Remediation
- Rotate all credentials found in the JS bundle immediately
- Remove Loki from public internet access — place it behind a VPN or restrict to internal IP ranges only
- Audit all frontend JS bundles for hardcoded secrets (automate this with tools like
trufflehog,gitleaks, orsemgrep) - Implement secret scanning in CI/CD pipelines to prevent future credential commits
- Review log verbosity in production — authentication token values and full PII objects generally should not appear in application logs
Key Takeaways for Bug Hunters
JS secret analysis is still one of the highest-signal recon steps. Automated tools like trufflehog, jsluice, and manual review of webpack bundles continue to yield real findings on production targets. Build this into your standard recon pipeline.
Exposed monitoring infrastructure is underrated as an attack surface. Loki, Grafana, Kibana, Prometheus — these services often get deployed quickly and quietly, sometimes without the same security scrutiny as customer-facing endpoints. Subdomain enumeration specifically targeting infrastructure tooling names pays off.
Know when to stop. The PoC for this bug was a single 200 OK response confirming auth. Going further would have crossed from responsible research into accessing live user data — which their own VDP explicitly prohibits. A strong report doesn't need extracted PII; it needs a clear demonstration of the access vector and an accurate description of the risk.
Thanks to the Workpay security team for their prompt response and for running a straightforward VDP process.
Follow me on Medium and Twitter/X at @himxchhhh for more writeups.