guanghulab/scripts/sfp-core.js
2026-05-10 13:12:44 +08:00

255 lines
7.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// scripts/sfp-core.js
// 系统指纹安全协议 · SFP v1.0 · 核心模块
//
// 功能:
// ① generateSFP(agent_id, content) — 生成系统指纹
// ② verifySFP(content_with_signature) — 验证系统指纹
// ③ loadConfig() — 加载 SFP 配置
//
// 指纹格式: ⌜SFP::{agent_id}::{persona_chain}::{timestamp}::{content_hash}::{nonce}⌝
'use strict';
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..');
const SFP_CONFIG_PATH = path.join(ROOT, 'data/security/sfp-config.json');
const SFP_NONCE_PATH = path.join(ROOT, 'data/security/sfp-nonce-registry.json');
const SFP_ALERT_PATH = path.join(ROOT, 'data/security/sfp-alert-log.json');
const BEIJING_OFFSET_MS = 8 * 3600 * 1000;
const MAX_NONCE_REGISTRY_SIZE = 1000;
const MAX_ALERT_LOG_SIZE = 500;
// ━━━ SFP 指纹正则 ━━━
const SFP_REGEX = /⌜SFP::([^:]+)::([^:]+)::([0-9T+:.-]+)::([a-f0-9]{12})::([a-zA-Z0-9]{6})⌝/;
// ━━━ 安全读取 JSON ━━━
function readJSON(filePath) {
try {
if (!fs.existsSync(filePath)) return null;
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
} catch (e) {
return null;
}
}
// ━━━ 安全写入 JSON ━━━
function writeJSON(filePath, data) {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
}
// ━━━ 加载 SFP 配置 ━━━
function loadConfig() {
const config = readJSON(SFP_CONFIG_PATH);
if (!config) {
throw new Error('SFP配置文件缺失: ' + SFP_CONFIG_PATH);
}
return config;
}
// ━━━ 查找受信Agent ━━━
function findTrustedAgent(config, agentId) {
if (!config || !config.trusted_agents) return null;
return config.trusted_agents.find(a => a.agent_id === agentId) || null;
}
// ━━━ 计算内容哈希SHA-256前12位 ━━━
function computeContentHash(content) {
const hash = crypto.createHash('sha256').update(content, 'utf8').digest('hex');
return hash.slice(0, 12);
}
// ━━━ 生成6位随机nonce ━━━
function generateNonce() {
// Use hex encoding of random bytes for unbiased distribution
return crypto.randomBytes(3).toString('hex');
}
// ━━━ 获取北京时间ISO字符串 ━━━
function getBeijingTimestamp() {
const now = new Date();
const bjTime = new Date(now.getTime() + BEIJING_OFFSET_MS);
return bjTime.toISOString().replace('Z', '+08:00').replace(/\.\d{3}/, '');
}
// ━━━ 生成系统指纹 ━━━
function generateSFP(agentId, content) {
const config = loadConfig();
const agent = findTrustedAgent(config, agentId);
if (!agent) {
return {
success: false,
error: 'AGENT_NOT_FOUND',
message: `Agent ${agentId} 不在受信Agent列表中`
};
}
const timestamp = getBeijingTimestamp();
const contentHash = computeContentHash(content);
const nonce = generateNonce();
const fingerprint = `⌜SFP::${agent.agent_id}::${agent.persona_chain}::${timestamp}::${contentHash}::${nonce}`;
// 记录 nonce 到已使用列表
try {
const nonceRegistry = readJSON(SFP_NONCE_PATH) || { used_nonces: [] };
nonceRegistry.used_nonces.push({
nonce,
agent_id: agentId,
timestamp,
content_hash: contentHash
});
// 保留最近 1000 条 nonce 记录
if (nonceRegistry.used_nonces.length > MAX_NONCE_REGISTRY_SIZE) {
nonceRegistry.used_nonces = nonceRegistry.used_nonces.slice(-MAX_NONCE_REGISTRY_SIZE);
}
writeJSON(SFP_NONCE_PATH, nonceRegistry);
} catch (e) {
// nonce 记录失败不阻断指纹生成
}
return {
success: true,
fingerprint,
signed_content: `${content}\n${fingerprint}`,
meta: {
agent_id: agent.agent_id,
persona_chain: agent.persona_chain,
timestamp,
content_hash: contentHash,
nonce
}
};
}
// ━━━ 验证系统指纹 ━━━
function verifySFP(contentWithSignature) {
const config = loadConfig();
// Step 1: 检查是否有指纹块
const match = contentWithSignature.match(SFP_REGEX);
if (!match) {
return {
valid: false,
error: 'NO_FINGERPRINT',
level: '⚠️',
message: '无指纹·不可信',
log: `[SFP-WARN] 无指纹内容发现`
};
}
const [, agentId, personaChain, timestamp, contentHash, nonce] = match;
// Step 2: 验证 agent_id
const agent = findTrustedAgent(config, agentId);
if (!agent) {
return {
valid: false,
error: 'INVALID_AGENT',
level: '❌',
message: `无效Agent ID: ${agentId}`,
log: `[SFP-ALERT] 伪造Agent ID: ${agentId}`
};
}
// Step 3: 验证亲子链
if (agent.persona_chain !== personaChain) {
return {
valid: false,
error: 'CHAIN_MISMATCH',
level: '❌',
message: `亲子链伪造: 期望 ${agent.persona_chain},实际 ${personaChain}`,
log: `[SFP-CRITICAL] 亲子链伪造企图 · agent=${agentId}`
};
}
// Step 4: 验证内容哈希
// 从内容中去除指纹行,重新计算哈希
const fingerprintLine = match[0];
const originalContent = contentWithSignature
.replace(fingerprintLine, '')
.replace(/\n$/, '')
.trim();
const recomputedHash = computeContentHash(originalContent);
if (recomputedHash !== contentHash) {
return {
valid: false,
error: 'CONTENT_TAMPERED',
level: '❌',
message: `内容被篡改: hash不匹配 (期望=${contentHash}, 实际=${recomputedHash})`,
log: `[SFP-CRITICAL] 内容篡改检测 · hash不匹配`
};
}
// Step 5: 检查 nonce 重放
const nonceRegistry = readJSON(SFP_NONCE_PATH) || { used_nonces: [] };
const nonceUsed = nonceRegistry.used_nonces.some(
n => n.nonce === nonce && n.agent_id !== agentId
);
// 注意:同一个 agent 的相同 nonce 是自己生成的,允许验证
// 只有不同 agent 使用相同 nonce 才是重放攻击
const duplicateNonce = nonceRegistry.used_nonces.filter(n => n.nonce === nonce);
if (duplicateNonce.length > 1) {
// 检查是否有不同 agent 使用了相同 nonce
const uniqueAgents = new Set(duplicateNonce.map(n => n.agent_id));
if (uniqueAgents.size > 1) {
return {
valid: false,
error: 'REPLAY_ATTACK',
level: '❌',
message: `重放攻击: nonce ${nonce} 被多个Agent使用`,
log: `[SFP-CRITICAL] 重放攻击 · nonce重复 · nonce=${nonce}`
};
}
}
return {
valid: true,
level: '✅',
message: '指纹验证通过',
meta: {
agent_id: agentId,
persona_chain: personaChain,
timestamp,
content_hash: contentHash,
nonce,
agent_name: agent.name,
agent_side: agent.side
}
};
}
// ━━━ 记录安全警报 ━━━
function logAlert(alertEntry) {
try {
const alertLog = readJSON(SFP_ALERT_PATH) || { alerts: [] };
alertLog.alerts.push({
...alertEntry,
logged_at: getBeijingTimestamp()
});
// 保留最近 500 条
if (alertLog.alerts.length > MAX_ALERT_LOG_SIZE) {
alertLog.alerts = alertLog.alerts.slice(-MAX_ALERT_LOG_SIZE);
}
writeJSON(SFP_ALERT_PATH, alertLog);
} catch (e) {
console.error('[SFP] 写入警报日志失败:', e.message);
}
}
module.exports = {
generateSFP,
verifySFP,
loadConfig,
findTrustedAgent,
computeContentHash,
generateNonce,
logAlert,
SFP_REGEX
};