How to hide the code of the script module?

Hello everyone,
I would like to ask how to hide the code of the script module. I am developing the script module recently, and I feel that it is a bit insecure to be able to directly see all the code. Hope to get everyone’s help. thanks!

best wishes,
Mary

You can search the internet for ways to hide your python code. You may be able to make it harder for people to see the code, but it won’t be possible to hide it completely.

Thanks Pieper.I will try this method. I learned that python code can be hidden by generating PYD files, and I also saw PYD files in Slicer’s generated project bin file, so when should I generate PYD files?Thanks again.
Mary

You’ll need to do some research on this. By default Slicer expects python scripts to be available in source form with any optimizations like compiling to byte codes being handled automatically by the python infrastructure.

Hello @qiqi5210 , continuing this discussion further,

I am interested in knowing if you have found any working methods for this?

Thanks,
Mujassim

You should be able to replace the original .py source code file by the automatically generated .pyc bytecode files or a .py file that to have run through a Python obfuscator tool.

1 Like

I suggest others also see the easily available uncompiling tools as well to take obfuscated Python code back to human readable.

See uncompyle6 as an example:

1 Like

@lassoan and @jamesobutler, thank you for your inputs.

Is there any way to have Slicer load scripted modules from .pyd files instead of .py files? Why does Slicer specifically look for module_name.py files in qt-scripted-modules directory?

.pyd files are not relevant if you develop your code in Python because .pyd files are created by compiling C/C++ code. The module factory is looking for .py (source) and .pyc (byte code) files in scripted module folders.

Probably the simplest solution for hiding your source code is to replace .py files by .pyc files. If you use obfuscation tools then you can keep using .py files but you need to be careful about keeping some publicly used symbols unchanged (e.g., the Slicer module and widget class names must not be obfuscated).

Indeed, I generated those .pyd files using the Cython build process.

Does this imply that the module factory utilizes these .py or .pyc files as a package (e.g., importing from .py) to be registered in the factory manager? If that’s the case, then .py files could potentially be replaced with .pyd files, as they are also usable as packages.

However, there’s a challenge. As suggested by @jamesobutler and confirmed through my own experiments on obfuscated scripts (or .pyc files), I discovered that these can be translated back into a human-readable form. Therefore, using .pyc files or obfuscation tools to conceal the source code might not be a secure approach. This is why I am exploring a way to use .pyd (or .so, .dylib) files in place of .py or .pyc files. There should be a solution!

If you write your code in C++ then you can create a C++ loadable module, as most of the Slicer core modules. There is no need to use Python then.

The .py/.pyc file is imported and then the module and widget classes are instantiated.

Yes, you can disassemble any code (including .pyd files) back to human-readable form. You can try to make it harder with some extra work, but you cannot prevent it completely.

Your extra measures would incur extra costs and there many ways around all of them anyway. Even if you run your software as a service on a server, your competitors can still hire your employees, hack into your system, deduce how you do things by observing what your software does, etc.

It would be also irresponsible to try to protect your key technologies just by attempting to keep them secret. Trade secrets are not protected, so any other company can come up with the same product (by taking ideas from you or inventing it independently) and sell it and you may not be able to do anything about it. The other company might even block you from using the technology that you (also) invented if they patent it first. If you patent the idea then it can ensure that only you can use it (and may significantly increase the value of your company), but it can be quite expensive. If you want to avoid patenting cost but want to ensure that you can use your ideas then you may consider publicly disclosing them (e.g., instead of trying to obfuscate the code, you could decide to make the source code publicly available or describe the idea in a research paper).

Overall, if you want to make it harder for people to see how your modules work, distributing .pyc files instead of .py files may be a resonable tradeoff between maixmum protection and minimizing extra costs and complexities.

Thanks a lot andras! I completely agree with your insights and suggestions.

By the way, I am working on my university end-semester research project, focusing on optimizing and securing Python source code. I came across Slicer and found it to be a good starting point, especially with the demonstration on scripted modules.

Yes, I have already created my own Python module, so I’m not planning to rewrite it in C++.

Could you please guide me to the file(s) where this happens? Your explanation would be quite helpful.

It seems like I don’t have many choices but .pyc. Anyways, instead of manually distributing the .pyc files, how can i automate this in the Slicer build process to generate and utilize .pyc files in the qt-scripted-module directory and completely eliminate the .py files?

You can search for “.pyc” in the slice source tree.

The simplest is probably to write a short Python script that compiles your .py files and then replaces the .py files by the .pyc files.

This script could added as a custom build or installation step in your extension build CMake files. If you are not sure how to do it, you can ask advice on the CMake forum or contract a CMake expert at Kitware.

1 Like

I found that .pyc files were already generated in __pycache__ folder of scripted modules after the build process. Can I use those .pyc files directly instead of recompiling .py files?

Furthermore, is there any Slicer-specific CMake flag to determine the directory path of Qt scripted modules?

Yes, those are cached automatically compiled files. You can certainly use them.

Slicer application looks for scripted module files in lib\Slicer-5.6\qt-scripted-modules folder and folders in the “additional module paths” (stored in application settings).

1 Like

Background: Support for discovering and loading scripted modules from .pyc files has already been implemented for all scripted plugins and modules. You can find the related changes in the Slicer GitHub repository.

However, it’s crucial to note that, starting with Python 3.2, the default behavior of py_compile.compile, which is utilized in ctk_compile_python_scripts.cmake.in, is to generate files like __pycache__/scriptname.cpython-XY.pyc, following the specifications in PEP-3147. This explains why we may not have *.pyc files compiled after building the CompileSlicerPythonFiles target.


Update: To address this, a pull request has been submitted to the CTK repository. This pull request, available at https://github.com/commontk/CTK/pull/1188, aims to ensure that Slicer scripts are consistently compiled as legacy .pyc files.

After you have a chance to test and review, we will look into integrating the corresponding changes into Slicer.

1 Like

@jcfr, I have reviewed the pull request. Thanks for bringing it up!

Hello @jcfr,

I have written the following script in slicersources-src\CMake\LastConfigureStep\CMakeLists.txt to replace .py with .pyc files during last configuration step. I didn’t encounter any errors during the build process; however, it appears that the script is not functioning as expected. Could you please help me troubleshoot this issue?

function(replace_py_with_pyc ${SCRIPTED_MODULE_PYCACHE_DIR_PATH})
  if (EXISTS ${SCRIPTED_MODULE_PYCACHE_DIR_PATH}/__pycache__ AND IS_DIRECTORY ${SCRIPTED_MODULE_PYCACHE_DIR_PATH}/__pycache__)
    message("--- Attempting to copy .pyc files...")
    file(GLOB PYC_FILES ${SCRIPTED_MODULE_PYCACHE_DIR_PATH}/__pycache__/*.pyc)
    file(COPY ${PYC_FILES} DESTINATION ${SCRIPTED_MODULE_PYCACHE_DIR_PATH}/)

    message("--- Attempting to remove .py files...")
    file(GLOB PY_FILES ${SCRIPTED_MODULE_PYCACHE_DIR_PATH}/*.py)
    file(REMOVE ${PY_FILES})
  endif()

  file(GLOB SUBDIRS LIST_DIRECTORIES True ${SCRIPTED_MODULE_PYCACHE_DIR_PATH}/*)
  foreach(subdir ${SUBDIRS})
    replace_py_with_pyc(${subdir})
  endforeach()

  message("--- Done copying .pyc files.")
endfunction()

replace_py_with_pyc(${Slicer_INSTALL_QTSCRIPTEDMODULES_LIB_DIR})

Never mind, the script is working now. However, I noticed that the build process is still copying Python script files again to the scripted modules.

image

@lassoan, how can I execute the script on slicer’s post build?

Technically, the .py files are not replaced, there are removed.

The target CompileSlicerPythonFiles depends on the CopySlicerPythonScriptFiles one, these are defined using the ctkFunctionAddCompilePythonScriptTargets CMake function.
See Slicer/CMakeLists.txt#L1280-L1309

To properly support “compiling” source file exclusively to .pyc, the CMake function ctkFunctionAddCompilePythonScriptTargets should be improved to accept an new parameter option (e.g SKIP_SCRIPT_COPY).
See CTK/CMake/ctkMacroCompilePythonScript.cmake

The ctkMacroCompilePythonScript.cmake CMake module could also be updated to define an option for controlling the behavior globally (e.g CTK_COMPILE_PYTHON_SCRIPT_SKIP_SCRIPT_COPY).

This new option should be added to SlicerConfig.cmake.in, that way extension build against Slicer would also leverage it.

This should provide you with enough hints to move forward and create a pull request at https://github.com/commontk/CTK

We will address corner cases during the review process.

1 Like