Security Review
Published
It arrives as one line in a forty-page report, or as a question from a customer’s security team two days before a contract closes: the application serves heavily obfuscated JavaScript. There is rarely an exploit attached, and often no severity that survives scrutiny — but there is a deadline, and somebody has to answer it. The good news is that the answer is short, provided you first work out which of three findings you actually received.
Three findings wearing the same words
1. An informational observation. The assessor noticed and wrote it down. No claim of impact, no recommendation beyond "be aware". This is most of them. It closes with a paragraph.
2. "We could not assess this code." The real complaint is scope: the tester was paid to review your client-side logic and could not read it. This is a legitimate professional problem and it has an easy fix that nobody thinks to offer — hand them the pre-protection bundle.
3. Reliance on security through obscurity. Usually cited as CWE-656. This is the only one of the three that can be a genuine defect, and it is not about the obfuscation at all. It says something in your client code is load-bearing: a key, an entitlement decision, an authorisation check that holds only because the bundle is hard to read. If that is true, it was true before you obfuscated, and protection is what drew attention to it.
Read which one you have before writing a word of response. Answering an informational note with an engineering change wastes a sprint; answering a real CWE-656 with a paragraph about provenance gets the finding reissued with a higher severity.
Why scanners flag it at all
Automated tooling is not being unreasonable. Obfuscation is genuinely common in malicious JavaScript, so "unreadable script served to browsers" is a signal worth surfacing — the same heuristic that produces antivirus false positives on legitimate protected files. Static analysis tools have a second, more concrete reason: they cannot produce findings for code they cannot model, so they report the blind spot instead of silently covering nothing.
Neither of those is a claim that your application is less secure. Obfuscation applies to code you had already decided to publish. It removes no control, adds no privilege, and changes no trust boundary — which is exactly the sentence your response should contain.
The evidence packet
Reviewers close findings on provenance and reviewability. Assemble these once, keep them current, and the second review costs you an email:
- Where protection sits in the build. One diagram or five lines of pipeline showing that it is the last step, applied to an artifact already destined for public distribution.
- A signed release attestation. An Ed25519 envelope over the build id, the fingerprint and a SHA-256 for every output file, verifiable against your published public key. This answers "is the file served the file you built?" without a conversation.
- A source-map statement. Confirmation that no maps are published, ideally enforced in CI rather than asserted —
--verify-manifest with --audit-source-maps fails the build if a .map or a sourceMappingURL comment escapes.
- A release label. The artifact ties back to a specific build record, so a file found in production can be traced to the commit that produced it.
- An offer of the readable bundle. Under the agreement already in place. This single sentence closes finding type 2 more often than anything else in the list.
Most of that already exists if you kept your manifests and reports; the security evidence hub covers the source-free reporting paths, and the CLI can emit several of these packets directly from a saved report rather than from source.
The findings you should fix
Some obfuscation-adjacent findings are real, and they are worth separating out so your response does not read as blanket denial.
A secret in the bundle. If the assessor extracted an API key, a signing secret or a database credential from your protected JavaScript, the finding stands at full severity. Protection slowed the extraction down and changed nothing about the outcome — you cannot hide a key in JavaScript, and the remediation is rotation plus a server-side proxy, not stronger settings.
A client-side authorisation decision. If bypassing a check in the browser actually grants access, that is the CWE-656 case done properly. Fix it server-side. Keep the client check for user experience, and keep the obfuscation, but the authority has to move.
An unsafe-eval requirement. A Content-Security-Policy scanner flagging eval in your bundle is a finding you can simply delete. Only self compression and the eval variants of code reordering emit eval-based output; every other transform produces ordinary JavaScript that runs under a strict policy. Turn those two off and the finding disappears — obfuscation and CSP covers the interaction in full.
An integrity gap. If your scripts are served without Subresource Integrity from a third-party origin, that is worth fixing on its own merits, and it is a common companion finding.
Writing the response
Four short paragraphs, in this order, cover almost every informational variant:
- Acknowledge the observation as accurate. The code is obfuscated; you did it on purpose. Never open with a dispute.
- State the business control. Protecting proprietary client logic from copying and reimplementation — a commercial control, not a security control, and saying so plainly is what makes the rest credible.
- State what did not change. No secrets in the client, authorisation server-side, no trust boundary moved, no privilege added.
- Offer the evidence and the readable bundle. Attach the attestation and the source-map statement; offer the pre-protection artifact for assessment.
If a customer questionnaire asks the flat question — do you obfuscate client-side code, and why? — that same structure fits in a text box. Vendors who answer it crisply pass; vendors who look evasive get a follow-up call.
Do it before the auditor does
The cheapest version of this conversation is the one you start. Ship a SECURITY.txt that names a contact. Keep signed attestations for every release. Enforce the no-source-maps rule in CI rather than in a wiki page. Note in your architecture documentation that protection is a distribution control rather than an access control — the single sentence that keeps a future reviewer from constructing a CWE-656 argument out of silence.
Teams shipping into customer-controlled environments feel this hardest, because someone else’s security team scans the delivery on arrival: protecting a self-hosted or on-premises app covers the delivery-side evidence in detail. And if the underlying question is really whether protection survives an expert reading it at all, that is a different and more interesting conversation — is JavaScript obfuscation reversible?
Frequently asked questions
Is obfuscated JavaScript a security vulnerability?
No. Obfuscation is a transform applied to code you already intended to publish, and it removes no control that was there before. What is a real finding is relying on obscurity instead of a control - a secret, an entitlement decision or an authorisation check that only works because nobody read the bundle. That is the distinction a good report draws and a scanner cannot.
What does CWE-656 actually say?
CWE-656 is Reliance on Security Through Obscurity. It describes a design that depends on an attacker not knowing how the system works. Applying it correctly requires showing that something in your client code is load-bearing for security. If the same request is rejected by the server when the client check is bypassed, the finding does not hold - and demonstrating that is usually a five-minute exercise with a proxy.
What evidence closes an obfuscation finding?
A short packet: a statement that protection is the final build step over code already destined for publication, a signed release attestation with per-file hashes, confirmation that no source maps are published, the release label linking the artifact to a build record, and an offer to share the pre-protection bundle with the assessor under the existing agreement. Reviewers close findings on provenance and reviewability, not on aesthetics.
Why does the scanner complain about eval in my protected bundle?
Because a small number of options emit eval-based output, and a Content-Security-Policy scanner is right to notice. Only self compression and the eval variants of code reordering produce it. Turn those off and the output runs under a strict policy with no unsafe-eval, which removes the finding rather than arguing with it.
Should I stop obfuscating to pass the review?
Almost never, and a reviewer who insists usually has an unstated concern worth surfacing - inability to assess the code, or suspicion about provenance. Both are answerable. Removing protection to satisfy a scanner heuristic trades a real business control for a cosmetic clean report.
Related reading