Search code examples
javascriptember.jsember-cliobfuscation

How do I obfuscate Javascript in Ember?


Does anyone know how to use the javascript-obfuscator (or similar) in Ember ?

I guess it needs to be called inside ember-cli-build.js but I don't know where and how.

Thank you in advance for any help :)


Solution

  • I don't think there's a really straightforward answer to this. If you're running on embroider, then all your Javascript will be handled by webpack, so you could try using webpack-obfuscator -- in your ember-cli-build.js something like

    return require('@embroider/compat').compatBuild(app, Webpack, {
      plugins: [
        new WebpackObfuscator(/*whatever args*/)
      ],
      rules: [
        {
          test: /\.js$/,
          enforce: 'post',
          use: { 
            loader: WebpackObfuscator.loader, 
          }
        }
      ]
    });
    

    The other options I know of would be to write a broccoli plugin. What you're doing is analogous to what ember-cli-terser does -- post-process Javascript files before they get concatenated together -- so you could use that as reference.

    You would need to write a broccoli plugin that actually performs the transformations (the analog is broccoli-terser-sourcemap) and an Ember addon to hook it into ember-cli's build pipeline (the analog is ember-cli-terser).

    Broccoli plugin

    Looking at broccoli-terser-sourcemap's index.js, which is only 114 lines of code, I would think you could adapt it to something like this:

    module.exports = class TerserWriter extends Plugin {
      constructor(_inputNodes, options = {}) {
        let inputNodes = Array.isArray(_inputNodes) ? _inputNodes : [_inputNodes];
    
        super(inputNodes, {
          name: options.name,
          annotation: options.annotation,
          needsCache: false,
        });
    
        this.options = defaults(options, {
          obfuscator: {},
        });
    
        let exclude = this.options.exclude;
        if (Array.isArray(exclude)) {
          this.excludes = new MatcherCollection(exclude);
        } else {
          this.excludes = MatchNothing;
        }
      }
    
      async build() {
        let pendingWork = [];
    
        this.inputPaths.forEach(inputPath => {
          walkSync(inputPath).forEach(relativePath => {
            if (relativePath.slice(-1) === '/') {
              return;
            }
            let inFile = path.join(inputPath, relativePath);
            let outFile = path.join(this.outputPath, relativePath);
    
            fs.mkdirSync(path.dirname(outFile), { recursive: true });
    
            if (this._isJSExt(relativePath) && !this.excludes.match(relativePath)) {
              pendingWork.push(() => this.processFile(inFile, outFile, relativePath, this.outputPath));
            } else {
              symlinkOrCopy.sync(inFile, outFile);
            }
          });
        });
    
        for (let fn of pendingWork) {
          await fn();
        }
      }
    
      _isJSExt(relativePath) {
        return relativePath.slice(-3) === '.js' || relativePath.slice(-4) === '.mjs';
      }
    
      async processFile(inFile, outFile, relativePath, outDir) {
        let input = await readFile(inFile).toString();
        let result = obfuscate(input, this.options.obfuscator);
        await writeFile(outFile, result.getObfuscatedCode());
      }
    };
    

    You could also do the worker pooling this that broccoli-terser-sourcemaps does, and if you care about source maps you'd need to handle them as well, but broccoli-terser-sourcemaps does just that, so you could use it as reference.

    ember-cli addon

    ember-cli-terser has even less code -- looking at its index.js, you could adapt it to something like

    'use strict';
    
    module.exports = {
      name: require('./package').name,
    
      included(app) {
        this._super.included.apply(this, arguments);
    
        let defaultOptions = {
          enabled: app.env === 'production',
    
          obfuscator: {
            // default `javascript-obfuscator` options
          },
        };
    
        let addonOptions = app.options['ember-cli-obfuscator'];
    
        this._obfuscatorOptions = Object.assign({}, defaultOptions, addonOptions);
      },
    
      postprocessTree(type, tree) {
        if (this._obfuscatorOptions.enabled === true && type === 'all') {
          // Import the plugin code above
          const Obfuscator = require('./broccoli-obfuscator');
    
          return new Obfuscator(tree, this._obfuscatorOptions);
        } else {
          return tree;
        }
      }
    };
    

    Then you'd have to install the above addon in your app (it could be an in-repo addon), and it should do its thing!

    This would definitely take some doing, but what you're doing is so similar to what ember-cli-terser is doing, just using the obfuscator API instead of the terser API, that you have a really good starting point.

    BUT, if embroider is an option for you, I'd definitely try that route first because it might just be a matter of configuration, rather than writing a bunch of code.