Skill: kuihuar/3d-guard (version 1.0.0)

--- name: 3d-guard description: 3D模型审计技能，用于检查STL、OBJ、3MF等3D模型文件的合规性。使用时需要：(1)验证文件完整性和格式有效性，(2)检测潜在的版权侵权内容（知名IP角色、品牌等），(3)识别政治敏感或不当内容，(4)检查模型尺寸是否超出目标3D打印机的打印范围。适用于任何3D模型文件审计场景。 --- # 3D Guard - 3D模型审计技能 ## 概述 本技能用于系统化地审计3D模型文件，确保其在使用前符合版权、内容安全、技术规格等要求。支持常见3D模型格式（STL、OBJ、3MF等），并可通过多模态视觉模型和文本分析进行综合检查。 ## 快速开始 **基本审计流程：** ``` 1. 文件完整性检查 → 2. 元数据快速分析 → 3. 尺寸验证 → 4. 深度检查（如需要） → 5. 生成审计报告 ``` **优化策略：优先元数据分析** - 元数据（Title、Description、Author等）通常能快速判断合规性 - 如果元数据**明确显示不合规** → 直接得出结论，无需进一步分析 - 如果元数据**模糊不清或可疑** → 才进行图像分析等深度检查 - 这样可以大幅提高审计效率，减少不必要的API调用 ## 核心审计任务 ### 1. 文件完整性检查 **目标：** 验证3D模型文件的完整性和格式有效性 **检查项：** - 文件是否能正常解析（不损坏、不截断） - 文件格式符合标准（STL二进制/ASCII、OBJ、3MF等） - 几何数据完整性（顶点、面片、法线等） - 纹理和材质文件是否存在（如OBJ文件引用的纹理） **工具：** - 使用 `scripts/validate_3d_model.py` 进行自动验证 - 手动检查文件头部和结构 **常见问题：** - 文件损坏或截断 → 需要重新下载或修复 - 缺少关联文件（纹理、材质） → 需要获取完整文件包 - 格式不规范 → 可能需要转换或修复 ### 2. 尺寸验证 **目标：** 确保模型尺寸适合目标3D打印机 **检查流程：** 1. 解析模型边界框（bounding box） 2. 计算X、Y、Z三个维度的最大尺寸 3. 与目标打印机规格对比 **常见打印机规格参考：** | 打印机类型 | 构建体积 (mm) | 备注 | |-----------|--------------|------| | 桌面级FDM | 200×200×200 | 标准入门级 | | 桌面级SLA | 120×68×160 | 小体积高精度 | | 中级FDM | 300×300×400 | 中等尺寸 | | 工业级 | 500×500×500+ | 大尺寸打印 | **超出范围处理：** - 稍微超出（<10%）→ 可考虑缩放或分割 - 严重超出（>50%）→ 需要大尺寸打印机或完全重新设计 - 可旋转打印 → 检查长边是否能放入构建平台 ### 3. 版权侵权检测 **目标：** 识别模型是否包含可能侵犯版权的内容 **检测范围：** - **知名IP角色：** 迪士尼、漫威、星球大战、任天堂、宝可梦等 - **潮玩品牌IP：** POP MART（泡泡玛特）- Molly、Dimoo、Skullpanda、Labubu等 - **品牌标志：** Nike、Adidas、Apple、Tesla等知名品牌 - **受保护的设计：** 商业产品、艺术作品、游戏道具等 **检测方法（优先级从高到低）：** **第一优先级：元数据快速分析** ⚡️ ```bash # 检查模型元数据中的文本信息 # Title、Description、Author、License等字段 ``` **检查内容：** - **Title字段：** 是否直接包含IP名称（如"米老鼠"、"马里奥"） - **Description字段：** 是否明确描述版权内容 - **License字段：** 是否有明确的授权声明 - **Author信息：** 是否为原创作者 **判断规则：** - ✅ **元数据明确显示IP侵权** → 直接判定高风险，无需进一步分析 - ❓ **元数据模糊或可疑** → 进行第二级检查 - ✅ **元数据显示原创** → 进入风险等级评估 - 🔴 **元数据不完整** → 如果Title、Description、License三项全部为空，直接判定为不合规 **合规性验证（重要）：** ``` 必须满足的条件： ✅ Title字段不为空 ✅ Description字段不为空 ✅ License字段不为空 如果不满足以上条件： 🔴 直接判定为“元数据不完整-不合规” 停止进一步分析，拒绝使用 ``` --- **第二优先级：文件名和路径分析** 🔍 - 文件名是否包含版权名称（如 "mario", "mickey", "marvel", "molly", "dimoo", "popmart"） - 文件路径是否暗示版权内容（如包含IP名称的文件夹） - 是否包含潮玩品牌关键词（"molly", "labubu", "skullpanda", "pucky", "popmart"） --- **第三优先级：文档和描述检查** 📄 - 模型是否附带说明文档 - 是否有授权声明或CC许可证信息 - 描述中是否引用版权内容 --- **第四优先级：视觉分析（多模态模型）** 🖼️ （仅在需要时） ```bash # 对模型渲染图或截图进行视觉分析 # 仅在元数据模糊不清时使用 ``` **使用场景：** - 元数据过于通用（如"卡通角色"、"有趣的设计"） - 元数据与文件名不一致 - 需要验证元数据的真实性 - 可疑的原创声明 **重点关注的视觉特征：** - 特定的角色造型和标志性特征 - 品牌logo、标志、字体 - 商业产品的独特设计元素 **潮玩品牌识别特征（以POP MART为例）：** - **Q版比例：** 头大身小，3:1到4:1的比例 - **眼睛设计：** 特别大的眼睛（通常占面部1/2-1/3） - **色彩风格：** 梦幻、马卡龙、糖果色系 - **细节特征：** 特定的发型、服装花纹、配饰 - **盲盒造型：** 标志性的站立姿势，可爱表情 **风险等级评估：** - **高风险：** 明确是知名IP的复制品（如完整的任天堂角色） - **中风险：** 灵感来源于版权作品但有一定原创性 - **低风险：** 通用设计、原创作品或明显在合理使用范围内 ### 4. 政治敏感性分析 **目标：** 识别模型是否包含政治敏感、暴力或不当内容 **检查类别：** - 政治人物、符号、标志 - 政治标语、口号 - 极端主义或仇恨相关符号 - 武器、军事装备（特别是现实主义武器） - 不当、暴力或恐怖主题 **检测方法（优先级从高到低）：** **第一优先级：元数据快速分析** ⚡️ ```bash # 检查模型元数据中的文本信息 # Title、Description、Author、DesignRegion等字段 ``` **检查内容：** - **Title字段：** 是否直接包含政治人物名称 - **Description字段：** 是否明确描述政治相关内容 - **DesignRegion/Author：** 判断来源和背景 **判断规则：** - ✅ **元数据明确显示政治敏感内容** → 直接判定高风险，无需进一步分析 - ❓ **元数据模糊不清** → 进行第二级检查 - ✅ **元数据显示非政治内容** → 进入风险评估 --- **第二优先级：文件名和路径分析** 🔍 - 文件名是否包含政治人物名称或敏感词 - 文件路径是否暗示敏感内容 --- **第三优先级：视觉分析** 🖼️ （仅在需要时） ```bash # 对模型渲染图或截图进行视觉分析 # 仅在元数据模糊不清时使用 ``` **使用场景：** - 元数据过于通用（如"人物头像"、"雕像"） - 元数据与文件名不一致 - 需要验证元数据的真实性 --- **第四优先级：上下文评估** 📊 - 结合模型用途判断合理性 - 考虑设计时间和背景 - 评估作者和来源可信度 **合理例外：** - 教育用途的通用历史模型 - 艺术表达（需谨慎评估） - 个人非政治用途 ### 5. 审计报告生成 **报告结构（优化版）：** ```markdown # 3D模型审计报告 ## 基本信息 - 模型文件：[文件名] - 文件格式：[STL/OBJ/3MF] - 文件大小：[大小] - 审计时间：[时间戳] ## 审计结果 ### 1. 文件完整性 ✅/❌ [结果详情] ### 2. 元数据分析 ⚡️ 快速检查 **首先验证元数据合规性：** - **Title状态：** [存在/缺失] - **Description状态：** [存在/缺失] - **License状态：** [存在/缺失] - **合规性：** [合规/不合规-元数据缺失] **如果不合规（元数据缺失）：** - **风险等级：** 🔴 高风险 - **结论：** 直接拒绝使用，无需进一步分析 - **理由：** 缺少必须的元数据字段 - **建议：** 要求模型提供者补充完整的元数据，或拒绝使用 **如果合规：继续分析** - **Title：** [元数据内容] - **Description：** [元数据内容] - **License：** [许可证信息] - **判断：** [明确/模糊/原创] ### 3. 尺寸验证 ✅/❌ - 模型尺寸：[X×Y×Z mm] - 目标打印机：[打印机型号] - 构建体积：[体积] - 结论：[是否符合] ### 4. 元数据合规性检查 🔴 - **Title状态：** [存在/缺失] - **Description状态：** [存在/缺失] - **License状态：** [存在/缺失] - **合规性：** [合规/不合规-元数据缺失] **如果不合规（元数据缺失）：** - **风险等级：** 🔴 高风险 - **理由：** 缺少必须的元数据（Title、Description、License） - **建议：** 要求模型提供者补充完整的元数据，或拒绝使用 ### 5. 版权检测 ⚠️ - 风险等级：[高/中/低] - 检测到的内容：[具体描述] - 检测依据：[元数据/视觉分析/两者结合] - 建议：[使用建议] ### 6. 政治敏感性 ✅/⚠️ - 检测结果：[内容描述] - 风险评估：[低/中/高] - 检测依据：[元数据/视觉分析/两者结合] ## 总体建议 [综合建议和使用限制] ``` ## 工作流程 ### 完整审计流程 ```mermaid graph TD A[接收3D模型] --> B[文件完整性检查] B -->|文件有效| C[尺寸验证] B -->|文件损坏| D[报告问题并停止] C -->|尺寸合规| E[版权检测] C -->|尺寸超限| F[标记需缩放/分割] E --> G[政治敏感性分析] F --> G G --> H[生成审计报告] ``` ### 条件决策树 **何时需要进行完整审计？** - 准备公开发布3D模型 - 计划商业化使用模型 - 模型来源不明或不可信 - 组织内部有合规要求 **何时可以简化审计？** - 自己创建的原创模型 - 已知可信来源的模型 - 个人非商业使用 ## 使用场景示例 ### 场景1：元数据不合规（新增） ``` 用户："检查这个3D模型的合规性" → 运行元数据验证：validate_metadata.py → 发现：缺少Title、Description、License → 结论：元数据不合规，直接拒绝 → 建议：要求补充元数据或拒绝使用 → 无需进行后续的版权/政治敏感性分析 ``` ### 场景2：社区分享前的审计 ``` 用户："我刚设计了一个3D模型，想在社区分享，帮我检查一下" → 运行完整审计流程 → 首先验证元数据合规性 → 通过后检查版权和政治敏感性 → 生成详细报告供用户参考 ``` ### 场景3：生产前的尺寸验证 ``` 用户："这个STL文件能用我的Ender 3打印吗？" → 简化审计：重点检查尺寸 → 与220×220×250mm构建体积对比 → 直接给出是否可打印的结论 ``` ### 场景4：版权风险评估 ``` 用户："这个模型看起来有点像任天堂的角色，能用吗？" → 优先检查元数据（Title、Description） → 如果元数据模糊 → 使用多模态模型分析视觉相似度 → 提供风险等级和法律建议 ``` ### 场景5：元数据明确的情况 ``` 用户："检查这个模型的合规性" → 发现元数据 Title: "米老鼠米奇" → 结论：明确版权侵权，无需进一步分析 → 直接给出风险评级和建议 ``` ### 场景6：元数据模糊的情况 ``` 用户："这个卡通模型有问题吗？" → 元数据 Title: "有趣的卡通角色" → 元数据不够明确，需要视觉分析 → 使用多模态模型判断是否是知名IP → 综合评估风险 ``` ## 参考资源 ### scripts/ **validate_metadata.py** - 元数据合规性验证脚本 ⭐ 新增 - 验证3MF文件的元数据完整性 - 检查必须字段：Title、Description、License - 提取所有可用的元数据字段 使用方法： ```bash python3 scripts/validate_metadata.py /path/to/model.3mf ``` 输出： ```json { "format": "3MF", "metadata": { "title": "模型名称", "description": "模型描述", "license": "许可证类型", "author": "作者" }, "compliance": { "is_compliant": true/false, "required_fields": { "title": {"exists": true/false, "value": "..."}, "description": {"exists": true/false, "value": "..."}, "license": {"exists": true/false, "value": "..."} }, "missing_fields": ["Title", "Description", "License"], "reason": "缺少必须的元数据字段" } } ``` **合规性规则：** - ✅ **合规：** Title、Description、License三个字段全部存在且非空 - 🔴 **不合规：** 任意一个或多个字段缺失或为空 --- **validate_3d_model.py** - 3D模型验证脚本 - 自动验证文件完整性 - 解析模型尺寸 - 提取基本元数据（STL/OBJ） 使用方法： ```bash python3 scripts/validate_3d_model.py /path/to/model.stl ``` 输出： ```json { "valid": true, "format": "STL", "size": {"x": 150.5, "y": 120.3, "z": 80.2}, "vertices": 12580, "faces": 25160 } ``` ### references/ **printer-specs.md** - 常见3D打印机规格参考 - 主流打印机构建体积 - 技术参数对比 - 适用场景说明 **copyright-guidelines.md** - 版权检查指南 - 主要IP列表和特征 - 检查要点和方法 - 合理使用原则 **sensitive-content.md** - 敏感内容识别参考 - 政治敏感内容分类 - 常见符号和标志 - 评估标准 ## 注意事项 1. **法律免责声明：** 本技能提供的审计仅供参考，不能替代专业法律意见 2. **元数据优先原则：** 优先使用元数据进行快速判断，节省资源和时间 3. **误判可能性：** AI检测可能存在误判，需要人工复核高风险项目 4. **模型隐私：** 审计过程中不要泄露用户模型文件或敏感信息 5. **持续更新：** 版权和敏感性标准会变化，需要定期更新参考数据 ## 优化说明（重要） **为什么优先元数据分析？** - ✅ **速度快**：文本分析比图像分析快得多 - ✅ **准确性高**：作者在元数据中通常会诚实地标注内容 - ✅ **成本低**：减少不必要的API调用和计算资源 - ✅ **效率高**：对于明确违规的模型，可以立即得出结论 **元数据合规性验证（新增）：** - 🔴 **强制要求**：3MF文件必须包含Title、Description、License三个字段 - 🚫 **不合规即拒绝**：缺少任意一个字段，直接判定为不合规 - ✅ **快速筛选**：可以在第一时间淘汰不合格的模型 - ⚡ **提升效率**：避免对不合格模型进行不必要的深度分析 **什么时候跳过元数据检查？** - STL/OBJ格式（不支持内嵌元数据） - 元数据字段缺失或损坏（已判定为不合规） - 需要验证元数据的真实性（怀疑被修改） **元数据不可靠的情况：** - 恶意修改元数据来隐藏真实内容 - 下载的模型元数据被篡改 - 不完整的元数据（已判定为不合规） ## 最佳实践 1. **先元数据后深度检查：** 优先使用元数据分析快速判断，只在必要时进行深度检查 2. **明确说明检测依据：** 在报告中清楚说明是基于元数据还是视觉分析 3. **保留审计记录：** 对重要模型保留审计报告和时间戳 4. **上下文很重要：** 结合模型用途和场景评估风险 5. **透明沟通：** 向用户清楚说明审计结果的不确定性 6. **效率优先：** 当元数据已明确时，不要进行不必要的图像分析

path: package.json
content: {
  "name": "3d-guard",
  "displayName": "3D模型审计技能",
  "version": "1.0.0",
  "description": "3D模型审计技能，用于检查STL、OBJ、3MF等3D模型文件的合规性。支持版权检测、政治敏感性分析、尺寸验证、元数据合规性验证等。",
  "keywords": [
    "3d-printing",
    "model-audit",
    "copyright-check",
    "compliance",
    "stl",
    "obj",
    "3mf"
  ],
  "changelog": "🎉 初始发布：完整的3D模型审计功能，支持多种格式和检测维度",
  "slug": "3d-guard",
  "author": "OpenClaw User",
  "license": "MIT"
}


path: publish.sh
content: #!/bin/bash
# 简化的Skill发布脚本
# 使用说明：./publish.sh

# 获取脚本所在目录
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SKILL_DIR="${SCRIPT_DIR}"

cd "$SKILL_DIR" || exit 1

# 尝试从package.json读取
if [ -f "package.json" ]; then
    SLUG=$(cat package.json | grep -o '"slug":[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
    NAME=$(cat package.json | grep -o '"displayName":[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
    VERSION=$(cat package.json | grep -o '"version":[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
    CHANGELOG=$(cat package.json | grep -o '"changelog":[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)".*/\1/')
fi

# 如果没有package.json，从SKILL.md读取（仅name）
if [ -z "$SLUG" ] && [ -f "SKILL.md" ]; then
    SLUG=$(grep "^name:" SKILL.md | cut -d':' -f2 | xargs)
    NAME=$(grep "^description:" SKILL.md | head -1 | sed 's/description: //' | head -c 100)
fi

# 设置默认值
SLUG=${SLUG:-"my-skill"}
NAME=${NAME:-"My Skill"}
VERSION=${VERSION:-"1.0.0"}
CHANGELOG=${CHANGELOG:-"Update"}

# 显示将要发布的信息
echo "========================================="
echo "准备发布Skill："
echo "========================================="
echo "Slug:      $SLUG"
echo "Name:       $NAME"
echo "Version:    $VERSION"
echo "Changelog:   $CHANGELOG"
echo "========================================="
echo ""

# 执行发布命令
echo "正在发布..."
clawhub publish . \
    --slug "$SLUG" \
    --name "$NAME" \
    --version "$VERSION" \
    --changelog "$CHANGELOG"


path: references/copyright-guidelines.md
content: # 版权检查指南

本文件提供3D模型版权检测的指导原则和方法。

## 主要版权持有者

### 娱乐公司
| 公司 | 知名IP | 识别特征 |
|------|--------|---------|
| Disney | 米老鼠、迪士尼公主、漫威、星球大战、皮克斯角色 | 特定造型、标志配色、角色设计 |
| Marvel | 蜘蛛侠、钢铁侠、美国队长等 | 标志性服装、武器、Logo |
| DC Comics | 蝙蝠侠、超人、神奇女侠 | 标志性符号（蝙蝠标、S形） |
| Nintendo | 马里奥、宝可梦、塞尔达 | 可爱风格、特定配色 |
| Warner Bros | 哈利波特、DC角色 | 特定道具、符号 |
| Lucasfilm | 星战角色、宇宙飞船 | 科幻风格、光剑等 |

### 潮玩品牌
| 品牌 | 知名IP | 识别特征 | 风险等级 |
|------|--------|---------|---------|
| POP MART (泡泡玛特) | Molly、Dimoo、Skullpanda、Labubu、Pucky、Hirono | Q版可爱风格、大眼睛、梦幻配色、标志性盲盒造型 | 高 |

### 品牌
| 品牌 | 识别特征 | 风险等级 |
|------|---------|---------|
| Apple | 苹果Logo、特定产品设计 | 高 |
| Nike | 勾形Logo、特定鞋款 | 中高 |
| Adidas | 三条纹Logo、特定鞋款 | 中高 |
| Tesla | Tesla T形Logo、汽车设计 | 中 |
| LEGO | 积木外观、特定拼接方式 | 中高 |

### 游戏公司
| 公司 | 知名IP | 识别特征 |
|------|--------|---------|
| Valve | 半条命、传送门 | 特定角色、武器设计 |
| Bethesda | 辐射、上古卷轴 | 标志性符号、物品 |
| Blizzard | 魔兽世界、暗黑破坏神 | 特定风格、图标 |
| Rockstar | GTA、荒野大镖客 | 特定角色、载具 |

## 检查方法

### 1. 视觉特征检查

**角色识别特征：**
- **整体轮廓：** 角色的标志性剪影
- **配色方案：** 特定的颜色组合（如马里奥的红蓝）
- **标志性元素：** 特定的服装、武器、配件
- **风格特征：** 特定的艺术风格（如任天堂的可爱风格）

**潮玩品牌识别特征（以POP MART为例）：**
- **Q版比例：** 头大身小，3:1到4:1的比例
- **眼睛设计：** 特别大的眼睛（通常占面部1/2-1/3）
- **色彩风格：** 梦幻、马卡龙、糖果色系
- **细节特征：** 特定的发型、服装花纹、配饰
- **盲盒造型：** 标志性的站立姿势，可爱表情

**POP MART主要IP识别：**
- **Molly：** 额头有刘海，小男孩形象，蓝色/绿色眼睛
- **Dimoo：** 小蘑菇发型，大眼睛，呆萌表情
- **Skullpanda：** 骷髅元素+可爱风格，梦幻色彩
- **Labubu：** 兔子耳朵，大嘴巴，尖牙设计
- **Pucky：** 精灵风格，翅膀元素，梦幻造型
- **Hirono：** 悲伤小丑形象，特殊表情
- **Yuki：** 熊猫造型，大眼睛
- **Pony：** 独角兽/小马元素

**品牌识别特征：**
- **Logo：** 商标标识
- **产品设计：** 独特的产品外形
- **字体：** 特定的字体样式
- **包装风格：** 特定的包装设计

**潮玩品牌识别特征（POP MART）：**
- **品牌Logo：** 英文"POP MART"或"泡泡玛特"字样
- **盲盒特征：** 标志性的盲盒包装设计
- **限量标识：** 特别版、限定版标签
- **品牌元素：** 独特的背景卡片、内盒设计

### 2. 文件名和元数据分析

**高风险文件名模式：**
- 包含品牌名：`nike_shoe.stl`, `apple_logo.obj`, `popmart_molly.stl`
- 包含角色名：`mario.stl`, `darth_vader.obj`, `molly_v2.obj`, `dimoo_panda.obj`
- 包含IP引用：`disney_castle.stl`, `pokemon_ball.obj`, `popmart_skullpanda.obj`
- 包含游戏名：`halo_cortana.stl`, `gta_car.obj`
- 包含潮玩IP：`molly.blindbox.stl`, `labubu.obj`, `pucky_3d.stl`

**元数据检查点：**
- 作者信息
- 创建时间
- 修改历史
- 版权声明

### 3. 描述和文档检查

**需要寻找的内容：**
- 明确的版权声明
- 授权许可类型（CC、MIT等）
- 原作者信息
- 修改历史

**合法使用的标识：**
- Creative Commons许可证
- MIT许可证
- 明确的授权声明
- 原创声明

### 4. 来源分析

**低风险来源：**
- 官方授权网站
- 明确标注原创的发布
- 知名的开源3D模型库（如Thingiverse、Printables的原创作品）

**高风险来源：**
- 无授权的知名IP复制品
- 缺少作者信息的分享
- 来历不明的文件

## 风险等级评估

### 高风险（立即避免）
- 完整复制知名角色（如完整的马里奥、漫威英雄）
- 商标LOGO的直接复制
- 明确侵犯专利的设计

**处理建议：**
- 拒绝使用
- 建议联系原作者获取授权

### 中风险（谨慎使用）
- 灵感来源于版权作品但有一定原创性
- 部分元素参考知名IP
- 修改过的版权作品

**处理建议：**
- 评估原创性和修改程度
- 考虑是否属于合理使用
- 非商业个人使用风险较低

### 低风险（可考虑使用）
- 完全原创设计
- 通用设计元素
- 明确授权的作品
- 公有领域内容

**处理建议：**
- 仍需验证授权
- 保留原作者信息
- 遵守许可证条款

## 合理使用原则

### 可能有合理使用的情况：
1. **教育目的：** 用于教学、研究
2. **评论和批评：** 评论原作品
3. **新闻报道：** 新闻报道中的使用
4. ** parody/讽刺：** 讽刺性改编
5. **转化性使用：** 有显著创新的重新创作

### 不属于合理使用的情况：
1. **商业用途：** 未经授权的商业化使用
2. **竞争替代：** 直接竞争原作品
3. **大量使用：** 使用原作品的核心部分

## 检查清单

### 视觉检查
- [ ] 是否识别出已知角色或品牌？
- [ ] 是否包含商标或Logo？
- [ ] 是否有特定的配色方案？
- [ ] 整体风格是否与知名作品相似？

### 文件检查
- [ ] 文件名是否包含版权名称？
- [ ] 元数据是否有版权声明？
- [ ] 是否有授权许可证？

### 描述检查
- [ ] 作者是否声明原创？
- [ ] 是否有明确的授权信息？
- [ ] 使用范围是否有限制？

### 来源检查
- [ ] 来源是否可靠？
- [ ] 是否有原作者授权？
- [ ] 是否符合平台政策？

## 法律免责声明

**重要提示：**
1. 本指南仅供参考，不能替代专业法律意见
2. 版权法因地区而异
3. AI检测存在误判可能
4. 合理使用的边界很模糊

**当遇到不确定情况时：**
- 寻求专业法律建议
- 联系原作者获取授权
- 避免使用有争议的模型

## 常见问题

### Q: 可以打印别人的3D模型用于个人使用吗？
A: 通常可以，但需要确认模型的使用许可证。有些许可证禁止任何形式的打印或修改。

### Q: 可以修改他人的模型并分享吗？
A: 取决于原模型的许可证。需要遵循原始许可证的要求。

### Q: 如何获取授权？
A: 直接联系原作者或版权持有者，说明使用意图和范围。

### Q: CC-BY-SA许可证意味着什么？
A: 需要署名作者，并且衍生作品必须使用相同的许可证。

### Q: 可以把知名IP的角色改成不同风格吗？
A: 仍然可能构成侵权。需要评估修改程度和使用目的。

## 参考资源

- **美国版权法：** https://www.copyright.gov/
- **Creative Commons：** https://creativecommons.org/
- **Thingiverse条款：** https://www.thingiverse.com/about/terms
- **Printables条款：** https://www.printables.com/terms


path: references/printer-specs.md
content: # 常见3D打印机规格参考

本文件提供主流3D打印机的技术规格，用于模型尺寸验证。

## 桌面级FDM打印机

### 入门级
| 型号 | 构建体积 (X×Y×Z mm) | 平台类型 | 特点 |
|------|-------------------|----------|------|
| Ender 3 / Ender 3 V2 | 220×220×250 | 热床 | 最受欢迎入门机 |
| CR-10 | 300×300×400 | 热床 | 稳定性好 |
| Prusa i3 MK3 | 250×210×210 | PEI | 高精度、可靠 |
| Anycubic i3 Mega | 220×220×205 | 热床 | 性价比高 |

### 中级
| 型号 | 构建体积 (X×Y×Z mm) | 平台类型 | 特点 |
|------|-------------------|----------|------|
| Prusa XL | 360×360×360 | PEI | 大体积、高精度 |
| Bambu Lab X1C | 256×256×256 | PEI | 多色、快速 |
| Voron 2.4 | 250×250×250 | PEI | 高速、开源 |
| Creality CR-6 SE | 235×235×250 | 热床 | 自调平 |

### 大尺寸
| 型号 | 构建体积 (X×Y×Z mm) | 平台类型 | 特点 |
|------|-------------------|----------|------|
| Creality CR-10 S5 | 500×500×500 | 热床 | 超大体积 |
| Anycubic Chiron | 400×400×455 | 热床 | 稳定大型打印 |
| Artillery Sidewinder X2 | 300×300×400 | 热床 | 直驱挤出 |

## 桌面级SLA/DLP打印机

### 入门级
| 型号 | 构建体积 (X×Y×Z mm) | 分辨率 | 特点 |
|------|-------------------|--------|------|
| Anycubic Photon Mono | 120×68×150 | 2K (50μm) | 便宜、高精度 |
| Elegoo Mars 3 | 143×89×175 | 4K (35μm) | 高分辨率 |
| Creality LD-002R | 119×68×120 | 2K (51μm) | 静音 |

### 中级
| 型号 | 构建体积 (X×Y×Z mm) | 分辨率 | 特点 |
|------|-------------------|--------|------|
| Prusa SL1 | 120×68×150 | 2560×1440 | 易用、可靠 |
| Anycubic Photon X | 192×120×200 | 4K | 大体积、高精度 |
| Elegoo Saturn 2 | 219×123×250 | 8K | 大尺寸超高精度 |

## 工业级打印机

### FDM工业机
| 型号 | 构建体积 (X×Y×Z mm) | 特点 |
|------|-------------------|------|
| Stratasys F900 | 914×610×914 | 高端、专业材料 |
| Ultimaker S5 | 330×240×300 | 双挤出、专业级 |
| Zortrax M300 | 300×300×300 | 封闭式、稳定 |

### SLA工业机
| 型号 | 构建体积 (X×Y×Z mm) | 特点 |
|------|-------------------|------|
| Formlabs Form 3+ | 145×145×185 | 智能剥离 |
| 3D Systems ProX 800 | 650×750×550 | 超大体积 |

## 打印机选择建议

### 根据模型尺寸选择：
- **<150mm 各维度：** 任何桌面级打印机
- **150-250mm：** 标准桌面级（Ender 3、Prusa i3等）
- **250-350mm：** 中大尺寸（Prusa XL、Bambu Lab X1等）
- **>350mm：** 需要大型打印机（CR-10 S5等）

### 根据精度要求选择：
- **一般用途（0.2-0.4mm）：** FDM打印机
- **高精度要求（<0.1mm）：** SLA/DLP打印机
- **珠宝/牙科/精密模型：** 必须使用SLA

### 根据材料选择：
- **PLA/PETG：** 任何FDM打印机
- **ABS/ASA：** 需要封闭式或加热舱
- **尼龙：** 需要高温挤出机
- **树脂：** 必须使用SLA打印机

## 尺寸超出范围的处理方案

### 1. 缩放模型
- **适用：** 模型可以等比例缩小
- **注意：** 功能零件可能需要保持特定尺寸

### 2. 分割打印
- **适用：** 模型不能缩放但可以拆分
- **工具：** MeshLab、Blender、Cura分割功能
- **后处理：** 需要拼接和打磨

### 3. 旋转打印
- **适用：** 单一维度超限
- **注意：** 可能需要支撑和特定朝向

### 4. 多部分打印
- **适用：** 复杂大模型
- **注意：** 需要精心的拼接设计

### 5. 使用更大的打印机
- **适用：** 无法采用上述方案
- **考虑：** 外包服务或租赁设备

## 特殊注意事项

### 最小特征尺寸
不同打印机的最小可打印特征：
- **FDM：** 线宽（通常0.4mm），最小孔径约1-2mm
- **SLA：** 可达0.05mm，最小细节更精细

### 壁厚建议
- **FDM：** 最小壁厚0.8-1.0mm（2-3层）
- **SLA：** 最小壁厚0.3-0.5mm

### 支撑需求
- **悬垂角度：** 通常超过45-60度需要支撑
- **SLA支撑：** 比FDM更难去除，可能影响表面质量

## 打印机维护建议

### 定期校准
- 热床调平：每次更换耗材后
- 挤出机校准：每月一次
- 步进电机电流：根据需要调整

### 材料存储
- **PLA：** 干燥环境，避免受潮
- **ABS：** 干燥密封，使用前干燥
- **树脂：** 避光、密封存储，避免固化

### 更换易损件
- **喷嘴：** 每3-6个月或堵塞时
- **热床：** 出现划痕时
- **皮带：** 松动时调整或更换


path: references/sensitive-content.md
content: # 敏感内容识别参考

本文件提供政治敏感、暴力、不当内容的识别和评估指南。

## 政治敏感性内容

### 高风险类别

#### 1. 政治人物和符号
- 现任或前任国家领导人头像、雕像
- 政党标志和旗帜
- 现实主义的政治符号和徽章

**评估标准：**
- 是否用于政治宣传？
- 是否有明显的政治立场表达？
- 是否在敏感政治环境下使用？

#### 2. 政治标语和口号
- 政治口号的标语牌
- 特定政治运动的相关物品
- 有争议的历史事件相关物品

#### 3. 极端主义相关
- 极端组织符号
- 恐怖主义相关物品
- 仇恨组织标志

### 中风险类别

#### 1. 历史相关
- 历史人物和事件相关模型
- 历史武器和装备
- 历史符号（需要在历史背景下评估）

**合理例外：**
- 教育用途的历史模型
- 博物馆展示
- 学术研究

#### 2. 地缘政治相关
- 有争议的领土模型
- 敏感区域的地图或地形
- 冲突地区相关物品

**评估要点：**
- 使用目的和场景
- 是否包含政治立场
- 是否可能引发争议

#### 3. 宗教相关
- 宗教符号和器具
- 宗教人物造像
- 宗教建筑模型

**注意事项：**
- 尊重宗教敏感性
- 避免冒犯性描述
- 考虑文化差异

### 低风险类别
- 通用装饰品
- 抽象艺术
- 非政治意义的符号

## 暴力内容

### 高风险

#### 1. 现实主义武器
- 真实枪械的高精度模型
- 刀具和利器模型
- 爆炸物模型

**使用限制：**
- 必须明确教育或收藏目的
- 不能用于指导制造
- 某些平台完全禁止

#### 2. 暴力场景
- 战争场景模型
- 暴力伤害相关
- 恐怖主义场景

### 中风险

#### 1. 武器配件
- 武器配件模型
- 弹药相关
- 枪械零件

**评估：**
- 是否完整武器？
- 使用目的？
- 相关法律法规？

#### 2. 战争和军事相关
- 军事装备
- 战争题材模型
- 军事人物

**合理用途：**
- 历史教育
- 军事爱好者收藏
- 游戏/电影道具

### 低风险
- 科幻武器（明显虚构）
- 玩具风格武器
- 装饰性武器道具

## 不当内容

### 性相关
- 成人内容模型
- 性暗示物品
- 裸露人物

**政策：**
- 大多数平台禁止
- 需要严格年龄限制
- 法律合规要求

### 仇恨相关
- 种族歧视相关
- 宗教仇恨内容
- 性别歧视内容

**评估标准：**
- 是否包含仇恨言论符号？
- 是否有歧视性内容？
- 是否针对特定群体？

### 自残相关
- 自残工具模型
- 与自残相关的内容

**注意：**
- 必须谨慎处理
- 考虑心理健康影响
- 避免传播可能有害的内容

## 评估框架

### 上下文评估
考虑模型的使用场景：
1. **教育用途** - 通常可接受
2. **艺术表达** - 需要谨慎评估
3. **个人收藏** - 相对宽容
4. **商业用途** - 需要严格审查
5. **公开分享** - 需要最高标准

### 地域考虑
不同地区的敏感性不同：
- **中国：** 政治内容敏感性高
- **美国：** 枪支内容相对宽容
- **欧洲：** 仇恨言论严格限制
- **中东：** 宗教内容敏感性高

### 平台政策
考虑目标平台的政策：
- **Thingiverse：** 相对宽松但有明确政策
- **Printables：** 比较严格
- **Cults3D：** 有明确的敏感内容政策
- **专业平台：** 通常最严格

## 检查清单

### 政治敏感性
- [ ] 是否包含政治人物？
- [ ] 是否有政治符号？
- [ ] 是否有政治标语？
- [ ] 使用场景是什么？

### 暴力内容
- [ ] 是否包含武器？
- [ ] 是否有暴力场景？
- [ ] 是否有血腥内容？
- [ ] 是否用于教育/收藏？

### 不当内容
- [ ] 是否有成人内容？
- [ ] 是否有仇恨言论？
- [ ] 是否有歧视内容？
- [ ] 是否有自残相关内容？

### 合法性
- [ ] 是否违反当地法律？
- [ ] 是否符合平台政策？
- [ ] 是否需要特殊许可？

## 处理建议

### 发现敏感内容时

#### 1. 明确分类
- 确定敏感内容类型
- 评估风险等级
- 考虑使用场景

#### 2. 提供建议
- 说明风险
- 建议限制使用范围
- 提供替代方案

#### 3. 记录和追踪
- 记录发现的内容
- 记录处理方式
- 跟踪后续使用

### 不同风险级别的处理

#### 高风险（拒绝）
- 现实主义武器
- 极端主义内容
- 成人内容
- 恨言论内容

**处理：**
- 明确拒绝
- 说明原因
- 建议合规替代

#### 中风险（限制使用）
- 历史武器
- 政治相关内容
- 军事装备

**处理：**
- 限制使用场景
- 明确使用要求
- 记录使用情况

#### 低风险（提醒）
- 科幻武器
- 一般历史物品
- 通用符号

**处理：**
- 提醒注意事项
- 建议谨慎使用
- 无需特别限制

## 法律合规

### 国际法规
- **《武器贸易条约》** - 控制武器相关3D打印
- **各国版权法** - IP保护
- **反恐法** - 禁止恐怖主义相关内容

### 地方法规
- **中国：** 网络安全法、反恐怖主义法
- **美国：** 出口管制条例
- **欧盟：** 数字服务法案

### 平台规则
- 遵循目标平台的使用条款
- 注意平台的年龄限制政策
- 了解内容审核机制

## 技术检测辅助

### 文本检测
- 检查文件名中的敏感词
- 分析描述中的关键词
- 识别敏感符号的名称

### 图像检测
- 使用视觉AI检测敏感内容
- 识别视觉符号和标志
- 分析图像内容类别

### 模式识别
- 检测已知的敏感内容模式
- 识别高风险模型类型
- 建立敏感内容数据库

## 持续更新

### 定期更新
- 跟踪政治事件和敏感话题
- 更新敏感内容列表
- 调整评估标准

### 用户反馈
- 收集用户对敏感内容判断的反馈
- 学习新的敏感内容案例
- 优化检测方法

### 政策跟踪
- 关注平台政策变化
- 了解新的法律法规
- 调整合规要求

## 参考资源

- **各国版权法数据库：** https://www.wipo.int/copyright/en/legislation/
- **平台政策：** 各3D打印平台的服务条款
- **反恐怖主义法律：** 各国相关法律文档


path: scripts/validate_3d_model.py
content: #!/usr/bin/env python3
"""
3D模型验证脚本
支持STL、OBJ、3MF等常见格式的完整性检查和尺寸提取
"""

import os
import sys
import json
import struct
from pathlib import Path

class ModelValidator:
    def __init__(self, filepath):
        self.filepath = Path(filepath)
        self.file_size = self.filepath.stat().st_size
        self.result = {
            "valid": False,
            "format": "unknown",
            "size": {"x": 0, "y": 0, "z": 0},
            "vertices": 0,
            "faces": 0,
            "file_size": self.file_size,
            "errors": []
        }

    def validate(self):
        """主验证方法"""
        if not self.filepath.exists():
            self.result["errors"].append("文件不存在")
            return self.result

        try:
            # 尝试识别格式
            ext = self.filepath.suffix.lower()

            if ext == '.stl':
                self._validate_stl()
            elif ext == '.obj':
                self._validate_obj()
            elif ext == '.3mf':
                self.result["errors"].append("3MF格式需要额外库支持，暂不支持")
            else:
                self.result["errors"].append(f"不支持的文件格式: {ext}")

            # 如果没有错误，标记为有效
            if not self.result["errors"]:
                self.result["valid"] = True

        except Exception as e:
            self.result["errors"].append(f"验证过程中出错: {str(e)}")

        return self.result

    def _validate_stl(self):
        """验证STL文件"""
        self.result["format"] = "STL"

        with open(self.filepath, 'rb') as f:
            # 读取前80字节
            header = f.read(80)
            f.seek(80)

            # 读取面数量
            try:
                face_count = struct.unpack('<I', f.read(4))[0]
            except:
                # 可能是ASCII STL
                f.seek(0)
                content = f.read().decode('ascii', errors='ignore')
                if 'facet normal' in content:
                    # ASCII STL
                    self._parse_ascii_stl(content)
                else:
                    self.result["errors"].append("无法识别STL格式")
                return

            # 二进制STL
            self.result["faces"] = face_count
            self.result["vertices"] = face_count * 3

            # 验证文件大小是否合理
            expected_size = 80 + 4 + (face_count * 50)  # 每个面50字节
            if self.file_size < expected_size:
                self.result["errors"].append("文件可能被截断")

            # 尝试读取几个面来提取尺寸
            f.seek(84)
            vertices = []
            for i in range(min(face_count, 1000)):  # 最多读1000个面
                # 跳过法向量
                f.read(12)
                # 读取三个顶点
                for j in range(3):
                    x = struct.unpack('<f', f.read(4))[0]
                    y = struct.unpack('<f', f.read(4))[0]
                    z = struct.unpack('<f', f.read(4))[0]
                    vertices.append((x, y, z))
                # 跳过属性字节
                f.read(2)

            if vertices:
                self._calculate_bounding_box(vertices)

    def _parse_ascii_stl(self, content):
        """解析ASCII STL"""
        self.result["format"] = "STL (ASCII)"

        # 统计面数和顶点
        face_count = content.count('facet normal')
        self.result["faces"] = face_count
        self.result["vertices"] = face_count * 3

        # 尝试提取顶点坐标
        import re
        vertex_pattern = r'vertex\s+([-\d.]+)\s+([-\d.]+)\s+([-\d.]+)'
        matches = re.findall(vertex_pattern, content)

        if matches:
            vertices = [(float(x), float(y), float(z)) for x, y, z in matches]
            self._calculate_bounding_box(vertices)

    def _validate_obj(self):
        """验证OBJ文件"""
        self.result["format"] = "OBJ"

        with open(self.filepath, 'r', encoding='utf-8', errors='ignore') as f:
            vertices = []
            face_count = 0

            for line in f:
                line = line.strip()
                if line.startswith('v '):
                    # 顶点
                    parts = line.split()
                    if len(parts) >= 4:
                        x = float(parts[1])
                        y = float(parts[2])
                        z = float(parts[3])
                        vertices.append((x, y, z))
                elif line.startswith('f '):
                    # 面
                    face_count += 1

            self.result["vertices"] = len(vertices)
            self.result["faces"] = face_count

            if vertices:
                self._calculate_bounding_box(vertices)

            # 检查材质文件
            obj_dir = self.filepath.parent
            for line in open(self.filepath, 'r', encoding='utf-8', errors='ignore'):
                if line.startswith('mtllib '):
                    mtl_file = line.split()[1]
                    mtl_path = obj_dir / mtl_file
                    if not mtl_path.exists():
                        self.result["warnings"] = f"材质文件不存在: {mtl_file}"

    def _calculate_bounding_box(self, vertices):
        """计算边界框"""
        if not vertices:
            return

        xs = [v[0] for v in vertices]
        ys = [v[1] for v in vertices]
        zs = [v[2] for v in vertices]

        self.result["size"] = {
            "x": round(max(xs) - min(xs), 2),
            "y": round(max(ys) - min(ys), 2),
            "z": round(max(zs) - min(zs), 2)
        }

        self.result["bounds"] = {
            "min": {"x": round(min(xs), 2), "y": round(min(ys), 2), "z": round(min(zs), 2)},
            "max": {"x": round(max(xs), 2), "y": round(max(ys), 2), "z": round(max(zs), 2)}
        }

def main():
    if len(sys.argv) < 2:
        print("使用方法: python3 validate_3d_model.py <model_file>")
        sys.exit(1)

    filepath = sys.argv[1]
    validator = ModelValidator(filepath)
    result = validator.validate()

    # 输出JSON格式结果
    print(json.dumps(result, indent=2, ensure_ascii=False))

    # 返回退出码
    sys.exit(0 if result["valid"] else 1)

if __name__ == "__main__":
    main()


path: scripts/validate_metadata.py
content: #!/usr/bin/env python3
"""
3D模型元数据验证脚本
验证3MF文件中的Title、Description、License等必须字段是否存在
"""

import os
import sys
import json
import zipfile
import xml.etree.ElementTree as ET
from pathlib import Path

class MetadataValidator:
    def __init__(self, filepath):
        self.filepath = Path(filepath)
        self.file_size = self.filepath.stat().st_size if self.filepath.exists() else 0
        self.result = {
            "format": "unknown",
            "file_size": self.file_size,
            "metadata": {},
            "compliance": {
                "is_compliant": False,
                "required_fields": {
                    "title": {"exists": False, "value": ""},
                    "description": {"exists": False, "value": ""},
                    "license": {"exists": False, "value": ""}
                },
                "missing_fields": [],
                "reason": ""
            }
        }

    def validate(self):
        """主验证方法"""
        if not self.filepath.exists():
            self.result["compliance"]["reason"] = "文件不存在"
            return self.result

        try:
            # 尝试识别格式
            ext = self.filepath.suffix.lower()

            if ext == '.3mf':
                self._validate_3mf()
            elif ext == '.stl':
                self._validate_stl()
            elif ext == '.obj':
                self._validate_obj()
            else:
                self.result["compliance"]["reason"] = f"不支持的文件格式: {ext}"

            # 验证合规性
            self._check_compliance()

        except Exception as e:
            self.result["compliance"]["reason"] = f"验证过程中出错: {str(e)}"
            import traceback
            traceback.print_exc()

        return self.result

    def _validate_3mf(self):
        """验证3MF文件的元数据"""
        self.result["format"] = "3MF"

        try:
            # 3MF是ZIP格式，解压后读取XML
            with zipfile.ZipFile(self.filepath, 'r') as zip_ref:
                # 查找3dmodel.model文件
                model_files = [f for f in zip_ref.namelist() if '3dmodel.model' in f]

                if not model_files:
                    self.result["compliance"]["reason"] = "未找到3dmodel.model文件"
                    return

                # 读取主模型文件
                with zip_ref.open(model_files[0]) as model_file:
                    content = model_file.read().decode('utf-8', errors='ignore')

                # 解析XML
                root = ET.fromstring(content)

                # 提取元数据
                self._extract_metadata_from_xml(root)

        except Exception as e:
            self.result["compliance"]["reason"] = f"解析3MF文件出错: {str(e)}"

    def _extract_metadata_from_xml(self, root):
        """从XML根元素提取元数据"""
        namespace = {
            'core': 'http://schemas.microsoft.com/3dmanufacturing/core/2015/02',
            'prod': 'http://schemas.microsoft.com/3dmanufacturing/production/2015/06'
        }

        metadata = {}

        # 遍历所有metadata元素
        for elem in root.iter():
            if 'name' in elem.attrib:
                metadata_name = elem.attrib.get('name', '').lower()
                value = elem.text or ""

                # 标准化字段名
                if metadata_name == 'title':
                    metadata['title'] = value
                elif metadata_name == 'description':
                    metadata['description'] = value
                elif metadata_name == 'license':
                    metadata['license'] = value
                elif metadata_name == 'author':
                    metadata['author'] = value
                elif metadata_name == 'designer':
                    metadata['designer'] = value
                elif metadata_name == 'copyright':
                    metadata['copyright'] = value
                elif metadata_name == 'creationdate':
                    metadata['creation_date'] = value

        self.result["metadata"] = metadata

    def _validate_stl(self):
        """验证STL文件的元数据"""
        self.result["format"] = "STL"
        # STL格式不支持内嵌元数据
        self.result["compliance"]["reason"] = "STL格式不支持内嵌元数据，建议使用3MF或OBJ格式"

    def _validate_obj(self):
        """验证OBJ文件的元数据"""
        self.result["format"] = "OBJ"
        # OBJ格式不支持内嵌元数据
        self.result["compliance"]["reason"] = "OBJ格式不支持内嵌元数据，建议使用3MF格式"

    def _check_compliance(self):
        """检查元数据合规性"""
        required = self.result["compliance"]["required_fields"]
        metadata = self.result["metadata"]

        # 检查必须字段
        required["title"]["exists"] = "title" in metadata and bool(metadata["title"].strip())
        required["title"]["value"] = metadata.get("title", "")

        required["description"]["exists"] = "description" in metadata and bool(metadata["description"].strip())
        required["description"]["value"] = metadata.get("description", "")

        required["license"]["exists"] = "license" in metadata and bool(metadata["license"].strip())
        required["license"]["value"] = metadata.get("license", "")

        # 检查哪些字段缺失
        missing_fields = []
        if not required["title"]["exists"]:
            missing_fields.append("Title")
        if not required["description"]["exists"]:
            missing_fields.append("Description")
        if not required["license"]["exists"]:
            missing_fields.append("License")

        self.result["compliance"]["missing_fields"] = missing_fields

        # 判断合规性
        if len(missing_fields) == 0:
            self.result["compliance"]["is_compliant"] = True
            self.result["compliance"]["reason"] = "元数据完整，符合要求"
        else:
            self.result["compliance"]["is_compliant"] = False
            self.result["compliance"]["reason"] = f"缺少必须的元数据字段: {', '.join(missing_fields)}"

def main():
    if len(sys.argv) < 2:
        print("使用方法: python3 validate_metadata.py <model_file>")
        sys.exit(1)

    filepath = sys.argv[1]
    validator = MetadataValidator(filepath)
    result = validator.validate()

    # 输出JSON格式结果
    print(json.dumps(result, indent=2, ensure_ascii=False))

    # 返回退出码
    sys.exit(0 if result["compliance"]["is_compliant"] else 1)

if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)