@@ -65,90 +65,107 @@ func LoadReleases(reader ReleaseReader) ([]ReleaseWithArtifacts, error) {
6565 return result , nil
6666}
6767
68- // filterArtifactsForVersion filters artifacts to only include those matching the specified version.
69- // For security-only releases where binaries use older patch versions (e.g., 3.12.10 for version 3.12.12),
70- // this function matches by major.minor prefix as a fallback.
68+ // filterArtifactsForVersion filters artifacts to only include those whose
69+ // filenames contain an exact match for the specified version.
70+ //
71+ // For aggregated security-only releases, upstream sometimes only ships a binary
72+ // for the latest patch in a minor line (e.g., python.org publishes
73+ // python-3.12.10-macos11.pkg as the macOS installer for the entire 3.12.x line).
74+ // We deliberately do NOT attach such binaries to other advertised patch
75+ // versions (e.g., 3.12.12), because doing so produced misleading entries like
76+ // `mac/python/3.12/3.12.12/python-3.12.10-macos11.pkg` with version "3.12.12".
77+ // If no binary in the release exactly matches the advertised version for a
78+ // platform, that platform is simply omitted from this version's entries.
7179func filterArtifactsForVersion (artifacts storage.ReleaseArtifacts , version string ) storage.ReleaseArtifacts {
7280 filtered := storage.ReleaseArtifacts {
7381 Platforms : []storage.PlatformArtifact {},
7482 CommonFiles : artifacts .CommonFiles , // Keep common files for all versions
7583 Metadata : artifacts .Metadata ,
7684 }
7785
78- // Extract major.minor from version for fallback matching
79- // e.g., "3.12.12" -> "3.12"
80- majorMinor := extractMajorMinor (version )
81-
82- // Filter platform artifacts by version in filename
8386 for _ , platform := range artifacts .Platforms {
84- // Check if any artifact in this platform matches the version
8587 hasMatchingArtifact := false
8688
87- if platform .Binary != nil && matchesVersion (platform .Binary .Filename , version , majorMinor ) {
89+ if platform .Binary != nil && matchesVersion (platform .Binary .Filename , version ) {
8890 hasMatchingArtifact = true
8991 }
90- if platform .Audit != nil && matchesVersion (platform .Audit .Filename , version , majorMinor ) {
92+ if platform .Audit != nil && matchesVersion (platform .Audit .Filename , version ) {
9193 hasMatchingArtifact = true
9294 }
93- if platform .MetadataFile != nil && matchesVersion (platform .MetadataFile .Filename , version , majorMinor ) {
95+ if platform .MetadataFile != nil && matchesVersion (platform .MetadataFile .Filename , version ) {
9496 hasMatchingArtifact = true
9597 }
9698
97- if hasMatchingArtifact {
98- // Create a filtered copy of the platform with only matching artifacts
99- filteredPlatform := storage.PlatformArtifact {
100- Platform : platform .Platform ,
101- PlatformOS : platform .PlatformOS ,
102- PlatformArch : platform .PlatformArch ,
103- }
99+ if ! hasMatchingArtifact {
100+ continue
101+ }
104102
105- if platform .Binary != nil && matchesVersion (platform .Binary .Filename , version , majorMinor ) {
106- filteredPlatform .Binary = platform .Binary
107- }
108- if platform .Audit != nil && matchesVersion (platform .Audit .Filename , version , majorMinor ) {
109- filteredPlatform .Audit = platform .Audit
110- }
111- if platform .Signature != nil && matchesVersion (platform .Signature .Filename , version , majorMinor ) {
112- filteredPlatform .Signature = platform .Signature
113- }
114- if platform .Certificate != nil && matchesVersion (platform .Certificate .Filename , version , majorMinor ) {
115- filteredPlatform .Certificate = platform .Certificate
116- }
117- if platform .MetadataFile != nil && matchesVersion (platform .MetadataFile .Filename , version , majorMinor ) {
118- filteredPlatform .MetadataFile = platform .MetadataFile
119- }
103+ filteredPlatform := storage.PlatformArtifact {
104+ Platform : platform .Platform ,
105+ PlatformOS : platform .PlatformOS ,
106+ PlatformArch : platform .PlatformArch ,
107+ }
120108
121- filtered .Platforms = append (filtered .Platforms , filteredPlatform )
109+ if platform .Binary != nil && matchesVersion (platform .Binary .Filename , version ) {
110+ filteredPlatform .Binary = platform .Binary
122111 }
112+ if platform .Audit != nil && matchesVersion (platform .Audit .Filename , version ) {
113+ filteredPlatform .Audit = platform .Audit
114+ }
115+ if platform .Signature != nil && matchesVersion (platform .Signature .Filename , version ) {
116+ filteredPlatform .Signature = platform .Signature
117+ }
118+ if platform .Certificate != nil && matchesVersion (platform .Certificate .Filename , version ) {
119+ filteredPlatform .Certificate = platform .Certificate
120+ }
121+ if platform .MetadataFile != nil && matchesVersion (platform .MetadataFile .Filename , version ) {
122+ filteredPlatform .MetadataFile = platform .MetadataFile
123+ }
124+
125+ filtered .Platforms = append (filtered .Platforms , filteredPlatform )
123126 }
124127
125128 return filtered
126129}
127130
128- // extractMajorMinor extracts the major.minor portion from a semver version.
129- // e.g., "3.12.12" -> "3.12", "22.15.0" -> "22.15"
130- func extractMajorMinor (version string ) string {
131- parts := strings .Split (version , "." )
132- if len (parts ) >= 2 {
133- return parts [0 ] + "." + parts [1 ]
131+ // matchesVersion reports whether a filename advertises the given exact version.
132+ //
133+ // Matching is intentionally strict: only an exact substring match on the full
134+ // version string counts. We previously fell back to a major.minor prefix
135+ // match, but that caused mac/windows installers from one patch version to be
136+ // re-published under unrelated patch versions in the same security-only
137+ // aggregated release (see filterArtifactsForVersion for context).
138+ //
139+ // To avoid false positives such as filenames containing "3.12.123" matching
140+ // version "3.12.12", the version must be bounded on both sides by either a
141+ // non-digit character or the start/end of the filename.
142+ func matchesVersion (filename , version string ) bool {
143+ idx := 0
144+ for {
145+ i := strings .Index (filename [idx :], version )
146+ if i < 0 {
147+ return false
148+ }
149+ start := idx + i
150+ end := start + len (version )
151+ if ! isVersionDigit (filename , start - 1 ) && ! isVersionDigit (filename , end ) {
152+ return true
153+ }
154+ idx = start + 1
155+ if idx >= len (filename ) {
156+ return false
157+ }
134158 }
135- return version
136159}
137160
138- // matchesVersion checks if a filename matches the given version.
139- // First tries exact version match, then falls back to major.minor match.
140- // This handles security-only releases where binaries use older patch versions.
141- func matchesVersion (filename , version , majorMinor string ) bool {
142- // First try exact version match
143- if strings .Contains (filename , version ) {
144- return true
145- }
146- // Fallback to major.minor match for security-only releases
147- // e.g., "python-3.12.10-amd64.exe" matches version "3.12.12" via "3.12"
148- if strings .Contains (filename , majorMinor + "." ) {
149- return true
161+ // isVersionDigit reports whether the byte at position i in s is an ASCII digit.
162+ // Out-of-bounds positions return false (treated as a non-digit boundary).
163+ func isVersionDigit (s string , i int ) bool {
164+ if i < 0 || i >= len (s ) {
165+ return false
150166 }
151- return false
167+ c := s [i ]
168+ return c >= '0' && c <= '9'
152169}
153170
154171// ReleaseWithArtifacts combines a Release with its parsed artifacts.
0 commit comments