fix(share): reject inherited style keys during restore

This commit is contained in:
Said committed 2026-10-20 00:38:09 +01:00
1 parent de2138e84c
commit d49877ad2e
6 files changed
+50 -2

No files matched your search

+5
View File
@@ -1,5 +1,10 @@
# God's Eye View Current State
Share links and structured views accept only own keys in their style registries.
Inherited names such as `constructor` and `__proto__` fall back to the existing
normal/null defaults without interrupting other restored fields. Programmatic
visual-style calls ignore unknown or non-string values before mutating state.
Shared distance checks use `src/geoDistance.js` with `{ lat, lon }` points and
the IUGG mean spherical Earth radius, 6371.0088 km. `greatCircleKm` is
canonical; `greatCircleMeters` is its exact metre conversion. Missing,
+17
View File
@@ -778,3 +778,20 @@ test('visual input listeners are revoked before asynchronous UI teardown', () =>
assert.match(VisualSettings.prototype.stop.toString(), /this\._styleParameters\?\.destroy\(\)/);
assert.match(VisualSettings.prototype.stop.toString(), /this\._visualEffects\.stop\(\)/);
});
test('share restore rejects inherited styles while continuing later fields', () => {
for (const style of ['constructor', '__proto__', 'toString', 'hasOwnProperty', 'unknown']) {
const restored = makeManager(`#lat=10&lon=20&style=${style}&cr=1`).parseInitialHash();
assert.equal(restored.style, 'normal');
assert.equal(restored.celestialRing, true);
}
});
test('programmatic invalid styles are ignored before changing visual state', () => {
const target = { activeStyle: 'normal' };
for (const value of ['constructor', '__proto__', 'toString', 'hasOwnProperty', 'unknown', {}, null, () => {}]) {
assert.doesNotThrow(() => VisualSettings.prototype.setStyle.call(target, value));
assert.equal(target.activeStyle, 'normal');
}
});
+3 -1
View File
@@ -175,7 +175,9 @@ export class ShareLinkManager {
parseOr(params.get('dd'), 50),
50,
);
const style = URL_TO_STYLE[params.get('style')] || 'normal';
const style = Object.hasOwn(URL_TO_STYLE, params.get('style'))
? URL_TO_STYLE[params.get('style')]
: 'normal';
const decodedLayerState = decodeLayerStateParams(params);
const state = {
lat,
+5
View File
@@ -1539,6 +1539,11 @@ export class VisualSettings {
preserveCyberRestore = false,
} = {},
) {
if (
typeof styleName !== 'string' ||
(styleName !== 'normal' && !Object.hasOwn(STYLES, styleName))
)
return;
const { setDetectionStyle } = this.services;
if (!restore) this.shareLinkManager?.claimRestoreLane?.('visual');
if (this.hud.getVariant() === 'cyber') {
+3 -1
View File
@@ -370,7 +370,9 @@ export function viewFromParams(params) {
pitch_deg: number('pitch'),
},
layers: state?.enabledLayerIds ?? [],
style: STYLES_BY_URL_NAME[params.get('style')] ?? null,
style: Object.hasOwn(STYLES_BY_URL_NAME, params.get('style'))
? STYLES_BY_URL_NAME[params.get('style')]
: null,
map: VIEW_MAPS.includes(map) ? map : null,
follow,
annotations: annotationsFromParams(params),
+17
View File
@@ -126,3 +126,20 @@ test('annotations travel in the link, keeping only what the app draws', async ()
// Too long for a link: the view keeps them, the link leaves them out.
assert.equal(viewToParams(many).has('an'), false);
});
test('inherited and unknown style keys do not corrupt a restored view', () => {
for (const style of [
'constructor',
'__proto__',
'toString',
'hasOwnProperty',
'unknown',
]) {
const view = viewFromParams(
new URLSearchParams({ lat: '10', lon: '20', style, map: 'osm' }),
);
assert.equal(view.style, null);
assert.equal(view.map, 'osm');
assert.equal(view.camera.lat, 10);
}
});