Let’s start with a scenario. You’re building a SaaS product: the backend is connected to a cloud database, the frontend is Next.js, and iteration is moving fast. Before launch, you ask the security team to run a black-box test. On the third day, the report comes back with two high-severity findings:
- C1: On the homepage, “View Source” reveals that the JSON inside
__NEXT_DATA__contains the full backend configuration that should have existed only on the server side—database address, message queue endpoint, internal service addresses, and signing keys. - C2: The frontend build artifacts contain key material related to secp256k1. Someone then starts checking on-chain balances.
Let’s pause here and pay attention to the wording in C2: “key material.” A hex string that looks like a private key does not mean it actually is one—it could be a public key, a demo value, or a long-revoked test key. But these two findings point to the same, more fundamental problem: developers have not realized that there must be a deliberately designed boundary between “data known to the server” and “data obtainable by the browser.” This article aims to explain three things clearly: where that boundary is; why it can disappear silently; and why, in the age of AI-generated code, it is disappearing faster and faster.
(Note: The opening scenario blends and rewrites details from test reports and does not refer to any specific product. This article only discusses principles, risks, and defensive design, and does not provide attack steps against real systems.)
1. What Can the Browser Actually See?
Let’s start with the baseline that is easiest to forget: the person using the browser is not your coworker. The browser runs on the attacker’s own device, and they have all the time, tools, and motivation they need to inspect every byte your app sends over.
Things they can do include: opening DevTools to inspect every request and response in the Network panel; fetching HTML and JSON directly from the command line; reading localStorage, sessionStorage, IndexedDB, and Cookie; formatting minified JavaScript; and automatically deobfuscating obfuscated code—open-source projects like webcrack have already turned “restoring obfuscated code” into a mature workflow, and engineering blogs from security vendors are full of complete deobfuscation playbooks. MITRE’s weakness database lists “relying on obfuscation or hiding as a security measure” separately as CWE-656 precisely because it is so common.
So there is only one rule: once data reaches the browser, it is no longer a server secret. The frontend is a public place, not a safe. As you’ll see later, almost every leak comes from ignoring this sentence.
2. What Exactly Is Next.js’s NEXT_DATA?
__NEXT_DATA__ is not a vulnerability; it is a data channel by design. In Next.js’s Pages Router architecture, every server-rendered page’s HTML contains a block like this:
| |
This JSON serializes the props returned to the page by getServerSideProps, getStaticProps, and getInitialProps, and puts them into the DOM along with the route, query, buildId, and various rendering flags (older versions also included publicRuntimeConfig, which was removed in Next 16). Why does it exist? To ensure hydration consistency: after the server prerenders the visible HTML, React in the browser must recompute the same component tree from the same props, otherwise the page will flicker or mismatch—and a JSON script embedded in the DOM is the most natural carrier for those props. This is the mechanical reason framework maintainers have explained in the official repository discussion (#15117); it is not a mistake.
The problem is the raw material that goes into it. The official getServerSideProps documentation states it plainly: props passed to the page component “will be visible on the client as part of the initial HTML,” and explicitly says not to put any data in props that should not be sent to the client. But that warning has about as much binding force as an umbrella stand at a restaurant entrance: too many people grab what is convenient. A common pattern is to spread an entire database record or an entire configuration object directly into props:
| |
As a result, the same secret can appear in three copies: text baked into the visible HTML, the __NEXT_DATA__ JSON block, and the /_next/data/<buildId>/<path>.json endpoint that returns JSON during client-side navigation (the official docs confirm that client-side navigation uses this API request to fetch props again). If the page is statically generated (SSG), the props are also baked into static files and distributed outward by the CDN with a cache lifetime of up to one year (s-maxage=31536000, per the official CDN caching docs)—one build-time mistake can sit on the CDN for a full year.

In the App Router era, the carrier changed, but the boundary problem did not. The Pages-style __NEXT_DATA__ has been replaced by the RSC (React Server Components) Payload: rendering output is streamed into the page in the React Flight serialization format (implemented as inline scripts that look like self.__next_f.push(...), which is an implementation detail), while client-side navigation reuses ordinary URLs with a ?_rsc= parameter. The key point is: anything passed as props to a Client Component must be serialized across the network. The official “Data Security” guide gives a negative example directly, with a comment saying “EXPOSED: This exposes all the fields in userData to the client,” and classifies it as an anti-pattern on the same level as the previous generation. The same applies to Server Actions: the official docs state that “exported actions are public endpoints” and can be called directly via POST. Although Next 16 added encrypted secure action IDs, the official Data Security guide still warns: do not treat closure encryption as a way to hide sensitive values from the client. CVE-2025-29927 in 2025 (middleware bypass) is a reminder of the other side of the issue: even the framework’s own boundary mechanisms can be bypassed, so authorization must live inside every endpoint, not as a single check in middleware.
The framework also provides convenient defensive tools: import 'server-only' causes the build to fail outright if that module is imported in any client environment. Officially recommended techniques also include wrapping data in a class before passing it along—because functions and class instances cannot be serialized by Flight, effectively using the type system as the final fence. As for the Taint API (taintObjectReference/taintUniqueValue), as of Next 16.3.4 in August 2026, the official documentation still marks it as experimental and warns not to treat the Taint API as the only line of defense for preventing sensitive data from being sent to the client—it can be a fallback, but not the foundation.
3. Why Does Configuration “Accidentally” Leak to the Frontend?
C1 in the black-box report is not an outlier. It is usually caused by one of four paths:
1. Spreading the entire config package into props. For convenience, teams stuff config, runtimeConfig, or settings wholesale into the page component, thinking, “I’ll just pick the fields the frontend needs later.” But serialization happens on the entire package—whether the frontend picks a field or not, the data has already gone out.
2. The intuition trap of environment variable prefixes. Next.js’s rule is: variables with the NEXT_PUBLIC_ prefix are inlined at build time into the JS bundle sent to the browser (under the hood, this is plain-text replacement via webpack’s DefinePlugin); variables without the prefix are only available on the Node server side, and referencing them in client modules results in an empty string. Here is the counterintuitive part: the “PUBLIC” in NEXT_PUBLIC_ is precisely the switch that makes the value public, not a switch for “publicly using a variable.” Vite’s VITE_ and CRA’s REACT_APP_ follow the same logic, and their official docs all discourage careless use: Vite’s wording suggests that “to prevent accidentally leaking env variables to the client, don’t use this prefix,” while CRA’s warning is even more direct—“do not store any secrets in your React app; environment variables are embedded into the build, meaning anyone can view them by inspecting your app’s files.”
Two knock-on effects are also easy to overlook: inlining happens at build time, so values are frozen from that point on—changing environment variables after deployment does not affect the production bundle; if, after a leak, you only change the value in Secret Manager without doing a full rebuild and clearing the CDN cache, the old value will continue to be served in production.
3. Passing entire database rows to Client Component. In the App Router, a Server Component queries the database and then throws the entire userData object to a Client Component—this is the most common anti-pattern explicitly called out in the official data security guide, with the comment marked EXPOSED.
4. Not thinking through the version chain of environment variables. .env has a priority chain (existing values in the shell > .env.$(NODE_ENV).local > .env.local > .env.$(NODE_ENV) > .env), and in many teams, no one can answer which layer a given variable actually comes from or whether it will be baked into the container image.
Notice the common pathology behind the first three: they are all the shortest path to “just make the code run.” This will become important in Part VI.
4. From an API Key to a secp256k1 Private Key
Classifying what was leaked makes the severity of C2 much easier to explain. Two official industry classifications are worth copying directly:
Stripe’s official position: a publishable key starting with pk_ is marked in the documentation as “Safe to expose: Yes”—it can only identify the account and exchange payment information for a token, and “cannot perform sensitive operations such as creating charges or reading account data”; a secret key starting with sk_ is marked “Safe to expose: No” and carries full privileges. Firebase’s official documentation uses the same two-tier framing: assuming API restrictions are properly applied, “API keys limited to Firebase services do not need to be treated as secrets”; real access control is handled by Security Rules and App Check. In one sentence, this classification is: publicly exposable material (public keys, addresses, publishable keys, frontend configuration) serves only an “identification” function; secret material (private keys, secret keys, seed phrases) serves an “authorization” function. Once something with an authorization function crosses the browser boundary, its nature changes.
secp256k1 is an elliptic curve in the SEC 2 standard from the SECG standards organization—the curve parameters themselves are public. It became famous because Bitcoin chose it during implementation for ECDSA (Elliptic Curve Digital Signature Algorithm) transaction signatures (the white paper only describes the use of digital signatures; the curve was chosen during the 2009 implementation and was settled with the standard in 2010), and Ethereum uses the same curve. Its most important property is one-way scalar multiplication: a public key can be easily derived from a private key, but going in reverse is cryptographically infeasible—this is exactly the logic used in the ethereum.org documentation. An Ethereum address is “the last 20 bytes of the Keccak-256 hash of the public key, plus 0x”. One level deeper: the only legitimate scenario where a user should hold a private key is when that key belongs to the user themselves—for example, in a non-custodial wallet like MetaMask, where the seed phrase remains only in an encrypted vault on the user’s device and the service provider never touches it. But “a website operator putting its own signing private key into its own frontend” is a different species entirely: it is equivalent to printing sk_ at the front door and handing it to every visitor.
“If what was exposed really is a private key,” its location determines the shape of the consequences:
- Bundled into a JS bundle: supply-chain incidents have already demonstrated what happens when keys end up in a bundle—the npm package event-stream was taken over in 2018, and malicious code was injected specifically to steal the private keys of the Copay wallet that integrated it; in December 2024, two fake versions of
@solana/web3.jsreplaced functions with code that stole private keys and exfiltrated them. Every line in your bundle can potentially be read by any link in the dependency chain. - localStorage / IndexedDB: persistent storage in the browser is the first target for XSS and malware. The AMOS malware, first seen in 2023, explicitly listed wallet extensions such as MetaMask and Phantom as theft targets—if legitimate wallets are targeted, homemade storage has even less chance of escaping.
- Frontend configuration / injected scripts: in 2021, BadgerDAO lost about $120 million after an attacker obtained its Cloudflare key and injected scripts into the frontend to trick users into authorizing transactions (Microsoft named this type of attack ice phishing); in 2023, the MyAlgo web wallet was injected with malicious JS, causing about $9.2 million in losses. Once frontend configuration is compromised, the right side of the equals sign is the user’s entire authorization.
- API Response / server-side telemetry: in 2022, the Slope wallet sent users’ seed phrases into its own Sentry, and 9,231 addresses were drained for about $4.1 million; in the same year, 3Commas leaked about 100,000 users’ exchange API keys on the platform side. Once keys enter responses or logging pipelines, there is no taking them back.
- Source Map: on March 31, 2026, Anthropic’s published Claude Code npm package accidentally included
cli.js.map, withsourcesContentembedding about 510,000 lines of complete proprietary source code, allowing anyone to reconstruct it with one click—source maps directly reduce the “unreadability” provided by minification to zero, and any constants hidden inside are exposed along with it. - As a boundary reference: the extreme consequence of a service provider’s signing private key being compromised was answered by the Harmony Horizon cross-chain bridge in 2022—validator private keys were breached, and about $100 million in assets were drained. A service provider’s private key needs no frontend involvement at all; it is already a complete landmine by itself.
Now back to the wording discipline for C2: the secp256k1 material seen in a black-box report “may involve” a real private key; “if what was exposed is a private key,” all of the consequences above apply. But it could just as well be a public key, a test vector, or a long-revoked demo value—this “requires further verification” (the verification direction is: whether it can be used to derive a public key and address corresponding to live assets, and what its source location and lifecycle are). The truly defensible conclusion is not “a specific private key was leaked here,” but “this application’s architecture does not perform any key classification, causing key-shaped material to appear somewhere it never should have appeared.” Obfuscation does not provide protection—minification, string encryption, and homemade “client-side encryption” all amount to handing the lock and the key to the adversary together (a variant of CWE-656); any code that can run in DevTools is code the adversary can analyze.
5. The Secret-Exposure Surface of Modern Web Apps: An Eight-Layer Map
Putting the scattered points together, the secret-exposure surface of modern web applications has roughly eight layers. Each layer is backed by an official definition or a public incident.
Layer 1: Frontend Bundle. Hardcoded keys appear in plaintext in files that every visitor can download; NEXT_PUBLIC_/VITE_/REACT_APP_ prefixes turn environment variables into plaintext literals at build time; the AKIA prefix for long-lived AWS keys, the -----BEGIN prefix for private keys, and the sk_ prefix are all stable signatures that machines can scan for. The open-source tooling on the detection side is already mature: gitleaks / trufflehog scan repositories and build artifacts, and semgrep has ready-made hardcoded-jwt-secret rules. Note that the scan target must be the build output; scanning only source code will miss inlined results.
Layer 2: Environment Variables. Prefix semantics (public or not), build-time freezing, and stale values lingering after rotation were covered in Part Three. One additional point: filenames like .env.local may be called “local,” but what they describe may not be “for local debugging” so much as “a risk of falling into git.”
Layer 3: API Overexposure. OWASP API Top 10 2019 defines Excessive Data Exposure as an API that “by design returns sensitive data to the client, usually filtered only on the client side before rendering.” The first official defense is to “Never rely on the client side to filter sensitive data.” The official example is extremely on point: a monitoring API returns the full list of cameras and the live_access_token for each camera—“the security guard’s UI only shows authorized buildings,” but the API returns everything. The 2023 version folded this item into API3 Broken Object Property Level Authorization (combining Excessive Data Exposure and Mass Assignment), shifting the focus from symptom to root cause: missing authorization at the object-property level. Another related category, BOLA (Broken Object Level Authorization, API1; the widely known IDOR, “Insecure Direct Object Reference,” is a typical form of it), is about being able to read someone else’s object by changing an ID. A real-world example of this class of issue is Venmo: starting in 2018, researchers used its public API to download roughly 208 million transaction records for all of 2017 and built a site to display them. The lesson is exactly this: the “privacy settings” in the app UI are client-side filtering; the data-layer API was still public by default. ASVS 5.0 (released in May 2025) states the baseline as an L2 requirement in its original wording: secrets “must not be included in application source code or included in build artifacts.”
Layer 4: Source Map. The ECMA-426 standard defines the sourcesContent field in .map files—when it exists, the complete original source code is embedded in the public map file; the sources array also often leaks absolute paths on the server or even developer machine usernames. Three dangerous misunderstandings: Vite’s hidden mode only removes the reference comment; the map file is still generated as usual. After Next.js enables productionBrowserSourceMaps, the official documentation explicitly says these files will be served publicly. “Uploaded to Sentry but not publicly referenced” does not mean safe—the wording of Vercel’s official Conformance rule NEXTJS_NO_PRODUCTION_SOURCE_MAPS says that enabling production source maps is equivalent to “publicly sharing your application source code,” and the official recommended workflow is to upload them to an error-tracking platform and then clear and redeploy. The real cost is documented: in a 2025 case from Sentry’s security team, an attacker reconstructed an undocumented password-change endpoint from a public map and used it to complete an account takeover; another case from the same year involved a researcher recovering a hardcoded Stripe secret key from a production map.
Layer 5: Browser Storage. localStorage / sessionStorage / IndexedDB / Cookie. A simple decision tree: any sensitive value (token, key) that needs to be read by JavaScript will share the fate of XSS no matter where it is stored; session credentials should use httpOnly Cookie; the category of “secrets” simply does not belong in browser storage. Wallet extensions also need to protect data with an encrypted vault—the storage location itself does not provide any isolation.
Layer 6: Git. History is forever. GitHub’s official “removing sensitive data” documentation takes a clear-eyed stance: the moment something enters a repository, it should be considered compromised by default, and the first step is always to revoke/rotate the key. Deleting history is only remediation—forks, coworkers’ clones, and cached views may still exist. The public incident chain is complete: in 2016, Uber had AWS keys on GitHub used to download an S3 backup repository, affecting 57 million users; it ultimately reached a $148 million settlement, and the then-CSO was convicted for covering it up. In December 2017, Toyota T-Connect pushed source code containing access keys to GitHub and did not discover it until September 2022, putting the information of 296,000 customers at risk of exposure. The scale numbers come from GitGuardian’s 2026 report: from 2024 to 2025, public GitHub saw 28.65 million newly leaked secrets, up 34% year over year, with AI service keys growing 81%; of leaked credentials validated in 2022, 64% were still valid when retested in early 2026—most people simply never rotate after a leak.
Layer 7: CI/CD. GitHub’s secret scanning scans full history, covers public repositories for free, and push protection blocks pushes before they land. The part that truly does not provide a safety net is CI log masking: the official documentation states that structured values containing whitespace (JSON/XML/YAML) “significantly reduce the probability of being masked correctly”—and CI logs themselves often print environment variable values. The CI supply chain is a higher-dimensional threat: in March 2025, tj-actions/changed-files, used by more than 23,000 repositories, had its tag altered to inject a backdoor that wrote secrets from runner memory into logs, prompting CISA to issue an alert (CVE-2025-30066); in 2021, Codecov’s Bash Uploader was tampered with, exfiltrating customer CI environment variables for months.
Layer 8: Docker and Cloud. Docker’s official documentation explicitly warns not to use ARG to pass secrets—build arguments remain in the final image metadata, provenance, and image history; the correct approach is BuildKit secret mounts. Empirical data: a study covering 337,171 Docker Hub images and 8,076 private registry images found that 8.5% of images overall contained secrets, totaling 52,107 private keys (9.0% for Docker Hub alone); the starting point of the Codecov incident was precisely that attackers dug GCS credentials out of the history layers of a public self-hosted image. The cloud side is isomorphic: the public S3 bucket leak wave of 2017 (Accenture, among others); Terraform officially acknowledges that state is a plaintext file and that sensitive values are still written into it; AWS officially recommends putting sensitive credentials in Secrets Manager rather than Lambda environment variables.

The bad news about this map is that it closes into a loop: credentials leaked from any one layer can be used to attack the other seven—for example, in 2021, BadgerDAO was compromised when a leaked Cloudflare key was used to inject a frontend script and phish away roughly $120 million. One layer falls, seven layers come under attack.
6. Why AI Programming Makes the Problem Worse
This is the part this article is really trying to discuss. The core point can be summed up in one sentence: In the age of AI Agents and AI Coding, the biggest security risk is not that attackers are getting smarter, but that developers are creating and deploying software faster than they can understand its security boundaries.
The evidence comes in three layers.
First, code quality itself. A user study published by Stanford and other institutions at ACM CCS 2023 (Perry et al.) was the first large-scale empirical study: participants using AI assistants wrote significantly less secure code, while also being more likely to believe that what they wrote was secure—overconfidence is an amplifier for insecure code. A control study (USENIX Security 2023’s Lost at C) reached the opposite, more moderate conclusion in the context of writing C, showing that this is not destiny, but a condition-dependent risk. Still, vendors’ ongoing testing shows that the risk is widespread: Veracode’s 2025 GenAI report found that in Java/Python/C#/JavaScript tests generated by multiple models, 45% of samples failed security tests and introduced OWASP Top 10-level vulnerabilities, with Java’s failure rate as high as 72%. Even more troublesome is the perception gap: in Snyk’s 2024 survey, more than 75% of developers believed AI-written code was more secure than human-written code, while 56% admitted that AI-generated code sometimes or often introduces security issues; only about 10% of developers scan most AI-generated code. GitClear’s analysis of 211 million lines of changes shows a structural decline: the share of cloned/copy-pasted code rose from 8.3% in 2021 to 12.3% in 2024, and for the first time in history, the number of “copy-pasted” lines exceeded the number of “refactor-moved” lines. Copy-paste debt does not encrypt keys, but it makes auditing “where keys may have appeared” nearly impossible.
Second, data specific to secrets. GitGuardian’s 2026 report provides a directly relevant new data point: commits assisted by Claude Code had a secret leakage rate of 3.2%, more than twice the 1.5% baseline across all public commits. The mechanism is easy to infer (the following behavioral patterns are common observations in the security community, though there are not yet public cases mapping to each one individually): AI’s objective function is “make the code run,” while the leak happens in the future and appears on someone else’s cost sheet, so there is no pain signal at generation time; one common pattern in training data is “put the key somewhere convenient for debugging”; and when an Agent has read permissions, files like .env are right within reach. The 2025 Hacker News post about a “vibe coding app losing $300 due to an API key leak,” along with security vendors’ documented cases of projects being forced to shut down and rewrite, are individual-scale samples of this mechanism. The term vibe coding itself was coined by Karpathy in February 2025 (“fully give in to the vibes, embrace exponentials, and forget that the code even exists,” paraphrased), and criticism from the security community followed quickly. ReversingLabs put it most sharply: the volume of AI-generated code has already exceeded humans’ capacity to review it, while vibe developers do not read code or write tests, letting vulnerabilities and malicious packages go straight into production.
Third, the Agent itself. Once you hand filesystem, Shell, Git, cloud, and network permissions to an Agent that can act autonomously, the exposure surface escalates from “the code is poorly written” to “the system can be roamed.” At the standards level, OWASP LLM Top 10 (2025) includes LLM05 (Improper Output Handling) and LLM06 (Excessive Agency), both of which directly hit this category. OWASP released Agentic AI – Threats and Mitigations in February 2025, and then released the 2026 Top 10 for Agentic Applications in December 2025 (the release specifically called out three items: Agent Behavior Hijacking, Tool Misuse and Exploitation, and Identity and Privilege Abuse). At the toolchain level, MCP’s official specification itself states that it uses an “implicit trust” model and requires clients to treat MCP servers as untrusted input sources; Invariant Labs’ April 2025 real-world test is a footnote to this path—a malicious MCP tool description guided a Cursor agent into reading ~/.ssh/id_rsa. Code Agents themselves can also be compromised: Claude Code successively fixed DNS-based data exfiltration (CVE-2025-55284) and RCE/secret theft caused by malicious repository configuration (CVE-2025-59536, CVSS score 8.7).

The positive answer in terms of permission models is actually something everyone is already working on: Claude Code’s official security documentation is structured around Manual mode being read-only by default, dangerous commands being reviewed by a classifier, filesystem + network sandboxing, bounded working directories, and repeatedly emphasizing one sentence—"Claude Code only has the permissions you give it"; Copilot’s agent mode is embedded in the workspace trust system; Replit has even launched “secure vibe coding,” scanning for and blocking secrets during AI generation. The lever for disaster prevention is not in the model, but in the hand that grants permissions.
Back to the opening word, “convenient.” AI does not necessarily hack into your system—it may simply help you complete a task through the fastest path, and the fastest path often looks like this: stuffing server-side configuration into pageProps so the page throws one less error; adding a NEXT_PUBLIC_ prefix to a key to keep the build from failing; temporarily disabling authentication to get the flow running, then committing that disabled authentication to production along with everything else. It has no malicious intent. It simply does not know where the boundary is—and knowing where the boundary lies was supposed to be the responsibility of “the person writing the code.” When the speed of writing code exceeds the speed of understanding boundaries, the interest on security debt turns from “the occasional incident” into “structural baseline leakage.”
7. A Truly Secure Secret Management Architecture
The right shape and the wrong shape are each a chain.
The wrong chain (all the previous cases fall into this chain):
| |
The right chain:
| |
There are six structural requirements in the right chain, and each one has official sources behind it:
- Secret Manager: the keystore requirements in ASVS (Application Security Verification Standard) 4.0.3 6.4.1 and 5.0 13.3.1 (corresponding content, unofficial mapping); the respective best practices for AWS Secrets Manager, GCP Secret Manager, and Azure Key Vault (least privilege, version references, audit logs) all point to the same thing: the creation, storage, access, and destruction of secrets must have a vehicle independent of the application.
- Environment isolation: a separate vault for each environment and the smallest possible blast radius (the idea from Azure’s official best practices); production credentials must never enter the development environment or an Agent’s working context.
- Least Privilege: grant the minimum permissions per service, and whenever possible use rotatable short-lived credentials and workload identity instead of long-lived keys. Stripe’s newer guidance around
rk_Restricted Key represents the direction of the industry: remove “unlimited-permission keys” from the default options. - Server-side signing: keep all signing, encryption, and private-key-holding operations on the server side (BFF, the backend for the frontend / API routes); the browser gets a session (httpOnly Cookie) or a one-time user authorization, not material that can sign long-term. A secp256k1 private key should never get a travel budget to the frontend.
- Key Rotation: rotation is a first-class citizen, not a post-incident remedy. The sequence in AWS’s official blog on handling leaks is: first assess the credential’s reachable scope, then immediately deactivate it (disable rather than delete, because disabling is recoverable); GitHub’s official stance is likewise that once a secret is leaked, revoke/rotate takes priority over cleaning history—once a key is invalidated, the ciphertext in history is harmless.
- Secret Scanning up front: push protection + gitleaks pre-commit + full CI scanning + scanning of build artifacts (bundles, images, map files) form four gates. Add response minimization on top: APIs should return DTOs (data transfer objects) containing only necessary fields rather than whole rows (OWASP EDE’s “never rely on client-side filtering”), with schema validation for responses.
An acceptance sentence that can hold up within a team: “Data the server knows” and “data the browser can obtain” must be two deliberately designed sets; any data crossing that line must have a written reason and type. A crossing without a reason is C1; a crossing without a type is C2.
8. Security Checklist for Indie Developers
Each item in this checklist corresponds to a layer discussed above, ordered by “stop the bleeding first, then harden”; all checks below are for your own applications and assets—running them against any target you do not own or are not authorized to test crosses the line:
- □ Check Git history: scan the full history with
gitleaks detect --allor trufflehog; if there’s a hit, revoke/rotate first, then consider cleaning the history - □ Check public repositories: make sure the repository is not public; enable GitHub push protection and secret scanning alerts
- □ Check build artifacts: scan the dist/build directory for patterns like
NEXT_PUBLIC_,VITE_,REACT_APP_,AKIA,-----BEGIN,sk_, etc. - □ Check page source: view-source the homepage and inspect the
__NEXT_DATA__JSON block and RSC payload for anything that should not be there - □ Check /_next/data and API responses: are endpoints returning entire objects? Are there more fields than the page needs?
- □ Check Source Map: make sure source maps are disabled in production; if they have been uploaded to Sentry, confirm
.mapfiles have been removed from the deployed artifacts - □ Check browser storage: look for keys, tokens, and ciphertext in localStorage/sessionStorage; sessions should use httpOnly cookies
- □ Check CI/CD logs: look for secrets printed in the logs; make sure output from sensitive steps is masked
- □ Check Docker images: inspect
docker historyand image layers in public registries; make sure build arguments use BuildKit secret mounts - □ Check the environment variable chain: where does the final value of an
Xvariable come from—which.envlayer can answer that? - □ Check the gate for AI-generated code: scan on generation (put semgrep/gitleaks in CI), review on generation, and clearly define secret boundaries in the project rules file
- □ Check Agent permissions: grant the Agent’s shell, network, and cloud credentials with the least necessary access; run sensitive projects in a sandbox/container
Conclusion
If this piece had to be compressed into one sentence: the first lesson in frontend security is not “prevent injection,” but first figure out which bytes will be sent to the browser. In the past, that lesson was enforced through code review. Today, production-scale code comes from a generator that does not care whether this process is safe—not as a reason to reject AI, but as a reason to move the security gate from “when code is being written” to “before code enters production.”
Review does not have to be as fast as generation, but at the very least, it cannot be absent.
References
- Official Next.js documentation: getServerSideProps, Environment Variables, Server and Client Components, Data Security Guide, CDN Caching, productionBrowserSourceMaps, Taint API
- Official React documentation: Server Functions / experimental_taintObjectReference; ECMA-426 Source Maps specification (tc39.es)
- OWASP: API Security Top 10 (2019/2023), Web Top 10 2021, ASVS 4.0.3 and 5.0, WSTG-INFO-05, LLM Top 10 2025, Agentic AI Threats & Mitigations, MCP Security Cheat Sheet
- Official Stripe Keys documentation (including the Restricted Keys rk_ section); official Google Firebase API Keys documentation; ethereum.org Accounts documentation; official Vite / CRA / webpack configuration documentation
- Official GitHub documentation: secret scanning, push protection, Security hardening for GitHub Actions, Removing sensitive data; official blog post from 2024-02-29 (over 1 million leaked secrets in the first eight weeks)
- GitGuardian, State of Secrets Sprawl 2026; Veracode, 2025 GenAI Code Security Report; Snyk, AI Code Security Report 2024; GitClear, AI Copilot Code Quality 2025
- Perry et al., “Do Users Write More Insecure Code with AI Assistants?”, ACM CCS 2023; Dahlmanns et al., 2023 empirical study of secrets on Docker Hub
- Sentry security blog, “Abusing Exposed Sourcemaps” (2025-01); Vercel Conformance NEXTJS_NO_PRODUCTION_SOURCE_MAPS
- Incident reports: Uber 2016 (The Verge), Toyota T-Connect 2022, CircleCI 2023 incident report, Codecov 2021, tj-actions 2025 (CISA), event-stream 2018, @solana/web3.js 2024, Slope 2022 (official Solana report), BadgerDAO 2021, MyAlgo 2023, Harmony 2022, Claude Code npm source-map 2026, Venmo public API (TechCrunch 2019)
- Invariant Labs MCP security series (2025-04); official Claude Code security documentation; Check Point analysis of CVE-2025-59536; Vercel postmortem of CVE-2025-29927
- EmbracetheRed disclosure of CVE-2025-55284; official VS Code blog on Copilot agent mode (2025-02-24); ReversingLabs, Vibe coding: What automating development means for AppSec; Aikido vibe coding security checklist; Hacker News, “Lost $300 due to an API key leak from “vibe coding””; Replit, Safe vibe coding; origin of the term vibe coding by Karpathy (Wikipedia)
