fix(GOV-007): 收紧 Schema 基线安全门禁

This commit is contained in:
2026-08-13 13:12:58 +08:00
parent da056681b6
commit d827bfadb1
3 changed files with 107 additions and 6 deletions
+75 -1
View File
@@ -1,11 +1,13 @@
import assert from 'node:assert/strict';
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import test from 'node:test';
import {
assertMysql57Metadata,
assertNotApplicableMatchesDevelopmentConfiguration,
assertCurrentReleasedBaselinePublished,
assertSafeDevelopmentConfiguration,
buildCapturedArtifacts,
@@ -45,6 +47,16 @@ function makeTemporaryDirectory() {
return mkdtempSync(path.join(tmpdir(), 'a-share-schema-baseline-'));
}
function replaceCapturedSql(target, sql) {
const checksum = createHash('sha256').update(sql).digest('hex');
const manifestPath = path.join(target, 'manifest.yaml');
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
manifest.schema_sha256 = checksum;
writeFileSync(path.join(target, 'schema.sql'), sql);
writeFileSync(path.join(target, 'checksum.sha256'), `${checksum} schema.sql\n`);
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
}
test('拒绝任何非开发环境和非固定开发库配置', () => {
assert.throws(
() => assertSafeDevelopmentConfiguration({ ...validConfig, APP_ENV: 'production' }),
@@ -179,6 +191,41 @@ test('数据扫描拒绝同行顶层写入但保留例程中的合法 DML', () =
}
});
test('数据扫描拒绝所有顶层数据修改和装载语句', () => {
for (const statement of [
'UPDATE `sample` SET `id` = 2',
'DELETE FROM `sample`',
'TRUNCATE TABLE `sample`',
"LOAD DATA LOCAL INFILE '/tmp/sample.csv' INTO TABLE `sample`",
"LOAD XML LOCAL INFILE '/tmp/sample.xml' INTO TABLE `sample`",
]) {
assert.throws(() => normalizeSchemaSql(`${statement};\n`), /禁止的数据操作/);
}
});
test('安全扫描拒绝导出噪声、疑似凭据、连接串和生产主机', () => {
for (const unsafeSql of [
'-- API_TOKEN=supersecret\nCREATE TABLE `sample` (`id` int);\n',
'-- CLIENT_SECRET: supersecret\nCREATE TABLE `sample` (`id` int);\n',
'-- mysql://schema_reader:secret@127.0.0.1/a_share_analysis_dev\nCREATE TABLE `sample` (`id` int);\n',
'-- production host: prod-db.internal\nCREATE TABLE `sample` (`id` int);\n',
]) {
assert.throws(() => normalizeSchemaSql(unsafeSql), /Schema SQL 包含禁止内容/);
}
const root = makeTemporaryDirectory();
const target = path.join(root, 'v0.0.5');
persistBaselineArtifacts(
target,
buildCapturedArtifacts(validMetadata, 'CREATE TABLE `sample` (`id` int);\n'),
);
replaceCapturedSql(
target,
'-- MySQL dump 10.13 Distrib 5.7.44\nCREATE TABLE `sample` (`id` int);\n',
);
assert.throws(() => verifyBaselineDirectory(target, 'v0.0.5'), /MySQL dump/);
});
test('真实基线的清单、校验文件和 SQL 必须三方一致', () => {
const sql = 'CREATE TABLE `sample` (`id` bigint NOT NULL AUTO_INCREMENT);\n';
const root = makeTemporaryDirectory();
@@ -384,6 +431,33 @@ test('不适用清单拒绝未知对象键和伪造的格式元数据', () => {
assert.throws(() => verifyBaselineDirectory(target, 'v0.0.5'), /清单格式/);
});
test('不适用基线只在当前开发环境仍关闭 MySQL 时有效', () => {
const root = makeTemporaryDirectory();
const envDirectory = path.join(root, 'deploy', 'env', 'development');
const envFile = path.join(envDirectory, 'api.env.example');
const result = { status: 'not_applicable' };
mkdirSync(envDirectory, { recursive: true });
writeFileSync(
envFile,
'APP_ENV=development\nMYSQL_ENABLED=false\nMYSQL_DATABASE=a_share_analysis_dev\nMYSQL_CHARSET=utf8mb4\nMYSQL_COLLATION=utf8mb4_general_ci\n',
);
assert.doesNotThrow(() =>
assertNotApplicableMatchesDevelopmentConfiguration(root, result),
);
writeFileSync(
envFile,
Object.entries(validConfig)
.map(([key, value]) => `${key}=${value}`)
.join('\n'),
);
assert.throws(
() => assertNotApplicableMatchesDevelopmentConfiguration(root, result),
/已启用 MySQL.*not_applicable/,
);
});
test('严格发布门禁通过已提交且远程同步的当前基线', () => {
assert.doesNotThrow(() => assertCurrentReleasedBaselinePublished(process.cwd()));
});