UNPKG

@bytehide/grunt-shield

Version:

Grunt plugin for ByteHide Shield code obfuscation

195 lines (170 loc) • 7.39 kB
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); } }); };