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

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

5 Likes