@yottameta/yotta-skills 0.20.0 → 0.20.2
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 +14 -0
- package/SKILL.md +11 -3
- package/bin/yotta-skills.js +34 -38
- package/install.sh +1 -1
- package/lib/install-pipeline.js +8 -1
- package/lib/npm-pack.js +104 -0
- package/lib/scan-policy.js +209 -0
- package/lib/verify-gate.js +65 -11
- package/package.json +2 -1
- package/references/install-flow.md +5 -1
- package/references/tutorial.md +2 -2
- package/scan-policy.json +525 -0
- package/scripts/yotta-skills-mcp.py +1 -1
- package/skill-manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## v0.20.2 (2026-09-29)
|
|
2
|
+
- 家族安装的 npm 拉包新增镜像回退:默认源返回 404(国内镜像未同步该版本 tarball)时自动改用官方源 `https://registry.npmjs.org/` 重试一次;安装输出显示回退行,安装证据新增 `npm_registry_fallback` 字段。
|
|
3
|
+
- 两次都失败时给出可直接复制的修复提示(`npm_config_registry` / `YOTTA_SKILLS_NPM_FLAGS`);已显式指定 registry 或设置 `YOTTA_SKILLS_NO_FALLBACK=1` 时不回退。
|
|
4
|
+
- 非 404 错误(网络超时等)不触发回退,保持原失败信息。
|
|
5
|
+
- 静态 playbook / 路由 / hook / scanPolicy 契约零变更。
|
|
6
|
+
|
|
7
|
+
## v0.20.1 (2026-09-29)
|
|
8
|
+
- 家族安装接入 scanPolicy 例外复核:元信判 `DO NOT INSTALL` 时,按包内 `scan-policy.json` 的已审查例外(逐条绑定技能 + 版本 + treeHash + 规则 + 路径)复核,豁免检测规则表 / 攻防样例 / 文档说明类命中;复核后无阻断级发现则继续安装。
|
|
9
|
+
- 修复元忆 / 元察 / 元鉴从元阁安装时被元信自指误报阻断的问题(检测类技能自带规则表字面量);例外表与 handoff / OpenCode 的 scanPolicy 同源,由 `tools/build_scan_policy.py` 生成。
|
|
10
|
+
- 技能升版或内容变化后旧例外自动失效(treeHash 绑定,fail-closed),安装回到元信原始判定。
|
|
11
|
+
- 安装日志新增 `scan_policy` 字段(是否应用 / 豁免条数 / version / treeHash)。
|
|
12
|
+
- 元信扫描输出解析失败(空输出 / 截断 / 子进程抖动)自动重试一次;仍失败时报错带退出码与 stderr 摘要。
|
|
13
|
+
- 静态 playbook / 路由 / hook 契约零变更。
|
|
14
|
+
|
|
1
15
|
## v0.20.0 (2026-09-28)
|
|
2
16
|
- 新增 `o1.route` 动态扩展口:`--route` / MCP `route_request` 可选调用用户配置的本地扩展提供方(provider),在已装注册表白名单内增补 / 重排路由结果。
|
|
3
17
|
- 静态 playbook 结果先算必算;未配置或未授权 / 超时 / 非法输出时一律回落静态结果,文本输出与历史一致,`--json` 增加 `dynamic` 状态块。
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yotta-skills
|
|
3
|
-
version: 0.20.
|
|
3
|
+
version: 0.20.2
|
|
4
4
|
description: 元阁 -- 元阁全家技能的总编排策划 + 编排路由 + 一键安装器 + 技能盘点 + 运行时 hook 适配。路由层:--route / route_request 按需求摘要给出候选组合、调用顺序、角色、置信度、依据、已装/缺失状态与安装命令,只建议不自动安装;非元阁家族已装技能按 frontmatter description 机械匹配作并列候选(标注来源与未扫描状态,只读不自动调用);策划层:按场景给出「该组合哪几个元技能、组合强在哪、怎么组合使用(安装与调用均由用户确认后执行)」;安装层:一条命令把 YottaMeta 已发布的全部 yotta-* 技能装进指定智能体或目录(默认 --pin 锁死清单精确版本);盘点层:--inventory / --reindex 扫描本机已装技能生成/更新注册表,新装技能自动被发现(install/update 后自动 re-index,会话开工只建议跑本地 --reindex;更新检查走手动 --check 或后台 --check --scheduled);运行时适配层:hook capabilities / evaluate / bind / unbind 按宿主能力矩阵执行六个统一事件并留证降级,元信 before_install 已接入安装管线;自包含零依赖,不依赖任何元技能;MCP 按需加载且需用户确认后写入配置(可选:list_installed_skills/describe_skill/reindex/route_request,不常驻,未加载降级 CLI)。支持 --list 清单 / --route 路由 / install / update / update --check(只读检查)/ update --check --scheduled(后台周检)/ update --auto(家族自动更新)/ hook 适配 / --inventory / --reindex / --dry-run 预览 / --pin(默认)/ --range。触发:需要批量安装或更新元阁全家技能、按场景组合多个元技能、路由或判断该用哪些技能、盘点或查看本机已装技能、重扫技能注册表、给某个智能体或目录一次性铺齐 yotta-* 技能、评估宿主 hook 能力、预览安装清单、锁版本安装、或用户说 元阁/装全家/一次装齐/yotta-skills/install-all/更新全家/检查更新/自动更新/hook 适配/该用哪个技能/路由技能/盘点技能/查看已装技能 等。边界(Do NOT trigger):只做「组合策划 + 静态路由建议 + 清单 + 下载 + 落位 + 汇总 + 盘点 + re-index + hook 适配」,不含技能本体、不做技能内容开发、不 -g 污染全局、不自动安装缺失技能、不静默写宿主配置或全局记忆;家族安装先自举或调用元信装前门禁,DO NOT INSTALL 阻断,非元阁家族包不自动安装。
|
|
5
5
|
license: MIT
|
|
6
6
|
metadata:
|
|
@@ -348,6 +348,12 @@ npx -y @yottameta/yotta-skills --route "检查代码质量,别糊弄" --json
|
|
|
348
348
|
- `INSTALL WITH CAUTION` / `REVIEW REQUIRED`:继续安装,但显示风险并写入证据;
|
|
349
349
|
- `DO NOT INSTALL` 或扫描失败:阻断,不替换旧版本。
|
|
350
350
|
|
|
351
|
+
扫描后元阁会用包内 `scan-policy.json`(已审查例外表)复核:每条例外绑定「技能 + 版本 +
|
|
352
|
+
内容 treeHash + 规则 + 路径」,只豁免检测规则表、攻防样例与文档说明这类已审查命中。
|
|
353
|
+
技能升版或内容有任何变化,旧例外自动失效(fail-closed),安装回到元信原始判定。
|
|
354
|
+
命中例外的安装会显示 `↳ scanPolicy 复核:豁免 N 条已审查发现` 并写入安装证据;
|
|
355
|
+
未命中例外的 `DO NOT INSTALL` 照常阻断。
|
|
356
|
+
|
|
351
357
|
引擎查找顺序(v0.19.13 收紧):`--verify` 指定路径 → 环境变量 `YOTTA_SKILLS_VERIFY` →
|
|
352
358
|
**受信安装记录**(`~/.yottaskills/trusted-verifier.json`,由安装管线在元信安装 / 更新成功后写入,
|
|
353
359
|
记录包身份 + 引擎路径 + SHA-256)。候选引擎必须同时满足:路径不经符号链接跳转、
|
|
@@ -369,14 +375,16 @@ npx -y @yottameta/yotta-skills --route "检查代码质量,别糊弄" --json
|
|
|
369
375
|
|---|---|
|
|
370
376
|
| `YOTTA_SKILLS_NPM` | 指定 npm 可执行文件(同 `--npm`) |
|
|
371
377
|
| `YOTTA_SKILLS_NPM_FLAGS` | 追加传给 `npm pack` 的参数(按空白拆分,如 `--registry=...`) |
|
|
378
|
+
| `YOTTA_SKILLS_NO_FALLBACK` | 设为 `1` 时禁用「默认源 404 → 官方源重试」的自动回退 |
|
|
372
379
|
| `YOTTA_SKILLS_PYTHON` | 指定 python 可执行文件(元信 scan 用,同 `--python`) |
|
|
373
380
|
| `YOTTA_SKILLS_VERIFY` | 指定 yotta_verify.py 路径(同 `--verify`) |
|
|
374
381
|
| `YOTTA_SKILLS_MANIFEST` | 指定技能清单 JSON 路径(默认随包 skills.json) |
|
|
375
382
|
|
|
376
383
|
## 常见问题
|
|
377
384
|
|
|
378
|
-
- **npmmirror 全新包 404
|
|
379
|
-
|
|
385
|
+
- **npmmirror 全新包 404**:默认源返回 404 时安装器会**自动**改用官方源
|
|
386
|
+
`https://registry.npmjs.org/` 重试一次(输出显示回退行,安装证据记 `npm_registry_fallback`);
|
|
387
|
+
若已显式指定 registry 或设置 `YOTTA_SKILLS_NO_FALLBACK=1`,则保持在指定源上,失败时给出可复制的修复提示。
|
|
380
388
|
- **未收录智能体**:`--agent <name>` 报未收录时,改用 `--dir` 指到它的技能目录。
|
|
381
389
|
- **Windows 下 npm 报错**:CLI 已内置 npm-cli.js 解析,无需额外处理;如需覆盖用 `--npm`。
|
|
382
390
|
- **某技能安装失败**:汇总报告会列出失败原因,可单装该技能排查。
|
package/bin/yotta-skills.js
CHANGED
|
@@ -35,10 +35,12 @@ const lifecycleLib = require('../lib/install-lifecycle');
|
|
|
35
35
|
const snapshotLib = require('../lib/install-snapshot');
|
|
36
36
|
const updateCheckLib = require('../lib/update-check');
|
|
37
37
|
const hookAdapterLib = require('../lib/hook-adapter');
|
|
38
|
+
const scanPolicyLib = require('../lib/scan-policy');
|
|
39
|
+
const npmPackLib = require('../lib/npm-pack');
|
|
38
40
|
const { createInstaller, isSafeTarEntry } = require('../lib/install-pipeline');
|
|
39
41
|
|
|
40
42
|
const PKG_ROOT = path.join(__dirname, '..');
|
|
41
|
-
let VERSION = '0.20.
|
|
43
|
+
let VERSION = '0.20.2';
|
|
42
44
|
try { VERSION = require(path.join(PKG_ROOT, 'package.json')).version; } catch (_) { /* keep fallback */ }
|
|
43
45
|
|
|
44
46
|
function loadManifest() {
|
|
@@ -586,50 +588,44 @@ function runScan(engine, skillDir) {
|
|
|
586
588
|
return gateLib.runVerifier(engine, skillDir, { python, spawnSync });
|
|
587
589
|
}
|
|
588
590
|
|
|
589
|
-
function scanTarget(engine, skillDir) {
|
|
591
|
+
function scanTarget(engine, skillDir, context) {
|
|
590
592
|
const scan = runScan(engine, skillDir);
|
|
591
|
-
if (scan.ok) {
|
|
592
|
-
const counts = scan.counts || {};
|
|
593
|
-
const line = 'critical ' + (counts.critical || 0) + ' / high ' + (counts.high || 0) +
|
|
594
|
-
' / medium ' + (counts.medium || 0) + ' / low ' + (counts.low || 0) +
|
|
595
|
-
' / info ' + (counts.info || 0);
|
|
596
|
-
out(' 元信 scan: ' + scan.verdict + '(' + line + ')');
|
|
597
|
-
if (scan.verdict === gateLib.BLOCK) out(' ⚠ 元信 verdict 为 DO NOT INSTALL,已阻断安装。');
|
|
598
|
-
else if (scan.verdict === gateLib.CAUTION || scan.verdict === gateLib.REVIEW) {
|
|
599
|
-
out(' ⚠ 元信 verdict 为 ' + scan.verdict + ',继续安装并保留风险证据。');
|
|
600
|
-
}
|
|
601
|
-
} else {
|
|
593
|
+
if (!scan.ok) {
|
|
602
594
|
out(' 元信 scan: ' + scan.error);
|
|
603
|
-
|
|
604
|
-
|
|
595
|
+
return scan;
|
|
596
|
+
}
|
|
597
|
+
const ctx = context || {};
|
|
598
|
+
const reviewed = scanPolicyLib.applyScanPolicy(scan, {
|
|
599
|
+
slug: ctx.slug,
|
|
600
|
+
version: ctx.version,
|
|
601
|
+
pkgDir: skillDir,
|
|
602
|
+
policy: scanPolicyLib.loadPolicy(path.join(PKG_ROOT, 'scan-policy.json')),
|
|
603
|
+
});
|
|
604
|
+
const counts = scan.counts || {};
|
|
605
|
+
const line = 'critical ' + (counts.critical || 0) + ' / high ' + (counts.high || 0) +
|
|
606
|
+
' / medium ' + (counts.medium || 0) + ' / low ' + (counts.low || 0) +
|
|
607
|
+
' / info ' + (counts.info || 0);
|
|
608
|
+
out(' 元信 scan: ' + scan.verdict + '(' + line + ')');
|
|
609
|
+
if (reviewed.policy && reviewed.policy.applied && reviewed.policy.excluded > 0) {
|
|
610
|
+
out(' ↳ scanPolicy 复核:豁免 ' + reviewed.policy.excluded +
|
|
611
|
+
' 条已审查发现(检测规则 / 文档说明,treeHash 绑定)→ ' + reviewed.verdict);
|
|
612
|
+
}
|
|
613
|
+
if (reviewed.verdict === gateLib.BLOCK) out(' ⚠ 元信 verdict 为 DO NOT INSTALL,已阻断安装。');
|
|
614
|
+
else if (reviewed.verdict === gateLib.CAUTION || reviewed.verdict === gateLib.REVIEW) {
|
|
615
|
+
out(' ⚠ 元信 verdict 为 ' + reviewed.verdict + ',继续安装并保留风险证据。');
|
|
616
|
+
}
|
|
617
|
+
return reviewed;
|
|
605
618
|
}
|
|
606
619
|
|
|
607
620
|
// ── 安装 ───────────────────────────────────────────────────────────────────
|
|
621
|
+
const runNpmPackBase = npmPackLib.createPackRunner({ spawnSync, specOf, resolveNpm });
|
|
622
|
+
|
|
608
623
|
function runNpmPack(skill, opts, packDir) {
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (flags) args.push(...flags.split(/\s+/));
|
|
613
|
-
const npm = resolveNpm(opts);
|
|
614
|
-
const r = spawnSync(npm.bin, [...npm.prefix, ...args], { encoding: 'utf8', timeout: 180000, maxBuffer: 64 * 1024 * 1024, shell: npm.shell });
|
|
615
|
-
if (r.status !== 0) {
|
|
616
|
-
const raw = (r.stderr || r.stdout || 'npm pack 失败').trim();
|
|
617
|
-
const ll = raw.split(/\r?\n/).filter(Boolean);
|
|
618
|
-
const brief = ll.slice(-4).join(' | ');
|
|
619
|
-
return { error: brief || 'npm pack 失败', detail: raw };
|
|
620
|
-
}
|
|
621
|
-
const lines = (r.stdout || '').split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
|
622
|
-
let tarball = null;
|
|
623
|
-
for (const l of lines) if (/\.tgz$/.test(l)) tarball = l;
|
|
624
|
-
if (!tarball) {
|
|
625
|
-
try {
|
|
626
|
-
const found = fs.readdirSync(packDir).filter(f => f.endsWith('.tgz'));
|
|
627
|
-
if (found.length === 1) tarball = found[0];
|
|
628
|
-
} catch (_) { /* ignore */ }
|
|
624
|
+
const packed = runNpmPackBase(skill, opts, packDir);
|
|
625
|
+
if (packed.registryFallback) {
|
|
626
|
+
out(' ↳ npm 默认源 404,已用官方源重试成功(' + npmPackLib.OFFICIAL_REGISTRY + ')');
|
|
629
627
|
}
|
|
630
|
-
|
|
631
|
-
const vm = String(tarball).match(/-([0-9]+\.[0-9]+\.[0-9]+)\.tgz$/);
|
|
632
|
-
return { tarball: path.join(packDir, tarball), resolved: vm ? vm[1] : null, spec };
|
|
628
|
+
return packed;
|
|
633
629
|
}
|
|
634
630
|
|
|
635
631
|
function extractTarball(tarball, extractDir) {
|
package/install.sh
CHANGED
|
@@ -89,7 +89,7 @@ install_to() {
|
|
|
89
89
|
rm -rf -- "$dest/$item"
|
|
90
90
|
fi
|
|
91
91
|
done
|
|
92
|
-
for item in SKILL.md skill-manifest.json references scripts assets skills.json LICENSE NOTICE README.md README.zh-CN.md CHANGELOG.md USER_GUIDE.md; do
|
|
92
|
+
for item in SKILL.md skill-manifest.json references scripts assets skills.json scan-policy.json LICENSE NOTICE README.md README.zh-CN.md CHANGELOG.md USER_GUIDE.md; do
|
|
93
93
|
if [ -e "$SOURCE_DIR/$item" ]; then
|
|
94
94
|
cp -RP "$SOURCE_DIR/$item" "$dest/"
|
|
95
95
|
fi
|
package/lib/install-pipeline.js
CHANGED
|
@@ -148,7 +148,10 @@ function createInstaller(deps) {
|
|
|
148
148
|
safeRecord({ event: 'before_install', skill: skill.slug, decision: 'block', error: gate.error });
|
|
149
149
|
return { skill, status: 'fail', version: packed.resolved, note: gate.error, exitCode: 5 };
|
|
150
150
|
}
|
|
151
|
-
const scanResult = deps.scanTarget(gate.engine, extracted.pkgDir
|
|
151
|
+
const scanResult = deps.scanTarget(gate.engine, extracted.pkgDir, {
|
|
152
|
+
slug: skill.slug,
|
|
153
|
+
version: packed.resolved,
|
|
154
|
+
});
|
|
152
155
|
if (!scanResult.ok) {
|
|
153
156
|
safeRecord({ event: 'before_install', skill: skill.slug, decision: 'block', error: scanResult.error });
|
|
154
157
|
return { skill, status: 'fail', version: packed.resolved, note: scanResult.error, exitCode: 5 };
|
|
@@ -193,6 +196,7 @@ function createInstaller(deps) {
|
|
|
193
196
|
decision: 'block',
|
|
194
197
|
verdict: scanResult.verdict,
|
|
195
198
|
counts: scanResult.counts,
|
|
199
|
+
scan_policy: scanResult.policy || null,
|
|
196
200
|
hook_decision: hookEvaluation.decision,
|
|
197
201
|
});
|
|
198
202
|
return {
|
|
@@ -208,6 +212,7 @@ function createInstaller(deps) {
|
|
|
208
212
|
verdict: scanResult.verdict,
|
|
209
213
|
counts: scanResult.counts,
|
|
210
214
|
warn: verdict.warn,
|
|
215
|
+
policy: scanResult.policy || null,
|
|
211
216
|
};
|
|
212
217
|
}
|
|
213
218
|
|
|
@@ -332,6 +337,8 @@ function createInstaller(deps) {
|
|
|
332
337
|
bootstrap_scan: gate.mode === 'trusted-bootstrap' && skill.slug === 'yotta-verify' ? 'self' : null,
|
|
333
338
|
verdict: scan.verdict,
|
|
334
339
|
decision: scan.decision,
|
|
340
|
+
scan_policy: scan.policy || null,
|
|
341
|
+
npm_registry_fallback: Boolean(packed.registryFallback),
|
|
335
342
|
snapshot,
|
|
336
343
|
});
|
|
337
344
|
} catch (error) {
|
package/lib/npm-pack.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const OFFICIAL_REGISTRY = 'https://registry.npmjs.org/';
|
|
6
|
+
const FALLBACK_HINT =
|
|
7
|
+
'提示:镜像同步延迟时可用官方源重试 —— 设置环境变量 npm_config_registry=' + OFFICIAL_REGISTRY +
|
|
8
|
+
',或 YOTTA_SKILLS_NPM_FLAGS=--registry=' + OFFICIAL_REGISTRY;
|
|
9
|
+
|
|
10
|
+
function isNotFound(raw) {
|
|
11
|
+
const text = String(raw || '');
|
|
12
|
+
return /\bE404\b/.test(text) || /\b404 Not Found\b/i.test(text) || /could not be found/i.test(text);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function hasRegistryFlag(args) {
|
|
16
|
+
return (args || []).some((arg) => arg === '--registry' || String(arg).startsWith('--registry='));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function briefOf(raw) {
|
|
20
|
+
const lines = String(raw || '')
|
|
21
|
+
.split(/\r?\n/)
|
|
22
|
+
.map((line) => line.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
return lines.slice(-4).join(' | ');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function findTarball(stdout, packDir, readdirSync) {
|
|
28
|
+
const lines = String(stdout || '')
|
|
29
|
+
.split(/\r?\n/)
|
|
30
|
+
.map((line) => line.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
let tarball = null;
|
|
33
|
+
for (const line of lines) if (/\.tgz$/.test(line)) tarball = line;
|
|
34
|
+
if (tarball) return tarball;
|
|
35
|
+
try {
|
|
36
|
+
const found = readdirSync(packDir).filter((name) => name.endsWith('.tgz'));
|
|
37
|
+
if (found.length === 1) return found[0];
|
|
38
|
+
} catch (_) {
|
|
39
|
+
/* ignore */
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 创建 npm pack 执行器。
|
|
46
|
+
*
|
|
47
|
+
* 默认源(国内镜像)拉不到 tarball 时会报 404 —— 此时自动改用官方源重试一次,
|
|
48
|
+
* 并把 registryFallback 标记回传给安装管线(写入证据)。用户已显式指定 registry、
|
|
49
|
+
* 或设置 YOTTA_SKILLS_NO_FALLBACK=1 时不重试。
|
|
50
|
+
*/
|
|
51
|
+
function createPackRunner(deps) {
|
|
52
|
+
const spawnSync = deps.spawnSync;
|
|
53
|
+
const specOf = deps.specOf;
|
|
54
|
+
const resolveNpm = deps.resolveNpm;
|
|
55
|
+
const readdirSync = deps.readdirSync || fs.readdirSync;
|
|
56
|
+
const fallbackDisabled = deps.fallbackDisabled || (() => process.env.YOTTA_SKILLS_NO_FALLBACK === '1');
|
|
57
|
+
|
|
58
|
+
return function runNpmPack(skill, opts, packDir) {
|
|
59
|
+
const spec = specOf(skill, opts.pin);
|
|
60
|
+
const baseArgs = ['pack', spec, '--pack-destination', packDir];
|
|
61
|
+
const flags = (process.env.YOTTA_SKILLS_NPM_FLAGS || '').trim();
|
|
62
|
+
if (flags) baseArgs.push(...flags.split(/\s+/));
|
|
63
|
+
const npm = resolveNpm(opts);
|
|
64
|
+
const attempt = (args) => spawnSync(npm.bin, [...npm.prefix, ...args], {
|
|
65
|
+
encoding: 'utf8',
|
|
66
|
+
timeout: 180000,
|
|
67
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
68
|
+
shell: npm.shell,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
let result = attempt(baseArgs);
|
|
72
|
+
let registryFallback = false;
|
|
73
|
+
if (result.status !== 0) {
|
|
74
|
+
const raw = (result.stderr || result.stdout || 'npm pack 失败').trim();
|
|
75
|
+
if (!fallbackDisabled() && !hasRegistryFlag(baseArgs) && isNotFound(raw)) {
|
|
76
|
+
result = attempt([...baseArgs, '--registry', OFFICIAL_REGISTRY]);
|
|
77
|
+
registryFallback = result.status === 0;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (result.status !== 0) {
|
|
81
|
+
const raw = (result.stderr || result.stdout || 'npm pack 失败').trim();
|
|
82
|
+
const brief = briefOf(raw) || 'npm pack 失败';
|
|
83
|
+
const hint = isNotFound(raw) ? '\n ' + FALLBACK_HINT : '';
|
|
84
|
+
return { error: brief + hint, detail: raw };
|
|
85
|
+
}
|
|
86
|
+
const tarball = findTarball(result.stdout, packDir, readdirSync);
|
|
87
|
+
if (!tarball) return { error: '未找到 npm pack 产物(' + spec + ')' };
|
|
88
|
+
const versionMatch = String(tarball).match(/-([0-9]+\.[0-9]+\.[0-9]+)\.tgz$/);
|
|
89
|
+
return {
|
|
90
|
+
tarball: path.join(packDir, tarball),
|
|
91
|
+
resolved: versionMatch ? versionMatch[1] : null,
|
|
92
|
+
spec,
|
|
93
|
+
registryFallback,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
OFFICIAL_REGISTRY,
|
|
100
|
+
FALLBACK_HINT,
|
|
101
|
+
isNotFound,
|
|
102
|
+
hasRegistryFlag,
|
|
103
|
+
createPackRunner,
|
|
104
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const VERDICT_SAFE = 'SAFE TO INSTALL';
|
|
7
|
+
const VERDICT_CAUTION = 'INSTALL WITH CAUTION';
|
|
8
|
+
const VERDICT_REVIEW = 'REVIEW REQUIRED';
|
|
9
|
+
const VERDICT_BLOCK = 'DO NOT INSTALL';
|
|
10
|
+
|
|
11
|
+
const VERDICT_BY_SEVERITY = {
|
|
12
|
+
critical: VERDICT_BLOCK,
|
|
13
|
+
high: VERDICT_CAUTION,
|
|
14
|
+
medium: VERDICT_REVIEW,
|
|
15
|
+
low: VERDICT_SAFE,
|
|
16
|
+
info: VERDICT_SAFE,
|
|
17
|
+
};
|
|
18
|
+
const SEVERITY_ORDER = ['critical', 'high', 'medium', 'low', 'info'];
|
|
19
|
+
const VERIFY_SCANNER = 'yotta-verify';
|
|
20
|
+
|
|
21
|
+
// 与 tools/build_opencode_bundle.py(handoff 构建器)逐字节同口径:
|
|
22
|
+
// 例外表绑定 prune 后的技能树 treeHash,技能升版或内容变化后旧例外自动失效(fail-closed)。
|
|
23
|
+
const REMOVE_NAMES = new Set([
|
|
24
|
+
'.git', '.github', '.gitignore', '.npmignore', 'test', 'bin',
|
|
25
|
+
'package.json', 'package-lock.json', 'install.sh', 'server.json',
|
|
26
|
+
'__pycache__', '.pytest_cache', '.mypy_cache',
|
|
27
|
+
]);
|
|
28
|
+
const KEEP_EXTRA = {
|
|
29
|
+
'yotta-memory': ['bin'],
|
|
30
|
+
'yotta-skills': ['bin', 'lib', 'skills.json', 'scan-policy.json'],
|
|
31
|
+
};
|
|
32
|
+
const PRUNE_DIRS = new Set(['__pycache__', '.pytest_cache', '.mypy_cache']);
|
|
33
|
+
const HASH_SKIP_FILES = new Set(['.yotta-managed.json']);
|
|
34
|
+
|
|
35
|
+
function normalizeRel(value) {
|
|
36
|
+
return String(value == null ? '' : value).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function loadPolicy(policyPath) {
|
|
40
|
+
try {
|
|
41
|
+
const raw = fs.readFileSync(policyPath, 'utf8');
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
if (!parsed || typeof parsed !== 'object' || !parsed.skills) return null;
|
|
44
|
+
return parsed;
|
|
45
|
+
} catch (_) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function keepExtraFor(slug) {
|
|
51
|
+
return new Set(KEEP_EXTRA[slug] || []);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function collectRows(dir, relBase, keepExtra, depth, rows) {
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
58
|
+
} catch (_) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.isSymbolicLink()) continue;
|
|
63
|
+
const rel = relBase ? relBase + '/' + entry.name : entry.name;
|
|
64
|
+
if (entry.isDirectory()) {
|
|
65
|
+
if (PRUNE_DIRS.has(entry.name)) continue;
|
|
66
|
+
if (depth === 0 && REMOVE_NAMES.has(entry.name) && !keepExtra.has(entry.name)) continue;
|
|
67
|
+
collectRows(path.join(dir, entry.name), rel, keepExtra, depth + 1, rows);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!entry.isFile()) continue;
|
|
71
|
+
if (depth === 0 && REMOVE_NAMES.has(entry.name) && !keepExtra.has(entry.name)) continue;
|
|
72
|
+
if (entry.name.endsWith('.pyc') || entry.name.endsWith('.pyo')) continue;
|
|
73
|
+
if (HASH_SKIP_FILES.has(rel)) continue;
|
|
74
|
+
let data;
|
|
75
|
+
try {
|
|
76
|
+
data = fs.readFileSync(path.join(dir, entry.name));
|
|
77
|
+
} catch (_) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
rows.push({ rel, hash: crypto.createHash('sha256').update(data).digest('hex') });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function computeTreeHash(pkgDir, slug) {
|
|
85
|
+
const rows = [];
|
|
86
|
+
collectRows(pkgDir, '', keepExtraFor(slug), 0, rows);
|
|
87
|
+
rows.sort((a, b) => Buffer.compare(Buffer.from(a.rel, 'utf8'), Buffer.from(b.rel, 'utf8')));
|
|
88
|
+
const manifest = rows.map((row) => Buffer.concat([
|
|
89
|
+
Buffer.from(row.rel, 'utf8'),
|
|
90
|
+
Buffer.from('\0', 'ascii'),
|
|
91
|
+
Buffer.from(row.hash, 'ascii'),
|
|
92
|
+
Buffer.from('\n', 'ascii'),
|
|
93
|
+
]));
|
|
94
|
+
const digest = crypto.createHash('sha256').update(Buffer.concat(manifest)).digest('hex');
|
|
95
|
+
return 'sha256:' + digest;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function summarize(findings) {
|
|
99
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
100
|
+
for (const finding of findings) {
|
|
101
|
+
const severity = finding && finding.severity;
|
|
102
|
+
if (Object.prototype.hasOwnProperty.call(counts, severity)) counts[severity] += 1;
|
|
103
|
+
}
|
|
104
|
+
let verdict = VERDICT_SAFE;
|
|
105
|
+
for (const severity of SEVERITY_ORDER) {
|
|
106
|
+
if (counts[severity]) {
|
|
107
|
+
verdict = VERDICT_BY_SEVERITY[severity];
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { counts, verdict };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function exceptionMatches(exception, finding) {
|
|
115
|
+
if (!exception || exception.action !== 'allow') return false;
|
|
116
|
+
const scanners = Array.isArray(exception.scanners) ? exception.scanners : [];
|
|
117
|
+
if (scanners.length > 0 && !scanners.includes(VERIFY_SCANNER)) return false;
|
|
118
|
+
if (String(exception.rule || '') !== String(finding.rule_id || '')) return false;
|
|
119
|
+
return normalizeRel(exception.path) === normalizeRel(finding.file);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 用随包分发的 scanPolicy 例外表复核元信扫描结果。
|
|
124
|
+
*
|
|
125
|
+
* 绑定校验(缺一即不生效,fail-closed):skill 条目存在 + version 一致 + treeHash 一致。
|
|
126
|
+
* 只有 action=allow 且 scanners 含 yotta-verify 的例外才会豁免对应 (rule, path) 发现。
|
|
127
|
+
*/
|
|
128
|
+
function applyScanPolicy(scan, context) {
|
|
129
|
+
const ctx = context || {};
|
|
130
|
+
const base = {
|
|
131
|
+
applied: false,
|
|
132
|
+
reason: null,
|
|
133
|
+
excluded: 0,
|
|
134
|
+
excludedFindings: [],
|
|
135
|
+
version: null,
|
|
136
|
+
treeHash: null,
|
|
137
|
+
};
|
|
138
|
+
const result = { ...scan, policy: base };
|
|
139
|
+
if (!scan || !scan.ok) {
|
|
140
|
+
result.policy.reason = 'scan-failed';
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
const policy = ctx.policy;
|
|
144
|
+
const slug = ctx.slug;
|
|
145
|
+
const version = ctx.version;
|
|
146
|
+
const pkgDir = ctx.pkgDir;
|
|
147
|
+
if (!policy || !slug || !pkgDir) {
|
|
148
|
+
result.policy.reason = 'policy-unavailable';
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
const entry = policy.skills[slug];
|
|
152
|
+
if (!entry) {
|
|
153
|
+
result.policy.reason = 'no-entry';
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
if (!Array.isArray(scan.findings)) {
|
|
157
|
+
result.policy.reason = 'findings-unavailable';
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
if (entry.version !== version) {
|
|
161
|
+
result.policy.reason = 'version-mismatch';
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
const treeHash = computeTreeHash(pkgDir, slug);
|
|
165
|
+
result.policy.version = entry.version;
|
|
166
|
+
result.policy.treeHash = treeHash;
|
|
167
|
+
if (entry.treeHash !== treeHash) {
|
|
168
|
+
result.policy.reason = 'treehash-mismatch';
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
const exceptions = Array.isArray(entry.exceptions) ? entry.exceptions : [];
|
|
172
|
+
const kept = [];
|
|
173
|
+
for (const finding of scan.findings) {
|
|
174
|
+
const hit = exceptions.find((exception) => exceptionMatches(exception, finding));
|
|
175
|
+
if (hit) {
|
|
176
|
+
result.policy.excludedFindings.push({
|
|
177
|
+
rule: finding.rule_id,
|
|
178
|
+
path: normalizeRel(finding.file),
|
|
179
|
+
severity: finding.severity,
|
|
180
|
+
reason: hit.reason || null,
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
kept.push(finding);
|
|
185
|
+
}
|
|
186
|
+
const summary = summarize(kept);
|
|
187
|
+
result.policy.applied = true;
|
|
188
|
+
result.policy.reason = 'applied';
|
|
189
|
+
result.policy.excluded = result.policy.excludedFindings.length;
|
|
190
|
+
result.verdict = summary.verdict;
|
|
191
|
+
result.counts = summary.counts;
|
|
192
|
+
result.findings = kept;
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = {
|
|
197
|
+
VERDICT_SAFE,
|
|
198
|
+
VERDICT_CAUTION,
|
|
199
|
+
VERDICT_REVIEW,
|
|
200
|
+
VERDICT_BLOCK,
|
|
201
|
+
REMOVE_NAMES,
|
|
202
|
+
KEEP_EXTRA,
|
|
203
|
+
normalizeRel,
|
|
204
|
+
loadPolicy,
|
|
205
|
+
computeTreeHash,
|
|
206
|
+
summarize,
|
|
207
|
+
exceptionMatches,
|
|
208
|
+
applyScanPolicy,
|
|
209
|
+
};
|
package/lib/verify-gate.js
CHANGED
|
@@ -48,11 +48,17 @@ function parseScanOutput(stdout) {
|
|
|
48
48
|
try {
|
|
49
49
|
const value = JSON.parse(String(stdout || ''));
|
|
50
50
|
if (!value || typeof value.verdict !== 'string') {
|
|
51
|
-
return { ok: false, verdict: null, counts: null, error: '扫描输出缺少 verdict' };
|
|
51
|
+
return { ok: false, verdict: null, counts: null, findings: null, error: '扫描输出缺少 verdict' };
|
|
52
52
|
}
|
|
53
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
ok: true,
|
|
55
|
+
verdict: value.verdict,
|
|
56
|
+
counts: value.counts || {},
|
|
57
|
+
findings: Array.isArray(value.findings) ? value.findings : null,
|
|
58
|
+
error: null,
|
|
59
|
+
};
|
|
54
60
|
} catch (error) {
|
|
55
|
-
return { ok: false, verdict: null, counts: null, error: error.message };
|
|
61
|
+
return { ok: false, verdict: null, counts: null, findings: null, error: error.message };
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
64
|
|
|
@@ -62,22 +68,70 @@ function evaluateVerdict(verdict) {
|
|
|
62
68
|
return { decision: 'block', block: true, warn: true };
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
function
|
|
66
|
-
|
|
71
|
+
function runScanOnce(engine, target, options) {
|
|
72
|
+
return options.spawnSync(options.python, ['-B', engine, 'scan', target, '--json'], {
|
|
67
73
|
encoding: 'utf8',
|
|
68
74
|
timeout: 60000,
|
|
75
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
69
76
|
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function stderrBrief(result) {
|
|
80
|
+
const text = String((result && result.stderr) || '').trim();
|
|
81
|
+
if (!text) return '';
|
|
82
|
+
return text.split(/\r?\n/).filter(Boolean).slice(-2).join(' | ').slice(0, 300);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 元信扫描是只读幂等操作:解析失败(空输出 / 截断 / 进程异常)时重试一次,
|
|
87
|
+
* 避免偶发的子进程抖动把安装判死;两次都失败才按阻断处理。
|
|
88
|
+
*/
|
|
89
|
+
function runVerifier(engine, target, options) {
|
|
90
|
+
const opts = options || {};
|
|
91
|
+
const retries = opts.retries === undefined ? 1 : Math.max(0, Number(opts.retries) || 0);
|
|
92
|
+
let result = null;
|
|
93
|
+
let parsed = null;
|
|
94
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
95
|
+
result = runScanOnce(engine, target, opts);
|
|
96
|
+
if (!result || result.error || result.status === null) {
|
|
97
|
+
parsed = null;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
parsed = parseScanOutput(result.stdout);
|
|
101
|
+
if (parsed.ok) break;
|
|
102
|
+
}
|
|
70
103
|
if (result && result.error) {
|
|
71
|
-
return { ok: false, verdict: null, counts: null, error: result.error.message, exitCode: null };
|
|
104
|
+
return { ok: false, verdict: null, counts: null, findings: null, error: result.error.message, exitCode: null };
|
|
72
105
|
}
|
|
73
106
|
if (!result || result.status === null) {
|
|
74
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
verdict: null,
|
|
110
|
+
counts: null,
|
|
111
|
+
findings: null,
|
|
112
|
+
error: '元信 scan 执行失败(超时或无法启动)',
|
|
113
|
+
exitCode: null,
|
|
114
|
+
};
|
|
75
115
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return {
|
|
116
|
+
if (!parsed || !parsed.ok) {
|
|
117
|
+
const brief = stderrBrief(result);
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
verdict: null,
|
|
121
|
+
counts: null,
|
|
122
|
+
findings: null,
|
|
123
|
+
error: '元信 scan 输出无法解析(exit ' + result.status + ')' + (brief ? ': ' + brief : ''),
|
|
124
|
+
exitCode: result.status,
|
|
125
|
+
};
|
|
79
126
|
}
|
|
80
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
ok: true,
|
|
129
|
+
verdict: parsed.verdict,
|
|
130
|
+
counts: parsed.counts,
|
|
131
|
+
findings: parsed.findings,
|
|
132
|
+
error: null,
|
|
133
|
+
exitCode: result.status,
|
|
134
|
+
};
|
|
81
135
|
}
|
|
82
136
|
|
|
83
137
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yottameta/yotta-skills",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"description": "YuanGe (元阁) - orchestration routing, inventory, and one-command installer for the YottaMeta skill family (zero dependencies, Node.js only).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"lib",
|
|
27
27
|
"scripts",
|
|
28
28
|
"skills.json",
|
|
29
|
+
"scan-policy.json",
|
|
29
30
|
"!**/__pycache__",
|
|
30
31
|
"!**/*.pyc"
|
|
31
32
|
],
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
对每个待装技能:
|
|
9
9
|
|
|
10
10
|
1. 读清单(`skills.json`,默认随包;`YOTTA_SKILLS_MANIFEST` 可覆盖);
|
|
11
|
-
2. `npm pack <pkg>@<spec> --pack-destination
|
|
11
|
+
2. `npm pack <pkg>@<spec> --pack-destination <临时目录>`(默认源返回 404 时自动改用官方源重试一次,
|
|
12
|
+
证据记 `npm_registry_fallback`);
|
|
12
13
|
3. 系统 `tar -xzf` 解压到临时目录(产物应有 `SKILL.md`);
|
|
13
14
|
4. 读取包内 `skill-manifest.json`;没有 manifest 时使用 `skills.json` 的家族默认契约;
|
|
14
15
|
5. 校验 slug / package / version / 权限 / 生命周期脚本路径;
|
|
@@ -85,6 +86,9 @@
|
|
|
85
86
|
与 `bootstrap_scan=self`,随后用元信扫描其余家族包;
|
|
86
87
|
- verdict 处置:`SAFE TO INSTALL` 继续;`INSTALL WITH CAUTION` / `REVIEW REQUIRED`
|
|
87
88
|
继续但显示风险并留证;`DO NOT INSTALL` 和扫描失败阻断,不替换目标;
|
|
89
|
+
- scanPolicy 复核:`DO NOT INSTALL` 时用包内 `scan-policy.json` 的已审查例外复核(逐条绑定
|
|
90
|
+
skill + version + treeHash + 规则 + 路径;缺失 / 不匹配一律 fail-closed);豁免后无阻断级
|
|
91
|
+
发现则继续,显示 `↳ scanPolicy 复核:豁免 N 条已审查发现` 并把 `scan_policy` 写进安装证据;
|
|
88
92
|
- `--skip-scan` 只保留为人工应急路径,使用时输出 `explicit-unverified` 并写入证据;
|
|
89
93
|
- `update --auto` 始终执行装前门禁,不接受 `--skip-scan`。
|
|
90
94
|
|
package/references/tutorial.md
CHANGED
|
@@ -108,8 +108,8 @@ npx -y @yottameta/yotta-skills rollback --slug yotta-memory --agent codex
|
|
|
108
108
|
|
|
109
109
|
## 10. 常见问题
|
|
110
110
|
|
|
111
|
-
- **npmmirror 全新包 404
|
|
112
|
-
|
|
111
|
+
- **npmmirror 全新包 404**:镜像同步有延迟时安装器会自动改用官方源重试一次(默认行为);
|
|
112
|
+
如需固定在指定源,设置 `YOTTA_SKILLS_NPM_FLAGS=--registry=...` 或 `YOTTA_SKILLS_NO_FALLBACK=1`。
|
|
113
113
|
- **`--agent` 报未收录**:改用 `--dir` 指到该智能体的技能目录(`.agents/skills` 不是
|
|
114
114
|
通用目录)。
|
|
115
115
|
- **某技能安装失败**:汇总报告给出失败原因;可单装该技能排查:
|
package/scan-policy.json
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"generatedFrom": "tools/build_opencode_bundle.py SCAN_SPECS",
|
|
4
|
+
"note": "元信装前扫描例外表;绑定 skill+version+treeHash(fail-closed),仅供已审查的检测规则/文档说明类命中豁免。",
|
|
5
|
+
"skippedSkills": [
|
|
6
|
+
"yotta-dev-mcp"
|
|
7
|
+
],
|
|
8
|
+
"skills": {
|
|
9
|
+
"yotta-anti-shallow": {
|
|
10
|
+
"version": "1.4.1",
|
|
11
|
+
"treeHash": "sha256:81fbbb00cb39ff09f8c6a35e6099228bfadcd71394cace26e5ca61d87cba5e56",
|
|
12
|
+
"files": 7,
|
|
13
|
+
"bytes": 220568,
|
|
14
|
+
"mode": "strict",
|
|
15
|
+
"exceptions": [
|
|
16
|
+
{
|
|
17
|
+
"scanners": [
|
|
18
|
+
"yotta-verify",
|
|
19
|
+
"yotta-agent-hardening"
|
|
20
|
+
],
|
|
21
|
+
"rule": "PIJ-001",
|
|
22
|
+
"path": "README.md",
|
|
23
|
+
"action": "allow",
|
|
24
|
+
"category": "prompt-injection",
|
|
25
|
+
"detection": false,
|
|
26
|
+
"reason": "user-control-documentation"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"yotta-chain": {
|
|
31
|
+
"version": "0.1.4",
|
|
32
|
+
"treeHash": "sha256:ba455f7460fc0b0b6469d57cbd13b60c7a103fb85c0619bb5f61efcd4085f5a2",
|
|
33
|
+
"files": 9,
|
|
34
|
+
"bytes": 274372,
|
|
35
|
+
"mode": "strict",
|
|
36
|
+
"exceptions": [
|
|
37
|
+
{
|
|
38
|
+
"scanners": [
|
|
39
|
+
"yotta-agent-hardening"
|
|
40
|
+
],
|
|
41
|
+
"rule": "HIS-001E",
|
|
42
|
+
"path": "scripts/yotta_chain.py",
|
|
43
|
+
"action": "allow",
|
|
44
|
+
"category": "sensitive-read",
|
|
45
|
+
"detection": false,
|
|
46
|
+
"reason": "dependency-manifest-analysis"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"scanners": [
|
|
50
|
+
"yotta-agent-hardening"
|
|
51
|
+
],
|
|
52
|
+
"rule": "HIS-002",
|
|
53
|
+
"path": "scripts/yotta_chain.py",
|
|
54
|
+
"action": "allow",
|
|
55
|
+
"category": "data-exfiltration",
|
|
56
|
+
"detection": false,
|
|
57
|
+
"reason": "dependency-manifest-analysis"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"yotta-guardian": {
|
|
62
|
+
"version": "0.1.7",
|
|
63
|
+
"treeHash": "sha256:7bc0b09d06b6b6dbbb8cb29601ccdb2a41b1b9bdfd96d78af2c10ede035dce16",
|
|
64
|
+
"files": 13,
|
|
65
|
+
"bytes": 267260,
|
|
66
|
+
"mode": "detection",
|
|
67
|
+
"exceptions": [
|
|
68
|
+
{
|
|
69
|
+
"scanners": [
|
|
70
|
+
"yotta-verify",
|
|
71
|
+
"yotta-agent-hardening"
|
|
72
|
+
],
|
|
73
|
+
"rule": "PIJ-020",
|
|
74
|
+
"path": "README.zh-CN.md",
|
|
75
|
+
"action": "allow",
|
|
76
|
+
"category": "prompt-injection",
|
|
77
|
+
"detection": true,
|
|
78
|
+
"reason": "detection-rule"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"scanners": [
|
|
82
|
+
"yotta-verify",
|
|
83
|
+
"yotta-agent-hardening"
|
|
84
|
+
],
|
|
85
|
+
"rule": "PIJ-020",
|
|
86
|
+
"path": "SKILL.md",
|
|
87
|
+
"action": "allow",
|
|
88
|
+
"category": "prompt-injection",
|
|
89
|
+
"detection": true,
|
|
90
|
+
"reason": "detection-rule"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"scanners": [
|
|
94
|
+
"yotta-verify",
|
|
95
|
+
"yotta-agent-hardening"
|
|
96
|
+
],
|
|
97
|
+
"rule": "NET-008",
|
|
98
|
+
"path": "SKILL.md",
|
|
99
|
+
"action": "allow",
|
|
100
|
+
"category": "network-call",
|
|
101
|
+
"detection": true,
|
|
102
|
+
"reason": "detection-rule"
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
"yotta-logwatch": {
|
|
107
|
+
"version": "0.3.0",
|
|
108
|
+
"treeHash": "sha256:fbe53b95f9fb738555f2936c8285fa6857613328d0c667f73a259fb3bccfbef5",
|
|
109
|
+
"files": 13,
|
|
110
|
+
"bytes": 267453,
|
|
111
|
+
"mode": "detection",
|
|
112
|
+
"exceptions": [
|
|
113
|
+
{
|
|
114
|
+
"scanners": [
|
|
115
|
+
"yotta-verify",
|
|
116
|
+
"yotta-agent-hardening"
|
|
117
|
+
],
|
|
118
|
+
"rule": "DEX-007",
|
|
119
|
+
"path": "SKILL.md",
|
|
120
|
+
"action": "allow",
|
|
121
|
+
"category": "remote-download",
|
|
122
|
+
"detection": true,
|
|
123
|
+
"reason": "detection-rule"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"scanners": [
|
|
127
|
+
"yotta-verify",
|
|
128
|
+
"yotta-agent-hardening"
|
|
129
|
+
],
|
|
130
|
+
"rule": "DEX-007",
|
|
131
|
+
"path": "README.zh-CN.md",
|
|
132
|
+
"action": "allow",
|
|
133
|
+
"category": "remote-download",
|
|
134
|
+
"detection": true,
|
|
135
|
+
"reason": "detection-rule"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"scanners": [
|
|
139
|
+
"yotta-verify",
|
|
140
|
+
"yotta-agent-hardening"
|
|
141
|
+
],
|
|
142
|
+
"rule": "DEX-007",
|
|
143
|
+
"path": "references/powershell-log-rules.md",
|
|
144
|
+
"action": "allow",
|
|
145
|
+
"category": "remote-download",
|
|
146
|
+
"detection": true,
|
|
147
|
+
"reason": "detection-rule"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"scanners": [
|
|
151
|
+
"yotta-verify",
|
|
152
|
+
"yotta-agent-hardening"
|
|
153
|
+
],
|
|
154
|
+
"rule": "OBF-001",
|
|
155
|
+
"path": "references/web-log-rules.md",
|
|
156
|
+
"action": "allow",
|
|
157
|
+
"category": "obfuscation",
|
|
158
|
+
"detection": true,
|
|
159
|
+
"reason": "detection-rule"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"scanners": [
|
|
163
|
+
"yotta-verify",
|
|
164
|
+
"yotta-agent-hardening"
|
|
165
|
+
],
|
|
166
|
+
"rule": "DEX-007",
|
|
167
|
+
"path": "scripts/yotta_logwatch.py",
|
|
168
|
+
"action": "allow",
|
|
169
|
+
"category": "remote-download",
|
|
170
|
+
"detection": true,
|
|
171
|
+
"reason": "detection-rule"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"scanners": [
|
|
175
|
+
"yotta-verify",
|
|
176
|
+
"yotta-agent-hardening"
|
|
177
|
+
],
|
|
178
|
+
"rule": "PIJ-020",
|
|
179
|
+
"path": "references/powershell-log-rules.md",
|
|
180
|
+
"action": "allow",
|
|
181
|
+
"category": "prompt-injection",
|
|
182
|
+
"detection": true,
|
|
183
|
+
"reason": "detection-rule"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"scanners": [
|
|
187
|
+
"yotta-verify",
|
|
188
|
+
"yotta-agent-hardening"
|
|
189
|
+
],
|
|
190
|
+
"rule": "PIJ-028",
|
|
191
|
+
"path": "scripts/yotta_logwatch.py",
|
|
192
|
+
"action": "allow",
|
|
193
|
+
"category": "credential-harvest",
|
|
194
|
+
"detection": true,
|
|
195
|
+
"reason": "detection-rule"
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
"yotta-memory": {
|
|
200
|
+
"version": "0.19.0",
|
|
201
|
+
"treeHash": "sha256:05b7befbd1dcc565bb231fa70971c3947ea24dcb2eca7cf353f3667fd02a68e3",
|
|
202
|
+
"files": 18,
|
|
203
|
+
"bytes": 1160686,
|
|
204
|
+
"mode": "strict",
|
|
205
|
+
"exceptions": [
|
|
206
|
+
{
|
|
207
|
+
"scanners": [
|
|
208
|
+
"yotta-verify",
|
|
209
|
+
"yotta-agent-hardening"
|
|
210
|
+
],
|
|
211
|
+
"rule": "DEX-007",
|
|
212
|
+
"path": "bin/yotta-memory.js",
|
|
213
|
+
"action": "allow",
|
|
214
|
+
"category": "remote-download",
|
|
215
|
+
"detection": true,
|
|
216
|
+
"reason": "memory-store-scanner-rule-table"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"scanners": [
|
|
220
|
+
"yotta-verify",
|
|
221
|
+
"yotta-agent-hardening"
|
|
222
|
+
],
|
|
223
|
+
"rule": "PIJ-002",
|
|
224
|
+
"path": "bin/yotta-memory.js",
|
|
225
|
+
"action": "allow",
|
|
226
|
+
"category": "prompt-injection",
|
|
227
|
+
"detection": true,
|
|
228
|
+
"reason": "memory-store-scanner-rule-table"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"scanners": [
|
|
232
|
+
"yotta-verify",
|
|
233
|
+
"yotta-agent-hardening"
|
|
234
|
+
],
|
|
235
|
+
"rule": "PIJ-007",
|
|
236
|
+
"path": "bin/yotta-memory.js",
|
|
237
|
+
"action": "allow",
|
|
238
|
+
"category": "prompt-injection",
|
|
239
|
+
"detection": true,
|
|
240
|
+
"reason": "memory-store-scanner-rule-table"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"scanners": [
|
|
244
|
+
"yotta-verify",
|
|
245
|
+
"yotta-agent-hardening"
|
|
246
|
+
],
|
|
247
|
+
"rule": "PIJ-009",
|
|
248
|
+
"path": "bin/yotta-memory.js",
|
|
249
|
+
"action": "allow",
|
|
250
|
+
"category": "prompt-injection",
|
|
251
|
+
"detection": true,
|
|
252
|
+
"reason": "memory-store-scanner-rule-table"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"scanners": [
|
|
256
|
+
"yotta-verify",
|
|
257
|
+
"yotta-agent-hardening"
|
|
258
|
+
],
|
|
259
|
+
"rule": "PIJ-012",
|
|
260
|
+
"path": "bin/yotta-memory.js",
|
|
261
|
+
"action": "allow",
|
|
262
|
+
"category": "prompt-injection",
|
|
263
|
+
"detection": true,
|
|
264
|
+
"reason": "memory-store-scanner-rule-table"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"scanners": [
|
|
268
|
+
"yotta-verify",
|
|
269
|
+
"yotta-agent-hardening"
|
|
270
|
+
],
|
|
271
|
+
"rule": "PIJ-020",
|
|
272
|
+
"path": "bin/yotta-memory.js",
|
|
273
|
+
"action": "allow",
|
|
274
|
+
"category": "prompt-injection",
|
|
275
|
+
"detection": true,
|
|
276
|
+
"reason": "memory-store-scanner-rule-table"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"scanners": [
|
|
280
|
+
"yotta-verify",
|
|
281
|
+
"yotta-agent-hardening"
|
|
282
|
+
],
|
|
283
|
+
"rule": "PIJ-022",
|
|
284
|
+
"path": "bin/yotta-memory.js",
|
|
285
|
+
"action": "allow",
|
|
286
|
+
"category": "prompt-injection",
|
|
287
|
+
"detection": true,
|
|
288
|
+
"reason": "memory-store-scanner-rule-table"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"scanners": [
|
|
292
|
+
"yotta-verify",
|
|
293
|
+
"yotta-agent-hardening"
|
|
294
|
+
],
|
|
295
|
+
"rule": "PIJ-026",
|
|
296
|
+
"path": "bin/yotta-memory.js",
|
|
297
|
+
"action": "allow",
|
|
298
|
+
"category": "prompt-injection",
|
|
299
|
+
"detection": true,
|
|
300
|
+
"reason": "memory-store-scanner-rule-table"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"scanners": [
|
|
304
|
+
"yotta-agent-hardening"
|
|
305
|
+
],
|
|
306
|
+
"rule": "HIS-002",
|
|
307
|
+
"path": "bin/yotta-memory.js",
|
|
308
|
+
"action": "allow",
|
|
309
|
+
"category": "data-exfiltration",
|
|
310
|
+
"detection": false,
|
|
311
|
+
"reason": "config-dir-read-and-local-http-server-are-separated"
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
},
|
|
315
|
+
"yotta-recon": {
|
|
316
|
+
"version": "0.1.7",
|
|
317
|
+
"treeHash": "sha256:d3d04d34fe4416017b8b4304b0ce78bc1447b908f7d01747826924cde2adead2",
|
|
318
|
+
"files": 11,
|
|
319
|
+
"bytes": 275665,
|
|
320
|
+
"mode": "detection",
|
|
321
|
+
"exceptions": [
|
|
322
|
+
{
|
|
323
|
+
"scanners": [
|
|
324
|
+
"yotta-verify",
|
|
325
|
+
"yotta-agent-hardening"
|
|
326
|
+
],
|
|
327
|
+
"rule": "NET-004",
|
|
328
|
+
"path": "references/protocol-probes.md",
|
|
329
|
+
"action": "allow",
|
|
330
|
+
"category": "network-call",
|
|
331
|
+
"detection": false,
|
|
332
|
+
"reason": "authorized-scan-capability"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"scanners": [
|
|
336
|
+
"yotta-verify",
|
|
337
|
+
"yotta-agent-hardening"
|
|
338
|
+
],
|
|
339
|
+
"rule": "NET-004",
|
|
340
|
+
"path": "scripts/yotta_recon.py",
|
|
341
|
+
"action": "allow",
|
|
342
|
+
"category": "network-call",
|
|
343
|
+
"detection": false,
|
|
344
|
+
"reason": "authorized-scan-capability"
|
|
345
|
+
}
|
|
346
|
+
]
|
|
347
|
+
},
|
|
348
|
+
"yotta-security-audit": {
|
|
349
|
+
"version": "0.3.0",
|
|
350
|
+
"treeHash": "sha256:bffcb3573f64b11d2b78e6aa5793e6db35f1f429f9a7dc7ad2b49cd8ab25ea8c",
|
|
351
|
+
"files": 15,
|
|
352
|
+
"bytes": 355942,
|
|
353
|
+
"mode": "detection",
|
|
354
|
+
"exceptions": [
|
|
355
|
+
{
|
|
356
|
+
"scanners": [
|
|
357
|
+
"yotta-agent-hardening"
|
|
358
|
+
],
|
|
359
|
+
"rule": "PIJ-020",
|
|
360
|
+
"path": "README.zh-CN.md",
|
|
361
|
+
"action": "allow",
|
|
362
|
+
"category": "prompt-injection",
|
|
363
|
+
"detection": true,
|
|
364
|
+
"reason": "detection-rule"
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"scanners": [
|
|
368
|
+
"yotta-agent-hardening"
|
|
369
|
+
],
|
|
370
|
+
"rule": "PIJ-020",
|
|
371
|
+
"path": "SKILL.md",
|
|
372
|
+
"action": "allow",
|
|
373
|
+
"category": "prompt-injection",
|
|
374
|
+
"detection": true,
|
|
375
|
+
"reason": "detection-rule"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"scanners": [
|
|
379
|
+
"yotta-agent-hardening"
|
|
380
|
+
],
|
|
381
|
+
"rule": "PIJ-020",
|
|
382
|
+
"path": "references/threat-patterns.md",
|
|
383
|
+
"action": "allow",
|
|
384
|
+
"category": "prompt-injection",
|
|
385
|
+
"detection": true,
|
|
386
|
+
"reason": "detection-rule"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"scanners": [
|
|
390
|
+
"yotta-agent-hardening"
|
|
391
|
+
],
|
|
392
|
+
"rule": "PIJ-028",
|
|
393
|
+
"path": "README.zh-CN.md",
|
|
394
|
+
"action": "allow",
|
|
395
|
+
"category": "credential-harvest",
|
|
396
|
+
"detection": true,
|
|
397
|
+
"reason": "detection-rule"
|
|
398
|
+
}
|
|
399
|
+
]
|
|
400
|
+
},
|
|
401
|
+
"yotta-triage": {
|
|
402
|
+
"version": "0.1.3",
|
|
403
|
+
"treeHash": "sha256:f1dabde26bf05cf9b36cc28ebe5522ba3af558a6824306bd4a85ad583684df5c",
|
|
404
|
+
"files": 10,
|
|
405
|
+
"bytes": 233925,
|
|
406
|
+
"mode": "detection",
|
|
407
|
+
"exceptions": [
|
|
408
|
+
{
|
|
409
|
+
"scanners": [
|
|
410
|
+
"yotta-verify",
|
|
411
|
+
"yotta-agent-hardening"
|
|
412
|
+
],
|
|
413
|
+
"rule": "DEX-007",
|
|
414
|
+
"path": "references/risk-model.md",
|
|
415
|
+
"action": "allow",
|
|
416
|
+
"category": "remote-download",
|
|
417
|
+
"detection": true,
|
|
418
|
+
"reason": "detection-rule"
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"scanners": [
|
|
422
|
+
"yotta-verify",
|
|
423
|
+
"yotta-agent-hardening"
|
|
424
|
+
],
|
|
425
|
+
"rule": "DEX-007",
|
|
426
|
+
"path": "references/triage-spec.md",
|
|
427
|
+
"action": "allow",
|
|
428
|
+
"category": "remote-download",
|
|
429
|
+
"detection": true,
|
|
430
|
+
"reason": "detection-rule"
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
"scanners": [
|
|
434
|
+
"yotta-verify",
|
|
435
|
+
"yotta-agent-hardening"
|
|
436
|
+
],
|
|
437
|
+
"rule": "DEX-007",
|
|
438
|
+
"path": "scripts/yotta_triage.py",
|
|
439
|
+
"action": "allow",
|
|
440
|
+
"category": "remote-download",
|
|
441
|
+
"detection": true,
|
|
442
|
+
"reason": "detection-rule"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"scanners": [
|
|
446
|
+
"yotta-verify",
|
|
447
|
+
"yotta-agent-hardening"
|
|
448
|
+
],
|
|
449
|
+
"rule": "OBF-003",
|
|
450
|
+
"path": "scripts/yotta_triage.py",
|
|
451
|
+
"action": "allow",
|
|
452
|
+
"category": "obfuscation",
|
|
453
|
+
"detection": true,
|
|
454
|
+
"reason": "detection-rule"
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
"scanners": [
|
|
458
|
+
"yotta-verify",
|
|
459
|
+
"yotta-agent-hardening"
|
|
460
|
+
],
|
|
461
|
+
"rule": "NET-008",
|
|
462
|
+
"path": "references/triage-spec.md",
|
|
463
|
+
"action": "allow",
|
|
464
|
+
"category": "network-call",
|
|
465
|
+
"detection": true,
|
|
466
|
+
"reason": "detection-rule"
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
},
|
|
470
|
+
"yotta-vetter": {
|
|
471
|
+
"version": "0.2.6",
|
|
472
|
+
"treeHash": "sha256:691ff78f9a7e1a46d1595d97b6a97d84e94bdcb5c159f9d743f7f0e875063382",
|
|
473
|
+
"files": 11,
|
|
474
|
+
"bytes": 222254,
|
|
475
|
+
"mode": "strict",
|
|
476
|
+
"exceptions": [
|
|
477
|
+
{
|
|
478
|
+
"scanners": [
|
|
479
|
+
"yotta-verify",
|
|
480
|
+
"yotta-agent-hardening"
|
|
481
|
+
],
|
|
482
|
+
"rule": "NET-006",
|
|
483
|
+
"path": "scripts/yotta_vetter.py",
|
|
484
|
+
"action": "allow",
|
|
485
|
+
"category": "network-call",
|
|
486
|
+
"detection": false,
|
|
487
|
+
"reason": "documented-source-check"
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
"scanners": [
|
|
491
|
+
"yotta-agent-hardening"
|
|
492
|
+
],
|
|
493
|
+
"rule": "CRE-002",
|
|
494
|
+
"path": "CHANGELOG.md",
|
|
495
|
+
"action": "allow",
|
|
496
|
+
"category": "credential-access",
|
|
497
|
+
"detection": false,
|
|
498
|
+
"reason": "detection-changelog"
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
"scanners": [
|
|
502
|
+
"yotta-agent-hardening"
|
|
503
|
+
],
|
|
504
|
+
"rule": "CRE-003",
|
|
505
|
+
"path": "CHANGELOG.md",
|
|
506
|
+
"action": "allow",
|
|
507
|
+
"category": "credential-access",
|
|
508
|
+
"detection": false,
|
|
509
|
+
"reason": "detection-changelog"
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
"scanners": [
|
|
513
|
+
"yotta-agent-hardening"
|
|
514
|
+
],
|
|
515
|
+
"rule": "CRE-005",
|
|
516
|
+
"path": "CHANGELOG.md",
|
|
517
|
+
"action": "allow",
|
|
518
|
+
"category": "credential-access",
|
|
519
|
+
"detection": false,
|
|
520
|
+
"reason": "detection-changelog"
|
|
521
|
+
}
|
|
522
|
+
]
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|