Skip to content

🔒 Security Advisories

Query disclosed security vulnerabilities (CVE / GHSA) on Packagist. Three methods support "full fetch", "by package", and "incremental by time", returning a unified AdvisoriesResponse structure. This is a core capability for dependency security monitoring, SCA tool integration, and CI vulnerability gating.

Endpoint Change

The old endpoint https://packagist.org/advisories.json has been deprecated by Packagist (returns 404). The SDK now uses the new endpoint https://packagist.org/api/security-advisories/, with query parameters updatedSince and packages[].

When to Use

  • 🔒 CI gating: Query project dependencies for new advisories on each build, block if found.
  • 📡 Security monitoring: Daily incremental fetch with GetSecurityAdvisoriesSince(last time), alert when new advisories appear.
  • 🧩 SCA scanning: Call GetSecurityAdvisoriesForPackages for a set of packages, determine if current versions fall within affected ranges.
  • 🛠️ Vulnerability database sync: Full fetch then store, periodically update incrementally.

Data Models

AdvisoriesResponse

pkg/domain/advisory.go, corresponds to /api/security-advisories/ response.

go
type AdvisoriesResponse struct {
    Advisories map[string][]*Advisory `json:"advisories"`
}
FieldTypeDescription
Advisoriesmap[string][]*AdvisoryKey is package name (e.g., symfony/http-foundation), value is the list of advisories for that package

Advisory

Single security advisory.

go
type Advisory struct {
    AdvisoryID        string    `json:"advisoryId"`
    PackageName       string    `json:"packageName"`
    RemoteID          string    `json:"remoteId"`
    Title             string    `json:"title"`
    Link              string    `json:"link"`
    Cve               string    `json:"cve"`
    AffectedVersions  string    `json:"affectedVersions"`
    Source            string    `json:"source"`
    ReportedAt        string    `json:"reportedAt"`
    ComposerRepository string   `json:"composerRepository"`
    Sources           []*Source `json:"sources"`
}
FieldTypeDescription
AdvisoryIDstringUnique advisory identifier, e.g., PKSA-38s9-s9dj
PackageNamestringAffected package name
RemoteIDstringRemote system ID, e.g., CVE-2022-24894
TitlestringAdvisory title
LinkstringDetail link (often points to GitHub Advisory)
CvestringCVE number
AffectedVersionsstringAffected version range (Composer constraint syntax, e.g., >=5.4.0,<5.4.19|>=6.0.0,<6.0.4)
SourcestringSource platform, e.g., GitHub
ReportedAtstringReport time, ISO 8601
ComposerRepositorystringRelated repository, e.g., packagist
Sources[]*SourceMulti-source reference list

Source

go
type Source struct {
    Name     string `json:"name"`
    RemoteID string `json:"remoteId"`
}
FieldTypeDescription
NamestringSource name, e.g., GitHub, NVD
RemoteIDstringRemote ID from the source platform, e.g., GHSA-rc93-5vf2-xh7q

GetSecurityAdvisories

🔒 Get all known security advisories. Corresponds to GET https://packagist.org/api/security-advisories/?updatedSince=0.

Signature

go
func (c *ComposerClient) GetSecurityAdvisories() (*domain.AdvisoriesResponse, error)

Parameters

None.

Return Values

ValueTypeDescription
Result*domain.AdvisoriesResponseSecurity advisory mapping grouped by package name
ErrorerrorReturned on HTTP failure, non-200, or JSON parsing failure

Implementation Notes

The new endpoint requires at least one query parameter, otherwise returns 400. The SDK internally uses updatedSince=0, indicating fetch all known advisories.

Example

go
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/scagogogo/composer-skills/pkg/client"
)

func main() {
    c := client.NewComposerClient(60 * time.Second) // Full data is large, give sufficient timeout

    adv, err := c.GetSecurityAdvisories()
    if err != nil {
        log.Fatalf("Failed to get security advisories: %v", err)
    }
    fmt.Printf("Involving %d packages\n", len(adv.Advisories))
    total := 0
    for pkg, list := range adv.Advisories {
        total += len(list)
        fmt.Printf("- %s: %d advisories\n", pkg, len(list))
    }
    fmt.Printf("Total advisories: %d\n", total)
}

Data Volume

Full advisory response can be large (tens of MB). Suggestions: ① Set timeout to 60s or more; ② After initial full fetch, use GetSecurityAdvisoriesSince for incremental updates; ③ For large-scale processing, consider using the lower-level Repository.ListSecurityAdvisories with a proxy.


GetSecurityAdvisoriesForPackages

🔒 Get security advisories for a specified set of packages. Corresponds to GET https://packagist.org/api/security-advisories/?packages[]={name}.

Signature

go
func (c *ComposerClient) GetSecurityAdvisoriesForPackages(packageNames []string) (*domain.AdvisoriesResponse, error)

Parameters

ParameterTypeDescription
packageNames[]stringPackage name list, each sent as a packages[] query parameter

Return Values

ValueTypeDescription
Result*domain.AdvisoriesResponseAdvisories only for requested packages (key is the requested package name)
ErrorerrorReturned on HTTP failure, non-200, or JSON parsing failure

Example

go
pkgs := []string{
    "symfony/http-foundation",
    "symfony/console",
    "monolog/monolog",
}
adv, err := c.GetSecurityAdvisoriesForPackages(pkgs)
if err != nil {
    log.Fatal(err)
}
for pkg, list := range adv.Advisories {
    fmt.Printf("=== %s ===\n", pkg)
    for _, a := range list {
        fmt.Printf("  [%s] %s\n", a.Cve, a.Title)
        fmt.Printf("  Affected versions: %s\n", a.AffectedVersions)
        fmt.Printf("  Details: %s\n\n", a.Link)
    }
}

Vulnerability Determination Logic

After obtaining AffectedVersions (e.g., >=5.4.0,<5.4.19|>=6.0.0,<6.0.4), you need to combine it with the project's actual installed version to determine if affected. The SDK does not perform version constraint parsing. We recommend using Composer CLI version constraint parsing or a third-party constraint library.


GetSecurityAdvisoriesSince

🔒 Get security advisories updated since a specified time (incremental query). Corresponds to GET https://packagist.org/api/security-advisories/?updatedSince={timestamp}.

Signature

go
func (c *ComposerClient) GetSecurityAdvisoriesSince(updatedSince time.Time) (*domain.AdvisoriesResponse, error)

Parameters

ParameterTypeDescription
updatedSincetime.TimeStart time; SDK takes its Unix() second-level timestamp as the updatedSince query parameter

Return Values

ValueTypeDescription
Result*domain.AdvisoriesResponseAdvisories updated after that time point
ErrorerrorReturned on HTTP failure, non-200, or JSON parsing failure

Example

go
// Fetch advisories updated in the last 24 hours
since := time.Now().Add(-24 * time.Hour)
adv, err := c.GetSecurityAdvisoriesSince(since)
if err != nil {
    log.Fatal(err)
}
for pkg, list := range adv.Advisories {
    for _, a := range list {
        fmt.Printf("[New/Updated] %s: %s (%s)\n", pkg, a.Title, a.ReportedAt)
    }
}

Incremental Sync

The typical approach is to persist "last query time", then use it as updatedSince each time:

go
last := loadLastSyncTime() // Read from database/file
adv, err := c.GetSecurityAdvisoriesSince(last)
// Process adv.Advisories ...
saveLastSyncTime(time.Now()) // Record new checkpoint

Timestamp Precision

ComposerClient.GetSecurityAdvisoriesSince uses second-level Unix() timestamp; while the lower-level Repository.ListSecurityAdvisories uses millisecond-level UnixMilli(). Both hit the same endpoint, only differing in timestamp precision. Choose based on your semantic needs.

Advanced Topics

Comparison of Three Methods

MethodQuery ParameterUse Case
GetSecurityAdvisoriesupdatedSince=0Initial full fetch to database
GetSecurityAdvisoriesForPackagespackages[]Query specific packages on demand (common for CI gating)
GetSecurityAdvisoriesSinceupdatedSince={ts}Long-term incremental sync

Difference from Composer CLI Audit

  • Composer CLI Audit audits local composer.lock, targeting actual locked versions for the current project; requires PHP / composer binary.
  • Methods in this section query Packagist full repository advisories, targeting package names rather than your installed versions; pure HTTP, no PHP needed.

Both are complementary: CLI audit precisely determines "whether current version is affected", SDK methods provide a panoramic view of "which advisories exist, which packages are affected".

Released under the MIT License