FE-523: Sanitize user-controlled keys in the simulation path - #9222
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
788420c to
3b2f5c9
Compare
PR SummaryHigh Risk Overview Reject reserved names ( Compiled programs now receive New Reviewed by Cursor Bugbot for commit 99c96ef. Bugbot is set up for automated code reviews on this repo. Configure here. |
Reject net identifiers that collide with Object.prototype member names at the file-import and simulation boundaries, build every record keyed by user-authored strings without a prototype, guard artifact and marking reads with own-property lookups, bind compiled-program parameters as frozen prototype-free copies, and give place visualizer code the same sandbox hardening as scenario code.
Fixed-literal sources in the instantiation test (CodeQL improper code sanitization), getOwn accepts an absent record, cloneUserKeyedRecord replaces three hand-rolled prototype-free copies, one success path in parseSDCPNFile, and role lines match what the validation folder holds.
9f7e067 to
99c96ef
Compare
🌟 What is the purpose of this PR?
Closes the
js/remote-property-injectionclass from FE-523: identity strings from imported.petrinautfiles can no longer corrupt the records they key, and user-authored code paths lose their unguardednew Functiongaps.The problem
Every identity string in a net comes from imported
.petrinautfiles, whose schema accepts any string: place, transition, colour, metric and scenario ids, parameter variable names, colour element names. Used as a plain-object key:record["__proto__"] = valuereplaces the record's prototype instead of storing the entry.constructorkey returns an inherited function, which defeats?? fallbackand truthiness guards.Two failures this caused: the HIR compiler dropped a
__proto__-keyed lambda artifact while the fingerprint check still passed, and a token field namedtoStringturned the packed-token encoder's?? 0default intoNaNbytes.The fix: three layers
A single
Object.create(null)at the flagged lines is not enough: a null prototype does not survivestructuredCloneor JSON revival across worker hops, and scenario code-mode keys never pass a schema. The shared vocabulary lives invalidation/record-keys.ts:parseSDCPNFileandbuildSimulation. The editor validators now also rejectconstructor, the one all-lowercaseObject.prototypemember they admitted.createUserKeyedRecord(no prototype) at every site that builds a record from these strings.getOwn(own properties only) wherever records cross serialization.flowchart LR F[".petrinaut file"] --> P["parseSDCPNFile<br/>rejects reserved names"] E["Embedder net"] --> B["buildSimulation<br/>rejects reserved names"] P --> B B --> C["createUserKeyedRecord<br/>records without a prototype"] C --> H["worker hop<br/>structuredClone / JSON"] H --> G["getOwn<br/>own-property reads only"]Also in this PR
new Functionwith no hardening at all, reachable by opening an imported file. It now gets the same sandbox treatment as scenario code, at compile and at each render.__paramsbinding is now a frozen prototype-free copy, so a hostile parameter name cannot readObject.prototypefrom compiled code.🔗 Related links
MutationProvider🚫 Blocked by
Nothing.
🔍 What does this change?
validation/record-keys.ts:DANGEROUS_RECORD_KEYS,createUserKeyedRecord,getOwn, andfindDangerousSdcpnKeys(walks a net's identity strings), exported from the package index.parseSDCPNFile(versioned and legacy formats) andbuildSimulation; the two ad-hocid === "__proto__"throws increateEngineFrameLayoutgeneralise to the shared list.variableNameSchema, colour element names and scenario parameter identifiers reject reserved property names.instantiate.tsbinds__paramsas a frozen prototype-free copy; metric evaluators keep their live rebinding on a prototype-free record.compile-visualizer.tsruns in strict mode withSHADOWED_GLOBALSand wraps module evaluation and each render inrunSandboxed.core.simulation.engine; the user-code and validation pages updated; parameter naming rules stated in the user guide.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
Object.create(null)as a sanitizer; if alerts 18495–18498 resurface, dismiss them pointing atrecord-keys.ts.🐾 Next steps
MutationProviderand surfacing pre-existing invalid names in the Diagnostics tab.🛡 What tests cover this?
validation/record-keys.test.ts(new): the key list, prototype-free construction, own-property reads (including own__proto__keys revived byJSON.parse), the net walk.hir/instantiate.test.ts(new): a parameter namedconstructorreads its own value; missing names readundefined; compiled code cannot mutate the caller's record.parameter-values,parse-sdcpn-fileandbuild-simulationtests;compile-visualizer.test.ts(new) covers shadowed globals, constructor-chain blocking and strict mode.❓ How to test this?
yarn devinlibs/@hashintel/petrinaut)..petrinautfile with a transition id ofconstructor: the import fails naming the id.constructoras a parameter variable name: the properties panel rejects it.export default Visualization(() => <div>{String(typeof fetch)}</div>)and view the place: it rendersundefined.📹 Demo
Error paths and internal containment; covered by the tests above.