SBOM 包含什么
SBOM 是清单,不是漏洞报告。它列出每个组件的名称、版本、供应方,以及 package URL(pURL)或 CPE 标识,还包括哈希和组件之间的关系。容器 SBOM 应包含操作系统包和基础镜像层,而不只是应用的语言依赖。
工作机制
生成应放在构建流水线中,因为那里有解析后的依赖图。工具读取清单和锁文件、检查容器层,输出 SPDX 或 CycloneDX JSON。产物应签名并与发布一起存储,最好由镜像或发布元数据引用。
使用方在事件响应中查询 SBOM:新 CVE 出现时,确认哪些已发布版本包含受影响组件、位于何处。结合 VEX 声明,即记录产品是否真的受影响,可以把存在与可利用区分开。
何时使用与取舍
每个对外交付的产物和容器镜像都应生成 SBOM,内部升级、许可证审计和并购尽调也用得上。受监管行业和公共采购越来越要求每次发布都提供。
完整 SBOM 体积大且噪声多,容器尤其如此。准确性取决于生成器,只读锁文件的工具会漏掉系统库。格式细节不同,工具必须规范化 pURL。持续生成并签名会增加构建时间和存储。
示例与常见误解
某日志库漏洞爆发后,团队查询 SBOM,发现三个服务包含受影响版本,其中一个是通过 SDK 间接引入的。没有 SBOM,这个服务就会被漏掉。
常见误解是 SBOM 等于安全评估。它只是配料表;风险取决于配料在上下文中是否可达、是否可利用。相关概念:供应链安全、SLSA、漏洞披露。
代码示例
import unicodedata
SCRIPT_RANGES = {
"LATIN": [(0x0041, 0x024F), (0x1E00, 0x1EFF)],
"CYRILLIC": [(0x0400, 0x04FF)],
"GREEK": [(0x0370, 0x03FF)],
}
def script_of(char: str) -> str:
code = ord(char)
for name, ranges in SCRIPT_RANGES.items():
if any(start <= code <= end for start, end in ranges):
return name
return "OTHER"
def find_mixed_scripts(text: str) -> list[str]:
scripts = {script_of(char) for char in text if char.isalpha()}
return sorted(scripts - {"OTHER"})
def is_suspicious(text: str) -> bool:
scripts = find_mixed_scripts(text)
return "LATIN" in scripts and len(scripts) > 1
def explain(text: str) -> list[dict[str, str]]:
return [
{
"char": char,
"name": unicodedata.name(char, "UNKNOWN"),
"script": script_of(char),
}
for char in text
if char.isalpha() and script_of(char) not in {"LATIN", "OTHER"}
]
if __name__ == "__main__":
for sample in ["paypal", "pаypal", "admin"]:
print(sample, find_mixed_scripts(sample), is_suspicious(sample))
print(explain("pаypal"))
export type SqlDialect = "postgres" | "mysql";
export function quoteIdentifier(
identifier: string,
dialect: SqlDialect = "postgres",
): string {
if (typeof identifier !== "string" || identifier.length === 0) {
throw new TypeError("identifier must be a non-empty string");
}
if (identifier.includes("\0")) {
throw new RangeError("identifier must not contain NUL");
}
if (dialect !== "postgres" && dialect !== "mysql") {
throw new RangeError("unsupported SQL dialect");
}
const quote = dialect === "mysql" ? "`" : '"';
return quote + identifier.split(quote).join(quote + quote) + quote;
}
export function qualifyTable(
schema: string,
table: string,
dialect: SqlDialect = "postgres",
): string {
return quoteIdentifier(schema, dialect) + "." + quoteIdentifier(table, dialect);
}