Skip to content

Commit 80a50b3

Browse files
authored
Fix __proto__ vulnerability (#91)
1 parent f1457ee commit 80a50b3

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class VectorTileFeature {
1616
// Public
1717

1818
/** @type {Record<string, number | string | boolean>} */
19-
this.properties = {};
19+
this.properties = Object.create(null);
2020

2121
this.extent = extent;
2222
/** @type {0 | 1 | 2 | 3} */
@@ -371,7 +371,7 @@ export class VectorTile {
371371
*/
372372
constructor(pbf, end) {
373373
/** @type {Record<string, VectorTileLayer>} */
374-
this.layers = pbf.readFields(readTile, {}, end);
374+
this.layers = pbf.readFields(readTile, Object.create(null), end);
375375
}
376376
}
377377

test/parse.test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,46 @@ test('https://github.com/mapbox/vector-tile-js/issues/60', () => {
205205
}
206206
}
207207
});
208+
209+
test('does not mutate prototypes via a "__proto__" layer name or property key', () => {
210+
// Hand-build a minimal MVT tile containing a layer named "__proto__"
211+
// with one feature whose properties include a "__proto__" key.
212+
const pbf = new Protobuf();
213+
214+
pbf.writeMessage(3, (_, p) => { // Tile.layers
215+
p.writeVarintField(15, 2); // version
216+
p.writeStringField(1, '__proto__'); // name
217+
p.writeMessage(2, (_, p) => { // feature
218+
p.writeVarintField(1, 1); // id
219+
p.writePackedVarint(2, [0, 0]); // tags: key[0] -> value[0]
220+
p.writeVarintField(3, 1); // type = POINT
221+
p.writePackedVarint(4, [9, 0, 0]); // geometry: moveTo(0,0)
222+
}, null);
223+
p.writeStringField(3, '__proto__'); // keys[0]
224+
p.writeMessage(4, (_, p) => { // values[0]
225+
p.writeStringField(1, 'evil');
226+
}, null);
227+
p.writeVarintField(5, 4096); // extent
228+
}, null);
229+
230+
const tile = new VectorTile(new Protobuf(pbf.finish()));
231+
232+
// tile.layers must keep a null prototype — i.e. its prototype wasn't
233+
// hijacked by `layers["__proto__"] = layer`.
234+
assert.equal(Object.getPrototypeOf(tile.layers), null);
235+
236+
// The "__proto__" layer is still reachable as an own property.
237+
// eslint-disable-next-line no-proto, dot-notation
238+
const layer = tile.layers['__proto__'];
239+
assert.ok(layer instanceof VectorTileLayer);
240+
assert.equal(layer.name, '__proto__');
241+
242+
// Feature.properties must also keep a null prototype.
243+
const feature = layer.feature(0);
244+
assert.equal(Object.getPrototypeOf(feature.properties), null);
245+
// eslint-disable-next-line no-proto, dot-notation
246+
assert.equal(feature.properties['__proto__'], 'evil');
247+
248+
// Sanity: a fresh {} elsewhere is untouched (no global pollution either).
249+
assert.equal(({}).evil, undefined);
250+
});

0 commit comments

Comments
 (0)