Triage And Disclosure

A bug bounty report says your obfuscated JavaScript was reversed

The report is usually short, slightly triumphant, and attaches a beautified copy of your bundle. Somewhere in it is a sentence saying the protection was bypassed. Teams tend to respond in one of two unhelpful ways: paying out because the attachment looks alarming, or dismissing it because the technique is not a vulnerability and missing the genuine finding buried three paragraphs down. There is a better first move, and most of it is work you do before any report arrives.

The technique is not the finding

Start from what protection is applied to. The bundle was always going to be published. It is delivered to every visitor, cached on their disk, and readable by anyone who opens the network panel. A protection step raises the cost of reading and modifying it. It does not introduce a security boundary, because there was never a boundary there to introduce.

So a researcher who beautifies the output and recovers something readable has returned themselves to the position every visitor already occupies. That is not a privilege escalation and there is no boundary crossed. Reported on its own, with nothing found inside, it is not a vulnerability.

The failure mode is treating that conclusion as the end of the triage. It is the beginning. The valuable question is what the researcher saw once the code was readable, and reports of this kind frequently contain a real finding stated almost in passing, because the submitter believed the reversal was the headline.

Read the report for contents, not for technique

Work through the submission looking for anything that is a defect regardless of how readable the code was. In practice the list is short and consistent.

  • Credentials with real privilege. A key that can read records or spend money is an exposed-credential incident, not a bounty curiosity.
  • An endpoint with no server-side authorisation, discovered by reading the client that calls it. The defect is on the server; the bundle was only the map.
  • A decision of consequence made only in the client - an entitlement, a price, an eligibility or feature gate with no server-side equivalent.
  • Personal data in the bundle, including test fixtures and seeded records that were never meant to ship.
  • A published source map, which hands over original source and file structure directly.

Every item on that list is a defect that exists whether or not anybody obfuscated anything, which is precisely why each is worth paying for. Equally, none of them is fixed by changing a protection setting, which is why the remediation belongs on your server or in your build pipeline.

What to say when the report is about dynamic evaluation

A common variant reports that the protected bundle uses dynamic evaluation, sometimes framed as a content security policy violation. Before responding, find out which part of your configuration is responsible, because the answer differs and the detail is widely misunderstood.

Two transforms in this engine emit dynamic evaluation into your own code. Separately, three runtime wrappers - the self-defending, self-healing and anti-monkey-patching wrappers - evaluate code of their own, and the engine emits an explicit warning saying so and advising you to test your deployed policy before enabling them. Identify which of the two groups produced what the researcher saw, then answer with the specifics.

There is a further detail worth knowing, because it changes the answer entirely for a lot of modern builds. Those three runtime wrappers are skipped when the source is an ES module, so that import and export linking remains valid. If your build emits modules, wrappers you believed were active were never applied, and your build warnings have been saying so. Read them before you reply to the report, and then read them again before your next release, because a protection measure you describe internally as enabled and which is silently skipped is a worse problem than the one the researcher submitted.

Write the scope paragraph before you need it

Almost all of the friction here is caused by a policy that never addressed the question. Add one short paragraph alongside your other exclusions.

State that client-side code is published by design, that recovering readable form from delivered JavaScript is not by itself treated as a vulnerability, and that reports are assessed on what the code reveals or permits. Then be explicit about what you do want: credentials with live privilege, endpoints accepting requests without server-side authorisation, consequential decisions made only in the client, personal data in a bundle, and exposed source maps.

That paragraph does two jobs. It sets the expectation before submission rather than arguing it afterwards, and it tells a competent researcher exactly where to look, which tends to improve the quality of what you receive.

Make it easy to reach you

Publish a file at the well-known security location naming a contact address, a link to the policy and a preferred language. It takes minutes and it removes the most common cause of a bad disclosure experience, which is a researcher who found something genuine, could not tell whether anyone was listening, and escalated somewhere public instead.

Keep the policy it points to short, current and specific about scope. A policy that is long, stale or vague produces the same outcome as no policy at all, with more reading involved.

Pay for the finding, not the technique - and say which

When a report contains both a reversal narrative and a genuine defect, pay for the defect and explain the split in the response. Something like: the recovery of readable code is out of scope for the reason stated in the policy, and the hardcoded key found in the process is in scope, rated on the privilege it carries, and here is the award.

That response is fair, it is consistent with a policy the researcher can read, and it teaches the next submitter what you value. Dismissing the whole report because the framing was wrong loses you a finding you needed. Paying for the framing teaches everyone to send you beautified bundles.

Close the loop properly

Three actions, in order. Rotate anything credential-shaped that appeared in the bundle, and fix the build step that put it there, treating the exposure window as the whole time the bundle has been deployed rather than the time since the report.

Then take the list of behaviours the researcher inferred from your code and check each for a server-side equivalent. This is the step teams skip, and it is where the real value of the report usually sits: the researcher has just handed you an inventory of what your client believes it is allowed to decide. Any item on that list without a server-side check is a finding whether or not they noticed it.

Finally, update the policy so the next report starts from your stated scope, and if you enable options that emit dynamic evaluation, publish that fact and the reason. Repeated reports about the same thing are a documentation defect rather than a triage workload.

The short version

Recovering readable code from a published bundle is not a vulnerability, and saying so is easier when your policy said it first. Triage the contents rather than the technique, because reports of this shape often carry a genuine finding stated in passing. Answer questions about dynamic evaluation with the specific option responsible, and check your build warnings first in case the wrappers you assumed were active were skipped for module source. Pay for the defect, explain the split, publish a contact file, and check every client-side decision the researcher enumerated for a server-side equivalent.

Frequently asked questions

Is deobfuscating our bundle a valid bug bounty finding on its own?

No, and your policy should say so before a report arrives rather than after. Protection is applied to code that was always going to be published, so recovering a readable form of it returns the researcher to the position every visitor already occupies. There is no security boundary between a minified bundle and a beautified one, because both were delivered to the client. What can be a valid finding is something the researcher found while reading it. Triage the contents of the report, not the technique that produced it, and reward on that basis.

What should the scope section of our policy actually say?

One short paragraph placed with your other exclusions. State that client-side code is published by design, that recovering readable form from delivered JavaScript is not by itself considered a vulnerability, and that reports are assessed on what the code reveals or permits. Then say explicitly what you do want: credentials or keys with live privilege, endpoints that accept requests without server-side authorisation, decisions of consequence made only in the client, and personal data present in a bundle. That paragraph converts an argument that happens after submission into an expectation set before it.

A researcher found an API key in our bundle. Is that a finding?

Almost always yes, and the severity comes from what the key can do rather than from where it was found. Work out the privilege first: a publishable identifier meant to appear in a page is a different matter from a key that can read customer records or spend money. If it carries real privilege, treat it as an exposed credential incident and not merely a bounty report, because the bundle has been publicly retrievable for as long as it has been deployed and you should assume the exposure window is the whole of that time. Rotate first, then triage, then fix the pipeline that let it be bundled.

The report says our protected bundle uses eval. What do we tell them?

Check which option is responsible before answering, because the honest reply depends on it and the detail is often misunderstood in both directions. Two transforms in this engine emit dynamic evaluation into your own code, and three runtime wrappers evaluate code of their own. The engine warns about the second group explicitly, naming the self-defending, self-healing and anti-monkey-patching wrappers as requiring a policy that permits dynamic evaluation. Identify the actual source, then answer with it. If the presence of dynamic evaluation is genuinely unacceptable for your policy, both groups are optional and can be switched off, and the correct response is to change the configuration rather than to argue the point.

Could the option we think is responsible not even be applied?

Yes, and this catches teams out regularly. Those three runtime wrappers are skipped when the source is an ES module, so that import and export linking stays valid. If your build produces modules, the wrappers you believed were protecting the bundle were never emitted, and the build warnings say so. That has two consequences worth acting on: the dynamic evaluation in your output is coming from somewhere else, and a protection measure you have been describing internally as active is not. Read the build warnings before answering the report, and again before your next release.

What findings in a client bundle are genuinely worth paying for?

The ones where reading the code let the researcher do something, rather than merely understand something. Live credentials with real privilege. An endpoint that accepts a request without checking authorisation on the server, discovered by reading the client that calls it. A decision of consequence made only in the client, such as an entitlement, a price or an eligibility check with no server-side equivalent. Personal data embedded in the bundle. A published source map exposing original source. Each of those is a defect that exists independently of how readable the code was, which is exactly why it deserves the reward.

Should we publish a security.txt file?

Yes, and it is the cheapest item on this list. A file at the well-known location naming a contact address, a policy link and a preferred language means a researcher who finds something has an obvious route that is not your support queue or a social media post. Keep the policy it points at short and current, including the scope paragraph above. Most of the friction in disclosure comes from a researcher being unsure whether anyone is listening, and a two-line file removes it.

How should we handle repeated reports about the same thing?

Fix the cause once and publish the answer, rather than triaging the same submission repeatedly. If several researchers have reported that your bundle can be beautified, that is a signal your policy is not saying what you think it says, and the fix is in the policy rather than in the responses. Add the exclusion, state the reasoning in one sentence, and link it from the report form. If the repeated report is about dynamic evaluation, publish which options you enable and why, and the volume drops.

Does any of this mean protection was not worth applying?

No, it means the benefit should be stated accurately. Protection raises the cost of reading and casually modifying delivered code, which is a real effect with real value: it slows down bulk copying, makes lifted functions harder to reuse, and removes the shortcut of searching for a recognisable name. What it does not do is create a boundary where none existed, so a researcher willing to spend the effort still reaches the contents. Both statements are true at once, and a policy written as though only the first is true will keep producing arguments you cannot win.

What should we change after closing a report like this?

Three things, in order. Rotate anything credential-shaped that appeared in the bundle and fix the build step that included it. Then take the list of behaviours the researcher inferred and check each one for a server-side equivalent, because the ones without a server-side check are the actual findings whether or not the researcher noticed. Finally, update the policy so the next report starts from your stated scope. The first is an incident, the second is engineering, and the third is what stops the same conversation recurring.

Related reading