I'm Shocked! Why Are Plugins Decrypted on Install?

You’re most welcome :pray:t2:

Same worked for me. I think both plugins passed review in Marketplace.

I was wondering… If you use React and your code gets compiled to plain JS, is there really an easy way to retrieve the original state? Sure, you could steal some individual parts but without seeing the whole picture they’d be out of context and kind of hard to understand, wouldn’t they?

That’s correct. Not even sure about reverse-engineering some parts, or one should have a lot of time and energy to dive in :wink:

Sounds fine to me then. I also feel like someone who’d take the time to reverse-engineer that compiled mess could also just write the plugin himself in the first place :man_shrugging:

1 Like

For the obfuscator.io I have seen the self defending break stuff. I would leave self defending and debugging off. A few months ago all of my plugins broke on a PS beta update with those on when they were working before the PS beta update. So I turned those options off before the beta went public. I think the self defending setting was the culprit but it may have been debugging protection too, can’t remember for sure.

Now I just use the basic preferences with rename globals (need to be careful with globals I think) and dead code injection as the only additional above the default settings. If anyone can work through that mess of characters and figure anything out then they certainly don’t need help from me to create a plugin anyway :slight_smile:

1 Like

Thanks for the advice @ddbell . I’ll keep my eye on that. I’ve just resubmitted my plugin and both of those options turned on. If I ever have any problems I’ll turn them off.

As a more general update to this thread, I think it’s because I am not using React, but I could not get this working using the webpack plugin. I spent a day and a half trying all kinds of configurations, but I couldn’t get my plugin to work via webpack. bundling all my files into a single obfuscated file just wouldn’t work. I have always built it via the Adobe UXP Developer Tool’s Package option.

My plugin is basically all of the necessary files that Adobe needs for the plugin, then my icons, a single gif graphic and a folder with around 10 JSX files in it, which make up the bulk of the functionality. Having failed to make it work using webpack, even without obfuscation, I wrote a script that creates a copy of all my .jsx files as regular .js files, then obfuscates them all individually using javascript-obfuscator. I have javascript-obfuscator store the obfuscated files in my dist directiory and change their extension back to .jsx. Packed as I usually do via the Adobe UXP Developer Tool the plugin works fine and when I check the unpacked installed plugin it’s correctly using my obfuscated jsx files, so I think I’m good now.

If anyone is obfuscating a non-React UXP plugin in a better way than this, I’d appreciate any advice. Conversely, if anyone wants me to share my script let me know. I’d be happy to drop it into this post if it might help anyone else.

I have also had issues with plugins breaking after probably to much aggressive obfuscation.

I am interested to see your script @MartinBailey as this sounds like it could speed up the workflow a bit.

In case you are not aware…

JSX may go away at some point in the future. Adobe has stated that they don’t want JSX files being used in UXP plugins that are distributed on the marketplace. They want you to use all UPX javascript only.

Thanks for the heads-up, but I’m not doing what is specified in that post. I used the non-React starter project form Adobe as a base and ended up with my core functionality in jsx extension files, but as far as I can see all of my code is based on current recommendations, UXP and batchplay in regular JavaScript. I’ll try changing them all to js files. I wouldn’t be surprised if nothing changes.

As I suspected, I just changed the extensions on my .jsx files to just .js and the plugin works exactly the same. I don’t recall at what part in my development I ended up with .jsx files, but it doesn’t seem to make a difference. I just Googled the difference, and see that jsx files are to make it easier to use React tags, and that seems in-line with the UXP framework.

I wonder if my use of JSX in capitals for the jsx files that I have in my project caused the confusion here. I will put my files back to .jsx now that I have checked that it works like this, as I’m pretty sure everything is fine.

The file extension (js vs jsx) ins’t the issue. The extendscript engine itself is being replaced by UXP in the future. So any script files running extenscript at some time will stop working if/when that occurs.

I’m not using any ExtendScript. I rewrote my plugin using the UXP framework, so this doesn’t affect me. Thanks anyway.

OK, I see. You just added an x to the file extension. JSX typically refers to an extendscript file so that is why I figured you were using extendscript. But I guess it doesn’t technically need to be .js to run. It still run the script file as javascript from the HTML with any extension you want to use for the file name, even if it is a made up extension too.

Yes, sorry about that. I thought the capitalization there may have been the problem. I’ll be more careful in future. :slight_smile:

Sure thing @IanBarber, here is my script. I added some comments and tweaked it to make it more generally useful for anyone creating non-React plugins with UXP.

The comments in the script should be all you need, but please note that if your javascript files use the .js extension and not the .jsx extension, javascript-obfuscator will obfuscate your files without changing the name, but simply removing the name changing commands will make this script overwrite your source files, and that’s not good. You’ll need to modify the script to make a copy of your .js files before obfuscation.

Note too that at this point I have to run this script manually with ./obfuscate.zsh on the command line in the root of my plugin folder before I install and text the plugin using the UXP Development Tool. If anyone is aware of a way to run this command and wait for the results to be in place as part of the UXP Development Tool Run and/or Package command, please let me know.

#!/bin/zsh
# Change zsh to bash if you use the Bash Shell

# NOTE: The script is created assuming that you place it in the top level
# folder of your UXP plugin folder containing your *-Production.json file.

# Store your plugin source files folder
source=./src

# The directory containing jsx files that you want to obfuscate
obfsrc=${source}/jsx

# Store your plugin distribution folder
target=./dist

# This is where you want your obfuscated files to be placed
obftgt=${target}/jsx

# Create the target and obfuscated file target directories if they don't already exist
mkdir -p $target
mkdir -p $obftgt

# Clean out .DS_Store files
find $source/ -name '.DS_Store' -type f -delete

# Copy the top level UXP plugin files to the distribution folder
cp -a $source/index.html $target/index.html
cp -a $source/manifest.json $target/manifest.json
cp -a $source/styles.css $target/styles.css

# Copy the production file to the distribution folder too
for prodfile in $source/*-Production.json
do
  cp -a "$prodfile" "$target/$1"
done

# Copy any other folders like icons and images to dist folder

cp -a $source/icons/. $target/icons/
cp -a $source/images/. $target/images/

# WARNING - IF YOU COMMENT THIS OUT AND RUN THE SCRIPT DIRECTLY
# ON YOUR .JS FILES THE SCRIPT WILL OVERWRITE THEM!
# This makes a copy of all of the .jsx files while changing the extension to .js for obfuscation
for file in $obfsrc/*.jsx
do
  cp -a "$file" "${file%.jsx}.js"
done

# This obfuscates all of the files in your src folder using the options seen below
# https://github.com/javascript-obfuscator/javascript-obfuscator

javascript-obfuscator ${obfsrc} -o ${obftgt} --compact true --dead-code-injection true --dead-code-injection-threshold 0.4 --debug-protection true --disable-console-output true --identifier-names-generator 'hexadecimal' --rename-globals false --rotate-string-array true --self-defending false --shuffle-string-array true --simplify true --string-array true --string-array-index-shift true --string-array-wrappers-count 1 --string-array-wrappers-chained-calls true --string-array-wrappers-parameters-max-count 2 --string-array-wrappers-type 'variable' --string-array-threshold 0.75 --unicode-escape-sequence false

# This changes the files back from .js to .jsx
# Comment it out if this step is not necessary
for file in $obftgt/*.js
do
  mv "$file" "${file%.js}.jsx"
done

# Remove the .js copies of your .jsx files
# If you aren't using .jsx files be careful not to remove your original files with this
rm -f $obfsrc/*.js

# All done! Your distribution jsx files should now all be individually obfscated.
# Note that to test your plugin you need to point the UXP Development Tool to the
# manifest.json file in your dist folder, not the original file in your source folder.
# Check that your plugin still works as expecting before distribution. :)

1 Like

Talking about workflow…

I have three separate configs for:

  • dev - pure unpacked JS
  • local - packed and obfuscated for local CCX install
  • dist - packed and obfuscated for marketplace (exact same as local, but ID is provided by Adobe and name is slightly different for me to see which plugin I’m using)

dist and local contain:

  • Plugin ID
  • Plugin name
  • Some security check config. If manifest is altered (eg. plugin ID is changed to install locally), plugin won’t work (this also is included in dev)

dev is used when plugin is loaded with Dev Tools while I’m actively developing.
local or dist builds are generated with a single command and I use local build myself, because there’s no point to buy my own plugin from marketplace (if someone knows the way to install own plugin from Marketplace for free, please let me know)

As I mentioned, I use Webpack to generate builds. Config looks like:

// webpack.config.js

const webpack = require('webpack');
const fs = require('fs');
const archiver = require('archiver');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const WebpackObfuscator = require('webpack-obfuscator');
const {version} = require('./package.json');

const triggerAfterBuild = function (cb) {
    this.apply = function (compiler) {
        if (compiler.hooks && compiler.hooks.done) {
            compiler.hooks.done.tap('webpack-arbitrary-code', cb);
        }
    };
};

module.exports = function (env, argv) {
    const prod = env.production || false;
    const build = prod ? 'dist' : 'local';

    const {pluginId, pluginName} = require('./src/env/config.' + build);
    const {id: pluginIdDev, name: pluginNameDev} = require('./src/manifest.json');
    const path = require('path').resolve(__dirname, 'build/' + build);

    const callbackAfterBuild = function () {
        const manifestFile = `${path}\\manifest.json`;

        <...>
    };

    return {
        mode: 'production',
        entry: './src/index.js',
        output: {
            path: path,
            filename: 'index.js',
        },
        externals: {
            'uxp': 'commonjs uxp',
            'photoshop': 'commonjs photoshop',
            'locales': 'commonjs locales'
        },
        plugins: [
            new CleanWebpackPlugin(),
            new webpack.NormalModuleReplacementPlugin(
                /..\/env\/config/ig,
                '../env/config.' + build
            ),
            new CopyPlugin({
                patterns: [
                    {from: 'src/icons', to: `${path}/icons`},
                    {from: 'src/locales', to: `${path}/locales`},
                    {from: 'src/manifest.json', to: `${path}/manifest.json`},
                ],
            }),
            new WebpackObfuscator({
                debugProtection: true,
                disableConsoleOutput: true,
                selfDefending: true,
            }),
            new triggerAfterBuild(callbackAfterBuild),
        ]
    };
};

Basically what config does, is:

  1. Clear the build folder
  2. dev config imports are replaced with a proper build imports
  3. /icons, /locales and /manifest.json are copied to the build folder
  4. Obfuscate the main /index.js
  5. callbackAfterBuild() is triggered after Webpack basically is done:
    1. Replace some strings in a manifest:
      1. Replace plugin version (0.0.0) with a version taken from /package.json
      2. Replace plugin ID with an ID taken from build config
      3. Replace plugin name with a name taken from build config
    2. Archive (ZIP) all the /build/dist (or .../local) contents to a CCX file

I have both dist and local shortcuts set up in PHP Storm, so it’s a single click to get a fully built CCX file either to install locally or to submit to Adobe Marketplace

2 Likes

Thanks for sharing this @Karmalakas!

I had a problem today with my obfuscated plugin builds not working and decided to look into using webpack and many of the settings you shared here. I’m now building, obfuscating, and packaging for upload with a single command. It took the entire afternoon, but your webpack.config.js made it so much easier than it would have been otherwise. Thanks again!

1 Like

For me it took probably a week of multiple iterations to get the final package result I want :smiley: But now packaging is a breeze :slight_smile: I also usually install local version to do final testing (as it’s the same as dist, just with different ID in manifest) and if I find some issue still, I fix it on dev and again package with a single click to re-test. Makes development so much easier

1 Like

I was thinking that would probably be the case, making it even more important to share my gratitude.

I just finished up some changes on my plugin and was also just working directly on the code in Visual Studio Code then rebuilding with the built-in terminal, and double-clicking the package to install and test changes. I haven’t set up a build with debugging and console output enabled yet, but I’ll do that soon. It will be a breeze with the help you provided. Thanks again!