UNPKG

@bytehide/webpack-shield

Version:

Webpack plugin for ByteHide Shield obfuscation.

208 lines (181 loc) • 7.65 kB
const fs = require('fs'); const https = require('https'); const crypto = require('crypto'); const path = require('path'); const packageJson = require('../package.json'); class ByteHideShieldPlugin { constructor(options = {}) { const projectInfo = this.getProjectInfo(); this.options = { projectToken: options.projectToken || '', replace: options.replace || false, obfuscatedExtension: options.obfuscatedExtension || '.obf', exclude: options.exclude || [], config: options.config || { controlFlowFlattening: true, debugProtection: false, devtoolsBlocking: false }, include: options.include || ['.js', '.mjs', '.cjs', '.jsx'], assembly: { version: projectInfo.version, framework: projectInfo.framework, name: projectInfo.name, }, integration: { name: packageJson.name, version: packageJson.version, } }; } generateRandomID() { return crypto.randomBytes(24).toString('hex'); } generateWatermark() { const uniqueId = crypto.randomBytes(4).toString('hex'); return `// _0xBHSHLD_${uniqueId}_marker`; } obfuscate(code, obfuscationID) { return new Promise((resolve, reject) => { const payload = JSON.stringify({ code: code, processId: obfuscationID, config: this.options.config, assembly: this.options.assembly, integration: this.options.integration, }); const options = { hostname: 'shield.microservice.bytehide.com', port: 443, path: `/api/start/${this.options.projectToken}/js`, method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(payload, 'utf8') } }; const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { try { const result = JSON.parse(data); if (res.statusCode === 200) { const watermark = this.generateWatermark(); const outputWithWatermark = `${watermark}\n${result.output}`; resolve(outputWithWatermark); } else { reject(result); } } catch (e) { reject('Unexpected response format'); } }); }); req.on('error', (error) => { reject(error); }); req.write(payload, 'utf8'); req.end(); }); } apply(compiler) { compiler.hooks.emit.tapAsync('ByteHideShieldPlugin', async (compilation, callback) => { const tasks = []; for (const filename in compilation.assets) { const ext = filename.split('.').pop().toLowerCase(); if (this.options.include.includes(`.${ext}`)) { const asset = compilation.assets[filename]; const source = asset.source(); if (source.includes('_0xBHSHLD_')) { console.log(`Skipping already obfuscated file: ${filename}`); continue; } if (this.options.exclude.some((excluded) => filename.includes(excluded))) { console.log(`Excluding file: ${filename}`); continue; } const task = this.obfuscate(source, this.generateRandomID()) .then(obfuscatedCode => { if (this.options.replace) { compilation.assets[filename] = { source: () => obfuscatedCode, size: () => obfuscatedCode.length }; } else { const obfuscatedFilename = filename.replace( path.extname(filename), `${this.options.obfuscatedExtension}${path.extname(filename)}` ); compilation.assets[obfuscatedFilename] = { source: () => obfuscatedCode, size: () => obfuscatedCode.length }; } }) .catch(error => { console.error(`Error obfuscating ${filename}:`, error.message || error); }); tasks.push(task); } } try { await Promise.all(tasks); callback(); } catch (error) { callback(error); } }); } 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; } } 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.charAt(0).toUpperCase() + framework.slice(1); // Capitalizar el nombre del framework } } return 'javascript'; } getProjectInfo(){ const packageJson = this.getProjectPackageJson(); const framework = this.detectFramework(packageJson); return { framework, version: packageJson?.version || null, name: packageJson?.name || null, }; } } module.exports = ByteHideShieldPlugin;