kingsokafor777-droid/basalt-core

GitHub: kingsokafor777-droid/basalt-core

Basalt Core 是一个为云端态势、IaC 和 Kubernetes 安全扫描器提供统一标准化安全发现结果模型与确定性风险评分的 Python 基础库。

Stars: 0 | Forks: 0

# Basalt Core [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/kingsokafor777-droid/basalt-core/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/basalt-core.svg)](https://pypi.org/project/basalt-core/) [![Python](https://img.shields.io/pypi/pyversions/basalt-core.svg)](https://pypi.org/project/basalt-core/) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) **为云端态势、IaC 和 Kubernetes 安全扫描器提供统一的发现结果 schema。** 安全工具在拼接处出现了割裂。一个 AWS 扫描器输出一种 JSON 结构,一个 Terraform 分析器输出另一种,一个 Kubernetes 检查器输出第三种,而下游的每个 仪表盘、数据仓库和工单集成系统都需要为每一个扫描器定制一个专用适配器。 Basalt Core 通过一次性定义好数据契约,从而移除了这个适配器层: - 一个 **标准化的发现结果模型**,与提供商无关且对数据仓库友好 - 一个 **确定性的风险评分**,附带生成该评分的各项因素 - **版本化的控制目录**,作为可替换的数据,而不是硬编码的映射 - 一个 **扫描器插件接口**,通过 Python entry points 发现 - **SARIF 2.1.0 和 OCSF** 发射器,使得发现结果无需翻译步骤即可进入 GitHub Code Scanning 和 原生支持 OCSF 的数据湖 本包不执行任何扫描。它是构建各类扫描器的基础。 ## 安装 ``` pip install basalt-core ``` 要求 Python 3.10+。唯一的运行时依赖是 Pydantic v2。 ## 快速开始 ``` from basalt_core import ( Evidence, Exploitability, Exposure, Finding, Provider, Remediation, ResourceRef, Severity, get_emitter, ) finding = Finding( rule_id="s3.public-read", title="S3 bucket allows public read access", description="The bucket ACL grants READ to the AllUsers group.", severity=Severity.CRITICAL, exposure=Exposure.PUBLIC, exploitability=Exploitability.TRIVIAL, resource=ResourceRef( provider=Provider.AWS, resource_type="AWS::S3::Bucket", uid="arn:aws:s3:::example-bucket", account="123456789012", region="ca-central-1", ), scanner="basalt-aws", control_ids=["cis-aws:storage.bucket-public-access", "nist-800-53-r5:AC-3"], evidence=[ Evidence( description="Bucket ACL grant", observed="AllUsers:READ", expected="no public grants", source="s3:GetBucketAcl", ) ], remediation=Remediation(summary="Enable S3 Block Public Access."), ) print(finding.risk.value) # 90 print(finding.risk.band) # critical print(finding.risk.explain()) # 90 (severity=critical) x 1.00 (exposure=public) ... print(finding.fingerprint) # stable 32-hex identity, unchanged across scans print(finding.resource.urn) # urn:basalt:aws:123456789012:ca-central-1:... ``` ## 核心概念 ### 发现结果 Basalt 生态系统中的每个扫描器都会生成 `Finding` 对象,并将其封装在 `ScanResult` 中返回。一个发现结果包含了观察到的内容、涉及的资源、严重程度、 映射到的合规控制项,以及如何修复它。 下游的大部分工作由两个派生属性完成: **`fingerprint`** — 基于 `(rule_id, resource URN, location)` 生成的稳定 SHA-256 标识。 它特意排除了严重程度、时间戳和观察到的值,因此调整规则的 严重程度不会导致其历史记录丢失。这使得 `basalt-warehouse` 中的“随时间推移的偏移”追踪成为可能, 且无需扫描器端保存任何状态。 **`resource.urn`** — 具有固定元数的标准化地址: ``` urn:basalt::::: ``` 空的段会折叠为 `-`,因此 URN 始终是可解析的,并且可作为跨扫描器和云环境的可靠关联键。 ### 风险模型 风险是一个纯函数。没有机器学习 (ML),没有隐藏状态,没有网络调用: ``` risk = base(severity) × exposure_factor × exploitability_factor ``` | 严重程度 | 基础分 | | 暴露程度 | 系数 | | 可利用性 | 系数 | |---|---|---|---|---|---|---|---| | critical | 90 | | public | 1.00 | | trivial | 1.00 | | high | 70 | | external | 0.90 | | moderate | 0.85 | | medium | 45 | | internal | 0.75 | | difficult | 0.65 | | low | 20 | | isolated | 0.55 | | theoretical | 0.40 | | info | 5 | | | | | | | 采用乘法而非加法,因此在隔离资源上仅有理论可利用性的 critical 发现结果, 其排名永远不会超过在公共资源上具有简单可利用性的 high 发现结果—— 这正是值班工程师实际想要的排序。每个评分都作为携带自身系数的 `RiskScore` 发出,因此仪表盘可以回答“为什么这个是 72 分?” 而无需重新推导。 ### 控制目录 控制项采用 `framework:control` 命名空间,并以 JSON 而非代码的形式存在: ``` from basalt_core import load_catalog catalog = load_catalog() # built-in seed catalog catalog.get("nist-800-53-r5:AC-6") # -> Control(title="Least Privilege", ...) catalog.unknown(["cis-aws:iam.root-mfa", "typo:X"]) # -> ["typo:X"] custom = load_catalog("./my-catalog.json") # bring your own ``` ### 扫描器 扫描器继承 `Scanner` 并通过 `basalt.scanners` entry point 注册: ``` from typing import Iterable from basalt_core import Finding, Provider, Scanner, ScanContext class S3Scanner(Scanner): name = "basalt-aws" version = "0.1.0" provider = Provider.AWS description = "AWS posture checks" def scan(self, context: ScanContext) -> Iterable[Finding]: for bucket in list_buckets(context.credentials): if is_public(bucket): yield Finding(...) ``` ``` [project.entry-points."basalt.scanners"] aws = "basalt_aws.scanner:S3Scanner" ``` `Scanner.run()` 为 `scan()` 包装了计时、来源追踪和错误捕获功能。在扫描过程中发生的异常 会被记录在 `ScanResult.metadata.errors` 中,而不是直接抛出,因此一个 失败的区域永远不会丢弃已经收集到的发现结果。无法导入的插件 会在发现过程中被跳过,而不会破坏整个工具链。 ### 发射器 | 格式 | 用途 | |---|---| | `sarif` | SARIF 2.1.0 — 直接上传至 GitHub Code Scanning | | `ocsf` | OCSF 1.3.0 Compliance Finding — 用于 Security Lake 和原生支持 OCSF 的 SIEM | | `basalt` | 无损的原生 JSON,可通过 `ScanResult` 往返转换 | | `jsonl` | 换行符分隔、扁平化、派生列已实体化 — 用于数据仓库加载 | ``` from basalt_core import get_emitter sarif = get_emitter("sarif").emit_json(result) ``` SARIF 发射器会将 Basalt 风险评分重新缩放映射到 `security-severity`,以便 GitHub 能一致地对告警进行分桶,并将指纹写入 `partialFingerprints`,以便 GitHub 在多次运行中追踪告警的身份。 ## CLI ``` basalt validate scan.json --strict # validate a document; fail on unknown control ids basalt convert scan.json --to sarif -o results.sarif basalt convert scan.json --to jsonl --dedupe basalt controls --framework cis-k8s # inspect the catalog basalt scanners # list installed scanner plugins basalt schema # JSON Schema for a scan result ``` 将 SARIF 提供给 GitHub Code Scanning: ``` - run: basalt convert scan.json --to sarif -o results.sarif - uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif ``` ## 项目定位 Basalt Core 是一组相互协作的仓库的基础。每个仓库 都有其独立的价值;它们结合在一起便形成了一条完整的 pipeline。 ``` basalt-aws ─┐ basalt-azure├─→ ScanResult ─→ basalt-warehouse ─→ basalt-dashboard basalt-iac │ │ basalt-k8s ─┘ └──────────→ basalt-rag ─→ basalt-agent │ ┌───────────────────────────────────────────────┘ └─→ Terraform remediation PR ``` 界线以上的所有内容都仅依赖于这个包。而这个包中的任何内容都 不依赖于界线以上的任何东西。 ## 设计决策 记录在 [`docs/adr/`](docs/adr/) 中作为 ADR: | # | 决策 | |---|---| | [0001](docs/adr/0001-normalized-finding-schema.md) | 采用单一且与提供商无关的发现结果模型,而不是每个提供商一套 schema | | [0006](docs/adr/0006-python-310-floor.md) | Python 3.10 作为最低支持版本 | | [0002](docs/adr/0002-deterministic-risk-scoring.md) | 确定性的乘法风险评分,评分逻辑中不使用机器学习 (ML) | | [0003](docs/adr/0003-sarif-and-ocsf-as-wire-formats.md) | 使用 SARIF 和 OCSF 作为传输格式,而不是专有 schema | | [0004](docs/adr/0004-entry-point-plugin-discovery.md) | 基于 entry-point 的插件发现机制,而非单体扫描器包 | | [0005](docs/adr/0005-control-catalogs-as-data.md) | 控制目录作为版本化数据,而非硬编码的映射 | ## 范围 **在范围内:**发现结果模型、风险评分、控制目录加载、扫描器 接口以及发射器。 **刻意排除在外:**云 SDK 调用、凭证处理、存储、调度、 通知以及任何用户界面。这些属于扫描器和消费者 仓库。保持此包不依赖任何云端依赖,正是使其依赖成本极低的原因—— 整个运行时依赖集仅有 Pydantic。 **尚未实现:**发现结果抑制规则、跨框架的目录映射 (CIS ↔ NIST),以及作为版本化工件发布的 JSON Schema。这些问题正在 issue 中跟踪。 ## 开发 ``` git clone https://github.com/kingsokafor777-droid/basalt-core cd basalt-core make install # editable install with dev extras make check # lint, type-check and test ``` `make check` 会运行 Ruff、严格模式下的 mypy 以及完整的 pytest 套件,并将覆盖率 阈值设定为 85%。CI 会在 Python 3.10、3.11、3.12 和 3.13 上执行相同的操作。 将 3.10 作为最低版本限制是刻意为之:Ubuntu 22.04 LTS 自带 Python 3.10 且其支持将延续至 2027 年,而且一个所有扫描器都要依赖的库,不应该 强制那些实际运行扫描器的机器升级它们的工具链。这导致失去的仅有的两个 3.11 专属便利特性 (`enum.StrEnum`,`datetime.UTC`)已在 `_compat.py` 中得到处理。 ## 安全 漏洞报告流程详见 [SECURITY.md](SECURITY.md)。请勿针对疑似漏洞 提交公开的 issue。 ## 许可证 Apache License 2.0。详见 [LICENSE](LICENSE)。
标签:Python, SARIF, 云安全态势, 加密, 数据模型, 无后门, 漏洞扫描器, 逆向工具