启用生产镜像服务端混淆构建

This commit is contained in:
WuYongJie 2026-05-26 14:51:00 +08:00
parent c7d93111c4
commit b267b36486
7 changed files with 76 additions and 8 deletions

View File

@ -59,9 +59,11 @@ docs
*.md
!VERSION.txt
# 脚本(保留 docker-entrypoint.sh)
scripts
# 脚本(只保留生产构建和容器启动必需入口)
scripts/*
!scripts/docker-entrypoint.sh
!scripts/build-obfuscated.js
!scripts/obfuscate-config.js
# 临时文件
uploads

View File

@ -158,7 +158,7 @@ RUN if [ ! -d "node_modules/.prisma" ]; then \
fi
# 构建 Next.js 应用(standalone 模式)
# 商业交付镜像必须在构建阶段暴露失败,不能吞掉构建错误继续出包。
# 商业交付镜像必须完成服务端混淆;混淆失败不能继续出包。
RUN set -ex && \
if [ -f "${PRISMA_ENGINE_CACHE_PATH}" ]; then \
export PRISMA_QUERY_ENGINE_LIBRARY="${PRISMA_ENGINE_CACHE_PATH}" && \
@ -172,7 +172,7 @@ RUN if [ ! -d "node_modules/.prisma" ]; then \
else \
unset PRISMA_SCHEMA_ENGINE_BINARY; \
fi && \
npm run build
npm run build:prod
# 验证构建产物(允许 ESLint 警告,但不允许致命错误)
RUN if [ ! -f ".next/standalone/server.js" ]; then \

View File

@ -0,0 +1,37 @@
import fs from 'fs'
import path from 'path'
describe('production build hardening', () => {
const root = process.cwd()
const dockerfile = fs.readFileSync(path.join(root, 'Dockerfile'), 'utf8')
const dockerignore = fs.readFileSync(path.join(root, '.dockerignore'), 'utf8')
const buildScript = fs.readFileSync(path.join(root, 'scripts/build-obfuscated.js'), 'utf8')
it('Docker 生产镜像必须走混淆构建', () => {
expect(dockerfile).toContain('npm run build:prod')
expect(dockerfile).not.toMatch(/\bnpm run build\s*$/m)
})
it('Docker 构建上下文必须包含混淆脚本和配置', () => {
expect(dockerignore).toContain('scripts/*')
expect(dockerignore).toContain('!scripts/build-obfuscated.js')
expect(dockerignore).toContain('!scripts/obfuscate-config.js')
expect(dockerignore).toContain('!scripts/docker-entrypoint.sh')
})
it('混淆依赖缺失时不能静默跳过', () => {
expect(buildScript).toContain('生产混淆不能跳过')
expect(buildScript).toContain('process.exit(1)')
expect(buildScript).not.toContain('跳过混淆步骤')
})
it('混淆配置保持保守,避免破坏 Next.js 服务端产物', () => {
const { obfuscatorOptions } = require('../scripts/obfuscate-config')
expect(obfuscatorOptions.compact).toBe(true)
expect(obfuscatorOptions.stringArray).toBe(true)
expect(obfuscatorOptions.controlFlowFlattening).toBe(false)
expect(obfuscatorOptions.deadCodeInjection).toBe(false)
expect(obfuscatorOptions.renameGlobals).toBe(false)
})
})

View File

@ -1238,6 +1238,8 @@ NAS 私有化系统可能在平板或窄屏浏览器里使用。模板编辑器
- `docker-compose.source-test.yml` 的 MinIO 和 MinIO Client 默认值已改成固定版本,源码测试入口不再默认使用 `latest`。
- `docs/deployment/部署说明.md` 已把 `APP_IMAGE` 必填、HTTP Registry 前置配置、版本化发布命令和完整 `APP_IMAGE` 回滚写成主线步骤。
- `Dockerfile` 已取消 `npm run build` 失败后继续出镜像的逻辑;构建失败会直接阻断镜像产出。
- `Dockerfile` 已改为生产镜像执行 `npm run build:prod`,构建后会对服务端 chunks 和许可证 API 产物做混淆;混淆失败会直接阻断镜像产出,避免“脚本存在但客户镜像未启用”的防拆空档。
- `scripts/build-obfuscated.js`、`scripts/obfuscate-config.js`、`.dockerignore`:混淆依赖缺失不再静默跳过;Docker 构建上下文只放行生产构建和容器启动必需脚本,确保混淆配置能进入构建阶段。
- `deploy-new-fixed.ps1`、`发布管理.ps1`、`license-server/deploy.ps1` 已加默认停止闸门;不追加明确确认参数时不会继续执行旧服务器、旧 Harbor、许可证部署或 Docker 清理逻辑。
- `flnxi-agent/README.md` 已把 Node 版 Agent 定位改成历史实现说明,不再把 Docker 运行命令写成新客户推荐安装方式。

View File

@ -38,7 +38,7 @@ npm run verify:template-data
- `build-material-image.ps1`
构建主应用镜像。它属于历史/内部维护备用入口,保留旧的测试/正式分层规则,但仍带 Harbor / `latest` 旧口径;客户正式交付、升级、回滚优先用根目录的 `deploy-registry.ps1`,不要把它当成客户升级主线。若确需用它推送旧 Harbor,必须显式追加 `-AcknowledgeLegacyHarborPush`。
- `build-obfuscated.js`
生产构建后的混淆处理
生产构建后的服务端混淆处理。客户交付镜像通过 `Dockerfile` 执行 `npm run build:prod` 时会自动调用;混淆依赖或配置缺失会直接失败,不能静默跳过。
- `docker-entrypoint.sh`
主应用容器启动入口
- `generate-shared-test-env.ps1`

View File

@ -20,9 +20,9 @@ let JavaScriptObfuscator;
try {
JavaScriptObfuscator = require('javascript-obfuscator');
} catch {
console.log('⚠️ javascript-obfuscator 未安装,跳过混淆步骤');
console.log(' 如需启用混淆,请运行: npm install -D javascript-obfuscator');
process.exit(0);
console.error('❌ javascript-obfuscator 未安装,生产混淆不能跳过');
console.error(' 请先运行: npm install');
process.exit(1);
}
const { obfuscatorOptions } = require('./obfuscate-config');

View File

@ -0,0 +1,27 @@
/**
* Production obfuscation options.
*
* Keep this conservative: the goal is to raise reverse-engineering cost for
* server-side license and paid-feature code without making Next.js output
* brittle or dramatically increasing bundle size.
*/
const obfuscatorOptions = {
compact: true,
controlFlowFlattening: false,
deadCodeInjection: false,
debugProtection: false,
disableConsoleOutput: false,
identifierNamesGenerator: 'hexadecimal',
renameGlobals: false,
rotateStringArray: true,
selfDefending: false,
simplify: true,
splitStrings: false,
stringArray: true,
stringArrayEncoding: ['base64'],
stringArrayThreshold: 0.35,
transformObjectKeys: false,
unicodeEscapeSequence: false,
}
module.exports = { obfuscatorOptions }