Skip to content

Commit 04c4c5c

Browse files
sbpublicclaude
andcommitted
fix(frontend): use parsed Zod output and pin id/chainId in customEvmNetworksStore
Two defense-in-depth changes on the store's write paths: - add and update now build the stored entry from parsed.data rather than the raw input/merged object. Zod strips unknown keys by default, so parsed.data is the canonical schema shape; the prior code left any caller-supplied extra properties in the in-memory state. - update explicitly pins id and chainId to the existing entry's values before validation. TypeScript already excludes these from CustomEvmNetworkPatch, but an `as any` caller could previously sneak them in; the schema would not catch an id that no longer corresponds to its chainId. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 51400d4 commit 04c4c5c

File tree

2 files changed

+34
-3
lines changed

2 files changed

+34
-3
lines changed

src/frontend/src/eth/stores/custom-evm-networks.store.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export const initCustomEvmNetworksStore = (): CustomEvmNetworksStore => {
109109
if (!parsed.success) {
110110
throw new Error(`Invalid custom EVM network: ${parsed.error.message}`);
111111
}
112-
const entry: CustomEvmNetwork = { ...input, id: toNetworkId(input.chainId) };
112+
const entry: CustomEvmNetwork = { ...parsed.data, id: toNetworkId(parsed.data.chainId) };
113113
const next: CustomEvmNetwork[] = [...current, entry];
114114
persist(next);
115115
return next;
@@ -121,13 +121,19 @@ export const initCustomEvmNetworksStore = (): CustomEvmNetworksStore => {
121121
if (index === -1) {
122122
throw new Error(`No custom EVM network with chainId ${chainId} exists; cannot update.`);
123123
}
124-
const merged: CustomEvmNetwork = { ...current[index], ...patch };
124+
const existing = current[index];
125+
const merged: CustomEvmNetwork = {
126+
...existing,
127+
...patch,
128+
id: existing.id,
129+
chainId: existing.chainId
130+
};
125131
const parsed = CustomEvmNetworkSchema.safeParse(merged);
126132
if (!parsed.success) {
127133
throw new Error(`Invalid custom EVM network update: ${parsed.error.message}`);
128134
}
129135
const next = [...current];
130-
next[index] = merged;
136+
next[index] = parsed.data;
131137
persist(next);
132138
return next;
133139
});

src/frontend/src/tests/eth/stores/custom-evm-networks.store.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,14 @@ describe('custom-evm-networks.store', () => {
190190
expect(get(store)).toEqual([]);
191191
});
192192

193+
it('strips unknown caller-supplied properties', () => {
194+
const store = initCustomEvmNetworksStore();
195+
196+
store.add({ ...optimism, junk: 'ignored' } as CustomEvmNetworkInput);
197+
198+
expect(get(store)[0]).not.toHaveProperty('junk');
199+
});
200+
193201
it('supports multiple distinct chains', () => {
194202
const store = initCustomEvmNetworksStore();
195203

@@ -224,6 +232,23 @@ describe('custom-evm-networks.store', () => {
224232

225233
expect(() => store.update({ chainId: 999n, patch: { name: 'x' } })).toThrow(/cannot update/);
226234
});
235+
236+
it('preserves id and chainId even if a caller tries to override them', () => {
237+
const store = initCustomEvmNetworksStore();
238+
store.add(optimism);
239+
const originalId = get(store)[0].id;
240+
241+
store.update({
242+
chainId: 10n,
243+
patch: { id: Symbol('hacked'), chainId: 99n, name: 'renamed' } as never
244+
});
245+
246+
const [network] = get(store);
247+
248+
expect(network.id).toBe(originalId);
249+
expect(network.chainId).toBe(10n);
250+
expect(network.name).toBe('renamed');
251+
});
227252
});
228253

229254
describe('remove', () => {

0 commit comments

Comments
 (0)