🔒 security_advisories — Security Advisory Query
This example demonstrates how to fetch security vulnerability advisories for Composer packages, covering both "incremental pull by time" and "targeted query by package name," and prints key fields like CVE and affected versions.
🎯 Example Positioning
security_advisories is the 5th example in the Packagist API Remote Operations series, rated "intermediate" difficulty. After mastering basic client initialization, package listing, and statistics queries, this example turns the view toward the security dimension — an unavoidable part of any serious PHP project.
- 📚 What you'll learn: how to use the underlying methods of the
pkg/repositorypackage to query Packagist's disclosed security advisories (CVE / GHSA) along two dimensions — "update time" and "package name" — and parse the core fields of theAdvisorystruct. - 🔗 Corresponding SDK methods:
repository.Repository.ListSecurityAdvisories(incremental by time) andrepository.Repository.ListAdvisories(query by package name). - 🧭 Difference from
security_monitor:security_monitoris a comprehensive hands-on example that chains advisory queries, version-constraint comparison, and alert notifications into a monitoring pipeline; this example focuses solely on how to pull advisory data back and understand it, serving as the prerequisite course forsecurity_monitor. - ⚖️ Difference from Composer CLI
audit: CLIauditaudits the actually locked versions in the localcomposer.lockand requires PHP; this example queries the Packagist全库 advisories — pure HTTP, no PHP required — suitable for SCA panoramic scanning and vulnerability database syncing.
💻 Full Code
package main
import (
"context"
"fmt"
"time"
"github.com/scagogogo/composer-skills/pkg/repository"
)
func main() {
// Example 5: Fetch security advisories for Composer packages
// Includes both by-time and by-package-name fetching, usable for security audits.
// Step 1: Initialize the repository client
options := &repository.Options{
ServerUrl: "https://packagist.org", // Use the official repository
}
repo := &repository.Repository{}
_ = options // In this example, only a config placeholder; the methods internally use the official endpoint
ctx := context.Background()
// Step 2: Fetch security advisories by time
fmt.Println("=== Fetch security advisories by time ===")
oneYearAgo := time.Now().AddDate(-1, 0, 0)
fmt.Printf("Fetching security advisories since %s...\n", oneYearAgo.Format("2006-01-02"))
advisoriesResp, err := repo.ListSecurityAdvisories(ctx, oneYearAgo)
if err != nil {
fmt.Printf("Failed to fetch security advisories: %v\n", err)
return
}
// Count the advisories
totalAdvisories := 0
for _, advisories := range advisoriesResp.Advisories {
totalAdvisories += len(advisories)
}
fmt.Printf("Found %d packages with security advisories, %d advisories in total\n",
len(advisoriesResp.Advisories), totalAdvisories)
// Print partial advisory details (first 3 packages, first 2 advisories each)
count := 0
fmt.Println("\nPartial security advisory details:")
for pkgName, advisories := range advisoriesResp.Advisories {
if count >= 3 {
break
}
fmt.Printf("\nPackage: %s\n", pkgName)
for i, advisory := range advisories {
if i >= 2 {
fmt.Printf(" ...%d more advisories not shown\n", len(advisories)-i)
break
}
fmt.Printf(" - Title: %s\n", advisory.Title)
fmt.Printf(" CVE: %s\n", advisory.Cve)
fmt.Printf(" Reported at: %s\n", advisory.ReportedAt)
fmt.Printf(" Affected versions: %s\n", advisory.AffectedVersions)
}
count++
}
// Step 3: Fetch security advisories for a specific package
fmt.Println("\n\n=== Fetch security advisories for a specific package ===")
packageName := "symfony/http-kernel"
fmt.Printf("Fetching security advisories for %s...\n", packageName)
packageAdvisories, err := repo.ListAdvisories(ctx, packageName)
if err != nil {
fmt.Printf("Failed to fetch security advisories for %s: %v\n", packageName, err)
} else {
fmt.Printf("Found %d security advisories\n", len(packageAdvisories))
fmt.Println("\nSecurity advisory details:")
for i, advisory := range packageAdvisories {
if i >= 5 {
fmt.Printf("...%d more advisories not shown\n", len(packageAdvisories)-i)
break
}
fmt.Printf("\n%d. %s\n", i+1, advisory.Title)
fmt.Printf(" - Advisory ID: %s\n", advisory.AdvisoryID)
fmt.Printf(" - CVE: %s\n", advisory.Cve)
fmt.Printf(" - Reported at: %s\n", advisory.ReportedAt)
fmt.Printf(" - Affected versions: %s\n", advisory.AffectedVersions)
fmt.Printf(" - Link: %s\n", advisory.Link)
}
}
}🧩 Code Walkthrough
- 🏗️ Initialize Repository:
repo := &repository.Repository{}constructs the underlying HTTP client with a zero value. Theoptionsfield is unexported, and the methods internally always request the officialhttps://packagist.orgendpoint, so in this exampleoptionsis only a config placeholder (_ = options). - 🕐 Incremental pull by time:
oneYearAgo := time.Now().AddDate(-1, 0, 0)constructs a time point one year ago and passes it torepo.ListSecurityAdvisories(ctx, oneYearAgo). The SDK takes its millisecond-levelUnixMilli()timestamp as theupdatedSincequery parameter and returns all advisories updated since that time. - 🗺️ Understanding the return structure:
advisoriesResp.Advisoriesismap[string][]*Advisory— the key is the package name (e.g.symfony/http-kernel), and the value is the list of advisories for that package. The outerforiterates packages, the innerforiterates that package's advisories, and summing each package's slice length gives the total count. - 📝 Key field interpretation: each
AdvisoryprintsTitle,Cve(CVE ID),ReportedAt(report time), andAffectedVersions(affected version range, in Composer constraint syntax like>=4.4.0,<4.4.44||>=5.0.0,<5.4.15). These fields are the core basis for deciding "whether you're affected." - 🎯 Targeted query by package name:
repo.ListAdvisories(ctx, "symfony/http-kernel")takes a package name directly and returns a flat[]*Advisoryslice (no longer a map). It hits the same endpoint but usespackages[]=symfony/http-kernelas the query parameter — suitable for directed checks on a single dependency in a CI gate. - ✂️ Truncated output: the example uses
count >= 3andi >= 2/i >= 5to truncate output, avoiding screen flooding when there are many advisories — in production code, replace this with persistent storage or structured logging. - 🛡️ Error handling: on by-time pull failure,
returndirectly (subsequent steps are meaningless); on by-package-name query failure, only print a warning and continue, demonstrating a "non-fatal error" tolerance strategy. In practice, adjust this based on business importance.
▶️ How to Run
Run directly from the example directory:
cd /home/cc11001100/github/scagogogo/composer-skills/examples/security_advisories
go run main.goOr run from the repository root:
go run examples/security_advisories/main.goPure Go, no PHP required
This example only calls the Packagist HTTP API — no local PHP or Composer needed. Just clone and go run.
⚠️ This example makes real Packagist API calls; avoid running it at high frequency. The by-time response can be large (tens of MB), so ensure adequate network and memory. For long-term monitoring, switch to
GetSecurityAdvisoriesSincewith a persisted "last sync time" for incremental pulls.
📚 SDK Methods Involved
| Method Name | Package | Endpoint / Query Params | Doc Link |
|---|---|---|---|
ListSecurityAdvisories | pkg/repository | GET /api/security-advisories/?updatedSince={ms} | /sdk/packagist/repository#listsecurityadvisories |
ListAdvisories | pkg/repository | GET /api/security-advisories/?packages[]={name} | /sdk/packagist/repository#listadvisories |
📝 Note: These two methods belong to the lower Repository layer and accept
context.Contextfor propagating timeout and cancellation. The higher-level facadepkg/client.ComposerClientprovides an equivalent trio —GetSecurityAdvisories(full),GetSecurityAdvisoriesSince(by time, second-level timestamp), andGetSecurityAdvisoriesForPackages(by package-name list); see /sdk/packagist/advisories. Both hit the same endpoint — they differ only in timestamp precision and parameter wrapping, so choose based on your semantic needs.
🚀 Going Further
- ⏱️ Add timeout control: replace
context.Background()withctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)anddefer cancel()to prevent the network from hanging during a full pull. - 🗄️ Persist to a database: serialize
AdvisoriesResponse.Advisoriesinto a database (upsert byAdvisoryIDfor dedup), and build a time index onReportedAtto construct your own vulnerability knowledge base. - 🔁 Incremental sync: after each query, store
time.Now()as a "checkpoint"; next time, use it as theupdatedSinceparameter to pull only new/updated advisories, avoiding repeated full requests. - 🎯 Version-constraint comparison: after getting
AffectedVersions(e.g.>=5.4.0,<5.4.19||>=6.0.0,<6.0.4), do constraint matching against the versions actually installed in your project'scomposer.lockto determine whether you're truly affected — this is exactly the core problem the security_monitor example solves. - 🔔 Hook up notification channels: when new advisories are discovered, push them to Slack / Feishu / email, or write them into a to-do system, forming a "discover → notify → fix" closed loop.
- 🧪 CI gate: in your CI pipeline, call
ListAdvisoriesagainst the dependency list incomposer.lockand block the build on any hit, shifting security left to the commit stage.