@yottameta/yotta-chain 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 +18 -0
- package/LICENSE +21 -0
- package/NOTICE +11 -0
- package/README.md +151 -0
- package/README.zh-CN.md +151 -0
- package/SKILL.md +110 -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/rules.md +124 -0
- package/scripts/test_yotta_chain.py +616 -0
- package/scripts/yotta_chain.py +1794 -0
package/install.sh
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# yotta-chain 多智能体安装脚本(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-chain"
|
|
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-chain",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Yuanlian — a zero-dependency supply-chain dependency validator for AI agents: detects dependency confusion (private package names resolved from public registries, mixed registries, suspicious registry URLs), lockfile consistency issues (missing entries, unsatisfied ranges, dangling references, missing integrity), missing lockfiles, unpinned versions, SNAPSHOT deps and typosquatting across npm (package.json + package-lock v1/v2/v3), Python (requirements.txt, pyproject.toml, poetry.lock, Pipfile/Pipfile.lock) and Maven (pom.xml); generates SBOM-lite (CycloneDX 1.5 subset). Pure Python 3.8+ standard library, fully local and offline, no online CVE lookups. Triggers when the user needs to check a project dependencies for supply-chain risks before build/release, verify a lockfile matches its manifest, detect dependency-confusion exposure, or generate an SBOM. Boundaries: local and offline; no CVE databases, no package-registry queries, no data leaves the machine; findings are review signals to be verified by humans; authorized / own-asset / educational use only.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"agent-skills",
|
|
8
|
+
"yotta-chain"
|
|
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-chain.git"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"bin": {
|
|
31
|
+
"yotta-chain": "bin/install.js"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# 元链 yotta-chain — 检测规则参考
|
|
2
|
+
|
|
3
|
+
> 每条规则的触发逻辑、严重度与修复指引。用于人工复核与教学。
|
|
4
|
+
|
|
5
|
+
## 严重度
|
|
6
|
+
|
|
7
|
+
`info < low < medium < high`。`scan` 默认 `--gate=info`(任何发现即退出 1),CI 可 `--gate high` 收紧。
|
|
8
|
+
|
|
9
|
+
## 一、依赖混淆(dependency confusion)
|
|
10
|
+
|
|
11
|
+
依赖混淆:攻击者把「只应存在于私有仓库的包名」发布到公共仓库(npm / PyPI),利用解析优先级让受害者装到恶意包。本引擎**纯本地**用「仓库配置 vs 实际解析来源」给强信号。
|
|
12
|
+
|
|
13
|
+
### confusion_scope_registry(high)
|
|
14
|
+
|
|
15
|
+
- 触发:`.npmrc` 为某 scope 配置了私有仓库(`@scope:registry=...`),但 package-lock.json 里该 scope 的包 `resolved` 指向其它(公共)仓库主机。
|
|
16
|
+
- 含义:私有包名本应从私有仓库解析,实际却可能被公共仓库的同名攻击者包抢占。
|
|
17
|
+
- 示例:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
# .npmrc
|
|
21
|
+
@corp:registry=https://npm.corp.example.com/
|
|
22
|
+
# package-lock.json
|
|
23
|
+
"node_modules/@corp/secret": { "resolved": "https://registry.npmjs.org/@corp/secret.tgz" }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- 修复:核对 resolved 是否应指向私有仓库;用正确的私有仓库重新 `npm install` 并检查锁文件 resolved 主机;把 `.npmrc` 提交入库(或用 CI 注入)。
|
|
27
|
+
|
|
28
|
+
### confusion_mixed_registry(high)
|
|
29
|
+
|
|
30
|
+
- 触发:同一包名在锁文件里被解析自多个不同仓库主机(如顶层来自公共 registry,嵌套来自公司镜像)。
|
|
31
|
+
- 含义:同一依赖来源不一致,可能是镜像污染或被替换。
|
|
32
|
+
- 修复:统一到单一可信仓库;删除不一致条目后重新安装。
|
|
33
|
+
|
|
34
|
+
### confusion_suspicious_registry(medium)
|
|
35
|
+
|
|
36
|
+
- 触发:仓库 / 索引地址为 `http://`(非 TLS)、IP 字面量(如 `http://192.168.1.5/...`)、本机地址(localhost / 127.0.0.1)。
|
|
37
|
+
- 含义:非 HTTPS 或指向内网 / 本机的仓库可被中间人篡改,或指向开发环境残留。
|
|
38
|
+
- 修复:改用 HTTPS 域名仓库;删除开发期残留地址。
|
|
39
|
+
|
|
40
|
+
### confusion_extra_index(medium)
|
|
41
|
+
|
|
42
|
+
- 触发:pip 同时配置 `--index-url`(私有)与 `--extra-index-url`(公共,如 PyPI);或 poetry 源标记 `secondary = true`;或 Pipfile 同时列出公共 PyPI 与私有源。
|
|
43
|
+
- 含义:公共仓库成为回退源——私有包名只要在公共仓库出现同名包,就可能被抢占(依赖混淆经典场景)。
|
|
44
|
+
- 修复:只保留单一可信源;私有包确保只在私有源解析;poetry 把私有源设为 `default` 并关闭 PyPI 回退;pip 用 `--index-url` 单一私有源 + 显式 `--extra-index-url` 白名单。
|
|
45
|
+
|
|
46
|
+
### confusion_registry_mismatch(medium)
|
|
47
|
+
|
|
48
|
+
- 触发:项目配置了私有默认仓库(`.npmrc registry=` 或 package.json publishConfig),但锁文件里包从公共仓库(registry.npmjs.org)解析。
|
|
49
|
+
- 含义:配置与锁定来源不一致,私有包名可能从公共仓库被抢占。
|
|
50
|
+
- 修复:核对默认仓库配置与锁文件生成时机;重新安装生成锁文件。
|
|
51
|
+
|
|
52
|
+
## 二、lockfile 一致性
|
|
53
|
+
|
|
54
|
+
### lockfile_missing_entry(high)
|
|
55
|
+
|
|
56
|
+
- 触发:清单(package.json / pyproject.toml / Pipfile)声明了依赖,锁文件里却没有。
|
|
57
|
+
- 修复:`npm install` / `poetry lock` / `pipenv lock` 重新生成锁文件;若锁文件被手工删条目需还原。
|
|
58
|
+
|
|
59
|
+
### lockfile_range_unsatisfied(high)
|
|
60
|
+
|
|
61
|
+
- 触发:锁文件版本不满足清单声明的版本范围(npm semver / PEP 440 语义)。
|
|
62
|
+
- 示例:`"lodash": "^5.0.0"` 但锁文件锁定 `4.17.21`。
|
|
63
|
+
- 修复:声明与锁定必须一致;升级或回退后重新生成锁文件。
|
|
64
|
+
|
|
65
|
+
### lockfile_dangling_ref(high)
|
|
66
|
+
|
|
67
|
+
- 触发:锁文件里某包依赖的包名不存在于锁文件包列表(依赖图断裂)。
|
|
68
|
+
- 修复:重新生成锁文件;手工编辑锁文件会破坏依赖图。
|
|
69
|
+
|
|
70
|
+
### lockfile_duplicate_conflict(high)
|
|
71
|
+
|
|
72
|
+
- 触发:同名同版本存在多个不同 `resolved` / `integrity` 来源。
|
|
73
|
+
- 含义:同一版本解析到不同 tarball,存在被替换风险。
|
|
74
|
+
- 修复:统一来源;核对 integrity 是否与官方一致。
|
|
75
|
+
|
|
76
|
+
### lockfile_root_mismatch(medium)
|
|
77
|
+
|
|
78
|
+
- 触发:锁文件根 `name` / `version` 与清单不一致。
|
|
79
|
+
- 修复:锁文件与清单应对应同一项目;复制 / 改名后需重新生成。
|
|
80
|
+
|
|
81
|
+
### lockfile_integrity_missing(medium)
|
|
82
|
+
|
|
83
|
+
- 触发:package-lock.json(v2/v3)条目缺 `integrity`、poetry.lock `files` 为空、Pipfile.lock 条目缺 `hashes`。
|
|
84
|
+
- 含义:缺少完整性校验,安装无法防篡改。
|
|
85
|
+
- 修复:重新生成锁文件(npm / poetry / pipenv 均会写入哈希)。
|
|
86
|
+
|
|
87
|
+
## 三、卫生
|
|
88
|
+
|
|
89
|
+
### missing_lockfile(medium)
|
|
90
|
+
|
|
91
|
+
- 触发:声明了依赖但没有锁文件(package.json 无 package-lock.json / npm-shrinkwrap.json;pyproject.toml 或 Pipfile 无 poetry.lock / Pipfile.lock)。
|
|
92
|
+
- 修复:提交锁文件并使用 `npm ci` / `poetry install --locked` / `pipenv install --deploy` 保证可复现。
|
|
93
|
+
|
|
94
|
+
### unpinned(low / medium)
|
|
95
|
+
|
|
96
|
+
- 触发:npm 依赖为 `*` / `latest` / 未指定;requirements.txt 无版本约束;Maven 依赖未声明 `<version>` 且不在 dependencyManagement(medium)。
|
|
97
|
+
- 修复:给出精确或收窄范围并配合锁文件。
|
|
98
|
+
|
|
99
|
+
### snapshot(low)
|
|
100
|
+
|
|
101
|
+
- 触发:Maven 依赖使用 `-SNAPSHOT` 版本。
|
|
102
|
+
- 修复:发布物改用 release 版本。
|
|
103
|
+
|
|
104
|
+
## 四、typo-squat
|
|
105
|
+
|
|
106
|
+
### typosquat(low)
|
|
107
|
+
|
|
108
|
+
- 触发:依赖名与内置知名 npm / PyPI 包列表编辑距离 ≤ 2(含转置),如 `lodassh`→lodash、`requets`→requests。
|
|
109
|
+
- 含义:拼写仿冒包是供应链投毒常见手法,需人工确认包名与来源。
|
|
110
|
+
- 修复:核对包名拼写与发布者;用精确版本 + 完整性哈希;可配置私有仓库白名单。
|
|
111
|
+
- 说明:内置列表是常用包子集,未覆盖 ≠ 安全;本规则只做提示,不自动判定恶意。
|
|
112
|
+
|
|
113
|
+
## 五、版本范围语义
|
|
114
|
+
|
|
115
|
+
- **npm semver**:`^` `~` `>=` `<=` `>` `<` `=`、x 范围(`1.x` / `1.2.x`)、`*`、`||` 或、连字符范围(`1.2.3 - 2.3.4`)、预发布规则(预发布版本默认不满足不含预发布的比较器)。
|
|
116
|
+
- **PEP 440**:`==` `!=` `>=` `<=` `>` `<` `~=` `===`、逗号 AND、`||` 或、`==1.2.*` 通配;`~=1.2` = `>=1.2,==1.2.*`。
|
|
117
|
+
|
|
118
|
+
## 六、已知限制(v0.1.0)
|
|
119
|
+
|
|
120
|
+
- 不做在线 CVE 比对(那是 snyk / trivy / npm audit 的地盘)。
|
|
121
|
+
- 依赖混淆为本地近似:真正确认「私有包名被公共仓库抢占」需要在线核对,引擎给强信号供人工复核。
|
|
122
|
+
- `yarn.lock` / `pnpm-lock.yaml` / `go.mod` / `Cargo.lock` 未支持(见 CHANGELOG 后续计划)。
|
|
123
|
+
- 只扫描给定目录(不递归子目录 monorepo);`-r` 递归的 requirements 会跟随(深度 ≤ 5)。
|
|
124
|
+
- 不读用户级 `.npmrc` / `pip.conf`(只读项目内配置),避免触碰项目外数据。
|