fix: deduplicate proxied security headers
This commit is contained in:
@@ -78,12 +78,17 @@ test('前端 package.json 与 lock 文件依赖同步且没有手工缓存版本
|
|||||||
test('生产部署统一使用 Vite dist 且不会删除 lock 文件', () => {
|
test('生产部署统一使用 Vite dist 且不会删除 lock 文件', () => {
|
||||||
const installer = fs.readFileSync(path.join(projectRoot, 'install.sh'), 'utf8');
|
const installer = fs.readFileSync(path.join(projectRoot, 'install.sh'), 'utf8');
|
||||||
const nginxConfig = fs.readFileSync(path.join(projectRoot, 'nginx', 'nginx.conf'), 'utf8');
|
const nginxConfig = fs.readFileSync(path.join(projectRoot, 'nginx', 'nginx.conf'), 'utf8');
|
||||||
|
const nginxExample = fs.readFileSync(path.join(projectRoot, 'nginx', 'nginx.conf.example'), 'utf8');
|
||||||
assert.ok(installer.includes('npm ci --include=dev'));
|
assert.ok(installer.includes('npm ci --include=dev'));
|
||||||
assert.ok(installer.includes('npm run build'));
|
assert.ok(installer.includes('npm run build'));
|
||||||
assert.ok(installer.includes('frontend/dist'));
|
assert.ok(installer.includes('frontend/dist'));
|
||||||
assert.strictEqual(/rm\s+-rf\s+node_modules\s+package-lock\.json/.test(installer), false);
|
assert.strictEqual(/rm\s+-rf\s+node_modules\s+package-lock\.json/.test(installer), false);
|
||||||
assert.ok(nginxConfig.includes('root /usr/share/nginx/html;'));
|
assert.ok(nginxConfig.includes('root /usr/share/nginx/html;'));
|
||||||
assert.ok(nginxConfig.includes('alias /runtime/downloads/;'));
|
assert.ok(nginxConfig.includes('alias /runtime/downloads/;'));
|
||||||
|
for (const config of [installer, nginxConfig, nginxExample]) {
|
||||||
|
assert.ok(config.includes('proxy_hide_header X-Request-ID;'));
|
||||||
|
assert.ok(config.includes('proxy_hide_header Content-Security-Policy;'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Docker 构建上下文排除依赖、环境配置和运行时数据', () => {
|
test('Docker 构建上下文排除依赖、环境配置和运行时数据', () => {
|
||||||
|
|||||||
63
install.sh
63
install.sh
@@ -2407,6 +2407,16 @@ server {
|
|||||||
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
||||||
|
|
||||||
|
# 对外安全头由 Nginx 统一输出,避免与后端重复
|
||||||
|
proxy_hide_header X-Frame-Options;
|
||||||
|
proxy_hide_header X-Content-Type-Options;
|
||||||
|
proxy_hide_header X-XSS-Protection;
|
||||||
|
proxy_hide_header Referrer-Policy;
|
||||||
|
proxy_hide_header Permissions-Policy;
|
||||||
|
proxy_hide_header Strict-Transport-Security;
|
||||||
|
proxy_hide_header X-Request-ID;
|
||||||
|
proxy_hide_header Content-Security-Policy;
|
||||||
|
|
||||||
# 隐藏Nginx版本号
|
# 隐藏Nginx版本号
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
|
||||||
@@ -2707,6 +2717,16 @@ server {
|
|||||||
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
||||||
|
|
||||||
|
# 对外安全头由 Nginx 统一输出,避免与后端重复
|
||||||
|
proxy_hide_header X-Frame-Options;
|
||||||
|
proxy_hide_header X-Content-Type-Options;
|
||||||
|
proxy_hide_header X-XSS-Protection;
|
||||||
|
proxy_hide_header Referrer-Policy;
|
||||||
|
proxy_hide_header Permissions-Policy;
|
||||||
|
proxy_hide_header Strict-Transport-Security;
|
||||||
|
proxy_hide_header X-Request-ID;
|
||||||
|
proxy_hide_header Content-Security-Policy;
|
||||||
|
|
||||||
# 隐藏Nginx版本号
|
# 隐藏Nginx版本号
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
|
||||||
@@ -2857,6 +2877,16 @@ server {
|
|||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
|
# 对外安全头由 Nginx 统一输出,避免与后端重复
|
||||||
|
proxy_hide_header X-Frame-Options;
|
||||||
|
proxy_hide_header X-Content-Type-Options;
|
||||||
|
proxy_hide_header X-XSS-Protection;
|
||||||
|
proxy_hide_header Referrer-Policy;
|
||||||
|
proxy_hide_header Permissions-Policy;
|
||||||
|
proxy_hide_header Strict-Transport-Security;
|
||||||
|
proxy_hide_header X-Request-ID;
|
||||||
|
proxy_hide_header Content-Security-Policy;
|
||||||
|
|
||||||
# 隐藏Nginx版本号
|
# 隐藏Nginx版本号
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
|
||||||
@@ -3621,6 +3651,35 @@ update_migrate_nginx_config() {
|
|||||||
mv "$secured_file" "$config_file"
|
mv "$secured_file" "$config_file"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "proxy_hide_header X-Request-ID" "$config_file"; then
|
||||||
|
local proxy_headers_file="${config_file}.proxy-headers.$$"
|
||||||
|
if ! awk '
|
||||||
|
/add_header Content-Security-Policy/ && !inserted_proxy_headers {
|
||||||
|
print
|
||||||
|
print ""
|
||||||
|
print " # 对外安全头由 Nginx 统一输出,避免与后端重复"
|
||||||
|
print " proxy_hide_header X-Frame-Options;"
|
||||||
|
print " proxy_hide_header X-Content-Type-Options;"
|
||||||
|
print " proxy_hide_header X-XSS-Protection;"
|
||||||
|
print " proxy_hide_header Referrer-Policy;"
|
||||||
|
print " proxy_hide_header Permissions-Policy;"
|
||||||
|
print " proxy_hide_header Strict-Transport-Security;"
|
||||||
|
print " proxy_hide_header X-Request-ID;"
|
||||||
|
print " proxy_hide_header Content-Security-Policy;"
|
||||||
|
inserted_proxy_headers = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END { if (!inserted_proxy_headers) exit 2 }
|
||||||
|
' "$config_file" > "$proxy_headers_file"; then
|
||||||
|
rm -f "$proxy_headers_file"
|
||||||
|
print_error "无法为 Nginx 配置添加上游响应头去重: $config_file"
|
||||||
|
cp -a "$backup_dir/$backup_name" "$config_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
mv "$proxy_headers_file" "$config_file"
|
||||||
|
fi
|
||||||
|
|
||||||
if ! grep -q "location /downloads/" "$config_file"; then
|
if ! grep -q "location /downloads/" "$config_file"; then
|
||||||
local migrated_file="${config_file}.migrate.$$"
|
local migrated_file="${config_file}.migrate.$$"
|
||||||
awk -v project_dir="$PROJECT_DIR" '
|
awk -v project_dir="$PROJECT_DIR" '
|
||||||
@@ -3638,7 +3697,9 @@ update_migrate_nginx_config() {
|
|||||||
mv "$migrated_file" "$config_file"
|
mv "$migrated_file" "$config_file"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! grep -q "root ${PROJECT_DIR}/frontend/dist;" "$config_file" || ! grep -q "location /downloads/" "$config_file"; then
|
if ! grep -q "root ${PROJECT_DIR}/frontend/dist;" "$config_file" || \
|
||||||
|
! grep -q "location /downloads/" "$config_file" || \
|
||||||
|
! grep -q "proxy_hide_header X-Request-ID" "$config_file"; then
|
||||||
print_error "无法安全迁移 Nginx 配置: $config_file"
|
print_error "无法安全迁移 Nginx 配置: $config_file"
|
||||||
cp -a "$backup_dir/$backup_name" "$config_file"
|
cp -a "$backup_dir/$backup_name" "$config_file"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ server {
|
|||||||
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
||||||
|
|
||||||
|
# 对外安全头由 Nginx 统一输出,避免与后端重复
|
||||||
|
proxy_hide_header X-Frame-Options;
|
||||||
|
proxy_hide_header X-Content-Type-Options;
|
||||||
|
proxy_hide_header X-XSS-Protection;
|
||||||
|
proxy_hide_header Referrer-Policy;
|
||||||
|
proxy_hide_header Permissions-Policy;
|
||||||
|
proxy_hide_header Strict-Transport-Security;
|
||||||
|
proxy_hide_header X-Request-ID;
|
||||||
|
proxy_hide_header Content-Security-Policy;
|
||||||
|
|
||||||
# 隐藏Nginx版本
|
# 隐藏Nginx版本
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,16 @@ server {
|
|||||||
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
add_header Permissions-Policy "camera=(), geolocation=(), microphone=()" always;
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; media-src 'self' blob: https:; connect-src 'self' https: ws: wss:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self';" always;
|
||||||
|
|
||||||
|
# 对外安全头由 Nginx 统一输出,避免与后端重复
|
||||||
|
proxy_hide_header X-Frame-Options;
|
||||||
|
proxy_hide_header X-Content-Type-Options;
|
||||||
|
proxy_hide_header X-XSS-Protection;
|
||||||
|
proxy_hide_header Referrer-Policy;
|
||||||
|
proxy_hide_header Permissions-Policy;
|
||||||
|
proxy_hide_header Strict-Transport-Security;
|
||||||
|
proxy_hide_header X-Request-ID;
|
||||||
|
proxy_hide_header Content-Security-Policy;
|
||||||
|
|
||||||
# 隐藏 Nginx 版本
|
# 隐藏 Nginx 版本
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user