@yottameta/yotta-intel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/NOTICE +11 -0
- package/README.md +170 -0
- package/README.zh-CN.md +181 -0
- package/SKILL.md +116 -0
- package/assets/banner.png +0 -0
- package/bin/install.js +163 -0
- package/install.sh +132 -0
- package/package.json +33 -0
- package/references/defang-rules.md +66 -0
- package/references/ioc-spec.md +66 -0
- package/references/stix-lite-spec.md +76 -0
- package/scripts/test_yotta_intel.py +520 -0
- package/scripts/yotta_intel.py +584 -0
package/bin/install.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* yotta-intel 跨平台安装器(YottaSkills)
|
|
4
|
+
* 用法:
|
|
5
|
+
* npx -y @yottameta/yotta-intel --agent <name> # 按智能体默认用户级目录安装(推荐)
|
|
6
|
+
* npx -y @yottameta/yotta-intel --dir PATH # 装到指定目录(用户改了目录的智能体)
|
|
7
|
+
* npx -y @yottameta/yotta-intel -g # 安装到全部已知智能体用户级目录
|
|
8
|
+
* npx -y @yottameta/yotta-intel # 安装到检测到的项目级目录
|
|
9
|
+
* npx -y @yottameta/yotta-intel --list # 列出智能体 -> 默认目录
|
|
10
|
+
*/
|
|
11
|
+
'use strict';
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
|
|
16
|
+
const SKILL_NAME = 'yotta-intel';
|
|
17
|
+
const PKG_ROOT = path.join(__dirname, '..');
|
|
18
|
+
|
|
19
|
+
// 智能体 -> 用户级默认技能目录(dirs 按优先级排列;--agent 装到第一个)
|
|
20
|
+
// 依据官方文档:.agents/skills 并非通用目录,被 OpenCode / Cursor / Cline / Amp /
|
|
21
|
+
// Kimi / Gemini CLI / GitHub Copilot 等读取;Claude Code 与 Codex 默认不读 .agents。
|
|
22
|
+
const AGENT_DIRS = {
|
|
23
|
+
claude: { label: 'Claude Code', dirs: ['.claude/skills'] },
|
|
24
|
+
cursor: { label: 'Cursor', dirs: ['.cursor/skills', '.agents/skills'] },
|
|
25
|
+
codex: { label: 'Codex', dirs: ['.codex/skills'] }, // 特判:$CODEX_HOME/skills
|
|
26
|
+
gemini: { label: 'Gemini CLI', dirs: ['.gemini/skills', '.agents/skills'] },
|
|
27
|
+
goose: { label: 'Goose', dirs: ['.config/goose/skills', '.agents/skills'] },
|
|
28
|
+
amp: { label: 'Amp', dirs: ['.config/agents/skills', '.agents/skills'] },
|
|
29
|
+
opencode: { label: 'OpenCode', dirs: ['.config/opencode/skills'] }, // 特判:$XDG_CONFIG_HOME
|
|
30
|
+
windsurf: { label: 'Windsurf', dirs: ['.codeium/windsurf/skills'] },
|
|
31
|
+
workbuddy: { label: 'WorkBuddy', dirs: ['.workbuddy/skills'] },
|
|
32
|
+
kiro: { label: 'Kiro', dirs: ['.kiro/skills'] },
|
|
33
|
+
trae: { label: 'Trae Code CLI', dirs: ['.traecli/skills'] },
|
|
34
|
+
'trae-cn': { label: 'Trae IDE(国内)', dirs: ['.trae-cn/skills'] },
|
|
35
|
+
qwen: { label: 'Qwen Code', dirs: ['.qwen/skills'] },
|
|
36
|
+
comate: { label: 'Comate 文心快码', dirs: ['.comate/skills'] },
|
|
37
|
+
codebuddy: { label: 'CodeBuddy Code', dirs: ['.codebuddy/skills'] },
|
|
38
|
+
kimi: { label: 'Kimi Code CLI', dirs: ['.kimi/skills'] },
|
|
39
|
+
agents: { label: '通用 AGENTS.md', dirs: ['.agents/skills'] },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Codex 用户级目录特判:优先 $CODEX_HOME/skills,否则 ~/.codex/skills
|
|
43
|
+
function codexUserDir() {
|
|
44
|
+
const base = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
45
|
+
return path.join(base, 'skills');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// OpenCode 用户级目录特判:优先 $XDG_CONFIG_HOME/opencode/skills,否则 ~/.config/opencode/skills
|
|
49
|
+
function opencodeUserDir() {
|
|
50
|
+
const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
51
|
+
return path.join(base, 'opencode', 'skills');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveUserDir(rel) {
|
|
55
|
+
if (rel === '.codex/skills') return codexUserDir();
|
|
56
|
+
if (rel === '.config/opencode/skills') return opencodeUserDir();
|
|
57
|
+
return path.join(os.homedir(), rel);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function installTo(dest) {
|
|
61
|
+
const target = path.join(dest, SKILL_NAME);
|
|
62
|
+
fs.mkdirSync(target, { recursive: true });
|
|
63
|
+
copyDir(PKG_ROOT, target, new Set(['package.json', 'bin', 'node_modules', '.git']));
|
|
64
|
+
console.log('installed -> ' + target);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function copyDir(src, dst, skip) {
|
|
68
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
69
|
+
if (skip.has(entry.name)) continue;
|
|
70
|
+
const s = path.join(src, entry.name);
|
|
71
|
+
const d = path.join(dst, entry.name);
|
|
72
|
+
if (entry.isDirectory()) {
|
|
73
|
+
fs.mkdirSync(d, { recursive: true });
|
|
74
|
+
copyDir(s, d, skip);
|
|
75
|
+
} else if (entry.isFile()) {
|
|
76
|
+
fs.copyFileSync(s, d);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function displayDir(rel) {
|
|
82
|
+
if (process.platform === 'win32') return '%USERPROFILE%\\' + rel.replace(/\//g, '\\');
|
|
83
|
+
return '~/' + rel;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function main() {
|
|
87
|
+
const args = process.argv.slice(2);
|
|
88
|
+
const isGlobal = args.includes('-g') || args.includes('--global');
|
|
89
|
+
const list = args.includes('--list') || args.includes('-l');
|
|
90
|
+
let explicitDir = null;
|
|
91
|
+
const di = args.indexOf('--dir');
|
|
92
|
+
if (di !== -1 && args[di + 1]) explicitDir = args[di + 1];
|
|
93
|
+
let agent = null;
|
|
94
|
+
const ai = args.indexOf('--agent');
|
|
95
|
+
if (ai !== -1 && args[ai + 1]) agent = String(args[ai + 1]).toLowerCase();
|
|
96
|
+
|
|
97
|
+
if (list) {
|
|
98
|
+
console.log('智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):');
|
|
99
|
+
for (const [key, v] of Object.entries(AGENT_DIRS)) {
|
|
100
|
+
const resolved = v.dirs.map(displayDir);
|
|
101
|
+
console.log(' ' + key.padEnd(10) + v.label.padEnd(18) + resolved.join('、'));
|
|
102
|
+
}
|
|
103
|
+
console.log('\n说明:Windows 用 %USERPROFILE%,Linux/macOS 用 ~;仅收录有官方默认目录的智能体。');
|
|
104
|
+
console.log('改了目录的请用 --dir <路径>,不要依赖默认位置;若设置了 CODEX_HOME / XDG_CONFIG_HOME,安装自动以该变量为准。');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (explicitDir) { installTo(explicitDir); return; }
|
|
109
|
+
|
|
110
|
+
if (agent) {
|
|
111
|
+
const info = AGENT_DIRS[agent];
|
|
112
|
+
if (!info) {
|
|
113
|
+
console.log('未收录智能体: ' + agent + '。请用 --dir <路径> 指定技能目录。');
|
|
114
|
+
console.log('可用: ' + Object.keys(AGENT_DIRS).join(', '));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
installTo(resolveUserDir(info.dirs[0]));
|
|
118
|
+
console.log('完成。');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (isGlobal) {
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
for (const v of Object.values(AGENT_DIRS)) {
|
|
125
|
+
for (const d of v.dirs) {
|
|
126
|
+
if (seen.has(d)) continue;
|
|
127
|
+
seen.add(d);
|
|
128
|
+
installTo(resolveUserDir(d));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
console.log('完成。');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const PROJECT_DIRS = [
|
|
136
|
+
'.claude/skills',
|
|
137
|
+
'.cursor/skills',
|
|
138
|
+
'.codex/skills',
|
|
139
|
+
'.config/goose/skills',
|
|
140
|
+
'.config/agents/skills',
|
|
141
|
+
'.opencode/skills',
|
|
142
|
+
'.codeium/windsurf/skills',
|
|
143
|
+
'.workbuddy/skills',
|
|
144
|
+
'.kiro/skills',
|
|
145
|
+
'.traecli/skills',
|
|
146
|
+
'.gemini/skills',
|
|
147
|
+
'.trae-cn/skills',
|
|
148
|
+
'.qwen/skills',
|
|
149
|
+
'.comate/skills',
|
|
150
|
+
'.codebuddy/skills',
|
|
151
|
+
'.kimi/skills',
|
|
152
|
+
'.agents/skills',
|
|
153
|
+
];
|
|
154
|
+
let installedAny = false;
|
|
155
|
+
for (const d of PROJECT_DIRS) {
|
|
156
|
+
if (fs.existsSync(d)) { installTo(d); installedAny = true; }
|
|
157
|
+
}
|
|
158
|
+
if (!installedAny) {
|
|
159
|
+
console.log('未检测到项目级智能体目录。可手动复制,或用 --agent <name> / -g 装到用户级。');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
main();
|
package/install.sh
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# yotta-intel 多智能体安装脚本(YottaSkills 模板)
|
|
3
|
+
# 用法:
|
|
4
|
+
# bash install.sh --agent <name> # 按智能体默认用户级目录安装
|
|
5
|
+
# bash install.sh --dir <path> # 装到指定目录(用户改过目录的智能体)
|
|
6
|
+
# bash install.sh -g # 装到全部已知智能体用户级目录
|
|
7
|
+
# bash install.sh # 检测并安装到已存在的项目级目录
|
|
8
|
+
# bash install.sh --list # 列出智能体 -> 默认目录
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
SKILL_NAME="yotta-intel"
|
|
12
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
case "$(uname -s)" in
|
|
14
|
+
MINGW*|MSYS*)
|
|
15
|
+
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -W)"
|
|
16
|
+
;;
|
|
17
|
+
esac
|
|
18
|
+
|
|
19
|
+
# 智能体 -> 用户级默认目录(--agent 装到第一个)
|
|
20
|
+
# .agents/skills 并非通用目录:OpenCode / Cursor / Cline / Amp / Kimi / Gemini CLI / GitHub Copilot 读取。
|
|
21
|
+
# 判断当前环境:Windows Git Bash 用 %USERPROFILE%,Unix 用 ~
|
|
22
|
+
_IS_WINDOWS=0
|
|
23
|
+
case "$(uname -s)" in
|
|
24
|
+
MINGW*|MSYS*|CYGWIN*) _IS_WINDOWS=1 ;;
|
|
25
|
+
esac
|
|
26
|
+
dirs_for() {
|
|
27
|
+
case "$1" in
|
|
28
|
+
claude) echo ".claude/skills" ;;
|
|
29
|
+
cursor) echo ".cursor/skills .agents/skills" ;;
|
|
30
|
+
codex) echo "__CODEX__" ;;
|
|
31
|
+
gemini) echo ".gemini/skills .agents/skills" ;;
|
|
32
|
+
goose) echo ".config/goose/skills .agents/skills" ;;
|
|
33
|
+
amp) echo ".config/agents/skills .agents/skills" ;;
|
|
34
|
+
opencode) echo "__OPENCODE__" ;;
|
|
35
|
+
windsurf) echo ".codeium/windsurf/skills" ;;
|
|
36
|
+
workbuddy) echo ".workbuddy/skills" ;;
|
|
37
|
+
kiro) echo ".kiro/skills" ;;
|
|
38
|
+
trae) echo ".traecli/skills" ;;
|
|
39
|
+
trae-cn) echo ".trae-cn/skills" ;;
|
|
40
|
+
qwen) echo ".qwen/skills" ;;
|
|
41
|
+
comate) echo ".comate/skills" ;;
|
|
42
|
+
codebuddy) echo ".codebuddy/skills" ;;
|
|
43
|
+
kimi) echo ".kimi/skills" ;;
|
|
44
|
+
agents) echo ".agents/skills" ;;
|
|
45
|
+
*) return 1 ;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
codex_dir() {
|
|
50
|
+
if [ -n "${CODEX_HOME:-}" ]; then printf '%s' "$CODEX_HOME/skills"; else printf '%s' "$HOME/.codex/skills"; fi
|
|
51
|
+
}
|
|
52
|
+
opencode_dir() {
|
|
53
|
+
if [ -n "${XDG_CONFIG_HOME:-}" ]; then printf '%s' "$XDG_CONFIG_HOME/opencode/skills"; else printf '%s' "$HOME/.config/opencode/skills"; fi
|
|
54
|
+
}
|
|
55
|
+
resolve_user() {
|
|
56
|
+
case "$1" in
|
|
57
|
+
__CODEX__) codex_dir ;;
|
|
58
|
+
__OPENCODE__) opencode_dir ;;
|
|
59
|
+
*) printf '%s' "$HOME/$1" ;;
|
|
60
|
+
esac
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
install_to() {
|
|
64
|
+
mkdir -p "$1/$SKILL_NAME"
|
|
65
|
+
cp -r "$SOURCE_DIR/." "$1/$SKILL_NAME/"
|
|
66
|
+
rm -rf "$1/$SKILL_NAME/.git"
|
|
67
|
+
echo "installed -> $1/$SKILL_NAME"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
list() {
|
|
71
|
+
echo "智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):"
|
|
72
|
+
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
73
|
+
local dirs first
|
|
74
|
+
dirs="$(dirs_for "$a")"
|
|
75
|
+
first="${dirs%% *}"
|
|
76
|
+
case "$first" in
|
|
77
|
+
__CODEX__) first=".codex/skills" ;;
|
|
78
|
+
__OPENCODE__) first=".config/opencode/skills" ;;
|
|
79
|
+
esac
|
|
80
|
+
if [ "$_IS_WINDOWS" = "1" ]; then
|
|
81
|
+
first="%USERPROFILE%\\${first//\//\\}"
|
|
82
|
+
else
|
|
83
|
+
first="~/$first"
|
|
84
|
+
fi
|
|
85
|
+
printf ' %-10s %s\n' "$a" "$first"
|
|
86
|
+
done
|
|
87
|
+
echo '说明:Windows 用 %USERPROFILE%,Linux/macOS 用 ~;仅收录有官方默认目录的智能体。'
|
|
88
|
+
echo '改了目录的请用 --dir <路径>,不要依赖默认位置;若设置了 CODEX_HOME / XDG_CONFIG_HOME,安装自动以该变量为准。'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
main() {
|
|
92
|
+
local agent="" dir="" global=0 show_list=0
|
|
93
|
+
while [ $# -gt 0 ]; do
|
|
94
|
+
case "$1" in
|
|
95
|
+
--agent) shift; agent="${1:-}" ;;
|
|
96
|
+
--dir) shift; dir="${1:-}" ;;
|
|
97
|
+
-g|--global) global=1 ;;
|
|
98
|
+
--list|-l) show_list=1 ;;
|
|
99
|
+
*) echo "未知参数: $1" >&2; exit 2 ;;
|
|
100
|
+
esac
|
|
101
|
+
shift
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
if [ "$show_list" = "1" ]; then list; return; fi
|
|
105
|
+
if [ -n "$dir" ]; then install_to "$dir"; echo "完成。"; return; fi
|
|
106
|
+
if [ -n "$agent" ]; then
|
|
107
|
+
local dirs first
|
|
108
|
+
if ! dirs="$(dirs_for "$agent")"; then
|
|
109
|
+
echo "未收录智能体: $agent。请用 --dir <路径> 指定技能目录。" >&2; exit 2
|
|
110
|
+
fi
|
|
111
|
+
first="${dirs%% *}"
|
|
112
|
+
install_to "$(resolve_user "$first")"; echo "完成。"; return
|
|
113
|
+
fi
|
|
114
|
+
if [ "$global" = "1" ]; then
|
|
115
|
+
echo "安装到全部已知智能体用户级目录..."
|
|
116
|
+
local dirs rel
|
|
117
|
+
for a in claude cursor codex gemini goose amp opencode windsurf workbuddy kiro trae trae-cn qwen comate codebuddy kimi agents; do
|
|
118
|
+
dirs="$(dirs_for "$a")"
|
|
119
|
+
for rel in $dirs; do install_to "$(resolve_user "$rel")"; done
|
|
120
|
+
done
|
|
121
|
+
echo "完成。"; return
|
|
122
|
+
fi
|
|
123
|
+
local installed=0 d
|
|
124
|
+
for d in .claude/skills .cursor/skills .codex/skills .config/goose/skills .config/agents/skills .opencode/skills .codeium/windsurf/skills .workbuddy/skills .kiro/skills .traecli/skills .gemini/skills .trae-cn/skills .qwen/skills .comate/skills .codebuddy/skills .kimi/skills .agents/skills; do
|
|
125
|
+
if [ -d "$d" ]; then install_to "$d"; installed=1; fi
|
|
126
|
+
done
|
|
127
|
+
if [ "$installed" = "0" ]; then
|
|
128
|
+
echo "未检测到项目级智能体目录。可用 --agent <name> / -g 装到用户级,或 --dir 指定。"
|
|
129
|
+
fi
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
main "$@"
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yottameta/yotta-intel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Yuanqing — a zero-dependency threat-intel IOC extraction & normalization engine for AI agents: extracts IP (IPv4/IPv6), domains, URLs, emails, hashes (MD5/SHA1/SHA256/SHA512) and CVE IDs from threat reports, phishing emails and logs; recognizes and reverses defanged forms (hxxp / [.] / (.) / [dot] / [:] / [@]); deduplicates, normalizes (IDN punycode, default-port stripping) and outputs CSV / JSON / STIX-lite (STIX 2.1 Bundle) with the pure Python 3.8+ standard library. Triggers when the user provides text containing suspicious IPs / domains / URLs / hashes and needs IOC extraction, normalization, dedup, format conversion, or safe sharing. Boundaries: fully local and offline; no reputation lookups, no sample downloads, no proactive scanning; authorized / own-asset / educational use only.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"agent-skills",
|
|
8
|
+
"yotta-intel"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
"SKILL.md",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"README.md",
|
|
14
|
+
"README.zh-CN.md",
|
|
15
|
+
"install.sh",
|
|
16
|
+
"references",
|
|
17
|
+
"scripts",
|
|
18
|
+
"assets",
|
|
19
|
+
"bin",
|
|
20
|
+
"NOTICE",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/YottaMeta/yotta-intel.git"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"yotta-intel": "bin/install.js"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# defang / refang 规则与共享建议
|
|
2
|
+
|
|
3
|
+
> 元情 yotta-intel v0.1.0:去活性(defang)写法识别、还原与安全共享指引。
|
|
4
|
+
|
|
5
|
+
## 1. 为什么要 defang
|
|
6
|
+
|
|
7
|
+
威胁情报共享时,邮件客户端 / 聊天平台 / 工单系统会把文本中的 URL、邮箱、IP 自动识别为可点击链接或可解析地址:
|
|
8
|
+
|
|
9
|
+
- 收件人误点恶意链接;
|
|
10
|
+
- 平台把 IOC 当普通链接处理、丢失原文;
|
|
11
|
+
- 自动化沙箱在共享环节触发外联。
|
|
12
|
+
|
|
13
|
+
defang(去活性)把 IOC 中的「可识别分隔符」替换成安全写法,让平台不再自动识别,同时人仍然能读懂。
|
|
14
|
+
|
|
15
|
+
## 2. 引擎识别的 defang 写法(refang 输入)
|
|
16
|
+
|
|
17
|
+
| 原始 | 常见 defang 写法(引擎都能还原) |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `.` | `[.]` `(.)` `{.}` `[dot]` `(dot)` `{dot}`(大小写不敏感) |
|
|
20
|
+
| `:` | `[:]` `(:)` `{:}` `[colon]` `(colon)` `{colon}` |
|
|
21
|
+
| `@` | `[@]` `(@)` `{@}` `[at]` `(at)` `{at}` |
|
|
22
|
+
| `/` | `[/]` `(/)` `[\/]` `[\\]` |
|
|
23
|
+
| `http` | `hxxp` `hXXp` |
|
|
24
|
+
| `https` | `hxxps` `hXXps` |
|
|
25
|
+
|
|
26
|
+
例:`hxxp://malware[.]example[.]com/loader.exe` → refang → `http://malware.example.com/loader.exe`。
|
|
27
|
+
|
|
28
|
+
## 3. 引擎输出的统一 defang 形态(defang_value)
|
|
29
|
+
|
|
30
|
+
引擎输出统一风格,保证同一 IOC 在任何地方 defang 后形态一致:
|
|
31
|
+
|
|
32
|
+
| 类型 | 原始 | defang |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| ipv4 | `203.0.113.5` | `203[.]0[.]113[.]5` |
|
|
35
|
+
| ipv6 | `2001:db8::1` | `2001[:]db8[:][:]1` |
|
|
36
|
+
| domain | `evil.example.com` | `evil[.]example[.]com` |
|
|
37
|
+
| url | `https://evil.example.com/a` | `hxxps://evil[.]example[.]com/a` |
|
|
38
|
+
| email | `admin@example.com` | `admin[@]example[.]com` |
|
|
39
|
+
| hash | `44d886…2f` | 不变(十六进制不会被自动链接) |
|
|
40
|
+
| cve | `CVE-2024-1234` | 不变(不会被自动链接) |
|
|
41
|
+
|
|
42
|
+
URL 的 defang 会同时处理 host 里的 `@`(userinfo):`http://user@evil.com/a` →
|
|
43
|
+
`hxxp://user[@]evil[.]com/a`。
|
|
44
|
+
|
|
45
|
+
## 4. 三个子命令怎么配合
|
|
46
|
+
|
|
47
|
+
- `extract`:提取 + 归一 + 去重 + 输出。每条结果自带 `value`(原始规范形态)与 `defanged`(安全共享形态)。
|
|
48
|
+
- `defang`:流式把一段文本中识别到的 IOC 替换为 defang 形态(其余原文保留)——适合「把报告转成可安全粘贴的版本」。
|
|
49
|
+
- `refang`:把 defang 文本还原为原始形态——适合「把别人发来的 defang 情报喂给 extract 或其它工具」。
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# 把一份威胁报告转成安全共享版
|
|
53
|
+
python3 scripts/yotta_intel.py defang --path report.txt --output safe.txt
|
|
54
|
+
|
|
55
|
+
# 把安全共享版还原后再提取
|
|
56
|
+
python3 scripts/yotta_intel.py refang --path safe.txt | python3 scripts/yotta_intel.py extract --stdin --format json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 5. 共享纪律建议
|
|
60
|
+
|
|
61
|
+
1. 在邮件 / 群聊 / 工单里共享 IOC 时,优先贴 defang 形态 + 注明「已 defang」。
|
|
62
|
+
3. 贴原始形态时,用代码块或等宽字体包起来,降低被自动链接的风险。
|
|
63
|
+
3. 不要把 defang 当加密:defang 只是防误点,不是防分析——机器可以一键还原。
|
|
64
|
+
4. 哈希与 CVE 无需 defang;不要给它们画蛇添足加括号,避免下游解析出错。
|
|
65
|
+
5. 共享 STIX-lite 时(stix-lite-spec.md),pattern 内用原始值(机器可解析),
|
|
66
|
+
人看的备注列用 defang 形态。
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# IOC 类型与判定规则(ioc-spec)
|
|
2
|
+
|
|
3
|
+
> 元情 yotta-intel v0.1.0 内部规范:七类 IOC 的提取 / 归一 / 误报控制规则。
|
|
4
|
+
> 本文件是引擎行为的权威说明;SKILL.md 只放入口,细节在这里。
|
|
5
|
+
|
|
6
|
+
## 1. 支持的类型
|
|
7
|
+
|
|
8
|
+
| 类型 key | 中文 | 示例 | STIX 对象 |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| `ipv4` | IPv4 地址 | `203.0.113.5` | `ipv4-addr` |
|
|
11
|
+
| `ipv6` | IPv6 地址 | `2001:db8::1` | `ipv6-addr` |
|
|
12
|
+
| `domain` | 域名(FQDN) | `evil.example.com` | `domain-name` |
|
|
13
|
+
| `url` | URL | `http://evil.example.com/a` | `url` |
|
|
14
|
+
| `email` | 邮箱 | `admin@example.com` | `email-addr` |
|
|
15
|
+
| `hash` | 哈希(MD5/SHA1/SHA256/SHA512) | `44d886…2f` | `file:hashes` |
|
|
16
|
+
| `cve` | CVE 编号 | `CVE-2024-1234` | `vulnerability` |
|
|
17
|
+
|
|
18
|
+
## 2. 提取流程
|
|
19
|
+
|
|
20
|
+
1. **refang 预处理**:先识别并还原 defang 写法(`hxxp`、`[.]`、`(.)`、`[dot]`、`[:]`、`[@]`、`[/]`),
|
|
21
|
+
让 defang 文本与原始文本走同一套提取逻辑;还原只替换分隔符,不改变行结构(行号可追溯)。
|
|
22
|
+
2. **逐行提取**:对每一行跑类型正则 + 校验函数,输出 `(start, end, type, canonical)`。
|
|
23
|
+
3. **归一化**:每种类型转成唯一的规范形态(见 §3)。
|
|
24
|
+
4. **去重计数**:以 `(type, canonical)` 为键合并,记录 `count`(出现次数)、`first_line`(首次行号)与
|
|
25
|
+
`snippet`(首次出现的整行上下文)。
|
|
26
|
+
|
|
27
|
+
## 3. 归一化规则
|
|
28
|
+
|
|
29
|
+
| 类型 | 规则 |
|
|
30
|
+
|---|---|
|
|
31
|
+
| ipv4 | 用 `ipaddress.IPv4Address` 校验并规范化;前导零(如 `010.0.0.1`)归一为 `10.0.0.1`;非法八位组(如 `999.1.1.1`)剔除 |
|
|
32
|
+
| ipv6 | 用 `ipaddress.IPv6Address` 校验;输出压缩写法(如 `2001:0db8::1` → `2001:db8::1`);IPv4-mapped(`::ffff:192.168.1.1`)输出规范十六进制 `::ffff:c0a8:101` |
|
|
33
|
+
| domain | 小写;去尾点(`example.com.` → `example.com`);IDN 转 punycode(`例子.测试` → `xn--fsqu00a.xn--0zwm56d`) |
|
|
34
|
+
| url | scheme/host 小写;host 做 IDN;去默认端口(http:80 / https:443 / ftp:21);去 fragment(`#…` 不发给服务器);保留 userinfo、path、query |
|
|
35
|
+
| email | 小写;域名段做 IDN;要求域名段合法 |
|
|
36
|
+
| hash | 十六进制小写;仅接受 32/40/64/128 位(MD5/SHA1/SHA256/SHA512) |
|
|
37
|
+
| cve | 统一大写 `CVE-YYYY-NNNN` |
|
|
38
|
+
|
|
39
|
+
## 4. 误报控制
|
|
40
|
+
|
|
41
|
+
- **域名 TLD 白名单**:`valid_domain()` 只接受内置 TLD 集合(常见 gTLD + 主流新 gTLD + 全部 ISO 3166-1 ccTLD)内
|
|
42
|
+
的末段,或 `xn--` 开头的 IDN TLD;`badexample.zzz` 这类不存在的 TLD 不会误报。
|
|
43
|
+
- **文件名过滤**:与常见文件扩展名重叠的 TLD(`md`/`py`/`sh`/`js`/`ts`/`json`/`log`/`png`/`pdf`…)在
|
|
44
|
+
**二段域名**命中时按文件名处理(`README.md`、`test.py` 不算域名);三段及以上(如 `cdn.evil.md`)仍按域名。
|
|
45
|
+
- **数字末段**:`1.2.3.44` 的末段 `44` 不是合法 TLD → 不算域名(由 IPv4 提取接管)。
|
|
46
|
+
- **单标签**:`localhost` 等无点名称不算域名。
|
|
47
|
+
- **邮箱内嵌域名**:`admin@example.com` 只提 email,`example.com` 不会被重复提为 domain。
|
|
48
|
+
- **URL 标点**:URL 正则排除中文/全角标点(`,。!?;:、()【】《》`),并去掉尾部 ASCII 标点
|
|
49
|
+
(`http://a.com/a.` → `http://a.com/a`)。
|
|
50
|
+
- **哈希**:只接受连续的 32/40/64/128 位十六进制串;UUID(带连字符)与非常规长度(如 48 位)不匹配。
|
|
51
|
+
- **IPv6**:`ipaddress` 严格校验;`namespace::method` 里的 `::` 因前邻单词字符不会误报为 IPv6。
|
|
52
|
+
|
|
53
|
+
## 5. 已知取舍
|
|
54
|
+
|
|
55
|
+
- 二段 `x.md` 这类「真域名 vs 文件名」无法仅凭文本区分,本引擎按文件名处理(降低常见误报),
|
|
56
|
+
需要时可在结果里人工放行。
|
|
57
|
+
- 域名不去 `www.` 前缀、不拆注册域(eTLD+1),去重键 = 完整 FQDN;同一域名的不同子域是不同 IOC。
|
|
58
|
+
- URL 与其中包含的 IP / 域名会同时输出(`http://1.2.3.4/x` 同时有 url 与 ipv4),便于下游按需取用。
|
|
59
|
+
- defang 流式输出(`defang` 子命令)只替换识别为 IOC 的部分,其余文本原样保留;非 IOC 的零散 `[.]` 会被
|
|
60
|
+
refang 归一为 `.`(见 defang-rules.md)。
|
|
61
|
+
|
|
62
|
+
## 6. 边界与红线
|
|
63
|
+
|
|
64
|
+
- 纯本地离线:不联网查证、不下载样本、不主动扫描任何系统;只做文本提取与规范化。
|
|
65
|
+
- 提取结果只是「候选指标」,是否恶意需人工 / 其他情报源核实;本引擎不给恶意定性。
|
|
66
|
+
- 仅用于已获授权 / 自有资产 / 教学环境的安全分析。
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# STIX-lite 输出规范
|
|
2
|
+
|
|
3
|
+
> 元情 yotta-intel v0.1.0:`extract --format stix` 的产物说明。
|
|
4
|
+
> 「lite」= 只生成 STIX 2.1 的 Indicator 对象 + 自定义扩展属性,不实现完整 STIX 库。
|
|
5
|
+
|
|
6
|
+
## 1. 为什么叫 lite
|
|
7
|
+
|
|
8
|
+
STIX 2.1 全量模型(SDO/SRO、Bundle、图谱关系、Confidence/Kill Chain 等)非常庞大。本引擎只做
|
|
9
|
+
**单条 IOC → 单条 Indicator** 的确定性映射,输出合法、可被主流 STIX 解析器读取的最小 Bundle:
|
|
10
|
+
不引入外部依赖,不生成复杂关系,方便直接贴进 MISP / OpenCTI 等平台做导入或人工核对。
|
|
11
|
+
|
|
12
|
+
## 2. Bundle 结构
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"type": "bundle",
|
|
17
|
+
"id": "bundle--<uuid5>",
|
|
18
|
+
"spec_version": "2.1",
|
|
19
|
+
"objects": [ { "type": "indicator", ... }, ... ]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `id`:由 `yotta-intel:<生成时间>` 经 uuid5 派生,同一输入在同一时刻生成结果稳定;
|
|
24
|
+
每次运行时间戳不同,`bundle id` 会变(符合 STIX 对 id 唯一性的要求)。
|
|
25
|
+
- `spec_version`:`2.1`。
|
|
26
|
+
|
|
27
|
+
## 3. Indicator 对象
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"type": "indicator",
|
|
32
|
+
"spec_version": "2.1",
|
|
33
|
+
"id": "indicator--<uuid5>",
|
|
34
|
+
"created": "2026-08-27T00:00:00+00:00",
|
|
35
|
+
"modified": "2026-08-27T00:00:00+00:00",
|
|
36
|
+
"name": "域名: evil.example.com",
|
|
37
|
+
"pattern": "[domain-name:value = 'evil.example.com']",
|
|
38
|
+
"pattern_type": "stix",
|
|
39
|
+
"valid_from": "2026-08-27T00:00:00+00:00",
|
|
40
|
+
"labels": ["malicious-activity"],
|
|
41
|
+
"x_yottameta_type": "domain",
|
|
42
|
+
"x_yottameta_value": "evil.example.com",
|
|
43
|
+
"x_yottameta_defanged": "evil[.]example[.]com",
|
|
44
|
+
"x_yottameta_count": 3
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- `id`:`uuid5(NAMESPACE_URL, "yotta-intel:<type>:<value>")`,同一 IOC 跨运行稳定(确定性)。
|
|
49
|
+
- `created` / `modified` / `valid_from`:均为生成时刻(UTC ISO 8601)。
|
|
50
|
+
- `labels`:默认 `["malicious-activity"]`——仅表示「这是待核实的可疑指标」,不是最终定性。
|
|
51
|
+
- `x_yottameta_*`:YottaMeta 自定义扩展属性,供下游直接取用;不认识的解析器会忽略未知字段。
|
|
52
|
+
|
|
53
|
+
## 4. pattern 映射
|
|
54
|
+
|
|
55
|
+
| 类型 | STIX 2.1 pattern |
|
|
56
|
+
|---|---|
|
|
57
|
+
| ipv4 | `[ipv4-addr:value = '203.0.113.5']` |
|
|
58
|
+
| ipv6 | `[ipv6-addr:value = '2001:db8::1']` |
|
|
59
|
+
| domain | `[domain-name:value = 'evil.example.com']` |
|
|
60
|
+
| url | `[url:value = 'http://evil.example.com/a']` |
|
|
61
|
+
| email | `[email-addr:value = 'admin@example.com']` |
|
|
62
|
+
| hash (MD5) | `[file:hashes.'MD5' = '44d886…2f']` |
|
|
63
|
+
| hash (SHA-1) | `[file:hashes.'SHA-1' = '…']` |
|
|
64
|
+
| hash (SHA-256) | `[file:hashes.'SHA-256' = '…']` |
|
|
65
|
+
| hash (SHA-512) | `[file:hashes.'SHA-512' = '…']` |
|
|
66
|
+
| cve | `[vulnerability:name = 'CVE-2024-1234']` |
|
|
67
|
+
|
|
68
|
+
pattern 内使用原始规范值(机器可解析);人看的 defang 形态放在 `x_yottameta_defanged`。
|
|
69
|
+
|
|
70
|
+
## 5. 使用建议
|
|
71
|
+
|
|
72
|
+
- **导入平台**:把 `--format stix` 的输出直接贴进 MISP / OpenCTI / Splunk 的 STIX 导入接口。
|
|
73
|
+
- **共享**:与报告一起发时,把 `defanged` 列给读者看,把 STIX 文件给机器用。
|
|
74
|
+
- **人工核对**:`labels` 是默认值,接入生产情报库前应由分析师复核并补充 Confidence / 来源。
|
|
75
|
+
- **升级提示**:本输出是 lite 子集,不含 Sighting / Relationship / Kill Chain;需要完整图谱时
|
|
76
|
+
请用专业 STIX 工具链。
|