🔒 构建安全审计流水线
15 分钟,组合三层安全数据源——本地 AuditWithJSON、远程 GetSecurityAdvisories、ValidateStructured——产出一份可落库的 JSON 报告。需要本机有 PHP + Composer。
为什么需要流水线
单一来源不够用:本地审计只看已安装版本,远程公告能看到全生态最新漏洞,validate 则能挡住"composer.json 本身就写错了"。三层叠加 + 一份统一报告,才算可上线的安全流水线。
第 1 步:本地审计
comp.AuditWithJSON() 执行 composer audit --format=json,返回 *composer.AuditResult。其中 Found 是漏洞总数,Advisories 是漏洞列表,每条含 Package、Title、Severity、Link、CVE 等字段。
第 2 步:拉远程公告
client.GetSecurityAdvisories() 返回 *domain.AdvisoriesResponse,Advisories 是 map[包名][]*Advisory。只关心几个关键包时用 GetSecurityAdvisoriesForPackages(names)。
第 3 步:校验 composer.json
comp.ValidateStructured() 返回 *composer.ValidateResult,含 Valid 布尔、Errors 和 Warnings 切片。
完整可运行示例
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"time"
"github.com/scagogogo/composer-skills/pkg/client"
"github.com/scagogogo/composer-skills/pkg/composer"
)
type Report struct {
GeneratedAt string `json:"generated_at"`
LocalVulns int `json:"local_vulns"`
LocalAdvisories []composer.Vulnerability `json:"local_advisories,omitempty"`
RemotePackages int `json:"remote_packages_with_advisories"`
ComposerJSON *composer.ValidateResult `json:"composer_json,omitempty"`
}
func main() {
comp, err := composer.New(composer.DefaultOptions())
if err != nil {
log.Fatal(err)
}
comp.SetWorkingDir("/tmp/my-php-project")
report := Report{GeneratedAt: time.Now().Format(time.RFC3339)}
// 1️⃣ 本地审计
if audit, err := comp.AuditWithJSON(); err == nil {
report.LocalVulns = audit.Found
report.LocalAdvisories = audit.Advisories
for _, v := range audit.Advisories {
fmt.Printf("⚠️ %s: %s [%s] %s\n", v.Package, v.Title, v.Severity, v.Link)
}
}
// 2️⃣ 远程公告(仅看本项目的直接依赖)
deps := comp.GetDirectDependencyNames()
c := client.NewComposerClient(30 * time.Second)
if adv, err := c.GetSecurityAdvisoriesForPackages(deps); err == nil {
report.RemotePackages = len(adv.Advisories)
for pkg, list := range adv.Advisories {
fmt.Printf("🌐 %s: %d 条远程公告\n", pkg, len(list))
}
}
// 3️⃣ 校验 composer.json
if vr, err := comp.ValidateStructured(); err == nil {
report.ComposerJSON = vr
if !vr.Valid {
fmt.Printf("❌ composer.json 无效:%v\n", vr.Errors)
}
}
// 4️⃣ 落盘报告
b, _ := json.MarshalIndent(report, "", " ")
if err := os.WriteFile("security-report.json", b, 0644); err != nil {
log.Fatal(err)
}
fmt.Printf("\n✅ 报告已写入 security-report.json(本地漏洞 %d,远程受影响包 %d)\n",
report.LocalVulns, report.RemotePackages)
}预期输出
⚠️ guzzlehttp/psr7: Objective PHPSR7 vulnerability [high] https://...
🌐 monolog/monolog: 1 条远程公告
❌ composer.json 无效:[The key "require.monolog/monolog" is invalid]
✅ 报告已写入 security-report.json(本地漏洞 1,远程受影响包 1)进阶:增量公告与高严重性筛选
GetSecurityAdvisoriesSince(t)只返回t之后更新的公告,适合定时轮询。comp.GetHighSeverityVulnerabilities()在本地审计后筛出high/critical的漏洞,用于阻断流水线。
// 流水线阻断:发现高危漏洞就退出非零
high, _ := comp.GetHighSeverityVulnerabilities()
if len(high) > 0 {
log.Fatalf("发现 %d 个高危漏洞,流水线失败", len(high))
}结构化返回值的意义
AuditResult、ValidateResult 都是纯 Go 结构体,可直接 json.Marshal 入库或喂给告警系统,无需正则解析 CLI 文本。
audit 的退出码
当 composer audit 发现漏洞时会以非零退出码结束,但 SDK 已把这种情况转换成 *AuditResult(Found > 0),不会当作 error 抛出。判断是否有漏洞请用 result.Found,而不是 err != nil。
下一步
- 把这条流水线塞进 GitHub Actions:🤖 CI/CD 中自动检测与安装。
- 把审计结果汇总进看板:📈 依赖仪表盘。