FE-1162: Add Petrinaut optimization workflow#9040
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
a6599a8 to
d031e87
Compare
0820a43 to
77c2d96
Compare
| const postToHost = (message: IframeToHostMessage) => { | ||
| // The sandboxed iframe has an opaque origin. This still targets only its | ||
| // parent window; the host independently verifies `event.source`. | ||
| window.parent.postMessage(message, "*"); |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
The target origin of the window.postMessage() API is set to "*". This could allow for information disclosure due to the possibility of any origin allowed to receive the message.
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by wildcard-postmessage-configuration.
You can view more details about this finding in the Semgrep AppSec Platform.
| window.addEventListener("message", (event) => { | ||
| if (event.source !== window.parent) { | ||
| return; | ||
| } | ||
|
|
||
| const data = event.data as unknown; | ||
| if ( | ||
| typeof data !== "object" || | ||
| data === null || | ||
| typeof (data as { kind?: unknown }).kind !== "string" | ||
| ) { | ||
| return; | ||
| } | ||
|
|
||
| const message = data as HostToIframeMessage; | ||
| if ( | ||
| message.kind !== "optimizationResponseStart" && | ||
| message.kind !== "optimizationChunk" && | ||
| message.kind !== "optimizationEnd" && | ||
| message.kind !== "optimizationError" | ||
| ) { | ||
| return; | ||
| } | ||
|
|
||
| const pending = pendingRequests.get(message.requestId); | ||
| if (!pending) { | ||
| return; | ||
| } | ||
|
|
||
| switch (message.kind) { | ||
| case "optimizationResponseStart": { | ||
| if (pending.responded) { | ||
| rejectPendingRequest( | ||
| message.requestId, | ||
| new Error("The optimizer sent more than one response header"), | ||
| ); | ||
| return; | ||
| } | ||
| pending.responded = true; | ||
| pending.clearResponseStartTimeout(); | ||
| pending.resolveResponse( | ||
| new Response(pending.stream, { | ||
| headers: { "content-type": "application/x-ndjson" }, | ||
| status: message.status, | ||
| statusText: message.statusText, | ||
| }), | ||
| ); | ||
| break; | ||
| } | ||
| case "optimizationChunk": { | ||
| try { | ||
| pending.controller.enqueue(message.bytes); | ||
| } catch { | ||
| // The consumer may already have cancelled the stream. | ||
| } | ||
| break; | ||
| } | ||
| case "optimizationEnd": { | ||
| if (!pending.responded) { | ||
| rejectPendingRequest( | ||
| message.requestId, | ||
| new Error("The optimizer ended before sending a response"), | ||
| ); | ||
| return; | ||
| } | ||
| try { | ||
| pending.controller.close(); | ||
| } catch { | ||
| // The stream is already settled. | ||
| } | ||
| pending.cleanup(); | ||
| pendingRequests.delete(message.requestId); | ||
| break; | ||
| } | ||
| case "optimizationError": | ||
| rejectPendingRequest(message.requestId, new Error(message.message)); | ||
| break; | ||
| } | ||
| }); |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
No validation of origin is done by the addEventListener API. It may be possible to exploit this flaw to perform Cross Origin attacks such as Cross-Site Scripting(XSS).
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Reply with Semgrep commands to ignore this finding.
/fp <comment>for false positive/ar <comment>for acceptable risk/other <comment>for all other reasons
Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by insufficient-postmessage-origin-validation.
You can view more details about this finding in the Semgrep AppSec Platform.
| const postToHost = (message: IframeToHostMessage) => { | ||
| // The sandboxed iframe has an opaque origin. This still targets only its | ||
| // parent window; the host independently verifies `event.source`. | ||
| window.parent.postMessage(message, "*"); |
| window.addEventListener("message", (event) => { | ||
| if (event.source !== window.parent) { | ||
| return; | ||
| } | ||
|
|
||
| const data = event.data as unknown; | ||
| if ( | ||
| typeof data !== "object" || | ||
| data === null || | ||
| typeof (data as { kind?: unknown }).kind !== "string" | ||
| ) { | ||
| return; | ||
| } | ||
|
|
||
| const message = data as HostToIframeMessage; | ||
| if ( | ||
| message.kind !== "optimizationResponseStart" && | ||
| message.kind !== "optimizationChunk" && | ||
| message.kind !== "optimizationEnd" && | ||
| message.kind !== "optimizationError" | ||
| ) { | ||
| return; | ||
| } | ||
|
|
||
| const pending = pendingRequests.get(message.requestId); | ||
| if (!pending) { | ||
| return; | ||
| } | ||
|
|
||
| switch (message.kind) { | ||
| case "optimizationResponseStart": { | ||
| if (pending.responded) { | ||
| rejectPendingRequest( | ||
| message.requestId, | ||
| new Error("The optimizer sent more than one response header"), | ||
| ); | ||
| return; | ||
| } | ||
| pending.responded = true; | ||
| pending.clearResponseStartTimeout(); | ||
| pending.resolveResponse( | ||
| new Response(pending.stream, { | ||
| headers: { "content-type": "application/x-ndjson" }, | ||
| status: message.status, | ||
| statusText: message.statusText, | ||
| }), | ||
| ); | ||
| break; | ||
| } | ||
| case "optimizationChunk": { | ||
| try { | ||
| pending.controller.enqueue(message.bytes); | ||
| } catch { | ||
| // The consumer may already have cancelled the stream. | ||
| } | ||
| break; | ||
| } | ||
| case "optimizationEnd": { | ||
| if (!pending.responded) { | ||
| rejectPendingRequest( | ||
| message.requestId, | ||
| new Error("The optimizer ended before sending a response"), | ||
| ); | ||
| return; | ||
| } | ||
| try { | ||
| pending.controller.close(); | ||
| } catch { | ||
| // The stream is already settled. | ||
| } | ||
| pending.cleanup(); | ||
| pendingRequests.delete(message.requestId); | ||
| break; | ||
| } | ||
| case "optimizationError": | ||
| rejectPendingRequest(message.requestId, new Error(message.message)); | ||
| break; | ||
| } | ||
| }); |
| "@hashintel/petrinaut-core": minor | ||
| "@hashintel/petrinaut": minor |
There was a problem hiding this comment.
| "@hashintel/petrinaut-core": minor | |
| "@hashintel/petrinaut": minor | |
| "@hashintel/petrinaut-core": patch | |
| "@hashintel/petrinaut": patch |
5a24666 to
5094e3b
Compare
e246e59 to
d868b29
Compare
| throw new Error("Petrinaut optimizer returned an invalid event"); | ||
| } | ||
| const upstreamEvent: PetrinautOptimizerOptimizationEvent = parsed.data; | ||
| if (!response.write(`${JSON.stringify(upstreamEvent)}\n`)) { |
There was a problem hiding this comment.
Detected directly writing to a Response object from user-defined input. This bypasses any HTML escaping and may expose your application to a Cross-Site-scripting (XSS) vulnerability. Instead, use 'resp.render()' to render safely escaped HTML.
🧁 Fixed in commit 18ce05d 🧁
b923813 to
bde4586
Compare
| `Scenario parameter "${identifier}" must be a finite number or boolean`, | ||
| ); | ||
| } | ||
| parameterValues[identifier] = parameterValue; |
|
Deployment failed with the following error: |
🌟 What is the purpose of this PR?
Provide one end-to-end draft for the Petrinaut optimization workflow, from Petrinaut CLI and the Python optimizer service through generated types, NodeAPI, and the HASH/Petrinaut UI.
This consolidates the former four-PR stack into one place so the contract can be iterated on coherently. The next pass will reduce and reshape the
apps/petrinaut-optchanges with Yannis; those changes are intentionally still provisional in this draft.🔗 Related links
🔍 What does this change?
Petrinaut CLI and Python service
petrinaut serve --model-stdin --stdio, with one immutable model bootstrap followed by JSON-line protocol requests.maximize/minimizedirection in Python rather than the CLI protocol.libs/@hashintel/petrinaut-cli/OPTIMIZATION_INTEGRATION.md.Generated contract and NodeAPI
HASH and Petrinaut UI
🚧 Review status
This remains a draft. The vertical contract and UI are available for joint iteration, but the current Python-service implementation is not proposed as a wholesale replacement for Yannis's implementation. The immediate follow-up is to keep only the CLI-facing and API-contract changes that his service needs, making the resulting diff focused and reviewable by him.
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:
🛡 What tests cover this?
metric_profit❓ How to test this?
HASH_PETRINAUT_OPT_HOSTandHASH_PETRINAUT_OPT_PORTfor NodeAPI.📹 Demo
Not included in this draft.