In-browser python interpreter

I made a simple app to test the PythonEvaluate functionality of Rhino Compute.
https://rhino-web.herokuapp.com/
You can write Rhino Common code in the text field and have it evaluated and rendered in THREE.js
You can create geometry and return as Brep, you can also reference geometry from viewport for further manipulation.
It’s just a test, so of course very buggy and limited.

4 Likes

Errr so anybody with access to the app can execute arbitrary Python code on your server?

Nice work @mkarimi! We have another user that is struggling to use rhino3dm with React. If you’re willing (and it’s up to you!) could you share some tips with them?

@Dancergraham we have a couple of things in place to mitigate malicious code execution in our own implementation of the Compute server. Firstly the Compute service runs as a non-administrator and secondly there are some restrictions as to what python code you can run on the server (note that the python endpoint has since been moved out of the Compute project into the IronPython plug-in).

2 Likes

Good idea!

Maybe add eval to the list of badwords and correct the typo in GetProperty ? It may also be worth checking out the bandit package on PyPi (from OpenStack) for other potential security issues with Python. It is widely used and recommended.

-Graham

We love pull requests! :slight_smile:

Edit: actually I’m not sure that’s a typo. I think it’s supposed to cover both GetProperty() and GetProperties().

Absolutely @will, happy to share details.

Working in React importing Rhino3dm package like import * from 'rhino' just doesn’t work. It’ll be much cleaner if that worked, but there are work arounds.

Here’s what I ended up doing, you basically have to make a script tag just like plain html and reference that. One way to do that in React is:

const fetchJsFromCDN = (src, key) => {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    script.src = src;
    script.addEventListener("load", () => {
      resolve(window[key]);
    });
    script.addEventListener("error", reject);
    document.body.appendChild(script);
  });
};

Which can be called like:

var rhinoProm = fetchJsFromCDN(
  "https://files.mcneel.com/rhino3dm/js/latest/rhino3dm.js",
  ["rhino3dm"]
);
var computeProm = fetchJsFromCDN(
  "https://files.mcneel.com/rhino3dm/js/latest/compute.rhino3d.js",
  ["RhinoCompute"]
);
var modules = await Promise.all([rhinoProm, computeProm]);
var rhino3dm = modules[0]
var compute = modules[1]

No that you have your modules resolved. We need to attach them to our window so we can reference them elsewhere

rhino3dm().then(async rh => {
  window.rhino3dm = rh;
});
compute.authToken = compute.getAuthToken();
window.compute = compute

rhino3dm and compute modules can be referenced anywhere using window.rhino3dm or window.compute

1 Like

I tried forking the repo but can’t find that code in the main branch of my fork …? https://github.com/dancergraham/compute.rhino3d/blob/master/src/compute.geometry/Program.cs

EDIT: Found it but it tells me I must be on a branch to edit…? https://github.com/dancergraham/compute.rhino3d/blob/a64afff9596aaec103699a04c4fd2505666db63f/src/compute.geometry/Program.cs

@Dancergraham I’m sorry, despite having written it shortly before in a post above, I forgot that the code is no longer in the open source Compute repository… @stevebaer, what do you think about adding eval to the list of banned words?

I’m still trying to figure out how eval could be used in a malicious way with the other restrictions in place.

I’m concerned about malicious scripts, but also don’t want to limit people. This is really just a prototype server and we do log who is calling compute if we end up with someone making a mess of things. The compute server already supports calls for solving grasshopper definitions and you could include custom scripts in those definitions. The compute server can also be taken down at any time and replaced with a fresh clone.

eval Is similar to exec and returns a value. Both are generally considered to be major security risks as a way of bypassing other safeguards, but I’m no security expert so I’m just passing on what I’ve heard.

A hint on how we solve these kind of problems at ShapeDiver: We implemented a manual approval process for scripts contained in Grasshopper models (C#, VB, Python). Whenever a model gets uploaded containing a script which has not been approved or denied before, we are notified by our backend system and review the script before approving or denying it. This allows us to ensure security and reliability of the backend systems shared by our customers.

4 Likes

I’ve just seen some discussion of this from someone who knows Python very well… not sure how this fits in with your implementation…

Hello Morteza,
I’m getting started with Rhino3dm and Compute in React, so I found your quick description very useful. Thanks.

Now I can’t find in the available JavaScript API a method to write the File3dm into a file, so that I can validate inside Rhino. Please can you help me with this.

Thanks in advance.

This sample will save a 3dm from the browser.

1 Like

Thanks a lot!

Hello I am having trouble using rhino3dm in react, here is my sandbox…

sleepy-easley-elqmk - CodeSandbox line 61

I am struggling with the workaround proposed by @mkarimi presumably in the async fetching of the modules

Thank your for any help !

Olivier

1 Like

@roievil I had some difficulty getting it to work on codesandbox too. I think my previous approach wouldn’t work here.
Try this one:

I’m basically creating the script tag in index.js and then using it in each component (App.js in this case) like window.rhino3dm wrapped in a useEffect hook.

@mkarimi, Thank you very much, it is exactly what I needed !

let me paste it here in case something happens to the sandbox

app.js :

import React, { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [sphere, setSphere] = useState(null);

  useEffect(() => {
    window.rhino3dm().then((Module) => {
      //creating a sphere using rhino3dm
      setSphere(new Module.Sphere([1, 2, 3], 16));
    });
  }, []);

  return <div className="App">{sphere && <p>{`sphere diameter is: ${sphere.diameter}`}</p>}</div>;
}

index.html:


import { StrictMode } from "react";
import ReactDOM from "react-dom";

import App from "./App";

const rootElement = document.getElementById("root");

const script = document.createElement("script");
// script.crossOrigin = "anonymous";
script.src = "https://cdn.jsdelivr.net/npm/rhino3dm@0.12.0/rhino3dm.min.js";
script.addEventListener("load", () => {
  ReactDOM.render(
    <StrictMode>
      <App />
    </StrictMode>,
    rootElement
  );
});
document.body.appendChild(script);

Glad I could help.
@fraguada I made a PR to add this instructions to the repo’s readme, hopefully it helps someone with a similar issue in the future.

2 Likes