code-abyss 1.6.15 → 1.7.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/bin/install.js +25 -4
- package/package.json +2 -2
- package/skills/SKILL.md +24 -16
- package/skills/domains/ai/SKILL.md +2 -2
- package/skills/domains/ai/prompt-and-eval.md +279 -0
- package/skills/domains/architecture/SKILL.md +2 -3
- package/skills/domains/architecture/security-arch.md +87 -0
- package/skills/domains/data-engineering/SKILL.md +188 -26
- package/skills/domains/development/SKILL.md +1 -4
- package/skills/domains/devops/SKILL.md +3 -5
- package/skills/domains/devops/performance.md +63 -0
- package/skills/domains/devops/testing.md +97 -0
- package/skills/domains/frontend-design/SKILL.md +12 -3
- package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
- package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
- package/skills/domains/frontend-design/engineering.md +287 -0
- package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
- package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
- package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
- package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
- package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
- package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
- package/skills/domains/infrastructure/SKILL.md +174 -34
- package/skills/domains/mobile/SKILL.md +211 -21
- package/skills/domains/orchestration/SKILL.md +1 -0
- package/skills/domains/security/SKILL.md +4 -6
- package/skills/domains/security/blue-team.md +57 -0
- package/skills/domains/security/red-team.md +54 -0
- package/skills/domains/security/threat-intel.md +50 -0
- package/skills/orchestration/multi-agent/SKILL.md +195 -46
- package/skills/run_skill.js +134 -0
- package/skills/tools/gen-docs/SKILL.md +6 -4
- package/skills/tools/gen-docs/scripts/doc_generator.js +349 -0
- package/skills/tools/verify-change/SKILL.md +8 -6
- package/skills/tools/verify-change/scripts/change_analyzer.js +270 -0
- package/skills/tools/verify-module/SKILL.md +6 -4
- package/skills/tools/verify-module/scripts/module_scanner.js +145 -0
- package/skills/tools/verify-quality/SKILL.md +5 -3
- package/skills/tools/verify-quality/scripts/quality_checker.js +276 -0
- package/skills/tools/verify-security/SKILL.md +7 -5
- package/skills/tools/verify-security/scripts/security_scanner.js +133 -0
- package/skills/domains/COVERAGE_PLAN.md +0 -232
- package/skills/domains/ai/model-evaluation.md +0 -790
- package/skills/domains/ai/prompt-engineering.md +0 -703
- package/skills/domains/architecture/compliance.md +0 -299
- package/skills/domains/architecture/data-security.md +0 -184
- package/skills/domains/data-engineering/data-pipeline.md +0 -762
- package/skills/domains/data-engineering/data-quality.md +0 -894
- package/skills/domains/data-engineering/stream-processing.md +0 -791
- package/skills/domains/development/dart.md +0 -963
- package/skills/domains/development/kotlin.md +0 -834
- package/skills/domains/development/php.md +0 -659
- package/skills/domains/development/swift.md +0 -755
- package/skills/domains/devops/e2e-testing.md +0 -914
- package/skills/domains/devops/performance-testing.md +0 -734
- package/skills/domains/devops/testing-strategy.md +0 -667
- package/skills/domains/frontend-design/build-tools.md +0 -743
- package/skills/domains/frontend-design/performance.md +0 -734
- package/skills/domains/frontend-design/testing.md +0 -699
- package/skills/domains/infrastructure/gitops.md +0 -735
- package/skills/domains/infrastructure/iac.md +0 -855
- package/skills/domains/infrastructure/kubernetes.md +0 -1018
- package/skills/domains/mobile/android-dev.md +0 -979
- package/skills/domains/mobile/cross-platform.md +0 -795
- package/skills/domains/mobile/ios-dev.md +0 -931
- package/skills/domains/security/secrets-management.md +0 -834
- package/skills/domains/security/supply-chain.md +0 -931
- package/skills/domains/security/threat-modeling.md +0 -828
- package/skills/run_skill.py +0 -88
- package/skills/tests/README.md +0 -225
- package/skills/tests/SUMMARY.md +0 -362
- package/skills/tests/__init__.py +0 -3
- package/skills/tests/test_change_analyzer.py +0 -558
- package/skills/tests/test_doc_generator.py +0 -538
- package/skills/tests/test_module_scanner.py +0 -376
- package/skills/tests/test_quality_checker.py +0 -516
- package/skills/tests/test_security_scanner.py +0 -426
- package/skills/tools/gen-docs/scripts/doc_generator.py +0 -491
- package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
- package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
- package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
- package/skills/tools/verify-security/scripts/security_scanner.py +0 -368
|
@@ -1,931 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: supply-chain
|
|
3
|
-
description: 软件供应链安全。SBOM、依赖扫描、签名验证、SLSA框架、制品完整性。当用户提到供应链安全、SBOM、依赖扫描、Sigstore、SLSA、制品安全、软件物料清单时使用。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# 🔗 供应链安全 · Supply Chain Security
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## 供应链威胁模型
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
源代码 → 构建 → 制品 → 分发 → 部署 → 运行
|
|
13
|
-
│ │ │ │ │ │
|
|
14
|
-
├─ 投毒 ├─ 篡改 ├─ 后门 ├─ 劫持 ├─ 提权 ├─ 横向
|
|
15
|
-
└─ 泄露 └─ 注入 └─ 恶意 └─ 伪造 └─ 逃逸 └─ 渗出
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
### 攻击向量
|
|
19
|
-
|
|
20
|
-
| 阶段 | 攻击方式 | 示例 |
|
|
21
|
-
|------|----------|------|
|
|
22
|
-
| 源代码 | 依赖投毒 | event-stream、ua-parser-js |
|
|
23
|
-
| 构建 | CI/CD 劫持 | SolarWinds、CodeCov |
|
|
24
|
-
| 制品 | 恶意包 | PyPI/npm 钓鱼包 |
|
|
25
|
-
| 分发 | 中间人攻击 | 镜像劫持 |
|
|
26
|
-
| 部署 | 配置篡改 | Kubernetes YAML 注入 |
|
|
27
|
-
| 运行 | 容器逃逸 | 特权容器、内核漏洞 |
|
|
28
|
-
|
|
29
|
-
## SBOM (软件物料清单)
|
|
30
|
-
|
|
31
|
-
### SBOM 标准对比
|
|
32
|
-
|
|
33
|
-
| 标准 | 组织 | 格式 | 特点 |
|
|
34
|
-
|------|------|------|------|
|
|
35
|
-
| SPDX | Linux Foundation | JSON/YAML/RDF | ISO 标准、许可证重点 |
|
|
36
|
-
| CycloneDX | OWASP | JSON/XML | 安全重点、漏洞关联 |
|
|
37
|
-
| SWID | ISO/IEC | XML | 软件识别标签 |
|
|
38
|
-
|
|
39
|
-
### 生成 SBOM (Syft)
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
# 安装 Syft
|
|
43
|
-
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh
|
|
44
|
-
|
|
45
|
-
# 扫描容器镜像
|
|
46
|
-
syft nginx:latest -o cyclonedx-json > sbom.json
|
|
47
|
-
syft nginx:latest -o spdx-json > sbom-spdx.json
|
|
48
|
-
|
|
49
|
-
# 扫描目录
|
|
50
|
-
syft dir:. -o cyclonedx-json
|
|
51
|
-
|
|
52
|
-
# 扫描 Git 仓库
|
|
53
|
-
syft git:https://github.com/user/repo -o json
|
|
54
|
-
|
|
55
|
-
# 多格式输出
|
|
56
|
-
syft nginx:latest -o table,json=sbom.json,spdx=sbom.spdx
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### CycloneDX SBOM 结构
|
|
60
|
-
|
|
61
|
-
```json
|
|
62
|
-
{
|
|
63
|
-
"bomFormat": "CycloneDX",
|
|
64
|
-
"specVersion": "1.5",
|
|
65
|
-
"version": 1,
|
|
66
|
-
"metadata": {
|
|
67
|
-
"timestamp": "2026-02-09T00:00:00Z",
|
|
68
|
-
"component": {
|
|
69
|
-
"type": "application",
|
|
70
|
-
"name": "my-app",
|
|
71
|
-
"version": "1.0.0"
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
"components": [
|
|
75
|
-
{
|
|
76
|
-
"type": "library",
|
|
77
|
-
"name": "express",
|
|
78
|
-
"version": "4.18.2",
|
|
79
|
-
"purl": "pkg:npm/express@4.18.2",
|
|
80
|
-
"licenses": [{"license": {"id": "MIT"}}],
|
|
81
|
-
"hashes": [
|
|
82
|
-
{
|
|
83
|
-
"alg": "SHA-256",
|
|
84
|
-
"content": "abc123..."
|
|
85
|
-
}
|
|
86
|
-
],
|
|
87
|
-
"externalReferences": [
|
|
88
|
-
{
|
|
89
|
-
"type": "vcs",
|
|
90
|
-
"url": "https://github.com/expressjs/express"
|
|
91
|
-
}
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
],
|
|
95
|
-
"dependencies": [
|
|
96
|
-
{
|
|
97
|
-
"ref": "pkg:npm/express@4.18.2",
|
|
98
|
-
"dependsOn": [
|
|
99
|
-
"pkg:npm/body-parser@1.20.1",
|
|
100
|
-
"pkg:npm/cookie@0.5.0"
|
|
101
|
-
]
|
|
102
|
-
}
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### SBOM 生成自动化
|
|
108
|
-
|
|
109
|
-
```python
|
|
110
|
-
#!/usr/bin/env python3
|
|
111
|
-
"""SBOM 生成与分析"""
|
|
112
|
-
import subprocess
|
|
113
|
-
import json
|
|
114
|
-
from typing import List, Dict
|
|
115
|
-
|
|
116
|
-
class SBOMGenerator:
|
|
117
|
-
def __init__(self, target: str):
|
|
118
|
-
self.target = target
|
|
119
|
-
self.sbom = None
|
|
120
|
-
|
|
121
|
-
def generate(self, format: str = "cyclonedx-json") -> Dict:
|
|
122
|
-
"""生成 SBOM"""
|
|
123
|
-
result = subprocess.run(
|
|
124
|
-
["syft", self.target, "-o", format],
|
|
125
|
-
capture_output=True,
|
|
126
|
-
text=True
|
|
127
|
-
)
|
|
128
|
-
self.sbom = json.loads(result.stdout)
|
|
129
|
-
return self.sbom
|
|
130
|
-
|
|
131
|
-
def get_components(self) -> List[Dict]:
|
|
132
|
-
"""提取组件列表"""
|
|
133
|
-
if not self.sbom:
|
|
134
|
-
self.generate()
|
|
135
|
-
return self.sbom.get("components", [])
|
|
136
|
-
|
|
137
|
-
def find_component(self, name: str) -> List[Dict]:
|
|
138
|
-
"""查找特定组件"""
|
|
139
|
-
components = self.get_components()
|
|
140
|
-
return [c for c in components if name.lower() in c["name"].lower()]
|
|
141
|
-
|
|
142
|
-
def get_licenses(self) -> Dict[str, int]:
|
|
143
|
-
"""统计许可证"""
|
|
144
|
-
licenses = {}
|
|
145
|
-
for comp in self.get_components():
|
|
146
|
-
for lic in comp.get("licenses", []):
|
|
147
|
-
lic_id = lic.get("license", {}).get("id", "Unknown")
|
|
148
|
-
licenses[lic_id] = licenses.get(lic_id, 0) + 1
|
|
149
|
-
return licenses
|
|
150
|
-
|
|
151
|
-
def export_report(self, output: str):
|
|
152
|
-
"""导出报告"""
|
|
153
|
-
components = self.get_components()
|
|
154
|
-
licenses = self.get_licenses()
|
|
155
|
-
|
|
156
|
-
report = f"# SBOM 报告: {self.target}\n\n"
|
|
157
|
-
report += f"## 统计\n"
|
|
158
|
-
report += f"- 组件总数: {len(components)}\n"
|
|
159
|
-
report += f"- 许可证类型: {len(licenses)}\n\n"
|
|
160
|
-
report += f"## 许可证分布\n"
|
|
161
|
-
for lic, count in sorted(licenses.items(), key=lambda x: -x[1]):
|
|
162
|
-
report += f"- {lic}: {count}\n"
|
|
163
|
-
|
|
164
|
-
with open(output, "w") as f:
|
|
165
|
-
f.write(report)
|
|
166
|
-
|
|
167
|
-
# 使用示例
|
|
168
|
-
gen = SBOMGenerator("nginx:latest")
|
|
169
|
-
gen.generate()
|
|
170
|
-
print(f"发现 {len(gen.get_components())} 个组件")
|
|
171
|
-
gen.export_report("sbom-report.md")
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## 依赖扫描
|
|
175
|
-
|
|
176
|
-
### Trivy 漏洞扫描
|
|
177
|
-
|
|
178
|
-
```bash
|
|
179
|
-
# 安装 Trivy
|
|
180
|
-
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
|
|
181
|
-
|
|
182
|
-
# 扫描容器镜像
|
|
183
|
-
trivy image nginx:latest
|
|
184
|
-
trivy image --severity HIGH,CRITICAL nginx:latest
|
|
185
|
-
|
|
186
|
-
# 扫描文件系统
|
|
187
|
-
trivy fs .
|
|
188
|
-
trivy fs --scanners vuln,secret,misconfig .
|
|
189
|
-
|
|
190
|
-
# 扫描 SBOM
|
|
191
|
-
trivy sbom sbom.json
|
|
192
|
-
|
|
193
|
-
# 生成报告
|
|
194
|
-
trivy image nginx:latest -f json -o report.json
|
|
195
|
-
trivy image nginx:latest -f sarif -o report.sarif
|
|
196
|
-
|
|
197
|
-
# 集成 CI/CD
|
|
198
|
-
trivy image --exit-code 1 --severity CRITICAL myapp:latest
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Grype 依赖扫描
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
# 安装 Grype
|
|
205
|
-
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh
|
|
206
|
-
|
|
207
|
-
# 扫描镜像
|
|
208
|
-
grype nginx:latest
|
|
209
|
-
|
|
210
|
-
# 扫描 SBOM
|
|
211
|
-
grype sbom:./sbom.json
|
|
212
|
-
|
|
213
|
-
# 指定漏洞数据库
|
|
214
|
-
grype nginx:latest --db /path/to/db
|
|
215
|
-
|
|
216
|
-
# 输出格式
|
|
217
|
-
grype nginx:latest -o json
|
|
218
|
-
grype nginx:latest -o table
|
|
219
|
-
grype nginx:latest -o cyclonedx
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Snyk 扫描集成
|
|
223
|
-
|
|
224
|
-
```bash
|
|
225
|
-
# 安装 Snyk CLI
|
|
226
|
-
npm install -g snyk
|
|
227
|
-
|
|
228
|
-
# 认证
|
|
229
|
-
snyk auth
|
|
230
|
-
|
|
231
|
-
# 扫描项目
|
|
232
|
-
snyk test
|
|
233
|
-
snyk test --severity-threshold=high
|
|
234
|
-
|
|
235
|
-
# 扫描容器
|
|
236
|
-
snyk container test nginx:latest
|
|
237
|
-
|
|
238
|
-
# 扫描 IaC
|
|
239
|
-
snyk iac test terraform/
|
|
240
|
-
|
|
241
|
-
# 监控项目
|
|
242
|
-
snyk monitor
|
|
243
|
-
|
|
244
|
-
# 修复建议
|
|
245
|
-
snyk fix
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### 依赖扫描自动化
|
|
249
|
-
|
|
250
|
-
```python
|
|
251
|
-
#!/usr/bin/env python3
|
|
252
|
-
"""依赖漏洞扫描自动化"""
|
|
253
|
-
import subprocess
|
|
254
|
-
import json
|
|
255
|
-
from typing import List, Dict
|
|
256
|
-
|
|
257
|
-
class VulnerabilityScanner:
|
|
258
|
-
def __init__(self, target: str):
|
|
259
|
-
self.target = target
|
|
260
|
-
self.vulnerabilities = []
|
|
261
|
-
|
|
262
|
-
def scan_with_trivy(self) -> List[Dict]:
|
|
263
|
-
"""使用 Trivy 扫描"""
|
|
264
|
-
result = subprocess.run(
|
|
265
|
-
["trivy", "image", "-f", "json", self.target],
|
|
266
|
-
capture_output=True,
|
|
267
|
-
text=True
|
|
268
|
-
)
|
|
269
|
-
data = json.loads(result.stdout)
|
|
270
|
-
|
|
271
|
-
vulns = []
|
|
272
|
-
for result in data.get("Results", []):
|
|
273
|
-
for vuln in result.get("Vulnerabilities", []):
|
|
274
|
-
vulns.append({
|
|
275
|
-
"id": vuln["VulnerabilityID"],
|
|
276
|
-
"package": vuln["PkgName"],
|
|
277
|
-
"version": vuln["InstalledVersion"],
|
|
278
|
-
"severity": vuln["Severity"],
|
|
279
|
-
"fixed_version": vuln.get("FixedVersion", "N/A")
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
self.vulnerabilities = vulns
|
|
283
|
-
return vulns
|
|
284
|
-
|
|
285
|
-
def filter_by_severity(self, severity: str) -> List[Dict]:
|
|
286
|
-
"""按严重性过滤"""
|
|
287
|
-
return [v for v in self.vulnerabilities if v["severity"] == severity]
|
|
288
|
-
|
|
289
|
-
def get_fixable(self) -> List[Dict]:
|
|
290
|
-
"""获取可修复漏洞"""
|
|
291
|
-
return [v for v in self.vulnerabilities if v["fixed_version"] != "N/A"]
|
|
292
|
-
|
|
293
|
-
def generate_report(self) -> str:
|
|
294
|
-
"""生成报告"""
|
|
295
|
-
critical = self.filter_by_severity("CRITICAL")
|
|
296
|
-
high = self.filter_by_severity("HIGH")
|
|
297
|
-
fixable = self.get_fixable()
|
|
298
|
-
|
|
299
|
-
report = f"# 漏洞扫描报告: {self.target}\n\n"
|
|
300
|
-
report += f"## 统计\n"
|
|
301
|
-
report += f"- 严重: {len(critical)}\n"
|
|
302
|
-
report += f"- 高危: {len(high)}\n"
|
|
303
|
-
report += f"- 可修复: {len(fixable)}\n\n"
|
|
304
|
-
|
|
305
|
-
if critical:
|
|
306
|
-
report += f"## 严重漏洞\n"
|
|
307
|
-
for vuln in critical[:10]:
|
|
308
|
-
report += f"- {vuln['id']}: {vuln['package']} {vuln['version']}\n"
|
|
309
|
-
|
|
310
|
-
return report
|
|
311
|
-
|
|
312
|
-
# 使用示例
|
|
313
|
-
scanner = VulnerabilityScanner("nginx:latest")
|
|
314
|
-
scanner.scan_with_trivy()
|
|
315
|
-
print(scanner.generate_report())
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## 签名验证 (Sigstore)
|
|
319
|
-
|
|
320
|
-
### Cosign 签名
|
|
321
|
-
|
|
322
|
-
```bash
|
|
323
|
-
# 安装 Cosign
|
|
324
|
-
curl -O -L https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
|
|
325
|
-
chmod +x cosign-linux-amd64
|
|
326
|
-
mv cosign-linux-amd64 /usr/local/bin/cosign
|
|
327
|
-
|
|
328
|
-
# 生成密钥对
|
|
329
|
-
cosign generate-key-pair
|
|
330
|
-
|
|
331
|
-
# 签名镜像
|
|
332
|
-
cosign sign --key cosign.key myregistry/myapp:v1.0
|
|
333
|
-
|
|
334
|
-
# 无密钥签名(Keyless)
|
|
335
|
-
cosign sign myregistry/myapp:v1.0
|
|
336
|
-
|
|
337
|
-
# 验证签名
|
|
338
|
-
cosign verify --key cosign.pub myregistry/myapp:v1.0
|
|
339
|
-
|
|
340
|
-
# 附加 SBOM
|
|
341
|
-
cosign attach sbom --sbom sbom.json myregistry/myapp:v1.0
|
|
342
|
-
|
|
343
|
-
# 附加证明
|
|
344
|
-
cosign attest --key cosign.key --predicate attestation.json myregistry/myapp:v1.0
|
|
345
|
-
|
|
346
|
-
# 验证证明
|
|
347
|
-
cosign verify-attestation --key cosign.pub myregistry/myapp:v1.0
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### in-toto 供应链证明
|
|
351
|
-
|
|
352
|
-
```python
|
|
353
|
-
#!/usr/bin/env python3
|
|
354
|
-
"""in-toto 供应链元数据"""
|
|
355
|
-
from in_toto import runlib, verifylib
|
|
356
|
-
from in_toto.models.layout import Layout
|
|
357
|
-
from in_toto.models.metadata import Metablock
|
|
358
|
-
|
|
359
|
-
# 定义供应链布局
|
|
360
|
-
layout = Layout.read({
|
|
361
|
-
"_type": "layout",
|
|
362
|
-
"steps": [
|
|
363
|
-
{
|
|
364
|
-
"name": "build",
|
|
365
|
-
"expected_materials": [["MATCH", "src/*", "WITH", "PRODUCTS", "FROM", "clone"]],
|
|
366
|
-
"expected_products": [["CREATE", "app"]],
|
|
367
|
-
"pubkeys": ["builder-key-id"],
|
|
368
|
-
"expected_command": ["make", "build"],
|
|
369
|
-
"threshold": 1
|
|
370
|
-
},
|
|
371
|
-
{
|
|
372
|
-
"name": "test",
|
|
373
|
-
"expected_materials": [["MATCH", "app", "WITH", "PRODUCTS", "FROM", "build"]],
|
|
374
|
-
"expected_products": [["CREATE", "test-results.xml"]],
|
|
375
|
-
"pubkeys": ["tester-key-id"],
|
|
376
|
-
"expected_command": ["make", "test"],
|
|
377
|
-
"threshold": 1
|
|
378
|
-
}
|
|
379
|
-
],
|
|
380
|
-
"inspect": [
|
|
381
|
-
{
|
|
382
|
-
"name": "verify-hash",
|
|
383
|
-
"expected_materials": [["MATCH", "app", "WITH", "PRODUCTS", "FROM", "build"]],
|
|
384
|
-
"expected_products": [["MATCH", "app", "IN", "dst"]],
|
|
385
|
-
"run": ["sha256sum", "app"]
|
|
386
|
-
}
|
|
387
|
-
]
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
# 记录构建步骤
|
|
391
|
-
def record_build_step():
|
|
392
|
-
"""记录构建元数据"""
|
|
393
|
-
runlib.in_toto_run(
|
|
394
|
-
name="build",
|
|
395
|
-
material_list=["src/"],
|
|
396
|
-
product_list=["app"],
|
|
397
|
-
command=["make", "build"]
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
# 验证供应链
|
|
401
|
-
def verify_supply_chain():
|
|
402
|
-
"""验证完整供应链"""
|
|
403
|
-
verifylib.in_toto_verify(
|
|
404
|
-
layout_path="root.layout",
|
|
405
|
-
layout_key_paths=["root.pub"]
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
if __name__ == "__main__":
|
|
409
|
-
record_build_step()
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
### Sigstore Policy Controller
|
|
413
|
-
|
|
414
|
-
```yaml
|
|
415
|
-
# policy.yaml - Kubernetes 准入控制
|
|
416
|
-
apiVersion: policy.sigstore.dev/v1beta1
|
|
417
|
-
kind: ClusterImagePolicy
|
|
418
|
-
metadata:
|
|
419
|
-
name: require-signed-images
|
|
420
|
-
spec:
|
|
421
|
-
images:
|
|
422
|
-
- glob: "myregistry.io/**"
|
|
423
|
-
authorities:
|
|
424
|
-
- keyless:
|
|
425
|
-
url: https://fulcio.sigstore.dev
|
|
426
|
-
identities:
|
|
427
|
-
- issuer: https://github.com/login/oauth
|
|
428
|
-
subject: "https://github.com/myorg/*"
|
|
429
|
-
- key:
|
|
430
|
-
data: |
|
|
431
|
-
-----BEGIN PUBLIC KEY-----
|
|
432
|
-
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
|
|
433
|
-
-----END PUBLIC KEY-----
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
## SLSA 框架
|
|
437
|
-
|
|
438
|
-
### SLSA 等级
|
|
439
|
-
|
|
440
|
-
```
|
|
441
|
-
Level 0: 无保证
|
|
442
|
-
│
|
|
443
|
-
v
|
|
444
|
-
Level 1: 文档化构建过程
|
|
445
|
-
│ - 构建脚本存在
|
|
446
|
-
│ - 可重现构建
|
|
447
|
-
│
|
|
448
|
-
v
|
|
449
|
-
Level 2: 防篡改构建服务
|
|
450
|
-
│ - 托管构建平台
|
|
451
|
-
│ - 服务生成的来源
|
|
452
|
-
│ - 签名的来源
|
|
453
|
-
│
|
|
454
|
-
v
|
|
455
|
-
Level 3: 额外的强化
|
|
456
|
-
│ - 安全的构建平台
|
|
457
|
-
│ - 不可伪造的来源
|
|
458
|
-
│ - 隔离的构建
|
|
459
|
-
│
|
|
460
|
-
v
|
|
461
|
-
Level 4: 最高保证
|
|
462
|
-
- 双方审查
|
|
463
|
-
- 密封的构建
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### SLSA Provenance 生成
|
|
467
|
-
|
|
468
|
-
```json
|
|
469
|
-
{
|
|
470
|
-
"_type": "https://in-toto.io/Statement/v0.1",
|
|
471
|
-
"subject": [
|
|
472
|
-
{
|
|
473
|
-
"name": "myapp",
|
|
474
|
-
"digest": {
|
|
475
|
-
"sha256": "abc123..."
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
],
|
|
479
|
-
"predicateType": "https://slsa.dev/provenance/v0.2",
|
|
480
|
-
"predicate": {
|
|
481
|
-
"builder": {
|
|
482
|
-
"id": "https://github.com/Attestations/GitHubActionsWorkflow@v1"
|
|
483
|
-
},
|
|
484
|
-
"buildType": "https://github.com/Attestations/GitHubActionsWorkflow@v1",
|
|
485
|
-
"invocation": {
|
|
486
|
-
"configSource": {
|
|
487
|
-
"uri": "git+https://github.com/myorg/myapp@refs/heads/main",
|
|
488
|
-
"digest": {
|
|
489
|
-
"sha1": "def456..."
|
|
490
|
-
},
|
|
491
|
-
"entryPoint": ".github/workflows/build.yml"
|
|
492
|
-
}
|
|
493
|
-
},
|
|
494
|
-
"metadata": {
|
|
495
|
-
"buildStartedOn": "2026-02-09T00:00:00Z",
|
|
496
|
-
"buildFinishedOn": "2026-02-09T00:10:00Z",
|
|
497
|
-
"completeness": {
|
|
498
|
-
"parameters": true,
|
|
499
|
-
"environment": true,
|
|
500
|
-
"materials": true
|
|
501
|
-
},
|
|
502
|
-
"reproducible": false
|
|
503
|
-
},
|
|
504
|
-
"materials": [
|
|
505
|
-
{
|
|
506
|
-
"uri": "git+https://github.com/myorg/myapp@refs/heads/main",
|
|
507
|
-
"digest": {
|
|
508
|
-
"sha1": "def456..."
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
]
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### GitHub Actions SLSA
|
|
517
|
-
|
|
518
|
-
```yaml
|
|
519
|
-
# .github/workflows/slsa.yml
|
|
520
|
-
name: SLSA Build
|
|
521
|
-
on: [push]
|
|
522
|
-
|
|
523
|
-
permissions:
|
|
524
|
-
id-token: write
|
|
525
|
-
contents: read
|
|
526
|
-
packages: write
|
|
527
|
-
|
|
528
|
-
jobs:
|
|
529
|
-
build:
|
|
530
|
-
runs-on: ubuntu-latest
|
|
531
|
-
steps:
|
|
532
|
-
- uses: actions/checkout@v3
|
|
533
|
-
|
|
534
|
-
- name: Build
|
|
535
|
-
run: make build
|
|
536
|
-
|
|
537
|
-
- name: Generate SLSA Provenance
|
|
538
|
-
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
|
|
539
|
-
with:
|
|
540
|
-
base64-subjects: "${{ steps.hash.outputs.hashes }}"
|
|
541
|
-
upload-assets: true
|
|
542
|
-
|
|
543
|
-
- name: Sign with Cosign
|
|
544
|
-
run: |
|
|
545
|
-
cosign sign --yes \
|
|
546
|
-
-a "repo=${{ github.repository }}" \
|
|
547
|
-
-a "workflow=${{ github.workflow }}" \
|
|
548
|
-
ghcr.io/${{ github.repository }}:${{ github.sha }}
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### SLSA 验证工具
|
|
552
|
-
|
|
553
|
-
```bash
|
|
554
|
-
# 安装 slsa-verifier
|
|
555
|
-
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
|
|
556
|
-
|
|
557
|
-
# 验证 Provenance
|
|
558
|
-
slsa-verifier verify-artifact \
|
|
559
|
-
--provenance-path provenance.json \
|
|
560
|
-
--source-uri github.com/myorg/myapp \
|
|
561
|
-
myapp
|
|
562
|
-
|
|
563
|
-
# 验证容器镜像
|
|
564
|
-
slsa-verifier verify-image \
|
|
565
|
-
--provenance-path provenance.json \
|
|
566
|
-
--source-uri github.com/myorg/myapp \
|
|
567
|
-
ghcr.io/myorg/myapp:v1.0
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
## 依赖锁定
|
|
571
|
-
|
|
572
|
-
### Package Lock 文件
|
|
573
|
-
|
|
574
|
-
```bash
|
|
575
|
-
# npm
|
|
576
|
-
npm ci # 使用 package-lock.json 精确安装
|
|
577
|
-
|
|
578
|
-
# yarn
|
|
579
|
-
yarn install --frozen-lockfile
|
|
580
|
-
|
|
581
|
-
# pip
|
|
582
|
-
pip install -r requirements.txt --require-hashes
|
|
583
|
-
|
|
584
|
-
# go
|
|
585
|
-
go mod verify
|
|
586
|
-
go mod download
|
|
587
|
-
|
|
588
|
-
# cargo
|
|
589
|
-
cargo build --locked
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
### 生成哈希锁定
|
|
593
|
-
|
|
594
|
-
```python
|
|
595
|
-
#!/usr/bin/env python3
|
|
596
|
-
"""生成依赖哈希锁定文件"""
|
|
597
|
-
import hashlib
|
|
598
|
-
import json
|
|
599
|
-
import subprocess
|
|
600
|
-
from typing import Dict, List
|
|
601
|
-
|
|
602
|
-
def generate_pip_hashes(requirements: str) -> Dict[str, str]:
|
|
603
|
-
"""为 pip 依赖生成哈希"""
|
|
604
|
-
result = subprocess.run(
|
|
605
|
-
["pip", "hash", "-r", requirements],
|
|
606
|
-
capture_output=True,
|
|
607
|
-
text=True
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
hashes = {}
|
|
611
|
-
for line in result.stdout.split("\n"):
|
|
612
|
-
if "==" in line and "--hash=" in line:
|
|
613
|
-
pkg = line.split("==")[0]
|
|
614
|
-
hash_val = line.split("--hash=")[1].split()[0]
|
|
615
|
-
hashes[pkg] = hash_val
|
|
616
|
-
|
|
617
|
-
return hashes
|
|
618
|
-
|
|
619
|
-
def verify_integrity(package: str, expected_hash: str) -> bool:
|
|
620
|
-
"""验证包完整性"""
|
|
621
|
-
result = subprocess.run(
|
|
622
|
-
["pip", "download", "--no-deps", package],
|
|
623
|
-
capture_output=True
|
|
624
|
-
)
|
|
625
|
-
|
|
626
|
-
# 计算实际哈希
|
|
627
|
-
actual_hash = hashlib.sha256(result.stdout).hexdigest()
|
|
628
|
-
return actual_hash == expected_hash
|
|
629
|
-
|
|
630
|
-
# 生成锁定文件
|
|
631
|
-
hashes = generate_pip_hashes("requirements.txt")
|
|
632
|
-
with open("requirements.lock", "w") as f:
|
|
633
|
-
json.dump(hashes, f, indent=2)
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
## 私有依赖仓库
|
|
637
|
-
|
|
638
|
-
### Nexus Repository 配置
|
|
639
|
-
|
|
640
|
-
```bash
|
|
641
|
-
# Docker 配置
|
|
642
|
-
docker login nexus.company.com:8082
|
|
643
|
-
|
|
644
|
-
# npm 配置
|
|
645
|
-
npm config set registry https://nexus.company.com/repository/npm-group/
|
|
646
|
-
|
|
647
|
-
# pip 配置
|
|
648
|
-
pip config set global.index-url https://nexus.company.com/repository/pypi-group/simple
|
|
649
|
-
|
|
650
|
-
# Maven 配置
|
|
651
|
-
# ~/.m2/settings.xml
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
```xml
|
|
655
|
-
<settings>
|
|
656
|
-
<mirrors>
|
|
657
|
-
<mirror>
|
|
658
|
-
<id>nexus</id>
|
|
659
|
-
<mirrorOf>*</mirrorOf>
|
|
660
|
-
<url>https://nexus.company.com/repository/maven-public/</url>
|
|
661
|
-
</mirror>
|
|
662
|
-
</mirrors>
|
|
663
|
-
<servers>
|
|
664
|
-
<server>
|
|
665
|
-
<id>nexus</id>
|
|
666
|
-
<username>${env.NEXUS_USER}</username>
|
|
667
|
-
<password>${env.NEXUS_PASS}</password>
|
|
668
|
-
</server>
|
|
669
|
-
</servers>
|
|
670
|
-
</settings>
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
### Artifactory 扫描集成
|
|
674
|
-
|
|
675
|
-
```yaml
|
|
676
|
-
# .jfrog-pipelines.yml
|
|
677
|
-
pipelines:
|
|
678
|
-
- name: scan_artifacts
|
|
679
|
-
steps:
|
|
680
|
-
- name: xray_scan
|
|
681
|
-
type: XrayScan
|
|
682
|
-
configuration:
|
|
683
|
-
inputResources:
|
|
684
|
-
- name: docker_image
|
|
685
|
-
failOnScan: true
|
|
686
|
-
scanThreshold: HIGH
|
|
687
|
-
```
|
|
688
|
-
|
|
689
|
-
## 供应链策略
|
|
690
|
-
|
|
691
|
-
### OPA 供应链策略
|
|
692
|
-
|
|
693
|
-
```rego
|
|
694
|
-
# supply-chain-policy.rego
|
|
695
|
-
package supply_chain
|
|
696
|
-
|
|
697
|
-
# 拒绝未签名镜像
|
|
698
|
-
deny[msg] {
|
|
699
|
-
input.kind == "Pod"
|
|
700
|
-
image := input.spec.containers[_].image
|
|
701
|
-
not is_signed(image)
|
|
702
|
-
msg := sprintf("镜像未签名: %v", [image])
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
# 拒绝高危漏洞
|
|
706
|
-
deny[msg] {
|
|
707
|
-
vuln := input.vulnerabilities[_]
|
|
708
|
-
vuln.severity == "CRITICAL"
|
|
709
|
-
not has_exception(vuln.id)
|
|
710
|
-
msg := sprintf("发现严重漏洞: %v in %v", [vuln.id, vuln.package])
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
# 要求 SBOM
|
|
714
|
-
deny[msg] {
|
|
715
|
-
input.kind == "Deployment"
|
|
716
|
-
not has_sbom(input.metadata.annotations)
|
|
717
|
-
msg := "缺少 SBOM 注解"
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
is_signed(image) {
|
|
721
|
-
# 检查 Cosign 签名
|
|
722
|
-
signatures := data.cosign.signatures[image]
|
|
723
|
-
count(signatures) > 0
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
has_sbom(annotations) {
|
|
727
|
-
annotations["sbom.url"]
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
has_exception(vuln_id) {
|
|
731
|
-
data.exceptions[vuln_id]
|
|
732
|
-
}
|
|
733
|
-
```
|
|
734
|
-
|
|
735
|
-
### Kyverno 供应链策略
|
|
736
|
-
|
|
737
|
-
```yaml
|
|
738
|
-
apiVersion: kyverno.io/v1
|
|
739
|
-
kind: ClusterPolicy
|
|
740
|
-
metadata:
|
|
741
|
-
name: verify-supply-chain
|
|
742
|
-
spec:
|
|
743
|
-
validationFailureAction: enforce
|
|
744
|
-
rules:
|
|
745
|
-
- name: verify-image-signature
|
|
746
|
-
match:
|
|
747
|
-
any:
|
|
748
|
-
- resources:
|
|
749
|
-
kinds:
|
|
750
|
-
- Pod
|
|
751
|
-
verifyImages:
|
|
752
|
-
- imageReferences:
|
|
753
|
-
- "*"
|
|
754
|
-
attestors:
|
|
755
|
-
- count: 1
|
|
756
|
-
entries:
|
|
757
|
-
- keys:
|
|
758
|
-
publicKeys: |-
|
|
759
|
-
-----BEGIN PUBLIC KEY-----
|
|
760
|
-
...
|
|
761
|
-
-----END PUBLIC KEY-----
|
|
762
|
-
|
|
763
|
-
- name: require-sbom
|
|
764
|
-
match:
|
|
765
|
-
any:
|
|
766
|
-
- resources:
|
|
767
|
-
kinds:
|
|
768
|
-
- Deployment
|
|
769
|
-
validate:
|
|
770
|
-
message: "必须提供 SBOM"
|
|
771
|
-
pattern:
|
|
772
|
-
metadata:
|
|
773
|
-
annotations:
|
|
774
|
-
sbom.url: "?*"
|
|
775
|
-
|
|
776
|
-
- name: block-critical-vulns
|
|
777
|
-
match:
|
|
778
|
-
any:
|
|
779
|
-
- resources:
|
|
780
|
-
kinds:
|
|
781
|
-
- Pod
|
|
782
|
-
validate:
|
|
783
|
-
message: "镜像包含严重漏洞"
|
|
784
|
-
deny:
|
|
785
|
-
conditions:
|
|
786
|
-
any:
|
|
787
|
-
- key: "{{ images.*.vulnerabilities[?severity=='CRITICAL'] | length(@) }}"
|
|
788
|
-
operator: GreaterThan
|
|
789
|
-
value: 0
|
|
790
|
-
```
|
|
791
|
-
|
|
792
|
-
## CI/CD 集成
|
|
793
|
-
|
|
794
|
-
### GitLab CI 供应链安全
|
|
795
|
-
|
|
796
|
-
```yaml
|
|
797
|
-
# .gitlab-ci.yml
|
|
798
|
-
stages:
|
|
799
|
-
- build
|
|
800
|
-
- scan
|
|
801
|
-
- sign
|
|
802
|
-
- deploy
|
|
803
|
-
|
|
804
|
-
variables:
|
|
805
|
-
IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
|
806
|
-
|
|
807
|
-
build:
|
|
808
|
-
stage: build
|
|
809
|
-
script:
|
|
810
|
-
- docker build -t $IMAGE .
|
|
811
|
-
- docker push $IMAGE
|
|
812
|
-
|
|
813
|
-
sbom:
|
|
814
|
-
stage: scan
|
|
815
|
-
script:
|
|
816
|
-
- syft $IMAGE -o cyclonedx-json > sbom.json
|
|
817
|
-
- cosign attach sbom --sbom sbom.json $IMAGE
|
|
818
|
-
artifacts:
|
|
819
|
-
paths:
|
|
820
|
-
- sbom.json
|
|
821
|
-
|
|
822
|
-
vulnerability_scan:
|
|
823
|
-
stage: scan
|
|
824
|
-
script:
|
|
825
|
-
- trivy image --exit-code 1 --severity CRITICAL $IMAGE
|
|
826
|
-
allow_failure: false
|
|
827
|
-
|
|
828
|
-
sign:
|
|
829
|
-
stage: sign
|
|
830
|
-
script:
|
|
831
|
-
- cosign sign --key $COSIGN_KEY $IMAGE
|
|
832
|
-
only:
|
|
833
|
-
- main
|
|
834
|
-
|
|
835
|
-
deploy:
|
|
836
|
-
stage: deploy
|
|
837
|
-
script:
|
|
838
|
-
- cosign verify --key $COSIGN_PUB $IMAGE
|
|
839
|
-
- kubectl set image deployment/myapp app=$IMAGE
|
|
840
|
-
only:
|
|
841
|
-
- main
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
### GitHub Actions 完整流程
|
|
845
|
-
|
|
846
|
-
```yaml
|
|
847
|
-
name: Secure Supply Chain
|
|
848
|
-
on: [push]
|
|
849
|
-
|
|
850
|
-
permissions:
|
|
851
|
-
contents: read
|
|
852
|
-
packages: write
|
|
853
|
-
id-token: write
|
|
854
|
-
|
|
855
|
-
jobs:
|
|
856
|
-
build-and-scan:
|
|
857
|
-
runs-on: ubuntu-latest
|
|
858
|
-
steps:
|
|
859
|
-
- uses: actions/checkout@v3
|
|
860
|
-
|
|
861
|
-
- name: Build
|
|
862
|
-
run: docker build -t myapp:${{ github.sha }} .
|
|
863
|
-
|
|
864
|
-
- name: Generate SBOM
|
|
865
|
-
run: |
|
|
866
|
-
syft myapp:${{ github.sha }} -o cyclonedx-json > sbom.json
|
|
867
|
-
|
|
868
|
-
- name: Scan Vulnerabilities
|
|
869
|
-
run: |
|
|
870
|
-
trivy image --exit-code 1 --severity CRITICAL myapp:${{ github.sha }}
|
|
871
|
-
|
|
872
|
-
- name: Sign Image
|
|
873
|
-
run: |
|
|
874
|
-
cosign sign --yes myapp:${{ github.sha }}
|
|
875
|
-
|
|
876
|
-
- name: Attach SBOM
|
|
877
|
-
run: |
|
|
878
|
-
cosign attach sbom --sbom sbom.json myapp:${{ github.sha }}
|
|
879
|
-
|
|
880
|
-
- name: Generate Provenance
|
|
881
|
-
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
|
882
|
-
```
|
|
883
|
-
|
|
884
|
-
## 工具清单
|
|
885
|
-
|
|
886
|
-
| 工具 | 类型 | 用途 |
|
|
887
|
-
|------|------|------|
|
|
888
|
-
| Syft | SBOM | 生成软件物料清单 |
|
|
889
|
-
| Trivy | 扫描 | 漏洞/配置/密钥扫描 |
|
|
890
|
-
| Grype | 扫描 | 依赖漏洞扫描 |
|
|
891
|
-
| Snyk | 商业 | 全方位安全扫描 |
|
|
892
|
-
| Cosign | 签名 | 容器签名验证 |
|
|
893
|
-
| in-toto | 框架 | 供应链完整性 |
|
|
894
|
-
| SLSA | 框架 | 供应链等级标准 |
|
|
895
|
-
| Sigstore | 平台 | 无密钥签名基础设施 |
|
|
896
|
-
|
|
897
|
-
## 最佳实践
|
|
898
|
-
|
|
899
|
-
### 供应链安全检查清单
|
|
900
|
-
|
|
901
|
-
```markdown
|
|
902
|
-
## 源代码阶段
|
|
903
|
-
- [ ] 启用分支保护
|
|
904
|
-
- [ ] 要求代码审查
|
|
905
|
-
- [ ] 使用依赖锁定文件
|
|
906
|
-
- [ ] 定期更新依赖
|
|
907
|
-
- [ ] 扫描密钥泄露
|
|
908
|
-
|
|
909
|
-
## 构建阶段
|
|
910
|
-
- [ ] 使用托管 CI/CD
|
|
911
|
-
- [ ] 隔离构建环境
|
|
912
|
-
- [ ] 生成 SBOM
|
|
913
|
-
- [ ] 记录构建来源
|
|
914
|
-
- [ ] 签名制品
|
|
915
|
-
|
|
916
|
-
## 制品阶段
|
|
917
|
-
- [ ] 扫描漏洞
|
|
918
|
-
- [ ] 验证签名
|
|
919
|
-
- [ ] 存储在私有仓库
|
|
920
|
-
- [ ] 实施访问控制
|
|
921
|
-
- [ ] 保留审计日志
|
|
922
|
-
|
|
923
|
-
## 部署阶段
|
|
924
|
-
- [ ] 验证签名和来源
|
|
925
|
-
- [ ] 检查 SBOM
|
|
926
|
-
- [ ] 准入控制策略
|
|
927
|
-
- [ ] 运行时监控
|
|
928
|
-
- [ ] 事件响应计划
|
|
929
|
-
```
|
|
930
|
-
|
|
931
|
-
---
|