Topic 03

Changing Content Safely

JavaScriptSecurity

Finding an element is half the job. The other half is changing it, and the most common change is its text: a greeting with the visitor's name, a count of search results, a message after a form is sent. JavaScript offers two properties for this. One treats the string you give it as plain text. The other treats it as HTML and builds whatever elements it describes. Used on text that came from a visitor or a server, the second one is how an attacker gets their own code into your page.

The difference is like reading a letter aloud versus following the instructions written in it. textContent reads the letter: whatever the words say, they stay words. innerHTML follows the instructions: if the letter says "open the back door", the door gets opened. With letters you wrote yourself, following them is convenient. With letters from strangers, it is the whole problem.

textContent: Text, Exactly as Written

Setting textContent replaces everything inside an element with a single text node holding the string you give it. Characters that look like HTML, angle brackets included, appear on the page as those characters. The browser never reads them as tags.

Open the site's Home page, open the Console, and try it on the main heading.

Console
const heading = document.querySelector("h1");
heading.textContent = "Welcome back";
heading.textContent = "Welcome <em>back</em>";

The first line finds the heading. The second replaces "Your Name" with "Welcome back". The third shows the whole string on the page exactly as typed, angle brackets, the letters e and m, and all. In the Elements panel the heading holds one text node, and no em element exists.

"Everything inside" is meant literally. If the element held other elements, such as a link or a word in bold, setting textContent removes them too and leaves only the new text. It replaces the contents; it does not add to them.

innerHTML: Text Read as HTML

Setting innerHTML hands the string to the browser's HTML parser, the same one that read your file, and puts the resulting nodes inside the element.

Console
heading.innerHTML = "Welcome <em>back</em>";

This time the heading reads "Welcome back", with the word back in italics. The Elements panel shows a text node followed by a real em element. For fixed markup that you wrote into your own code, this is handy: one line produces formatted content.

What Does Injection Look Like?

Now imagine a page that greets visitors by a name they typed into a form, or by a name taken from the page's address. Most people type a name. Someone could type HTML instead: an img element whose picture cannot load, with an onerror attribute. The browser runs the JavaScript in onerror when an image fails to load, which is exactly what this one is built to do.

The first line below stands in for what a visitor typed. It uses backticks because the text contains both kinds of quotation mark. The second line puts it in the heading with innerHTML. The code it carries only writes a harmless message to the Console, so you can watch the mechanism without any risk.

Console
const visitorName = `<img src="missing.png" onerror="console.log('This code came from the name field')">`;
heading.innerHTML = `Hello, ${visitorName}`;
Console output
This code came from the name field

The browser built a real img element, tried to load a picture that does not exist, failed, and ran the code in onerror. The heading now reads "Hello," followed by nothing, or by a small broken-image icon, depending on the browser. The Console shows the message, and it may also show a red line saying missing.png was not found, which is the failed picture request. The code came from the "name", and your page ran it.

A real attacker's code would not write a polite message. It runs with the same powers as your own script: it can read what the visitor types, change links, or send data to another server. When the name is stored and shown to other visitors, as a comment or a profile is, one person's text runs in everyone's browser. This attack is called cross-site scripting, or XSS, and it is one of the most common security flaws on the web. The course Security for Beginners covers the attacker's side.

Now put the same text in with textContent.

Console
heading.textContent = `Hello, ${visitorName}`;

The heading shows "Hello," followed by the whole string, character by character: the angle bracket, img, the missing file name, onerror and the rest. Nothing is logged and no picture is requested. It looks odd, and it is harmless: the visitor sees exactly what they typed. Reload the page to restore the heading.

The same text, two methods
textContent
Shown as text. The visitor sees the characters they typed; nothing runs.
innerHTML
Run as code. The img element is built, fails to load, and its onerror runs.

The Rule

Text from anywhere outside your own code goes into the page with textContent, or as the text of elements you create yourself, which topic 5 shows. "Outside" means a form field, the page's address, stored data, a file or a server's answer. innerHTML is only for fixed markup that you wrote.

The rule is about the method, not about spotting dangerous tags. A script element put in with innerHTML does not run, because browsers skip it on purpose, and that fools people into thinking they are safe. Event attributes such as onerror do run, and there are dozens of them. Filtering out every dangerous piece of text would mean knowing every trick in advance. Never letting outside text be read as HTML needs no such list.

Common Confusions
  • "Only big sites get attacked." Automated tools try common tricks against forms and addresses on every site they can reach. Size does not matter; a page that runs visitors' text is found.
  • "A script tag is the danger." Scripts added through innerHTML do not run, but event attributes such as onerror do. That is why the rule names the method, not a tag.
  • "innerHTML is faster, so it is better." The difference rarely matters on a real page. The risk always does.
Why It Matters
  • This is the one security rule every frontend developer needs on day one, and it costs nothing to follow.
  • The site's contact form in Chapter 10 and its live data in Chapter 11 both put outside text on the page, and both follow this rule.

Knowledge Check

A page shows a comment a visitor typed. Which property should put it on the page?

  • innerHTML, so formatting in the comment works
  • textContent, so the comment stays plain text
  • Either one, as long as the comment is short
  • innerHTML, after removing any script tags

In the injection example, what made the browser run the code hidden in the name?

  • A script element hidden inside the name
  • The Console, which runs every string it prints
  • An onerror attribute on an image that failed
  • The textContent property reading the tag

Why is the rule about the method rather than about blocking dangerous tags?

  • Because no HTML tag is dangerous on its own
  • Because browsers already remove dangerous tags
  • Because checking the tags would make pages slow
  • Because many different attributes can run code

You set an element's textContent to a string containing HTML tags. What appears on the page?

  • The string, with the tags shown as characters
  • Formatted content built from those tags
  • The text with the tags removed from it
  • An error message in the Console instead

You got correct