Course / 09 Web security / Lesson 25 of 28

Prototype pollution

Estimated time: 18 min reading · 12 min activity

A JavaScript object stores named properties. It may also refer to a prototype, another object whose properties can be found through inheritance. An own property is stored directly on the object. An inherited property is found through its prototype chain. Reading a value does not tell you which of those locations holds it.

Prototype pollution occurs when untrusted input can modify a prototype used by other objects. A merge operation copies properties, and an unsafe merge can accidentally give input access to that shared structure. Ordinary inheritance itself is not a vulnerability; the failure is an unauthorized write to a shared prototype and a later use that trusts the resulting value.

Before you begin

Estimated time
18 min reading, 12 min activity. Setup and optional practice may take longer.
Where you work
Read the original fictional source, object tables, token claims and policy models in any current desktop or mobile browser. No loader, template engine, provider registration, service or network request is required.
Without a lab
Follow the printed data through each stated rule on paper. Record the decision, its supporting fact and a limit, then compare with the guided rubric. Model reasoning does not establish a live tool result. Reading works without JavaScript. Checking requires JavaScript and a secure context; saving also requires usable storage.

After this lesson you can:

  • Define a prototype as the object another object inherits from.
  • Separate the own key name from the key color on the diagram.
  • Copy only allowlisted keys in the merge rule.

01Example

Use a fresh object to separate property lookup from ownership. These rows are a conceptual table, not console output.

Own properties: size = small
Prototype properties: weight = light
Lookup size → small
Lookup weight → light
weight remains on the prototype after the read.

02Lookup is not ownership

In the fresh worked table, the item owns size and inherits weight. A lookup can return both values even though they have different owners. The provided original object page uses name and color as a separate evidence table. You can classify a property by its stated location without executing that page.

Constrain merge keys to the fields the application expects and reject paths that select prototypes or constructors. For permission-sensitive decisions, avoid trusting inherited defaults and use suitable own-property checks such as Object.hasOwn. These measures fit different parts of the flow; an own-property check alone cannot repair every unsafe merge.

Read a value that lookup finds, and check the ownership table before you treat it as the object's own key.Model. A teaching model drawn by hand. It shows the idea, not real tool output.

Read the full explanation below.

sizeweight

Own properties

size: small

Prototype

weight: light

Lookup follows ownership
Found on the object
size = small

size resolves to small from the own-property store.

Follow the explanation

  1. size resolves to small from the own-property store.
  2. weight resolves to light on the prototype and remains there after reading.
Read every state and value

1. size resolves to small from the own-property store.

Lookup
size
Selected Owner
own
Result Visible
Yes

2. weight resolves to light on the prototype and remains there after reading.

Lookup
weight
Selected Owner
prototype
Result Visible
Yes
A successful lookup does not establish ownership; reading does not move a property onto the object.

03Apply the allowlist

Read the evidence before comparing with the activity rubric. Do not open the HTML file in a browser for the checkpoint.

Object table. Not executed.

item own keys: name
item inherited keys: color
lookup name: lamp
lookup color: red

Use the stated key lists to classify ownership. A lookup does not move a key between those lists.

Merge rule: copy a key only when the key is on the allowlist name and title.
Do not copy a key that selects the shared prototype or the constructor.

Download the object table

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Own and inherited</title>
</head>
<body>
  <script>
    var base = { color: "red" };
    var item = Object.create(base);
    item.name = "lamp";
  </script>
</body>
</html>

Download the unrun object page

  1. Write the own key and the inherited key from the table.
  2. Write the keys the merge rule is allowed to copy.
  3. Check: the merge copies name and title only. color stays on the list the table already gave it. You do not need to run the page.
  4. Stop. Do not add an assignment that selects the shared prototype.

04Optional challenge

Allow about ten minutes. Suppose an object owns enabled=false while its prototype has enabled=true. Predict the ordinary lookup and explain why checking ownership is a different question from reading the value. Rubric: the nearer own property supplies false, and an own-property check confirms where it is stored.

05Fresh cases for the checks

Use these fictional cases for the checks below. They have different inputs from the worked examples. Read them here or make notes; no installation or external service is needed.

Object case A: fresh unexecuted sourceOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

const base = { color: "violet" };
const item = Object.create(base);
item.name = "moss";
No other keys are defined. Ordinary lookup checks an own property before the prototype.
A read does not move a property to the reading object.

Object case B: independent later statementOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

Start from case A, then perform this source-model change:
item.color = "gold";
The statement writes only item’s own color. No prototype write occurs.

Object case C: fresh constrained mergeOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

Allowed copied fields: name and title only.
Submitted keys: name, title, color, a path selecting the prototype, a path selecting a constructor.
Rule: copy only allowlisted fields; reject prototype/constructor paths. No merge executes.

06Explain your evidence

Optional written self-check: explain one decision using the supplied case. No tool run is required.

Check your explanation: derive own and inherited properties from the source; predict own shadowing without changing the prototype; apply the exact key allowlist; distinguish normal inheritance from an unauthorized prototype write.

07Common mistakes

If you call color own because lookup returns red, you skipped the ownership table.

If you copy every key in the merge, you left the allowlist.

If you treat the HTML file as a recorded console result, read it as a prediction. The page was not a live console.

References

  • MDN: Object.hasOwn

    Tests whether an object directly defines a property, independently of the value found by ordinary lookup.

  • PortSwigger: Prototype pollution

    Further explanation of this mechanism and its defenses. The original printed model supplies the required evidence.