Executive summary
I spent time digging into a question that most frontend teams don’t think about until it’s too late: what actually happens if a malicious commit lands in a Vite app and reaches production?
The answer is bigger than “a broken UI.” In a modern React + TypeScript stack, a compromised frontend can observe user input, access browser-readable data, interact with authenticated APIs, tamper with workflows, and even persist through mechanisms like service workers. In other words, once hostile code is shipping to users, the browser becomes part of the attack surface.
This research breaks down the real blast radius of a malicious production change, where the risks are highest, and what teams can do to reduce exposure. It also looks at build-time and CI/CD risks, because a frontend compromise is often only one step in a broader supply-chain incident.
If an attacker can modify a Vite + TypeScript + React repository and cause that change to be deployed, assume the application’s frontend integrity has been lost. The attacker’s JavaScript runs under the website’s origin in every affected visitor’s browser, with essentially the same browser privileges as the legitimate React application. It can inspect the rendered page, observe user interactions, read JavaScript-accessible storage, call the application’s APIs, alter transactions, and send accessible data elsewhere. Third-party JavaScript carries the same core risks: arbitrary client-side execution, loss of control over application changes, and disclosure of sensitive information.[^1]
The incident can also compromise the build and deployment plane, which is separate from visitor-browser compromise. Malicious repository code may execute during dependency installation, testing, or building; npm supports arbitrary package scripts and lifecycle hooks such as preinstall, install, postinstall, and prepare. If CI exposes secrets, a writable GITHUB_TOKEN, cloud credentials, signing keys, deployment tokens, or internal network access to that process, those assets may also be stolen or abused. GitHub specifically warns that privileged workflows which execute untrusted checked-out code can expose repository secrets and writable tokens.[^2][^3][^4][^5]
The most important distinctions are:
- Repository access alone: The attacker can read whatever the granted repository permission exposes, including source and history, but cannot necessarily affect users.
- Ability to merge or push deployable code: The attacker can place malicious behavior into source, configuration, dependencies, workflows, or public assets.
- Successful production deployment: Visitors who load the affected release execute the malicious client bundle.
- Privileged CI execution: Build-time code may access CI secrets, deployment credentials, runners, artifacts, or internal services according to the workflow’s permissions.
- Backend compromise: This is not automatic. It occurs if the repository includes backend code, CI exposes backend credentials, publicly embedded credentials are overprivileged, or application APIs fail to enforce authorization.
This report is a threat-model and audit guide, not a forensic judgment about a specific repository. No project files, deployed URL, HTTP headers, workflow definitions, or hosting configuration were supplied, so it cannot establish whether compromise has occurred.
Scope and architecture
The stated stack—Vite, TypeScript, React, shadcn/ui, and Tailwind CSS—is primarily a client-side application stack. Vite takes index.html as its default production entry point and emits a bundle suitable for static hosting. TypeScript types do not create a runtime security boundary: after compilation, the browser executes JavaScript. React and shadcn/ui structure the user interface, while Tailwind generates styling; none prevents intentionally malicious code already accepted into the production build.[^6]
A useful model separates the system into four trust zones:
| Zone | Typical contents | Consequence of attacker control |
|---|---|---|
| Source control |
src/, public/, configuration, lockfile, workflows, history |
The attacker can alter application and build definitions and inspect committed material. |
| Build/CI | Runner, environment variables, package installation, build commands, artifacts | Build-time theft, artifact tampering, deployment takeover, or persistence, limited by runner and token permissions. |
| Browser | Production JavaScript, DOM, web storage, API calls, service workers | Collection or manipulation of everything the page can legitimately access. |
| Backend/services | APIs, databases, authentication, cloud services | Compromise depends on credentials, authorization, API design, and whether server code is also controlled. |
The browser’s same-origin policy still isolates unrelated origins. Web Storage and IndexedDB are separated by scheme, host, and port, so ordinary code from one origin cannot directly read another origin’s storage. This means malicious code on app.example.edu does not automatically gain the storage, DOM, or passwords of an unrelated site such as a bank. However, it has strong access inside app.example.edu and can make outbound requests unless browser policy, extensions, or a restrictive Content Security Policy blocks them.[^7][^8]
Attacker pathways
Direct source modification
The most obvious path is a malicious change in a normal application file. High-value locations include:
-
src/main.tsx,src/App.tsx, router/layout components, authentication providers, global state, and API clients. - Form components for login, registration, profile, recovery, checkout, messages, uploads, and administration.
- Reusable shadcn/ui wrappers, because a small change to a widely used
Input,Form,Button,Dialog, orSelectwrapper can affect many pages. - Hooks and context providers that already see user identity, tokens, form state, or API responses.
- Error reporting, analytics, telemetry, and logging modules, where additional collection may appear superficially legitimate.
-
index.html, which can load an external script before the React application starts. - Files in
public/, which Vite copies to the root of the production output unchanged.[^9]
React normally escapes values rendered through JSX, which helps prevent accidental HTML injection. That protection is irrelevant to intentionally malicious code committed to the application, and it can be bypassed by dangerous rendering mechanisms. React’s dangerouslySetInnerHTML requires special caution because improperly handled HTML can create XSS. Review it, direct DOM APIs, dynamically generated URLs, and any library that converts untrusted markup into DOM nodes.[^10][^11]
Vite configuration and plugins
vite.config.ts is a particularly sensitive file. Vite plugins can participate in source resolution, loading, transformation, HTML transformation, and bundle generation; transformIndexHtml can modify the entry HTML in development and builds. A malicious plugin or inline plugin could therefore inject code into output without placing an obvious payload in a React component.[^12]
Review:
- Every item in the Vite
pluginsarray. - Local plugin code and imported configuration helpers.
-
define,envPrefix,envDir, aliases, proxy configuration, and custom build inputs. -
build.rollupOptionsor equivalent bundler hooks. - HTML-transform,
renderChunk,generateBundle,writeBundle,closeBundle, and custom middleware hooks. - Conditional behavior based on
mode,command, branch, hostname, date, or environment variables.
A Vite plugin can affect both development and production unless constrained using its apply setting. Consequently, a plugin may target only production, making local development appear clean, or only the CI environment, complicating reproduction.[^12]
Dependency and lockfile tampering
An attacker can add a malicious dependency, replace a package with a similarly named package, alter a version or registry source, modify the lockfile, or add install/build scripts. npm’s scripts field allows arbitrary commands, and lifecycle events can execute during installation. Such code runs on the developer or CI machine, not merely in the browser, so its reach depends on the operating-system account, environment variables, mounted credentials, network, and workflow permissions.[^4][^5]
Use a committed lockfile and npm ci in automation. npm ci requires an existing lockfile, fails if it disagrees with package.json, removes an existing node_modules, and does not rewrite the manifest or lockfile. This improves reproducibility but does not prove that the locked code is benign; a maliciously changed lockfile remains malicious.[^13][^14]
Dependabot alerts identify dependencies associated with reviewed advisories and should be enabled, but GitHub explicitly notes that alerts cannot catch every issue. npm provenance can link a package to source and build instructions, and npm audit signatures can verify registry signatures and available provenance attestations, but npm also states that provenance does not guarantee the absence of malicious code.[^15][^16][^17]
CI/CD workflow compromise
Repository control may include .github/workflows, deployment manifests, shell scripts, Dockerfiles, and host-specific configuration. A malicious change can attempt to print or transmit environment variables, change the deployed artifact, deploy a second workload, alter cache contents, or expand token permissions.
The highest-risk GitHub Actions pattern is privileged execution of untrusted code. pull_request_target runs in an elevated context with the base repository’s token and potentially secrets; checking out and then executing pull-request code can expose those credentials. GitHub recommends avoiding that trigger when unnecessary, never executing untrusted checked-out code in the privileged context, and restricting secrets and GITHUB_TOKEN permissions.[^18][^3][^2]
Self-hosted runners increase impact if they are persistent or can reach internal systems. GitHub recommends isolated, ephemeral compute for privileged workflows handling untrusted material. Also review artifacts, caches, reusable workflows, composite actions, and third-party actions; pinning an action only by a movable branch or tag gives weaker integrity than pinning a reviewed commit SHA.[^19]
External scripts and tag managers
A repository change can add a script URL, analytics tag, customer-support widget, tag-manager container, or remotely controlled configuration. OWASP identifies compromise of a third-party script server as a major risk because injected JavaScript then executes in the host application and may disclose sensitive DOM data.[^1]
An apparently unchanged deployment can also become malicious later if it loads mutable code from elsewhere. Inventory every external script and explain why it is needed. For static third-party resources, Subresource Integrity can make the browser verify reviewed content, provided the hosting and CORS setup support it. Cross-origin sandboxed iframes can reduce direct access to the parent DOM and cookies when the integration permits isolation.[^1]
Service-worker persistence
A malicious deployment may register or replace a service worker. Service workers operate like origin-scoped proxies: they can intercept page and subresource requests and return cached or synthesized responses. Browser registrations persist beyond the lifetime of an individual page object, and an active worker controls clients within its registered scope.[^20][^21][^22]
This matters during remediation. Reverting the source repository and redeploying may not immediately remove malicious cached resources or an active registration from browsers that visited during the incident. Investigators should inspect registrations, service-worker script changes, CacheStorage, PWA update behavior, and whether the clean release explicitly retires unexpected registrations and cache entries. The browser Application panel exposes service workers and cache storage for inspection.[^23]
Data exposed in browsers
Direct user input
Malicious first-party JavaScript can observe input and interaction events on the affected page. Depending on which pages are present, this can include:
- Usernames, email addresses, passwords typed into that site, and password-reset values.
- Names, postal addresses, phone numbers, dates of birth, student identifiers, and profile information.
- Search terms, private messages, support tickets, survey answers, notes, and free-text content.
- Payment-card details if the fields are part of the compromised page’s DOM.
- Files explicitly selected or dropped into a page, to the extent the page has received access to those
Fileobjects. - One-time passcodes, recovery codes, or MFA values entered into the compromised interface.
- Form contents even if the user cancels and never presses Submit, because page code can observe fields while they are being edited.
This class of attack is often called web skimming when it targets payment interfaces. PCI SSC guidance treats browser-executed payment scripts as a significant target and calls for script authorization, integrity controls, inventories, and tamper monitoring. The broader principle applies to any sensitive form: once malicious code shares the same document, encryption in transit does not stop that code from seeing data before HTTPS sends it.[^24]
DOM and application state
The script can read visible and hidden information already present in the page’s DOM or JavaScript state, including:
- The authenticated user’s displayed name, email, role, account number, or organization.
- Data in tables, dashboards, modal dialogs, hidden tabs, data attributes, and preloaded page state.
- CSRF tokens embedded in markup or readable JavaScript variables.
- API data the legitimate client fetches and renders.
- Route, query string, URL fragment, referrer, and navigation history available to the application.
- Values in React context, stores, query caches, or variables if the attacker modifies the relevant code path or instruments application APIs.
A malicious commit does not need to “break into React.” It becomes part of React and can be placed where data already flows. Minification may change names, but it does not reduce runtime access.
Browser storage
The principal stores to audit are:
| Store | Malicious same-origin JavaScript access | Security significance |
|---|---|---|
localStorage |
Read/write | Persistent strings; dangerous place for tokens or PII. |
sessionStorage |
Read/write within the tab’s origin/context | Cleared with the tab, but still accessible during compromise. |
| IndexedDB | Read/write for the origin | May contain structured offline data, caches, profiles, or tokens. |
| JavaScript-readable cookies | Read/write subject to attributes and scope | May expose identifiers or sessions if not HttpOnly. |
HttpOnly cookies |
Value cannot be read through JavaScript | Reduces token theft, but does not neutralize malicious code running in an authenticated page. |
| Cache Storage | Read/write for the origin | Can expose cached responses or support persistence and content substitution. |
OWASP advises against placing sensitive information or session identifiers in localStorage, because any JavaScript executing in the origin can access it; HttpOnly cookies mitigate direct session-cookie theft. OWASP’s session guidance likewise advises not storing authentication tokens, session IDs, JWTs, refresh tokens, or credentials in localStorage or sessionStorage and favors appropriately secured cookies or a backend-for-frontend pattern.[^25][^26]
An HttpOnly cookie cannot be read through document.cookie. However, that does not make a compromised page safe: same-origin malicious code may still initiate authenticated requests for which the browser automatically supplies the cookie, inspect same-origin API responses available to the page, alter account data, or perform actions using the victim’s active session. HttpOnly is therefore an important containment boundary against reusable cookie theft, not a complete defense against first-party script compromise.[^27][^28][^29]
API responses and actions
The code can call any API that the legitimate frontend can call from the same origin or through allowed cross-origin configuration. The browser generally restricts scripts from reading cross-origin responses unless the target server grants access through CORS. That restriction does not protect same-origin application APIs from code deliberately shipped as part of the application.[^30]
Potential impact includes:
- Reading account, profile, document, course, grade, order, or message data returned to the current user.
- Creating, changing, or deleting records with the user’s permissions.
- Changing email, notification, privacy, or recovery settings.
- Initiating transfers, orders, submissions, invitations, or administrative actions if the user is authorized.
- Modifying displayed transaction details while sending different details to the server.
- Harvesting API schemas, identifiers, and error responses to support later attacks.
Backend authorization remains decisive. A frontend “admin check” is not a security control; every API operation must enforce identity, authorization, object ownership, and permitted fields on the server. If an API accepts a privileged public key, trusts client-supplied roles, or has insecure direct-object references, malicious frontend code can exploit those defects at scale.
Device and network metadata
Even without special permission, browser requests reveal network and protocol metadata to the destination receiving them. Page scripts can also obtain varying amounts of browser and device information and combine characteristics into a fingerprint. Relevant signals may include browser/OS family, language, time zone, screen characteristics, hardware-concurrency approximations, graphics rendering behavior, installed-font exposure, and interaction patterns. Mozilla describes fingerprinting as assembling such details into an identifier that can work even when cookies are blocked.[^31][^32]
This data is generally less severe than a password or session token, but it can support tracking, profiling, fraud analysis, correlation, or targeted social engineering. Browsers increasingly reduce or randomize exposed characteristics, so the exact data varies by browser and privacy settings.[^32][^33]
Permission-gated sensors
Malicious code can request high-impact browser capabilities, but it does not automatically bypass browser permission controls:
- Camera and microphone access through
getUserMedia()requires a secure context and user permission; browsers must show usage indicators.[^34] - Precise geolocation prompts the user unless permission was already granted or denied for the origin.[^35][^36]
- Clipboard APIs require HTTPS and are subject to permission and/or recent-user-activation rules that vary by operation and browser.[^37][^38]
- Screen capture, notifications, Bluetooth, USB, serial devices, and similar capabilities have their own prompts, secure-context requirements, or user gestures.
A crucial caveat is previously granted origin permission. Users may have already trusted the legitimate site. A malicious release can attempt to use permissions still granted to that origin, subject to current browser behavior and indicators. A restrictive Permissions-Policy response header can disable unneeded capabilities or limit which origins may use them.[^34][^35]
What it normally cannot steal
A compromised frontend is powerful but not equivalent to malware installed on the operating system. Under ordinary browser security boundaries, it normally cannot directly:
- Read arbitrary files from the user’s disk that the user has not selected or granted through a file-system API.
- Read another unrelated website’s DOM,
localStorage,sessionStorage, or IndexedDB because those are origin-isolated.[^8][^7] - Read another site’s cross-origin API responses without that server’s CORS permission.[^30]
- Read
HttpOnlycookie values through JavaScript.[^27] - Obtain camera, microphone, or precise location for a never-approved origin without the relevant browser permission flow.[^35][^34]
- Extract passwords merely saved in the browser for other domains.
- Bypass server-side authorization solely because it controls the frontend.
- Decrypt properly protected server-side data for which neither the browser session nor CI environment has keys or authorized API access.
These limits can fail if a browser or extension is vulnerable, the user approves a deceptive prompt, origins are misconfigured, sibling subdomains are overly trusted, CORS is permissive, an API lacks authorization, or the attacker also compromises backend/cloud infrastructure.
Build-time assets at risk
The following assets are at risk only if present or reachable in the build/deployment context:
- Repository and organization secrets injected into CI jobs.
- The job’s
GITHUB_TOKEN, according to its configured permissions. - Cloud-provider credentials, service-account keys, deployment tokens, package-registry tokens, and container-registry credentials.
- Error-monitoring upload tokens and source-map credentials.
- SSH keys, deploy keys, signing keys, release credentials, and environment-protection approvals.
- Build artifacts, private package contents, dependency caches, and previous outputs.
- Files or credentials left on persistent self-hosted runners.
- Internal services reachable from runner networks.
- Secrets embedded in committed history, not just the current tree.
A malicious frontend commit cannot infer an unexposed server secret merely because the application uses it. The risk appears when the build process receives that secret, the repository already contains it, a workflow can request it, or a credential available to the application is overprivileged.
Vite environment variables
Vite’s environment-variable behavior is central to this analysis. Variables beginning with VITE_ are exposed to client-side code and statically replaced during the build; Vite explicitly warns that VITE_* values must not contain secrets. The envPrefix setting controls which variables are exposed, and Vite warns against an empty prefix because that could leak sensitive environment variables.[^39][^40]
Therefore:
-
VITE_API_URL, a public analytics ID, or a browser-safe publishable key may be appropriate. - Database passwords, private API keys, service-role keys, signing keys, and unrestricted cloud credentials must never use a client-exposed prefix.
- Renaming a secret, minifying the bundle, or hiding the source map does not make a browser-delivered value secret.
- A value used in client code should be treated as recoverable by every visitor.
- Review
defineinvite.config.ts, because it can explicitly substitute process environment values into the client bundle even without the default prefix. - Review custom
envPrefixandloadEnvusage, including mode-specific files and CI overrides.
Vite loads .env, .env.local, and mode-specific files according to documented precedence; local variants are intended to be ignored by Git. Nonetheless, .gitignore is not a security boundary after a secret has been committed. A discovered credential must be revoked or rotated, not merely removed from the latest commit. GitHub advises immediately rotating detected credentials because secret scanning covers repository history and exposed values remain usable until revoked.[^41][^39]
Source maps and client artifacts
Vite’s production source maps are disabled by default, but build.sourcemap can produce separate, inline, or hidden maps. Public maps can make source review and reverse engineering easier, expose original names and comments, and reveal code paths; they should not contain secrets in any case. “Hidden” only omits the bundle’s source-map comment—it does not itself guarantee the map file is private if the deployment uploads it.[^42]
Review the complete deployed dist/, not just src/. A compromised build can inject content that has no direct one-to-one source file, and assets in public/ are copied as-is. Compare production artifacts against a clean, reproducible build from a trusted commit.[^9]
Malicious behavior patterns
The following patterns are indicators for defensive review. Their presence is not proof of compromise because legitimate applications use many of the same APIs.
Collection indicators
- Broad
documentorwindowlisteners forinput,change,keydown,keyup,submit,paste,copy,click, or pointer events. - Listeners registered in global providers, layout components, bootstrap files, or generic input components.
- Reads from
document.cookie,localStorage,sessionStorage, IndexedDB, Cache Storage, or credential-related application stores. - Form serialization outside the expected submit handler.
- Mutation observers watching forms or dynamically created fields.
- Reads of password, card, recovery, OTP, SSN/national-ID, health, grade, or private-message fields.
- Instrumentation of
fetch,XMLHttpRequest, WebSocket, form submission, history, or navigation APIs. - Unexpected camera, microphone, geolocation, clipboard, screen-capture, notification, or device permission requests.
Exfiltration indicators
- New domains in
fetch, XHR, WebSocket, EventSource,sendBeacon, image URLs, script URLs, CSS URLs, or form actions. - Data placed in query strings, fragments, image requests, telemetry payloads, error messages, or analytics properties.
- Requests to raw IP addresses, newly registered domains, URL shorteners, paste services, temporary tunnels, serverless endpoints, or unrelated cloud projects.
- Large or unusual Base64/hex encoding, encryption wrappers, compression, chunking, or payloads labeled as innocuous metrics.
- Requests that occur on blur, navigation, page unload, visibility changes, or after delays.
CORS is not an outbound-data firewall. Browsers can send some cross-origin “simple” requests even when the attacker cannot read the response, and opaque no-cors responses hide response data rather than guaranteeing that no request was sent. A tight CSP connect-src provides a stronger browser-enforced destination allowlist for fetch, XHR, WebSocket, EventSource, and sendBeacon.[^43][^44][^45]
Stealth indicators
- Minified or obfuscated code committed into source directories.
- Dynamic code execution such as
eval,new Function, or decoded strings used as code. - Computed property names and string arrays that conceal browser APIs or domains.
- Payloads split across unrelated files or activated by a remote configuration flag.
- Hostname, user-agent, account, role, geography, time, referrer, or sampling conditions.
- Behavior disabled on localhost, preview deployments, test accounts, automated browsers, or developer tools.
- Very small changes to a common component, API wrapper, analytics utility, or error handler.
- Dependencies introduced only through lockfile changes or remote URLs.
- Build-only injection in Vite/Rollup plugin hooks.
- Service-worker registration and cache manipulation.
Repository audit plan
Preserve first
If compromise is suspected, preserve evidence before cleanup: save suspicious commits, diffs, deployed artifacts, workflow logs, hosting logs, HTTP headers, DNS state, access logs, and relevant timestamps. GitHub recommends preserving screenshots, logs, affected files, indicators, and decision records during incident response.[^46]
Do not execute an untrusted repository on a normal workstation or privileged runner. Inspect it as data first. Build only in a disposable, isolated environment with no secrets, no cloud metadata access, no writable production credentials, restricted networking, and no mounted home-directory credentials.
Establish trusted baselines
- Identify the last independently verified good commit and the exact commit deployed to production.
- Record the deployment artifact hash, deployment time, workflow run, actor, approvals, and hosting project.
- Compare Git history from the trusted commit through the deployed commit, including merge commits and force-push events.
- Build the trusted commit and suspect commit in isolated, equivalent environments.
- Compare generated
dist/files, script URLs, content hashes, source maps, service-worker files, and HTTP headers. - Compare what production serves from multiple clean locations, because conditional server or CDN behavior may differ.
Review high-risk files
Prioritize these paths and file classes:
-
index.html,src/main.*,src/App.*, route definitions, layouts, providers, authentication, API, analytics, and form components. -
vite.config.*, local Vite/Rollup plugins, PostCSS/Tailwind configuration, and any code-generating plugins. -
package.json, lockfiles,.npmrc, registry configuration, patches, vendored code, and install scripts. -
.github/workflows/**, actions, shell scripts, Dockerfiles, deployment manifests, and hosting adapters. -
public/**, service workers, manifests, robots files, redirects, and header configuration. -
.env*names and history, while avoiding printing real values into logs. - Serverless/API directories if the repository is not purely static.
- Any newly added binary, archive, minified JavaScript, WebAssembly, or generated file.
Defensive search commands
The following examples are triage aids, not a substitute for reading the surrounding code. Run them only against a local copy being treated as untrusted data:
# Review changes and authorship from a known-good commit
git log --all --decorate --graph --oneline
git diff --stat <KNOWN_GOOD>..HEAD
git diff <KNOWN_GOOD>..HEAD -- . ':!package-lock.json'
git log -p --all -- package.json vite.config.ts index.html .github/workflows public src
# Find browser data-access and transmission surfaces
grep -RInE "document\.cookie|localStorage|sessionStorage|indexedDB|caches\.|serviceWorker|sendBeacon|WebSocket|EventSource|XMLHttpRequest|fetch\(" src public index.html vite.config.*
grep -RInE "addEventListener\([^)]*(input|change|key|submit|paste|copy)|MutationObserver|FormData" src public
grep -RInE "getUserMedia|geolocation|clipboard|displayMedia|Notification|bluetooth|usb|serial" src public
# Find dynamic code and risky DOM sinks
grep -RInE "dangerouslySetInnerHTML|innerHTML|outerHTML|document\.write|eval\(|new Function|setTimeout\([^,]*['\"]|setInterval\([^,]*['\"]" src public index.html
# Find external destinations and remotely loaded code
grep -RInE "https?://|wss?://|<script|import\(|new URL\(" src public index.html vite.config.* package.json
# Examine package and CI execution paths
npm pkg get scripts dependencies devDependencies optionalDependencies
grep -RInE "preinstall|postinstall|prepare|pull_request_target|workflow_run|permissions:|secrets\.|actions/checkout" package.json .github/workflows
Search results require interpretation. For example, fetch is normal, analytics may be intentional, and minified vendor files may be legitimate. The audit question is whether each access and destination is documented, necessary, reviewed, constrained, and consistent with the deployed application’s privacy claims.
Dependency review
- Diff both
package.jsonand the lockfile; do not review only the short manifest. - Investigate new package names, maintainers, versions, tarball URLs, integrity fields, Git dependencies, local paths, and post-install behavior.
- Recreate dependencies with
npm ciin an isolated environment.[^13] - Run vulnerability and signature/provenance checks, while recognizing their detection limits.[^16][^15]
- Inspect packages newly introduced near the incident window, especially tiny utility packages, abandoned packages, install-script packages, and unexpected transitive changes.
- Pin known-good dependency versions while investigating; avoid an automatic bulk upgrade that destroys evidence or introduces unrelated differences.
Browser and network verification
Use a clean browser profile and non-sensitive test account. Before loading the application, open developer tools and enable preserved network logging. Inspect:
- Every loaded script, its initiator, origin, response headers, and whether it is expected.
- All XHR/fetch, beacon, WebSocket, EventSource, image, media, font, and form requests.
- Request payloads, query strings, headers, timing, redirects, and destinations.
-
localStorage,sessionStorage, IndexedDB, cookies, Cache Storage, and service-worker registrations. - Console messages, CSP reports, permission prompts, and unexpected errors.
- Behavior during typing, paste, submit, cancellation, route changes, tab hiding, and logout.
Repeat while blocking suspected domains and with a production-equivalent account role. Static review can miss runtime-generated or remotely activated behavior; runtime review can miss conditional branches. Both are required.
Prevention architecture
Protect the deployment branch
GitHub branch protection can require pull requests, approving reviews, status checks, conversation resolution, signed commits, successful deployments, and restrictions on who may push. For a university project with multiple contributors, a strong baseline is:[^47]
- No direct pushes to the production branch.
- At least one independent approval; use two for sensitive projects.
- Dismiss approvals when new commits are pushed.
- Require review by code owners for workflows, Vite configuration, lockfiles, authentication, API clients, and deployment files.
- Require passing tests, type checking, linting, secret scanning, dependency review, and a production build.
- Disallow force pushes and branch deletion.
- Prevent administrators from casually bypassing the policy where the platform supports it.
- Protect release tags and production environments as well as the branch.
A CODEOWNERS file can automatically request designated reviewers, and branch rules can require their approval for owned files. Treat .github/workflows/**, vite.config.*, package*.json, lockfiles, index.html, public/**, authentication, and deployment configuration as security-sensitive ownership zones.[^48]
Harden identities and tokens
- Require phishing-resistant MFA or at minimum strong 2FA for repository and hosting accounts.
- Use individual accounts; never share credentials.
- Grant least privilege and remove inactive collaborators promptly.
- Prefer short-lived, fine-grained credentials over broad personal access tokens.
- Review SSH keys, deploy keys, OAuth apps, GitHub Apps, webhooks, and authorized integrations regularly; GitHub explicitly recommends auditing these access paths.[^49]
- Separate source-control administration, deployment approval, and production secrets where feasible.
- Require production environment approval so a merge alone is insufficient for deployment.
Harden CI/CD
- Set explicit minimal
permissionsforGITHUB_TOKEN; do not rely on broad defaults. - Do not expose production secrets to pull-request builds.
- Avoid
pull_request_targetfor building or testing contributor code; never execute untrusted checked-out code in a privileged workflow.[^3][^2] - Pin third-party actions to reviewed commit SHAs.
- Use ephemeral hosted runners where possible; isolate and reimage self-hosted runners.[^19]
- Separate untrusted test workflows from privileged deployment workflows and pass only verified artifacts across the boundary.
- Apply environment-level secrets, approval gates, and branch restrictions.
- Ensure deployment credentials can deploy only the intended site/environment and cannot administer unrelated infrastructure.
- Log deployments and retain artifact hashes and provenance.
- Prevent CI logs and artifacts from exposing secrets.
Prevent secret leakage
Enable secret scanning and push protection. GitHub push protection can block recognized secrets before they enter a repository, although coverage depends on supported patterns and large or complex pushes may have detection limitations. Add custom patterns for institution-specific credentials where available.[^50][^51]
Also:
- Keep real secrets outside the repository and out of all
VITE_*variables.[^39] - Maintain a documented list of browser-public configuration values versus server-only secrets.
- Rotate any credential that was ever committed; deleting it from the latest revision is insufficient.
- Limit every API key by origin, operation, quota, service, and environment where the provider supports those controls.
- Use a backend or backend-for-frontend for operations requiring secrets.
- Scan the final bundle for high-entropy values and known secret patterns.
Reduce browser-readable secrets
- Do not store session tokens, refresh tokens, credentials, or sensitive PII in Web Storage.[^26]
- Prefer server-managed sessions in cookies with
HttpOnly,Secure, and an appropriateSameSitesetting.[^52][^27] - Add CSRF defenses to state-changing endpoints; OWASP recommends framework protection or validated tokens and appropriate SameSite controls.[^53]
- Keep access tokens short-lived and, where architecture permits, in memory rather than persistent browser storage.
- Clear sensitive client state on logout and consider
Clear-Site-Datafor appropriate cache, cookie, and storage cleanup.[^26] - Return only the fields the current view requires and avoid embedding excessive user records in initial page state.
Content Security Policy
CSP is a defense-in-depth control that lets the server restrict resource and connection destinations and helps mitigate injected scripts. For a Vite SPA, develop the tightest policy compatible with the application, beginning in Content-Security-Policy-Report-Only, collecting reports, then enforcing it.[^54][^55]
A conceptual baseline is:
Content-Security-Policy:
default-src 'self';
script-src 'self';
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
connect-src 'self' https://api.example.edu;
img-src 'self' data: https://approved-image-host.example;
font-src 'self';
frame-src https://approved-embed.example;
The actual policy must reflect the application’s real hosts. script-src restricts JavaScript sources, while connect-src covers fetch, XHR, WebSocket, EventSource, and sendBeacon destinations. Avoid unsafe-inline and unsafe-eval; strict nonce- or hash-based policies are preferred where the serving architecture supports them.[^56][^55][^57][^43]
CSP has limits. If a trusted first-party bundle itself becomes malicious, script-src 'self' still permits it. A permissive analytics or API domain in connect-src may also provide an allowed exfiltration path. CSP reduces options and can stop unexpected destinations, but it does not replace trusted builds, review, least privilege, or runtime monitoring.
Additional browser controls
- Set a restrictive
Permissions-Policythat disables camera, microphone, geolocation, payment, USB, and other unused features. - Use
frame-ancestorsin CSP to prevent unauthorized framing; use sandboxed cross-origin iframes for untrusted widgets. - Use Subresource Integrity for stable externally hosted scripts and styles where practical.[^1]
- Serve all content over HTTPS and enable appropriate HSTS on controlled production domains.
- Avoid production source maps being public unless their benefit is deliberate and access is controlled.
- Inventory all scripts, destinations, permissions, and browser storage keys.
- Minimize third-party JavaScript and monitor it for changes.[^1]
Detection and monitoring
Preventive review can fail, so add detection:
- Record the exact commit and artifact digest for every production deployment.
- Alert on unreviewed production deployment, branch-rule bypass, force push, new deploy key, new GitHub App, workflow permission expansion, or hosting configuration change.
- Monitor production script and security-header hashes from outside the deployment system.
- Baseline network destinations and alert on newly observed domains.
- Collect CSP violation reports at a protected endpoint.
- Periodically crawl critical user journeys and compare loaded scripts, form access, storage behavior, and outbound requests.
- Retain CDN, API gateway, application, authentication, GitHub, and hosting audit logs long enough for investigation.
- Test incident procedures for revoking releases, tokens, service workers, and caches.
For payment pages, PCI SSC specifically emphasizes authorization of scripts, integrity assurance, script inventory, and tamper detection for scripts and security-impacting headers. Even when PCI rules do not apply, these controls provide a useful model for login, student records, health information, administration, and other sensitive pages.[^24]
Incident response
If an attacker may already have pushed a release, treat it as an active security incident rather than only a code bug.
Immediate containment
- Stop or roll back deployment using a trusted artifact; if trust is uncertain, place the application in maintenance mode.
- Remove or suspend the suspected account’s access and block further production changes.
- Preserve the suspect deployment, repository state, logs, workflow runs, artifacts, headers, and service-worker files before destructive cleanup.[^46]
- Revoke exposed or potentially exposed GitHub, cloud, registry, deployment, API, monitoring, and third-party credentials. GitHub identifies credential revocation as the most immediate response to exposed or exploited credentials.[^46]
- Rotate secrets at repository, environment, organization, hosting, and external-service levels.[^46]
- Invalidate affected user sessions if session theft or authenticated abuse is plausible.
- Disable compromised API keys, webhooks, OAuth apps, deploy keys, or GitHub Apps.
- Notify the university supervisor, security team, data-protection contact, and service owners according to institutional policy.
Eradication and recovery
- Identify the first malicious commit and every path by which it reached production.
- Inspect all branches, tags, releases, forks, caches, artifacts, package publications, container images, and deployment environments—not only the default branch.
- Review newly added collaborators, keys, tokens, apps, webhooks, environment variables, secrets, and branch-rule changes.
- Rebuild from a verified good commit in a clean environment with rotated credentials.
- Reinstall dependencies from a reviewed lockfile and pin known-good versions; GitHub recommends pinning dependencies to known-good versions or commit SHAs after an incident.[^46]
- Purge CDN and hosting caches.
- Replace or unregister unexpected service workers and clear compromised Cache Storage through a clean release strategy.
- Verify production from multiple networks and fresh browser profiles before reopening.
- Monitor for renewed changes, unusual API activity, stolen-session use, or data sent to known indicators.
Exposure assessment
Determine:
- The exact time window in which the malicious release was live.
- Which URLs and roles loaded it.
- Whether activation was conditional.
- Which forms, storage keys, cookies, API responses, and permission-gated APIs were accessed.
- Which outbound destinations received data and what their request logs show.
- Whether build credentials were accessed, printed, cached, or transmitted.
- Whether user sessions or accounts were abused after the release.
- Whether the event triggers contractual, university, privacy, breach-notification, or sector-specific obligations.
Do not conclude “no data was stolen” merely because the source was reverted. Establish what the code could access, whether network or API logs corroborate transmission, how complete those logs are, and what uncertainty remains.
Risk ranking
| Scenario | Likely impact | Severity |
|---|---|---|
| Attacker can read a public or non-secret repository but cannot modify or deploy | Source disclosure only; may aid later attacks | Low to moderate |
| Attacker can write a branch but protected production requires independent approval | Malicious proposal, supply-chain attempt, possible CI abuse if PR jobs are unsafe | Moderate to high |
| Attacker can deploy frontend code to a site with no authentication or sensitive forms | Tracking, UI manipulation, phishing, visitor metadata | High |
| Attacker deploys to an authenticated SPA using Web Storage tokens | Token theft, account takeover, API data theft | Critical |
Sessions use HttpOnly cookies but malicious code runs in authenticated pages |
In-session API reading and action abuse; reusable cookie value is better protected | Critical |
| CI executes attacker-controlled code with production secrets | Cloud/deployment takeover, secret theft, broader supply-chain compromise | Critical |
| Sensitive operations are server-authorized, data-minimized, and browser policy is restrictive | Reduced blast radius, but client data and user actions remain exposed | High |
| Malicious service worker was deployed | Possible persistence, request interception, cached malicious content | High to critical |
Severity depends more on application data, authentication, CI privileges, and backend authorization than on whether the visual components come from shadcn/ui or styles come from Tailwind.
Minimum secure baseline
For a university Vite/React project, implement at least the following:
- Protect the production branch; require pull requests, independent approval, and passing checks.[^47]
- Add
CODEOWNERSfor workflows, Vite configuration, package files, authentication, API clients, and deployment code.[^48] - Require MFA and remove unnecessary repository/hosting access.
- Use
npm ciwith a reviewed lockfile in CI.[^13] - Enable Dependabot, secret scanning, push protection, and dependency review.[^58][^50][^15]
- Give CI explicit least-privilege permissions and never expose production secrets to untrusted pull-request code.[^2][^3]
- Keep secrets out of
VITE_*,define, source,public/, and client bundles.[^40][^39] - Store sessions in secure
HttpOnlycookies where feasible, with CSRF protection; do not persist bearer credentials in Web Storage.[^53][^26] - Deploy a restrictive CSP and
Permissions-Policy; tightly limitconnect-srcdestinations.[^57][^43] - Inventory third-party scripts and isolate or integrity-check them where possible.[^1]
- Record deployed commit/artifact hashes and monitor production scripts, headers, and network destinations.
- Maintain a tested rollback, credential-rotation, session-invalidation, cache-purge, and service-worker-removal procedure.
Central conclusion
The core security rule is simple: code deployed to a frontend origin is trusted with nearly everything that the legitimate frontend can see and do. In a Vite/React application, a malicious production commit can steal data typed into that site, JavaScript-readable tokens and storage, DOM and API data available to the current user, and device metadata; it can also alter actions and transactions. Browser isolation, HttpOnly cookies, server-side authorization, permissions, CSP, and data minimization constrain the blast radius, but none makes unauthorized production JavaScript acceptable.
Repository integrity and deployment integrity must therefore be treated as part of user-data security. The strongest program combines protected changes, independent review, least-privilege CI, reproducible builds, secret separation, restrictive browser policies, server-side authorization, third-party script governance, production monitoring, and a rehearsed incident response process.
References
Third Party JavaScript Management Cheat Sheet - The invocation of third-party JS are three basic deployment mechanisms for tags. These mechanisms ca...
Securely using pull_request_target - GitHub Enterprise Cloud Docs - Learn about the security risks of the pull_request_target event.
Secure use reference - GitHub Docs - Avoid using the pull_request_target and workflow_run workflow triggers with untrusted pull requests ...
scripts | npm Docs - How npm handles the "scripts" field
Scripts - npm Docs - How npm handles the "scripts" field
Same-origin policy - Security | MDN - The same-origin policy is a critical security mechanism that restricts how a document or script load...
Same-origin policy - Security - MDN Web Docs - Mozilla - The same-origin policy is a critical security mechanism that restricts how a document or script load...
Dangerously Set innerHTML | React - GitHub Pages - A JavaScript library for building user interfaces
Introducing JSX - React - A JavaScript library for building user interfaces
vite/docs/guide/api-plugin.md at main · vitejs/vite - Next generation frontend tooling. It's fast! Contribute to vitejs/vite development by creating an ac...
npm-ci - npm Docs - Clean install a project
npm-ci - Clean install a project
docs.github.com › dependabot-alertsDependabot alerts - GitHub Docs - Dependabot alerts help you find and fix vulnerable dependencies before they become security risks.
Generating provenance statements - You can verify the provenance attestations of downloaded packages with the following audit command: ...
npm-audit - $ npm audit signatures. The audit signatures command will also verify the provenance attestations of...
Securely using pull_request_target - To help protect your workflows from untrusted pull requests, GitHub provides a default event policy ...
Securely using pull_request_target - GitHub Enterprise Server ... - Learn about the security risks of the pull_request_target event.
ServiceWorkerRegistration - Web APIs | MDN - The ServiceWorkerRegistration interface of the Service Worker API represents the service worker regi...
Service Worker API - MDN Web Docs - Interfaces · Cache. Represents the storage for Request / Response object pairs that are cached as pa...
ServiceWorkerGlobalScope: fetch event - Web APIs | MDN - It enables the service worker to intercept network requests and send customized responses (for examp...
Debug a Progressive Web App (PWA) - Use the Application tool to inspect, modify, and debug web app manifests, service workers, and servi...
Payment Page Security and Preventing E-Skimming - PCI SSC's new 'Payment Page Security and Preventing E-Skimming' guides merchants and service provide...
HTML5 Security Cheat Sheet - Therefore, it's recommended to avoid storing any sensitive information in local storage where authen...
Session Management Cheat Sheet - Unlike HTTP cookies, the contents of localStorage and sessionStorage are not automatically shared wi...
Using HTTP cookies - MDN Web Docs - Mozilla - A cookie with the HttpOnly attribute can't be accessed by JavaScript, for example using Document.coo...
Document: cookie property - Web APIs | MDN - The HTTPOnly cookie attribute can help to mitigate this attack by preventing access to cookie value ...
HttpOnly | OWASP Foundation - HttpOnly on the main website for The OWASP Foundation. OWASP is a nonprofit foundation that works to...
Cross-Origin Resource Sharing (CORS) - HTTP | MDN - Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indic...
blog.mozilla.org › en › firefoxFirefox expands fingerprint protections: advancing towards a ... - With Firefox 145, we’re rolling out major privacy upgrades that take on browser fingerprinting — a p...
Privacy on the web - MDN Web Docs - People use websites for several important tasks such as banking, shopping, entertainment, and paying...
Firefox's protection against fingerprinting - Mozilla Support - Fingerprinting Protection in Firefox protects you from websites that try to identify you based on a ...
MediaDevices: getUserMedia () method - Web APIs | MDN - The getUserMedia() method of the MediaDevices interface prompts the user for permission to use a med...
Geolocation API - MDN Web Docs - The Permissions API geolocation permission can be used to test whether access to use location inform...
Geolocation - Web APIs | MDN - It gives Web content access to the location of the device. This allows a website or app to offer cus...
Clipboard - Web APIs - MDN Web Docs - All the methods require a secure context. Additional requirements for using the API are discussed in...
content/files/en-us/web/api/clipboard_api/index.md at main · mdn/content - The content behind MDN Web Docs. Contribute to mdn/content development by creating an account on Git...
Env Variables and Modes - Next Generation Frontend Tooling
Shared Options - Next Generation Frontend Tooling
Secret scanning - Secret scanning scans your entire Git history on all branches of your repository for hardcoded crede...
Build Options - Next Generation Frontend Tooling
Content-Security-Policy: connect-src directive - MDN Web Docs - The HTTP Content-Security-Policy (CSP) connect-src directive restricts the URLs which can be loaded ...
Fetch metadata - HTTP - MDN Web Docs - Fetch metadata is the term for a group of HTTP request headers that give the server information abou...
content/files/en-us/web/api/fetch_api/using_fetch ... - GitHub - The official source for MDN Web Docs content. Home to over 14,000 pages of documentation about HTML,...
Responding to a security incident - Respond strategically to a security incident affecting organizations or repositories in your GitHub ...
docs.github.com › about-protected-branchesAbout protected branches - GitHub Docs - You can protect important branches by setting branch protection rules, which define whether collabor...
About code owners - You can use a CODEOWNERS file to define individuals or teams that are responsible for code in a repo...
Keeping your account and data secure - GitHub Docs - To protect your personal information, you should keep both your account on GitHub and any associated...
About push protection - Secure your secrets by stopping them from ever reaching your repository with push protection.
Secret scanning detection scope - Secret scanning uses pattern matching and validation to detect secrets. Detection varies based on pa...
Secure cookie configuration - MDN Web Docs - Limit access to cookies as much as possible.
Cross-Site Request Forgery Prevention Cheat Sheet - OWASP - Website with the collection of all the cheat sheets of the project.
Content-Security-Policy (CSP) header - HTTP - MDN Web Docs - The HTTP Content-Security-Policy response header allows website administrators to control resources ...
Content Security Policy (CSP) - HTTP - MDN Web Docs - Content Security Policy (CSP) is a feature that helps to prevent or minimize the risk of certain typ...
Content-Security-Policy: script-src directive - MDN Web Docs - The HTTP Content-Security-Policy (CSP) script-src directive specifies valid sources for JavaScript. ...
Content Security Policy (CSP) implementation - MDN Web Docs - The Content-Security-Policy HTTP header provides fine-grained control over the code that can be load...
Managing your dependency security - Customize and configure features for dependency management.
Top comments (0)