In April 2026 a support ticket became the way into Canvas, the learning platform used by roughly 275 million students at 8,809 institutions. Our founder published a full analysis of the breach, and this page is the short version of why it happened the way it did.
The mechanism is worth understanding even if you have never touched an education platform, because the architecture behind it is extremely common.
On 22 April a user of the free tier filed a support ticket containing, in Instructure's own phrasing, “a linked file with hidden code”. On 25 April a customer service representative opened it, and the payload executed inside their authenticated session. Between 28 and 30 April that session was used to call Canvas APIs and pull data out.
Instructure noticed unusual API activity on 29 April and revoked the session on the 30th — four days after the payload fired. On 7 May a second stored cross-site scripting flaw was found in the discussions feature, this time granting administrative access, which was used to push malicious CSS to the login portals of around 300 schools through the legitimate custom-themes feature. Canvas went offline that evening, in the middle of finals.
The reported scope is usernames, email addresses, course names, enrolment information and in-product messages. The actor is ShinyHunters, who claimed 3.65 TB of data and, according to reporting, sought a ransom of around $10 million — both figures are the attacker's and reporting's rather than Instructure's. Instructure has said an agreement was reached, which is not the same as saying the data was destroyed.
The vulnerability alone would have been an incident report. What made it a breach was the combination underneath it:
First, untrusted input from unauthenticated free-tier accounts — anyone could file a ticket. Second, that input rendered in privileged internal tooling carrying production session cookies. Third, sessions with cross-tenant API reach spanning all 8,809 institutions.
Remove any one of those and the story ends early. That is also the checklist worth running against your own estate, because most organisations have all three and have never drawn the line between them.
The exact injection route has not been confirmed by Instructure. The analysis sets out the plausible ones — an HTML or SVG attachment rendered without sandboxing, a linked URL whose contents were previewed, a document passed through Canvas's viewer — and deliberately does not pick between them.
What is worth taking from it is the shape. The payload arrived as a file, not as inline markup in the ticket body. There is precedent: in 2023, Roundcube failed to sanitise SVG files carried inside HTML messages, so a crafted SVG became JavaScript in the reader's session. Two unrelated products, both sanitising the markup they expected, both missing the file attached to it.
Stage one, the support ticket: the script would not have executed. It carries no nonce on any of the routes above, so the representative's session is never stolen.
Stage two, the discussions flaw: it would not have executed for the same reason, which also removes the privilege escalation that followed.
Stage three, the defacement of school login portals: not blocked, and it is worth conceding that plainly. The CSS was uploaded through legitimate admin tooling and served from the application's own origin, which a style-src directive has no reason to refuse. Any inline script rendering a ransom message would have hit script-src, but the CSS defacement itself is not a CSP win.
A nonce-based policy is what breaks this chain. With script-src 'self' 'nonce-{random}' and no 'unsafe-inline', an injected script carries no nonce, so the browser refuses to run it — and the session it was reaching for is never taken. It does not matter which field the payload arrived in or which bug let it through.
And then the part this page exists for. Had Canvas been running that policy in report-only on the help-desk interface and sending the reports somewhere, the injection would have announced itself on day one rather than being inferred from API traffic on day four. An inline script with no nonce is the loudest and simplest signal a browser can give you.
The policy is the mitigation and the reporting is the detection, and it is the second half almost nobody switches on. Run CSP in report-only across the internal tools where untrusted content gets rendered, and an injected inline script announces itself the first time it fires — from the real session it fired in, with the page it fired on.
One line of code or config to deploy, and nothing is blocked while you learn what the policy would have caught. Reach out to sales@report-uri.com for a demo or a trial with no commitment.
One header. No code. Your script inventory starts populating from the first visitor.
30-day free trial · One header · No code · Cancel anytime
Script Watch will monitor all JavaScript dependencies across your entire site and immediately notify you of any changes. A new JavaScript dependency could be the start of a Magecart attack.
Because Script Watch leverages the browser native Content Security Policy, there is no code or agent to deploy and running in the browser means we analyse your site in real-time as your users are browsing. We don't have the same limitations as external scanning services such as authentication or pay walls, geo-sensitive content or an attacker potentially serving safe content to the crawler.
Data Watch will monitor all of the locations that your webpages are sending data to. If your website starts sending data to a new location, it could be the start of a Magecart attack.
With Script Watch and Data Watch combined, you can monitor for clear indicators that your site has been compromised. Attackers will always want to inject their hostile JavaScript, and they'll always want to exfiltrate their stolen data.
We often find that creating a CSP is the first difficult step that organisations face. Having a complete list of all resource dependencies across your entire site like images, scripts or styles, from both 1st-party and 3rd-party locations, is tough to achieve.
The CSP Wizard was created to solve this problem, and in seven days or less, it can give you a complete list of all resources used across your entire site.
With the list of all resources you use on your site, and our easy-to-use tool, creating a viable Content Security Policy is easier than ever with just a few clicks.
All Content Security Policies will need to be tweaked at some point. New resources may be added to the site or old resources removed, and the policy needs to be updated to reflect those changes and kept up to date.
You can import your existing policy into the CSP Builder and use our fully featured tool to make any changes that you require right there in the UI. When you're done, hit Generate, and the CSP Builder will provide you with your new, updated policy.
Script Watch and Data Watch will allow you to rapidly detect and respond to a Magecart attack and combined, that capability puts you ahead of the field. If you want to take it a step further, Content Security Policy can mitigate a Magecart attack and stop it from even happening.
Deploying an effective Content Security Policy can be difficult, but our CSP Reporting allows you to gather feedback and safely test a policy before deployment. Once deployed, an effective Content Security Policy will block a Magecart attack and stop the hostile JavaScript from even running.
We subscribe to various feeds of Threat Intelligence data, along with managing our own internally generated feeds, to keep apprised of the latest threats that exist online.
Using this Threat Intelligence Data, we can better analyse the sources of JavaScript on your website and detect malicious activity sooner.