Affiliate Disclosure: This site contains affiliate links. We earn a commission when you purchase through our links at no additional cost to you.

Not every JS bundle finding is a Firebase key that turns out to be public by design. Sometimes you decompile a production bundle and find something that makes you stop and read the function twice.

That is what happened when Peng’s SecurityClaw scanner ran js-bundle-recon against pro.onedoc.ch, the professional portal for OneDoc — a Swiss HealthTech platform that handles appointment booking between patients and healthcare providers. The scanner flagged 31 findings. Twenty-nine were false positives. One was a genuine credential concern, and it was in a function that provisions medical assistant accounts.


What OneDoc is

OneDoc is a Swiss appointment booking platform used by doctors and clinics. pro.onedoc.ch is the professional-facing portal — the interface doctors and healthcare administrators use to manage their practices, appointment schedules, and staff. Medical assistant accounts on the portal have access to patient appointment data, contact information, and practice management functions.

The platform runs as an Angular 18+ SPA backed by what looks like a NestJS API. Firebase Auth handles authentication. HSTS is enabled, X-Frame-Options is set to DENY, TLS is clean. For a healthcare portal, the header posture is reasonable. What the scanner found was not in the headers.


The finding

Inside main.f54ddbef4b3ef773.js — the primary production bundle — there is a provisioning function:

gsApiService.createAssistant({
  groupId: (0, s.C1)(this._groupIdControl.value),
  data: {
    firstName: _,
    lastName: `Assistant ${U}`,
    email: `${l}+${_.toLowerCase()}-assistant-${U.toLowerCase()}@onedoc.ch`,
    password: "Password1234",
    gender: Nn.VXP.Male,
    lang: "en",
    notificationLang: "en"
  }
})

createAssistant sets a hardcoded password of Password1234 for every account it provisions. The email pattern is deterministic: {prefix}+{firstname}-assistant-{lastname}@onedoc.ch. If you can guess a name, you can construct the email.

The function sits alongside a _populatePatients() call, which suggests it is part of a practice setup or seeding flow — something that runs when a new practice is being configured. That context matters. If this function was used in production to provision real assistant accounts, every account it created shares the same password.

The scanner classified it MEDIUM. Peng’s manual triage agreed. Not because the hardcoded credential itself is ambiguous — it is not — but because there is a real limitation: you cannot verify whether any accounts were actually provisioned this way without attempting account enumeration, which the YesWeHack RoE for this program explicitly prohibits. The finding is based on the code alone.


Why this matters more in healthcare

On a general-purpose SaaS, a hardcoded provisioning password is a medium finding. On a platform with patient appointment data, the calculus shifts.

Medical assistant accounts on pro.onedoc.ch sit at the intersection of a predictable email pattern and a known password. An attacker who finds this bundle can:

  1. Pull the email pattern from the same decompiled function
  2. Enumerate likely account names against the login form (the RoE requires care, but a real attacker is not constrained by it)
  3. Attempt Password1234 against any matching address

Patient names, appointment histories, contact information, and potentially healthcare provider details are all within reach if an account logs in. None of that exposure requires exploiting a code injection vulnerability or breaking encryption. It is a credential problem.

Peng’s submission recommended removing the hardcoded password, enforcing a first-login password change for all provisioned accounts, and auditing whether any accounts were created via this code path and never had their password updated.


The rest of the scan: a false positive catalogue

The scanner returned 31 findings. Knowing why 29 were noise is useful — it’s where most of the actual work happens.

Firebase API key (scanner: HIGH → FALSE POSITIVE)

The bundle contained AIzaSyAt9Vrk3XT_ygT1tzUQEPNc5b_DzUOYmsE. The scanner flagged it HIGH. Firebase web API keys starting with AIzaSy are the client-side identifier for the Firebase project — they are designed to be in client-side code and are not themselves secret. Peng tested it: HTTP referer restrictions blocked use from outside pro.onedoc.ch, and Firestore access returned PERMISSION_DENIED. Not a finding. Firebase keys in bundles are almost always false positives; the check is whether they have any access without the application’s own origin headers.

Recurly public key (scanner: HIGH → FALSE POSITIVE)

ewr1-LlA3MOTOHgMNEHjYi4mO6m as recurlyApiKey. Recurly ewr1-* keys are JavaScript client library keys for payment tokenization. They cannot create charges, access billing data, or modify subscriptions — that is by design. Recurly explicitly documents them as client-side keys. Another false positive category that will eat time if you treat every payment SDK token as a secret.

generic_secret pattern matches (18 findings, all FALSE POSITIVES)

This is where most of the noise came from. The scanner’s generic_secret pattern matches on key names rather than values:

MatchedWhat it actually is
Password:"needCurrentPassword"Angular i18n key / form error string
Token:"activateMfaToken"Enum value / i18n key
Password:"invalidCurrentPassword"Form validation message
TOKEN="LegacyInvalidFormPipeContextToken"Angular injection token
Password="password"CSS class or URL route segment
refreshTokenStandard JWT field name, not a token value

None of these are credentials. The pattern is matching on Password as a substring of a string key, not on anything that looks like an actual credential value. Flagging Password:"needCurrentPassword" as a secret is the scanner finding variable names, not secrets. The createAssistant case was different because the matched value — "Password1234" — is an actual string being passed to a service call as an account credential.

.env soft-404 (FALSE POSITIVE)

https://pro.onedoc.ch/.env returned HTTP 200. Angular SPAs serve index.html for all unknown routes — that is the SPA catch-all pattern. The file is not accessible. If your scanner is flagging HTTP 200 responses on .env paths against SPA targets, add a content-type or body check before calling it a finding.

Missing CSP

pro.onedoc.ch has no Content-Security-Policy header. HSTS and X-Frame-Options: DENY are present, but CSP is absent — not even a report-only policy. For a healthcare professional portal, missing CSP raises the blast radius of any XSS if one were found. That said, missing CSP alone is informational on YesWeHack without a companion XSS finding to demonstrate impact. Not submitted standalone.


What separates the real finding from the noise

The createAssistant case stood out for a specific reason: the value being passed as password was a literal credential string, not a variable name, key identifier, or route fragment. The function is provisioning real accounts — it calls a service with a data object that has an email, a firstName, a lastName, and a password. It is doing what it says it is doing.

Compare that to Password:"needCurrentPassword". That is an Angular form error key. The word “Password” appears in the key name, and the value is a description of a validation state, not a credential. A scanner cannot make this distinction automatically, which is why manual triage of JS bundle findings is not optional.

Three questions help filter the real ones:

  1. Is the value a literal string that could be a credential, or a variable name / message key? Credential-looking values: hashes, long random strings, things that look like passwords. Non-credential values: camelCase keys, UI message strings, route paths.
  2. What is the function doing with the value? A value passed to a createUser or createAssistant call as a password field is in a different risk class than the same string appearing in a CSS class definition.
  3. Is the credential pattern public by design? Firebase AIzaSy* and Recurly ewr1-* are documented public keys. Most payment tokenization SDKs, maps SDKs, and analytics SDKs have client-side keys that are meant to be visible.

The hardcoded provisioning password passes all three: it is a literal credential string, it is used in an account creation call, and nothing about Password1234 is designed to be public.


On submitting this class of finding to YesWeHack

YesWeHack severity for hardcoded credentials depends on exploitability, and the RoE limitation here matters. You cannot submit a finding that requires account enumeration to verify impact without disclosing the limitation explicitly. Peng’s submission writeup included:

“Note: I did not attempt to log in with these credentials or enumerate production accounts (per RoE — ‘Do not leak, manipulate, or destroy any user data’). This finding is based solely on the production bundle.”

Programs want to know what you did and did not verify. That is not negotiable. On YesWeHack, severity can be negotiated down if impact is theoretical — but the disclosure of a limitation is also what separates a legitimate security report from an unsupported claim.

The YesWeHack submission guide has platform-specific field notes on how they handle severity negotiation when impact depends on conditions you cannot directly test.



FAQ

Is hardcoding a default password in a JS bundle always a submittable finding?

Not automatically. The key question is whether the password is used in a function that creates or authenticates real accounts. A hardcoded default in a provisioning call that creates production accounts is a genuine finding. The same string appearing in a test fixture, a mock, or an i18n key is not.

How do you distinguish a real Firebase key leak from a false positive?

Test it. Firebase web keys (AIzaSy*) are commonly public by design — the real question is what permissions they carry. Try GET https://firestore.googleapis.com/v1/projects/{project}/databases/(default)/documents with the key. PERMISSION_DENIED means the key has no Firestore access. REQUEST_DENIED on a Maps API means referer restrictions are in place. Neither is a finding unless you can demonstrate actual access.

What severity does a hardcoded provisioning password typically get on YesWeHack?

MEDIUM is the typical range for hardcoded credentials where impact requires additional conditions (account enumeration, account confirmation). If you can demonstrate a working login with the hardcoded credential, severity can reach HIGH. If the finding is purely based on code analysis without confirmed impact, MEDIUM with a clear limitation disclosure is the right framing.

Should I report missing CSP on a healthcare portal?

On YesWeHack, missing CSP as a standalone finding is typically informational. The value comes when you pair it with an XSS — at that point the absence of CSP becomes part of the impact chain. Missing CSP on its own, across a site that has HSTS and X-Frame-Options, does not meet the bar for a solo submission on most programs.