Merge pull request #1271 from bilawalsidhu/codex/fix-share-style-keys

fix(share): reject inherited style keys during restore
This commit is contained in:
Said Turksever authored and GitHub committed 2026-10-22 23:30:16 +01:00
commit 62c319b575
7 files changed
+50 -8

No files matched your search

+1
View File
@@ -0,0 +1 @@
- Reject inherited style keys during restore (asturksever, #1271).
+4 -6
View File
@@ -9,12 +9,10 @@ broader suffix variants. Follow-ups retain boundary provenance, and cancelled
lookups preserve the previous analyst cohort and boundary source. Navigation
search keeps a successful geocode, with no search or view-based region recovery.
Global OpenSky and adsb.lol military snapshot refreshes are single-flight through
body reading and cache publication. Each caller retains its own response and
viewport-specific regional fallback. OpenSky 5xx responses cancel their bodies,
start a 15-second cooldown and serve dated last-good data with the upstream status
in the stale reason. Cold failures use the existing regional fallback or a JSON
upstream error; no error HTML is relayed as JSON.
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
+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
@@ -1538,6 +1538,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);
}
});