Google's data center and data leaks in React
Someone in Lincoln, Nebraska, put a black box over a document about Google's data center and thought the problem was solved. It wasn't. The water and electricity numbers that were supposed to stay hidden were still readable, and the local press went after them. The story comes from 10/11 NOW's report, which ended with more questions than answers. It sounds like a city hall story. But it's the exact same mistake I see every week in React code. It's the most common data leak in React there is: hiding on screen something that was already delivered to the browser.
What happened in Lincoln
The context is simple. Data centers use a lot of water for cooling and a lot of power. Cities that host these projects want to know how much. Companies would rather not say, because resource usage reveals a lot about capacity and expansion plans.
In the middle of this, a public document came out with parts marked as confidential. But the redaction was done the wrong way. The information was still there, visually covered but recoverable. The result: numbers that were supposed to stay between the city and Google became news.
I won't repeat the figures here. That's not what matters for people who write code. What matters is how the failure works. It has a well-known name in information security: confusing hiding with removing.
If the data reached the client, it belongs to the client. The black box is just decoration.
Why this is the same bug in your front end
Think about how a bad redaction works in a PDF. There's a text layer, and there's a rectangle drawn on top of it. Anyone looking at the page sees black. Anyone who selects, copies, or opens the file in an editor sees the whole text.
Now think about a React component like this:
function UserCard({ user }: { user: User }) {
return (
<div>
<h2>{user.name}</h2>
{user.role === 'admin' && <p>Tax ID: {user.taxId}</p>}
</div>
)
}
A regular user doesn't see the tax ID. Mission accomplished? No. If the user object came whole from the API, the tax ID is in the response JSON. It's in the Network tab. It's in React DevTools, in the component props. The && is the black rectangle drawn on top of the text.
The same goes for:
display: noneorhiddenon something that shouldn't be in the HTML at all- filtering a list on the front end that the API returned in full
- "hiding" an admin button instead of blocking the route on the server
- a cost price field that comes in the payload and just isn't rendered
I once found an e-commerce dashboard where the product listing returned profit margin and supplier name to any visitor. The screen showed only name and price. The JSON showed the whole business. Nobody did anything malicious. Just a SELECT * and a res.json(products).
Server Components made this sneakier
With React Server Components and the Next.js App Router, a lot of people relaxed. "The component runs on the server, so it's safe." Partly. The problem lives at the boundary between server and client.
When a Server Component passes props to a Client Component, those props are serialized and sent in the RSC payload. Everything you pass goes along. Look at this case:
// page.tsx (Server Component)
export default async function Page() {
const user = await db.user.findUnique({ where: { id } })
return <ProfileForm user={user} />
}
// ProfileForm.tsx
'use client'
export function ProfileForm({ user }) {
return <input defaultValue={user.name} />
}
The form only uses name. But the whole user object, with passwordHash, stripeCustomerId and whatever else is in the table, ended up in the page HTML. Open the page source and search for self.__next_f.push. It's there, in plain text.
This is the kind of leak no visual test catches. The screen is perfect. The snapshot passes. QA approves. And the password hash is in the HTML.
How to avoid data leaks in React in practice
The rule is boring and it works: the server decides what goes out, not the component. In practice, that turns into a few habits.
1. Build explicit DTOs. Never pass the database record straight to the client. Pick the fields.
const user = await db.user.findUnique({
where: { id },
select: { id: true, name: true, avatarUrl: true },
})
With Prisma, Drizzle or hand-written SQL, the principle is the same: explicit select. If someone adds a document column to the table tomorrow, it won't leak on its own.
2. Isolate code that must only run on the server. The server-only package breaks the build if a data access module is imported into a Client Component.
// lib/data/users.ts
import 'server-only'
export async function getPublicProfile(id: string) {
// ...
}
It's one line. It costs nothing. And it turns a silent leak into a build error.
3. Know the taint APIs. React has experimental_taintObjectReference and experimental_taintUniqueValue, which mark an object or value as forbidden from crossing to the client. If someone tries to pass it as a prop, it throws. They're still experimental, so I treat them as a safety net, not the main strategy. The DTO is still the first line of defense.
4. Authorization on the server, always. Hiding the delete button is UX. Blocking the DELETE in the Server Action or the route is security. You need both, but only one of them protects anything.
The five-minute test I run on every project
You don't need expensive tools. Open the app logged in as the user with the lowest permissions and do this:
Ctrl+Uon the page and search for words likepassword,hash,taxId,token,secret,cost.- Network tab, filter by Fetch/XHR, open each response and read the whole JSON, not just what shows on screen.
- React DevTools, click the main components and look at the props.
On legacy projects, this test almost always finds something. The last time I ran it on a system I inherited, I found the email of every other user in an organization coming along in a comment listing. The screen showed only the first name. It's the Lincoln black box, JavaScript edition.
You can automate part of this in a simple test:
const html = await fetch('http://localhost:3000/profile').then(r => r.text())
for (const forbidden of ['passwordHash', 'stripeCustomerId']) {
if (html.includes(forbidden)) throw new Error(`Leaked: ${forbidden}`)
}
It's not pretty. It catches the obvious. And the obvious is what leaks the most.
What the Google story teaches people who write code
The Lincoln case has a political side, about big tech transparency and the use of public resources. That debate is legitimate. Honestly, I think data center water usage should be public by default. A city that gives up water and power has the right to know how much.
But the technical side is what interests me here, and it's universal. Someone had sensitive data, wanted to protect it, and applied the protection at the presentation layer. It worked visually. It failed for real.
In React, this temptation is constant because the mental model is visual. You think in components, in screens, in what shows up. Conditional rendering looks like access control. It isn't. A Server Component looks like a vault. It's a vault with a window called props.
My take: most of the leaks I see in React apps don't come from sophisticated attacks. They come from SELECT * plus JSON.stringify. It's laziness at the boundary, not a lack of encryption. And the fix isn't sophisticated either: pick your fields, mark modules as server-only, and look at the payload now and then with the eyes of someone who wants to find something.
If the city had deleted the text instead of painting over it, there would be no story. If your select had three fields instead of twenty, there wouldn't be one either. If you want to see how I organize this boundary between server and client in real projects, take a look at my projects.
LinkedIn summary
Someone drew a black box over a document about Google's data center and thought the numbers were hidden. They weren't.
In Lincoln, the water and power usage data was still readable under the redaction, and the local press went after it.
I see this same mistake every week in React: `{isAdmin && user.taxId}` hides the data on screen, but the JSON already sent everything to the browser.
With Server Components it gets even sneakier. You pass the whole `user` as a prop and the password hash ends up in the page HTML.
If the data reached the client, it belongs to the client. Hiding is not removing.
My rule: the server decides what goes out. Explicit `select`, `server-only`, and every now and then a Ctrl+U with the eyes of someone looking for trouble.
I wrote the full step by step on the blog, with the five-minute test I run on every project. Link in the comments.
#React #NextJS #CyberSecurity #WebDevelopment #JavaScript