@bytehide/grunt-shield
Version:
Grunt plugin for ByteHide Shield code obfuscation
195 lines (170 loc) • 7.39 kB
JavaScript
const fs = require('fs');
const path = require('path');
const https = require('https');
const crypto = require('crypto');
const {glob} = require('glob');
const packageJson = require('../package.json');
module.exports = function (grunt) {
const getProjectPackageJson = () => {
try {
const packageJsonPath = path.join(process.cwd(), 'package.json');
if (!fs.existsSync(packageJsonPath)) {
throw new Error('No se encontrĂ³ package.json en el proyecto.');
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
return packageJson;
} catch (error) {
console.error('Error al leer package.json:', error.message);
return null;
}
}
const detectFramework = (packageJson) => {
const frameworks = {
nextjs: ['next'],
react: ['react', 'react-dom', 'next'],
angular: ['@angular/core', '@angular/cli', 'rxjs', 'zone.js'],
vue: ['vue', '@vue/cli-service', 'nuxt'],
vuejs: ['vue', '@vue/cli'],
svelte: ['svelte', '@sveltejs/kit'],
express: ['express', 'body-parser'],
nestjs: ['@nestjs/core', '@nestjs/common', '@nestjs/platform-*'],
ember: ['ember-cli', 'ember-source'],
gulp: ['gulp', 'gulp-cli'],
grunt: ['grunt', 'grunt-cli'],
webpack: ['webpack', 'webpack-cli', 'webpack-dev-server'],
rollup: ['rollup', 'rollup-plugin-*'],
javascript: [],
};
const dependencies = Object.keys(packageJson.dependencies || {});
const devDependencies = Object.keys(packageJson.devDependencies || {});
const allDependencies = [...dependencies, ...devDependencies];
for (const [framework, keywords] of Object.entries(frameworks)) {
if (keywords.some(keyword => allDependencies.includes(keyword))) {
return framework
}
}
return 'javascript';
}
const getProjectInfo = () => {
const packageJson = getProjectPackageJson();
const framework = detectFramework(packageJson);
return {
framework,
version: packageJson?.version || null,
name: packageJson?.name || null,
};
}
grunt.registerMultiTask('bytehideShield', 'Protect your JavaScript files using ByteHide Shield', async function () {
const done = this.async();
const projectInfo = getProjectInfo();
const options = this.options({
projectToken: process.env.BYTEHIDE_TOKEN,
distDir: 'dist',
replace: false,
obfuscatedExtension: '.obf',
exclude: [],
config: {
controlFlowFlattening: true,
debugProtection: false,
devtoolsBlocking: false,
},
assembly: {
version: projectInfo.version,
framework: projectInfo.framework,
name: projectInfo.name,
},
integration: {
name: packageJson.name,
version: packageJson.version,
}
});
if (!options.projectToken) {
grunt.fail.fatal('ByteHide project token is required');
return done(false);
}
const generateWatermark = () => `// _0xBHSHLD_${crypto.randomBytes(4).toString('hex')}_marker`;
const obfuscateCode = (code) => {
return new Promise((resolve, reject) => {
const payload = JSON.stringify({
code,
config: options.config,
processId: crypto.randomBytes(24).toString('hex'),
assembly: options.assembly,
integration: options.integration,
});
const requestOptions = {
hostname: 'shield.microservice.bytehide.com',
port: 443,
path: `/api/start/${options.projectToken}/js`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
},
};
const req = https.request(requestOptions, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
if (res.statusCode === 200) {
const watermark = generateWatermark();
resolve(`${watermark}\n${JSON.parse(data).output}`);
} else {
try{
reject(new Error(`Obfuscation failed: ${JSON.parse(data).message}`));
} catch (error) {
reject(new Error(`Obfuscation failed: ${data}`));
}
}
});
});
req.on('error', reject);
req.write(payload);
req.end();
});
};
const protectFile = async (filePath) => {
try {
const content = fs.readFileSync(filePath, 'utf-8');
const ext = path.extname(filePath).toLowerCase();
if (!['.js', '.mjs', '.cjs', '.jsx'].includes(ext)) {
grunt.log.writeln(`Skipping unsupported file: ${filePath}`);
return;
}
if (content.includes('_0xBHSHLD_')) {
grunt.log.writeln(`Skipping already protected file: ${filePath}`);
return;
}
grunt.log.writeln(`Obfuscating: ${filePath}`);
const obfuscatedContent = await obfuscateCode(content);
const obfuscatedFilePath = options.replace ?
filePath :
filePath.replace(ext, `${options.obfuscatedExtension}${ext}`);
fs.writeFileSync(obfuscatedFilePath, obfuscatedContent, 'utf-8');
grunt.log.writeln(`File obfuscated: ${obfuscatedFilePath}`);
} catch (error) {
grunt.log.error(`Error obfuscating file ${filePath}:`, error.message);
}
};
try {
grunt.log.writeln('Starting ByteHide Shield Obfuscation Process...');
const resolvedDistDir = path.resolve(process.cwd(), options.distDir);
if (!fs.existsSync(resolvedDistDir)) {
grunt.fail.fatal(`The directory '${options.distDir}' does not exist.`);
return done(false);
}
const files = glob.sync(`${resolvedDistDir}/**/*.js`).filter((file) => {
const fileName = path.basename(file);
return !options.exclude.includes(fileName);
});
for (const file of files) {
await protectFile(file);
}
grunt.log.writeln('ByteHide Shield Obfuscation Process Completed.');
done();
} catch (error) {
grunt.fail.fatal(error);
done(false);
}
});
};