One grep, full repo access
A security camera shipped a full-scope GitHub PAT in its login page bundle. The credential exposure, supply-chain exploit path, GitHub audit-log telemetry, and why rotation - not removal - is the only fix.
A consumer security camera served a GitHub personal access token in plaintext on its login page. The token was embedded in the JavaScript bundle the device delivered to every browser that loaded the admin interface. Scope: full repository access. Anyone who opened DevTools and searched the sources tab for ghp_ had it. No exploitation required. No memory corruption. A find-in-files against a minified bundle.
This is not a CVE-class bug. There is no memory safety violation, no protocol flaw, no logic error to diff. It is credential exposure - CWE-798, use of hard-coded credentials; CWE-540, inclusion of sensitive information in source code; CWE-312, cleartext storage of sensitive information. Assessed severity is maximum. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H, base 10.0. Network-reachable, no privileges, no user interaction, scope changed. The scope change is the whole story. A token baked into a camera crosses the trust boundary from the device into the vendor’s GitHub organisation. The vulnerable component and the impacted component are not the same system.
The mechanism is a build pipeline that treats a client bundle as private. Frontend build tools inline environment variables at compile time. Vite exposes anything prefixed VITE_ to the client. Create React App does the same with REACT_APP_. The token sat in the .env file the build read, was referenced somewhere in the source, and got substituted directly into the emitted bundle as a string literal. Minification is not obfuscation. It renames locals and strips whitespace. It does not remove string constants. ghp_ and github_pat_ survive verbatim. If sourcemaps shipped alongside, the original variable name shipped with them. The device firmware served the static asset from its embedded web server, or the cloud login portal served it from a CDN. The path does not matter. The bundle runs on the client. The client is the adversary’s machine. There is no such thing as a client-side secret.
Why the token existed is predictable. The login page checked for firmware updates. To read the release feed of a private repository, the app called api.github.com with authentication. To authenticate, it needed a credential. The credential went into the frontend config because that is where the code that called the API lived. A convenience token, provisioned once, scoped wide, never rotated. The developer solved a private-read problem by shipping the private-read credential to every public visitor.
Token scope is what converts disclosure into a fleet-level event. A classic PAT with repo scope grants full read and write to every repository the issuing account can reach, private included. workflow scope grants the ability to modify GitHub Actions definitions. admin:org grants organisation management. With write access to the firmware repository, an attacker edits release artifacts, tags a poisoned release, or injects a step into the CI workflow so the next build produces an attacker-controlled image. That is T1195.002, compromise of the software supply chain. Every camera that pulls the next OTA update pulls signed, authentic-looking, malicious firmware. The token also enables T1213, data from information repositories - clone the source, walk the git history, and harvest secondary secrets: cloud keys in CI config, signing material, database strings committed years ago and never purged. Every action is authenticated and authorised. That is T1078, valid accounts. In the audit log, the traffic looks like the developer working late.
The pivot chain runs one direction. Token to private repository clone. Clone to AWS keys or firmware signing keys recovered from history. Signing key to a validly signed malicious image. Signed image to OTA push across the fleet. If the release repository also holds the signing pipeline, the integrity of every deployed device collapses in a single step. The disclosure is trivial. The blast radius is the entire product line.
This pattern has a body count. Okta had private GitHub repositories accessed and source code copied in December 2022 after a token was compromised. Cloudflare was breached in November 2023 using one access token and three service-account credentials that were not rotated after the October 2023 Okta incident - the credentials were known-exposed and still live, so they got used against Atlassian, Confluence, and Jira. Mercedes-Benz exposed a GitHub token in a public repository in January 2024; RedHunt Labs traced it to an internal GitHub Enterprise Server holding source, Azure and AWS keys, and database connection strings. Toyota left a hardcoded access key in a public repository for roughly five years, exposing around 296,000 customer records. Same failure every time. A long-lived credential where someone misjudged the trust boundary.
Harvesting is automated and fast. Scanners watch new commits and new deployments for ghp_, github_pat_, and AKIA patterns. Bots clone and authenticate within minutes of exposure. GitHub’s secret scanning partners with token issuers to auto-revoke credentials found in public repositories, and that pipeline is effective - for public GitHub. A token embedded in a device bundle served from the vendor’s own CDN never touches GitHub. Secret scanning never sees it. It stays live until someone notices the authenticated abuse or the vendor rebuilds the bundle. In this case, the token was reachable by every customer, every reviewer, every teardown video, and every automated crawler that fetched the login page.
Telemetry splits cleanly into what fires and what does not. On the GitHub side, the audit log carries the evidence: git.clone, git.fetch, repo.download_zip, protected_branch.policy_override, workflows.updated, org.update_member. The PAT settings expose a last-used timestamp and last-used IP. The signals worth alerting on are anomalous ASN - a developer token suddenly authenticating from a hosting provider or an unfamiliar country - and volume anomalies, mass cloning of many repositories in a short window, which is the ShinyHunters clone signature. Push events from an unexpected client. Workflow file edits outside a pull request. For GitHub Enterprise, audit log streaming forwarded to a SIEM with per-identity clone-rate and workflow-modification rules turns these into detections.
What does not fire is everything on the device. The token sits in a static asset. Serving a JavaScript bundle is a routine 200. No server log distinguishes a customer login from a credential harvest - the request is identical. There is no EDR on the camera. Opening DevTools emits nothing. Client-side secret exposure has no emit point on the defender’s side. The disclosure is undetectable by design. Only the abuse is detectable, and only on GitHub, and only if audit log streaming was enabled before the fact. A vendor watching its own infrastructure sees nothing until attacker actions land inside the GitHub organisation. That is the detection hole: the leak lives entirely outside the vendor’s observable surface until it is already being used against them.
The correct response order matters. Revoke the token - regenerate it, do not merely delete the reference and ship a new build. The old bundle is cached, mirrored, archived in teardown repositories, and baked into every device already in the field running that firmware version. Rotation is the only remediation. Removal from the next release changes nothing about the credential that is already public. Assume compromise from the first ship date of the affected firmware. Pull the full GitHub audit log back to that date and review clone volume, push origin, and workflow edits across the exposure window. Rotate every secondary secret the token could have reached - the Cloudflare case is the lesson: a known-exposed credential that is not rotated gets used, on the attacker’s schedule, not the defender’s.
For the release process itself, the structural fixes are specific. Secrets do not belong in client bundles, because the bundle is distributed to untrusted endpoints by definition. The firmware update check moves to a backend endpoint that holds the credential server-side and returns only the release manifest to the client - the device never sees the token. CI-to-cloud authentication moves to GitHub Actions OIDC federation, exchanging a short-lived identity for cloud access with no stored long-lived key. Where a GitHub credential is genuinely required, a GitHub App installation token with a one-hour TTL replaces the classic PAT. Tokens are scoped to a single repository, read-only, with an expiry date set. And a secret scanner - gitleaks or trufflehog - runs pre-build and fails the pipeline on any ghp_ or github_pat_ string before an artifact is produced. The same grep the attacker runs against the shipped bundle runs first, in CI, on the source. GitHub push protection covers the commit path; a build-time scan covers the artifact path, which is where this token escaped.
Residual exposure after rotation is real. The credential is dead, but rotation closes future use, not past exfiltration. Any firmware signed or released during the exposure window is suspect and warrants artifact review against known-good hashes. Any secret pulled from the repositories while the token was live is burned and must be rotated on the same assumption. For an Australian vendor, exposure of customer footage or account data through this chain is assessable under the Notifiable Data Breaches scheme in the Privacy Act, and a camera operator classed as a critical infrastructure provider carries SOCI reporting obligations on top of that. Active abuse belongs with the vendor’s security team and CERT, not a solo investigator. The credential is gone. The blast radius is defined by everything it touched while it was live, and that accounting is the actual work.
See also: NordVPN for tunneled traffic when operating outside controlled networks.
#ad Contains an affiliate link.
Keep Reading
aws-govcloudCISA contractor leaked GovCloud keys to GitHub
Technical analysis of a CISA contractor's leaked AWS GovCloud admin keys on GitHub - blast radius, IAM persistence paths, CloudTrail detections, supply-chain tail.
supply-chain-securityA valid go.sum hash proves nothing
Argegy is not a CVE. It's a Go supply chain claim against go-ethereum - module trust, init() execution, T1195, and where telemetry goes blind.
supply-chain-securitySpain rips Palantir out of its data pipelines
Spain's Palantir blacklist is a supply chain concentration risk - a privileged vendor data plane mapped to MITRE T1199, and why customer telemetry stays blind.
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.