Files
OpenClaw Bot 95a558acc9 Phase 7 Task 3: 数据安全与合规
- 创建 security_manager.py 安全模块
  - SecurityManager: 安全管理主类
  - 审计日志系统 - 记录所有数据操作
  - 端到端加密 - AES-256-GCM 加密项目数据
  - 数据脱敏 - 支持手机号、邮箱、身份证等敏感信息脱敏
  - 数据访问策略 - 基于用户、角色、IP、时间的访问控制
  - 访问审批流程 - 敏感数据访问需要审批

- 更新 schema.sql 添加安全相关数据库表
  - audit_logs: 审计日志表
  - encryption_configs: 加密配置表
  - masking_rules: 脱敏规则表
  - data_access_policies: 数据访问策略表
  - access_requests: 访问请求表

- 更新 main.py 添加安全相关 API 端点
  - GET /api/v1/audit-logs - 查询审计日志
  - GET /api/v1/audit-logs/stats - 审计统计
  - POST /api/v1/projects/{id}/encryption/enable - 启用加密
  - POST /api/v1/projects/{id}/encryption/disable - 禁用加密
  - POST /api/v1/projects/{id}/encryption/verify - 验证密码
  - GET /api/v1/projects/{id}/encryption - 获取加密配置
  - POST /api/v1/projects/{id}/masking-rules - 创建脱敏规则
  - GET /api/v1/projects/{id}/masking-rules - 获取脱敏规则
  - PUT /api/v1/masking-rules/{id} - 更新脱敏规则
  - DELETE /api/v1/masking-rules/{id} - 删除脱敏规则
  - POST /api/v1/projects/{id}/masking/apply - 应用脱敏
  - POST /api/v1/projects/{id}/access-policies - 创建访问策略
  - GET /api/v1/projects/{id}/access-policies - 获取访问策略
  - POST /api/v1/access-policies/{id}/check - 检查访问权限
  - POST /api/v1/access-requests - 创建访问请求
  - POST /api/v1/access-requests/{id}/approve - 批准访问
  - POST /api/v1/access-requests/{id}/reject - 拒绝访问

- 更新 requirements.txt 添加 cryptography 依赖

- 更新 STATUS.md 和 README.md 记录完成状态
2026-02-23 18:11:11 +08:00

197 lines
6.4 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.
// InsightFlow Chrome Extension - Content Script
// 在网页上下文中运行,负责提取页面内容
(function() {
'use strict';
// 避免重复注入
if (window.insightFlowInjected) return;
window.insightFlowInjected = true;
// 提取页面主要内容
function extractContent() {
const result = {
url: window.location.href,
title: document.title,
content: '',
html: document.documentElement.outerHTML,
meta: {
author: getMetaContent('author'),
description: getMetaContent('description'),
keywords: getMetaContent('keywords'),
publishedTime: getMetaContent('article:published_time') || getMetaContent('publishedDate'),
siteName: getMetaContent('og:site_name') || getMetaContent('application-name'),
language: document.documentElement.lang || 'unknown'
},
extractedAt: new Date().toISOString()
};
// 尝试提取正文内容
const article = extractArticleContent();
result.content = article.text;
result.contentHtml = article.html;
result.wordCount = article.text.split(/\s+/).length;
return result;
}
// 获取 meta 标签内容
function getMetaContent(name) {
const meta = document.querySelector(`meta[name="${name}"], meta[property="${name}"]`);
return meta ? meta.getAttribute('content') : '';
}
// 提取文章正文(使用多种策略)
function extractArticleContent() {
// 策略1使用 Readability 算法(简化版)
let bestElement = findBestElement();
if (bestElement) {
return {
text: cleanText(bestElement.innerText),
html: bestElement.innerHTML
};
}
// 策略2回退到 body 内容
const body = document.body;
return {
text: cleanText(body.innerText),
html: body.innerHTML
};
}
// 查找最佳内容元素(基于文本密度)
function findBestElement() {
const candidates = [];
const elements = document.querySelectorAll('article, [role="main"], .post-content, .entry-content, .article-content, #content, .content');
elements.forEach(el => {
const text = el.innerText || '';
const linkDensity = calculateLinkDensity(el);
const textDensity = text.length / (el.innerHTML.length || 1);
candidates.push({
element: el,
score: text.length * textDensity * (1 - linkDensity),
textLength: text.length
});
});
// 按分数排序
candidates.sort((a, b) => b.score - a.score);
return candidates.length > 0 ? candidates[0].element : null;
}
// 计算链接密度
function calculateLinkDensity(element) {
const links = element.getElementsByTagName('a');
let linkLength = 0;
for (let link of links) {
linkLength += link.innerText.length;
}
const textLength = element.innerText.length || 1;
return linkLength / textLength;
}
// 清理文本
function cleanText(text) {
return text
.replace(/\s+/g, ' ')
.replace(/\n\s*\n/g, '\n\n')
.trim();
}
// 高亮选中的文本
function highlightSelection() {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const selectedText = selection.toString().trim();
if (selectedText.length > 0) {
return {
text: selectedText,
context: getSelectionContext(range)
};
}
}
return null;
}
// 获取选中内容的上下文
function getSelectionContext(range) {
const container = range.commonAncestorContainer;
const element = container.nodeType === Node.TEXT_NODE ? container.parentElement : container;
return {
tagName: element.tagName,
className: element.className,
id: element.id,
surroundingText: element.innerText.substring(0, 200)
};
}
// 监听来自 background 的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'extractContent') {
const content = extractContent();
sendResponse({ success: true, data: content });
} else if (request.action === 'getSelection') {
const selection = highlightSelection();
sendResponse({ success: true, data: selection });
} else if (request.action === 'ping') {
sendResponse({ success: true, pong: true });
}
return true;
});
// 添加浮动按钮(可选)
function addFloatingButton() {
const button = document.createElement('div');
button.id = 'insightflow-clipper-btn';
button.innerHTML = '📎';
button.title = 'Clip to InsightFlow';
button.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background: #4CAF50;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
z-index: 999999;
font-size: 24px;
transition: transform 0.2s;
`;
button.addEventListener('mouseenter', () => {
button.style.transform = 'scale(1.1)';
});
button.addEventListener('mouseleave', () => {
button.style.transform = 'scale(1)';
});
button.addEventListener('click', () => {
chrome.runtime.sendMessage({ action: 'openClipper' });
});
document.body.appendChild(button);
}
// 如果启用,添加浮动按钮
chrome.storage.sync.get(['showFloatingButton'], (result) => {
if (result.showFloatingButton !== false) {
addFloatingButton();
}
});
console.log('[InsightFlow] Content script loaded');
})();