feat: new-release detector tools + missing 25H2 V2 China catalog entries #36

Merged
starkSV merged 2 commits from feat/check-new-releases-script into main 2026-07-14 11:29:25 +05:30
9 changed files with 501 additions and 12 deletions

View file

@ -285,8 +285,8 @@ Returns real-time cache statistics for the running instance. Auth via `?secret=`
|---|---|---|
| Windows 11 25H2 | 3262 | x64 |
| Windows 11 25H2 | 3265 | ARM64 |
| Windows 11 25H2 (updated) | 3321 | x64 |
| Windows 11 25H2 (updated) | 3324 | ARM64 |
| Windows 11 25H2 (V2) | 3321 | x64 |
| Windows 11 25H2 (V2) | 3324 | ARM64 |
| Windows 11 24H2 | 3113 | x64 |
| Windows 11 24H2 | 3131 | ARM64 |
coderabbitai[bot] commented 2026-07-14 11:28:58 +05:30 (Migrated from github.com)
Review

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the missing China variants to the catalog table.

The PR objectives mention adding the catalog entries for the Windows 11 25H2 V2 China variants (3322, 3323, 3325, 3326) to the README, but they are missing from this table.

📝 Proposed additions
 | Windows 11 25H2 (V2) | 3321 | x64 |
+| Windows 11 25H2 Home China (V2) | 3322 | x64 |
+| Windows 11 25H2 Pro China (V2) | 3323 | x64 |
 | Windows 11 25H2 (V2) | 3324 | ARM64 |
+| Windows 11 Arm64 25H2 Home China (V2) | 3325 | ARM64 |
+| Windows 11 Arm64 25H2 Pro China (V2) | 3326 | ARM64 |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

| Windows 11 25H2 (V2) | 3321 | x64 |
| Windows 11 25H2 Home China (V2) | 3322 | x64 |
| Windows 11 25H2 Pro China (V2) | 3323 | x64 |
| Windows 11 25H2 (V2) | 3324 | ARM64 |
| Windows 11 Arm64 25H2 Home China (V2) | 3325 | ARM64 |
| Windows 11 Arm64 25H2 Pro China (V2) | 3326 | ARM64 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 288 - 289, Update the Windows 11 25H2 (V2) catalog
table in README.md to add the missing China variant entries for catalog IDs
3322, 3323, 3325, and 3326, preserving the existing table format and
architecture associations.
_📐 Maintainability & Code Quality_ | _🟡 Minor_ | _⚡ Quick win_ **Add the missing China variants to the catalog table.** The PR objectives mention adding the catalog entries for the Windows 11 25H2 V2 China variants (`3322`, `3323`, `3325`, `3326`) to the README, but they are missing from this table. <details> <summary>📝 Proposed additions</summary> ```diff | Windows 11 25H2 (V2) | 3321 | x64 | +| Windows 11 25H2 Home China (V2) | 3322 | x64 | +| Windows 11 25H2 Pro China (V2) | 3323 | x64 | | Windows 11 25H2 (V2) | 3324 | ARM64 | +| Windows 11 Arm64 25H2 Home China (V2) | 3325 | ARM64 | +| Windows 11 Arm64 25H2 Pro China (V2) | 3326 | ARM64 | ``` </details> <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion | Windows 11 25H2 (V2) | 3321 | x64 | | Windows 11 25H2 Home China (V2) | 3322 | x64 | | Windows 11 25H2 Pro China (V2) | 3323 | x64 | | Windows 11 25H2 (V2) | 3324 | ARM64 | | Windows 11 Arm64 25H2 Home China (V2) | 3325 | ARM64 | | Windows 11 Arm64 25H2 Pro China (V2) | 3326 | ARM64 | ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@README.md` around lines 288 - 289, Update the Windows 11 25H2 (V2) catalog table in README.md to add the missing China variant entries for catalog IDs 3322, 3323, 3325, and 3326, preserving the existing table format and architecture associations. ``` </details> <!-- fingerprinting:phantom:poseidon:panther --> <!-- cr-indicator-types:potential_issue --> <!-- cr-comment:v1:d4203a33505f6e3c65801827 --> <!-- This is an auto-generated comment by CodeRabbit -->
| Windows 10 22H2 | 2618 | x64 / x86 |

View file

@ -479,7 +479,8 @@ var validContributeProducts = map[string]bool{
"3131": true, "3132": true, "3133": true,
"3262": true, "3263": true, "3264": true,
"3265": true, "3266": true, "3267": true,
"3321": true, "3324": true,
"3321": true, "3322": true, "3323": true,
"3324": true, "3325": true, "3326": true,
}
// allowedCDNSuffixes is the CDN host allow-list for contributed download URLs.

View file

@ -29,8 +29,12 @@ var consumerProducts = []Product{
{"3265", "Windows 11 Arm64 25H2 (26200.6584)"},
{"3266", "Windows 11 Arm64 25H2 Home China (26200.6584)"},
{"3267", "Windows 11 Arm64 25H2 Pro China (26200.6584)"},
{"3321", "Windows 11 25H2 (Updated Oct)"},
{"3324", "Windows 11 Arm64 25H2 (Updated Oct)"},
{"3321", "Windows 11 25H2 (V2)"},
{"3322", "Windows 11 25H2 Home China (V2)"},
{"3323", "Windows 11 25H2 Pro China (V2)"},
{"3324", "Windows 11 Arm64 25H2 (V2)"},
{"3325", "Windows 11 Arm64 25H2 Home China (V2)"},
{"3326", "Windows 11 Arm64 25H2 Pro China (V2)"},
}
var evalProducts = []EvalProduct{

View file

@ -169,7 +169,7 @@
]
},
"3321": {
"name": "Windows 11 25H2 (Updated Oct)",
"name": "Windows 11 25H2 (V2)",
"badge": "LATEST",
"archs": [
"x64"
@ -180,8 +180,30 @@
"3113"
]
},
"3322": {
"name": "Windows 11 25H2 Home China (V2)",
"badge": "LATEST",
"archs": [
"x64"
],
"related": [
"3321",
"3323"
]
},
"3323": {
"name": "Windows 11 25H2 Pro China (V2)",
"badge": "LATEST",
"archs": [
"x64"
],
"related": [
"3321",
"3322"
]
},
"3324": {
"name": "Windows 11 Arm64 25H2 (Updated Oct)",
"name": "Windows 11 Arm64 25H2 (V2)",
"badge": "LATEST",
"archs": [
"ARM64"
@ -191,5 +213,27 @@
"3262",
"3265"
]
},
"3325": {
"name": "Windows 11 Arm64 25H2 Home China (V2)",
"badge": "LATEST",
"archs": [
"ARM64"
],
"related": [
"3324",
"3326"
]
},
"3326": {
"name": "Windows 11 Arm64 25H2 Pro China (V2)",
"badge": "LATEST",
"archs": [
"ARM64"
],
"related": [
"3324",
"3325"
]
}
}

View file

@ -63,5 +63,9 @@
<url><loc>https://msdl.tech-latest.com/product/3266</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/3267</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/3321</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/3322</loc><lastmod>2026-07-14</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/3323</loc><lastmod>2026-07-14</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/3324</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/3325</loc><lastmod>2026-07-14</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
<url><loc>https://msdl.tech-latest.com/product/3326</loc><lastmod>2026-07-14</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
</urlset>

View file

@ -0,0 +1,91 @@
# check-new-releases
Two complementary tools for finding Windows product edition IDs that exist on
Microsoft's side but aren't yet in `cli/catalog.go` / `products.json` /
`validContributeProducts`. Neither touches those files automatically --
naming a new entry correctly (build number, refresh qualifiers like "V2",
etc.) still needs a human checking Microsoft's own release-health pages.
Both only *discover* candidates.
Only Windows 11 is worth checking at all: Windows 8.1 is fully frozen (no
updates since Jan 2023) and Windows 10 is past end-of-life (ESU security
patches only, delivered via Windows Update, not new consumer ISOs). Neither
will ever produce a new release again.
## Method 1 -- `msdls_v3.py` (brute-force range scan)
Checks every ID in a range you specify, one by one, against Microsoft's
SKU-info API. Use this when you already have a rough idea of where to look
(e.g. "the last release was around 3260, check up to 3350") or want to
directly merge results into a JSON catalog file.
```bash
pip install requests
python msdls_v3.py --first 3320 --last 3330
```
Optionally write results to a JSON file (merges with an existing one if
present, preserving fields like `badge`/`archs`/`related`, marking IDs that
stopped responding as `"active": false`):
```bash
python msdls_v3.py --first 3320 --last 3330 --write scan-results.json
```
Don't point `--write` directly at `frontend/public/data/products.json` --
write to a scratch file first and review before merging into the real
catalog.
**How it identifies a release:** reads `ProductDisplayName` from the first
SKU in the API response (e.g. `"Windows 11 25H2__V2"`). This field name was
wrong in an earlier version of this script (checked `EditionName`/
`ReleaseName`/`FriendlyName`, none of which exist in the real response) --
confirmed live and fixed 2026-07-14.
**Session handling:** registers the session via `vlscppe`'s permit endpoint
before scanning. A single SKU lookup was confirmed to work without this, but
this script's use case -- many rapid consecutive requests across a range --
is a much higher-risk pattern for triggering a Sentinel block than a
one-shot lookup, so the permit call is worth keeping. The CLI/backend's full
session flow additionally replays an `ov-df.microsoft.com` fingerprint; add
that too if the permit call alone isn't enough under real scanning load.
## Method 2 -- `main.go` (auto-discovery)
Scrapes Microsoft's public `/windows11` download page for the *current*
flagship product edition ID (no range-guessing needed), compares it against
`cli/catalog.go`, and if it's new, probes a bounded range of adjacent IDs to
discover the accompanying variant family (Home China, Pro China, ARM64).
Use this when you just want to know "is there anything new" without knowing
where to look.
```bash
cd scripts/check-new-releases
go run .
```
If a new release is found, it makes real requests to Microsoft to probe the
variant family -- let it finish rather than re-running immediately.
Flags:
- `-catalog <path>` — path to `cli/catalog.go` (default: `../../cli/catalog.go`)
- `-probe-range <n>` — how many adjacent IDs to check after a new flagship is found (default: 20)
**Known limitation:** the ARM64 family's offset from the flagship isn't
consistent release to release (observed: +3 for the 25H2 family, +18 for
24H2's). The default probe range of 20 covers both known cases, but if no
ARM64 variant shows up, try a wider `-probe-range`.
## Which one to use
| | Method 1 (Python) | Method 2 (Go) |
|---|---|---|
| Need to guess a starting range? | Yes | No -- finds the current flagship automatically |
| Gets the real Microsoft name? | Yes (`ProductDisplayName`) | Yes (`ProductDisplayName`) |
| Can write results to a catalog-shaped JSON? | Yes (`--write`) | No -- prints a report only |
| Best for | "I want to scan a specific range and get a mergeable file" | "Just tell me if anything's new" |
Both were verified end-to-end against live Microsoft endpoints on
2026-07-14, which surfaced a real gap: product IDs `3322`/`3323`/`3325`/`3326`
(Home/Pro China variants of the 25H2 "V2" refresh) existed on Microsoft's
side but weren't yet in the catalog. Since fixed.

View file

@ -0,0 +1,3 @@
module check-new-releases
go 1.25.6

View file

@ -0,0 +1,321 @@
// check-new-releases scrapes Microsoft's public Windows 11 download page for
// the current "flagship" product edition ID and compares it against the
// catalog already tracked in cli/catalog.go. Windows 8.1 and Windows 10 are
// intentionally not checked here -- 8.1 is fully frozen and Windows 10 is
// past end-of-life, so neither will ever produce a new consumer ISO release.
//
// If a new flagship ID is found, it probes a bounded range of adjacent IDs to
// discover the accompanying variant family (Home China, Pro China, ARM64,
// ...), since Microsoft allocates those in a cluster near the flagship ID
// but the exact offset isn't consistent release to release (observed gaps:
// +3 for the 25H2 family, +18 for 24H2's ARM64 family).
//
// This tool only *discovers* candidate IDs -- it does not touch cli/catalog.go,
// products.json, or validContributeProducts automatically. Naming a new entry
// correctly (build number, "Updated Oct"-style qualifiers, etc.) still needs a
// human checking Microsoft's own release-health pages.
//
// Usage: go run . [-catalog path/to/cli/catalog.go] [-probe-range N]
package main
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"net/url"
"os"
"regexp"
"strconv"
"sync"
"time"
)
// --- Copied from cli/microsoft.go (not imported -- cli/ is package main and
// can't be imported from elsewhere). This is a standalone maintenance tool,
// not part of the shipped CLI/backend; keep in sync manually if the real
// session flow ever changes. ---
const (
msUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
msProfile = "606624d44113"
msLocale = "en-US"
msOrgID = "y6jn8c31"
msCustomer = "560dc9f3-1aa5-4a2f-b63c-9e18f8d0e175"
)
var (
reW = regexp.MustCompile(`[&?]w=([^&"'\s]+)`)
reRt = regexp.MustCompile(`rticks[="]+\+?\s*(\d{10,})`)
)
type skuLang struct {
ID string `json:"Id"`
Language string `json:"Language"`
ProductDisplayName string `json:"ProductDisplayName"`
}
type simpleCookieJar struct {
mu sync.Mutex
cookies []*http.Cookie
}
func (j *simpleCookieJar) SetCookies(_ *url.URL, cookies []*http.Cookie) {
j.mu.Lock()
defer j.mu.Unlock()
for _, c := range cookies {
found := false
for i, existing := range j.cookies {
if existing.Name == c.Name {
j.cookies[i] = c
found = true
break
}
}
if !found {
j.cookies = append(j.cookies, c)
}
}
}
func (j *simpleCookieJar) Cookies(_ *url.URL) []*http.Cookie {
j.mu.Lock()
defer j.mu.Unlock()
out := make([]*http.Cookie, len(j.cookies))
copy(out, j.cookies)
return out
}
func newSessionID() string {
b := make([]byte, 16)
rand.Read(b)
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
return fmt.Sprintf("%s-%s-%s-%s-%s",
hex.EncodeToString(b[0:4]), hex.EncodeToString(b[4:6]),
hex.EncodeToString(b[6:8]), hex.EncodeToString(b[8:10]),
hex.EncodeToString(b[10:16]))
}
func newSession() (*http.Client, string) {
sessionID := newSessionID()
jar := &simpleCookieJar{}
client := &http.Client{Timeout: 15 * time.Second, Jar: jar}
q1 := url.Values{}
q1.Set("org_id", msOrgID)
q1.Set("session_id", sessionID)
req1, _ := http.NewRequest("GET", "https://vlscppe.microsoft.com/tags?"+q1.Encode(), nil)
req1.Header.Set("User-Agent", msUA)
client.Do(req1)
q2 := url.Values{}
q2.Set("instanceId", msCustomer)
q2.Set("PageId", "si")
q2.Set("session_id", sessionID)
req2, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/mdt.js?"+q2.Encode(), nil)
req2.Header.Set("User-Agent", msUA)
resp2, err := client.Do(req2)
if err != nil {
return client, sessionID
}
body, _ := io.ReadAll(resp2.Body)
resp2.Body.Close()
wMatch := reW.FindStringSubmatch(string(body))
rtMatch := reRt.FindStringSubmatch(string(body))
if len(wMatch) > 1 && len(rtMatch) > 1 {
q3 := url.Values{}
q3.Set("session_id", sessionID)
q3.Set("CustomerId", msCustomer)
q3.Set("PageId", "si")
q3.Set("w", wMatch[1])
q3.Set("mdt", fmt.Sprintf("%d", time.Now().UnixMilli()))
q3.Set("rticks", rtMatch[1])
req3, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/?"+q3.Encode(), nil)
req3.Header.Set("User-Agent", msUA)
client.Do(req3)
}
return client, sessionID
}
coderabbitai[bot] commented 2026-07-14 11:28:58 +05:30 (Migrated from github.com)
Review

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Close response bodies to prevent resource leaks.

When client.Do returns successfully, the Response.Body must be closed to avoid leaking the underlying HTTP connection. Although this is a short-lived script, it is best practice to close the response bodies for req1 and req3.

🛠️ Proposed fix
-	client.Do(req1)
+	if resp1, err := client.Do(req1); err == nil {
+		resp1.Body.Close()
+	}
 
 	q2 := url.Values{}
 	q2.Set("instanceId", msCustomer)
 	q2.Set("PageId", "si")
 	q2.Set("session_id", sessionID)
 	req2, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/mdt.js?"+q2.Encode(), nil)
 	req2.Header.Set("User-Agent", msUA)
 	resp2, err := client.Do(req2)
 	if err != nil {
 		return client, sessionID
 	}
 	body, _ := io.ReadAll(resp2.Body)
 	resp2.Body.Close()
 
 	wMatch := reW.FindStringSubmatch(string(body))
 	rtMatch := reRt.FindStringSubmatch(string(body))
 	if len(wMatch) > 1 && len(rtMatch) > 1 {
 		q3 := url.Values{}
 		q3.Set("session_id", sessionID)
 		q3.Set("CustomerId", msCustomer)
 		q3.Set("PageId", "si")
 		q3.Set("w", wMatch[1])
 		q3.Set("mdt", fmt.Sprintf("%d", time.Now().UnixMilli()))
 		q3.Set("rticks", rtMatch[1])
 		req3, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/?"+q3.Encode(), nil)
 		req3.Header.Set("User-Agent", msUA)
-		client.Do(req3)
+		if resp3, err := client.Do(req3); err == nil {
+			resp3.Body.Close()
+		}
 	}
 	return client, sessionID
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func newSession() (*http.Client, string) {
	sessionID := newSessionID()
	jar := &simpleCookieJar{}
	client := &http.Client{Timeout: 15 * time.Second, Jar: jar}

	q1 := url.Values{}
	q1.Set("org_id", msOrgID)
	q1.Set("session_id", sessionID)
	req1, _ := http.NewRequest("GET", "https://vlscppe.microsoft.com/tags?"+q1.Encode(), nil)
	req1.Header.Set("User-Agent", msUA)
	if resp1, err := client.Do(req1); err == nil {
		resp1.Body.Close()
	}

	q2 := url.Values{}
	q2.Set("instanceId", msCustomer)
	q2.Set("PageId", "si")
	q2.Set("session_id", sessionID)
	req2, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/mdt.js?"+q2.Encode(), nil)
	req2.Header.Set("User-Agent", msUA)
	resp2, err := client.Do(req2)
	if err != nil {
		return client, sessionID
	}
	body, _ := io.ReadAll(resp2.Body)
	resp2.Body.Close()

	wMatch := reW.FindStringSubmatch(string(body))
	rtMatch := reRt.FindStringSubmatch(string(body))
	if len(wMatch) > 1 && len(rtMatch) > 1 {
		q3 := url.Values{}
		q3.Set("session_id", sessionID)
		q3.Set("CustomerId", msCustomer)
		q3.Set("PageId", "si")
		q3.Set("w", wMatch[1])
		q3.Set("mdt", fmt.Sprintf("%d", time.Now().UnixMilli()))
		q3.Set("rticks", rtMatch[1])
		req3, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/?"+q3.Encode(), nil)
		req3.Header.Set("User-Agent", msUA)
		if resp3, err := client.Do(req3); err == nil {
			resp3.Body.Close()
		}
	}
	return client, sessionID
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-new-releases/main.go` around lines 103 - 143, Update newSession
to capture successful responses from the req1 and req3 client.Do calls and close
each Response.Body, while preserving the existing request flow and ignoring only
the returned errors as appropriate.
_🩺 Stability & Availability_ | _🟡 Minor_ | _⚡ Quick win_ **Close response bodies to prevent resource leaks.** When `client.Do` returns successfully, the `Response.Body` must be closed to avoid leaking the underlying HTTP connection. Although this is a short-lived script, it is best practice to close the response bodies for `req1` and `req3`. <details> <summary>🛠️ Proposed fix</summary> ```diff - client.Do(req1) + if resp1, err := client.Do(req1); err == nil { + resp1.Body.Close() + } q2 := url.Values{} q2.Set("instanceId", msCustomer) q2.Set("PageId", "si") q2.Set("session_id", sessionID) req2, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/mdt.js?"+q2.Encode(), nil) req2.Header.Set("User-Agent", msUA) resp2, err := client.Do(req2) if err != nil { return client, sessionID } body, _ := io.ReadAll(resp2.Body) resp2.Body.Close() wMatch := reW.FindStringSubmatch(string(body)) rtMatch := reRt.FindStringSubmatch(string(body)) if len(wMatch) > 1 && len(rtMatch) > 1 { q3 := url.Values{} q3.Set("session_id", sessionID) q3.Set("CustomerId", msCustomer) q3.Set("PageId", "si") q3.Set("w", wMatch[1]) q3.Set("mdt", fmt.Sprintf("%d", time.Now().UnixMilli())) q3.Set("rticks", rtMatch[1]) req3, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/?"+q3.Encode(), nil) req3.Header.Set("User-Agent", msUA) - client.Do(req3) + if resp3, err := client.Do(req3); err == nil { + resp3.Body.Close() + } } return client, sessionID } ``` </details> <!-- suggestion_start --> <details> <summary>📝 Committable suggestion</summary> > ‼️ **IMPORTANT** > Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. ```suggestion func newSession() (*http.Client, string) { sessionID := newSessionID() jar := &simpleCookieJar{} client := &http.Client{Timeout: 15 * time.Second, Jar: jar} q1 := url.Values{} q1.Set("org_id", msOrgID) q1.Set("session_id", sessionID) req1, _ := http.NewRequest("GET", "https://vlscppe.microsoft.com/tags?"+q1.Encode(), nil) req1.Header.Set("User-Agent", msUA) if resp1, err := client.Do(req1); err == nil { resp1.Body.Close() } q2 := url.Values{} q2.Set("instanceId", msCustomer) q2.Set("PageId", "si") q2.Set("session_id", sessionID) req2, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/mdt.js?"+q2.Encode(), nil) req2.Header.Set("User-Agent", msUA) resp2, err := client.Do(req2) if err != nil { return client, sessionID } body, _ := io.ReadAll(resp2.Body) resp2.Body.Close() wMatch := reW.FindStringSubmatch(string(body)) rtMatch := reRt.FindStringSubmatch(string(body)) if len(wMatch) > 1 && len(rtMatch) > 1 { q3 := url.Values{} q3.Set("session_id", sessionID) q3.Set("CustomerId", msCustomer) q3.Set("PageId", "si") q3.Set("w", wMatch[1]) q3.Set("mdt", fmt.Sprintf("%d", time.Now().UnixMilli())) q3.Set("rticks", rtMatch[1]) req3, _ := http.NewRequest("GET", "https://ov-df.microsoft.com/?"+q3.Encode(), nil) req3.Header.Set("User-Agent", msUA) if resp3, err := client.Do(req3); err == nil { resp3.Body.Close() } } return client, sessionID } ``` </details> <!-- suggestion_end --> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/check-new-releases/main.go` around lines 103 - 143, Update newSession to capture successful responses from the req1 and req3 client.Do calls and close each Response.Body, while preserving the existing request flow and ignoring only the returned errors as appropriate. ``` </details> <!-- fingerprinting:phantom:poseidon:panther --> <!-- cr-indicator-types:potential_issue --> <!-- cr-comment:v1:06aabc48a5593dffe39bf1e7 --> <!-- This is an auto-generated comment by CodeRabbit -->
type msErrorEntry struct {
Type float64 `json:"Type"`
Value string `json:"Value"`
}
func firstError(errs []msErrorEntry) string {
if len(errs) == 0 {
return ""
}
if int(errs[0].Type) == 9 {
if errs[0].Value != "" {
return errs[0].Value
}
return "Your IP has been temporarily blocked by Microsoft (Code 715-123130)"
}
if errs[0].Value != "" {
return errs[0].Value
}
return "Microsoft API error"
}
func fetchLanguages(client *http.Client, sessionID, productID string) ([]skuLang, error) {
q := url.Values{}
q.Set("profile", msProfile)
q.Set("productEditionId", productID)
q.Set("SKU", "undefined")
q.Set("friendlyFileName", "undefined")
q.Set("Locale", msLocale)
q.Set("sessionID", sessionID)
req, _ := http.NewRequest("GET", "https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?"+q.Encode(), nil)
req.Header.Set("User-Agent", msUA)
req.Header.Set("Referer", "https://www.microsoft.com/en-us/software-download/windows11")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Microsoft returned HTTP %d", resp.StatusCode)
}
if len(body) > 0 && body[0] == '"' {
var unquoted string
json.Unmarshal(body, &unquoted)
body = []byte(unquoted)
}
var data struct {
Skus []skuLang `json:"Skus"`
Errors []msErrorEntry `json:"Errors"`
}
if err := json.Unmarshal(body, &data); err != nil {
return nil, fmt.Errorf("invalid SKU response: %w", err)
}
if msg := firstError(data.Errors); msg != "" {
return nil, fmt.Errorf("%s", msg)
}
if len(data.Skus) == 0 {
return nil, fmt.Errorf("no languages found for this product")
}
return data.Skus, nil
}
// --- New logic specific to this tool ---
var catalogEntryRe = regexp.MustCompile(`\{"(\d+)",\s*"([^"]*)"\}`)
func knownCatalogIDs(catalogPath string) (map[string]string, error) {
data, err := os.ReadFile(catalogPath)
if err != nil {
return nil, err
}
matches := catalogEntryRe.FindAllStringSubmatch(string(data), -1)
out := make(map[string]string, len(matches))
for _, m := range matches {
out[m[1]] = m[2]
}
return out, nil
}
var flagshipRe = regexp.MustCompile(`<option value="(\d+)">Windows`)
func scrapeFlagshipID(pageURL string) (string, error) {
req, _ := http.NewRequest("GET", pageURL, nil)
req.Header.Set("User-Agent", msUA)
client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("fetching download page: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
m := flagshipRe.FindStringSubmatch(string(body))
if len(m) < 2 {
return "", fmt.Errorf("could not find a product edition ID on the page -- Microsoft may have changed the page layout")
}
return m[1], nil
}
func main() {
catalogPath := flag.String("catalog", "../../cli/catalog.go", "path to cli/catalog.go")
probeRange := flag.Int("probe-range", 20, "how many adjacent IDs to probe after a new flagship is found")
flag.Parse()
const windows11Page = "https://www.microsoft.com/en-us/software-download/windows11"
fmt.Printf("Checking %s for the current flagship product edition ID...\n", windows11Page)
flagshipID, err := scrapeFlagshipID(windows11Page)
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
fmt.Printf("Flagship ID found: %s\n", flagshipID)
known, err := knownCatalogIDs(*catalogPath)
if err != nil {
fmt.Fprintln(os.Stderr, "error reading catalog:", err)
os.Exit(1)
}
fmt.Printf("Loaded %d known product IDs from %s\n\n", len(known), *catalogPath)
if name, ok := known[flagshipID]; ok {
fmt.Printf("Up to date -- flagship ID %s is already in the catalog as %q\n", flagshipID, name)
return
}
fmt.Printf("NEW RELEASE DETECTED -- product edition ID %s is not in the current catalog.\n\n", flagshipID)
fmt.Println("Probing adjacent IDs to discover the variant family (this makes real")
fmt.Println("requests to Microsoft -- allow it to finish rather than re-running):")
fmt.Println()
client, sessionID := newSession()
idNum, err := strconv.Atoi(flagshipID)
if err != nil {
fmt.Fprintln(os.Stderr, "error: flagship ID is not numeric:", err)
os.Exit(1)
}
fmt.Printf("%-8s %-8s %s\n", "ID", "STATUS", "NOTES")
for offset := 0; offset <= *probeRange; offset++ {
candidate := strconv.Itoa(idNum + offset)
langs, err := fetchLanguages(client, sessionID, candidate)
time.Sleep(300 * time.Millisecond) // be a good citizen -- don't hammer Microsoft
if err != nil {
continue // not a valid product ID; skip silently, only report hits
}
realName := ""
if len(langs) > 0 {
realName = langs[0].ProductDisplayName
}
note := fmt.Sprintf("%q -- %d language(s)", realName, len(langs))
switch {
case len(langs) == 1:
note += " -- likely a China-only single-edition variant"
case len(langs) > 30:
note += " -- likely a worldwide multi-edition variant"
}
if existingName, ok := known[candidate]; ok {
note += fmt.Sprintf(" (already in catalog as %q)", existingName)
}
fmt.Printf("%-8s %-8s %s\n", candidate, "FOUND", note)
}
fmt.Println()
fmt.Println("Note: the ARM64 family's offset from the flagship isn't consistent release")
fmt.Println("to release (seen +3 for 25H2, +18 for 24H2) -- if no ARM64 variant showed up")
fmt.Println("above, try a wider -probe-range.")
fmt.Println()
fmt.Println("Next steps: confirm the build number from Microsoft's release-health pages,")
fmt.Println("then add entries to cli/catalog.go, frontend/public/data/products.json,")
fmt.Println("and the validContributeProducts map in backend/main.go.")
}

View file

@ -12,8 +12,27 @@ LOCALE = "en-US"
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
def setup_session():
"""Initializes a session with Microsoft tracking servers."""
"""Initializes a session with Microsoft tracking servers.
Registers the session_id via vlscppe's "permit" endpoint before use --
confirmed live that getskuinformationbyproductedition still returns valid
data without this for a single lookup, but this script's brute-force
range scan makes many rapid consecutive requests, which is a much
higher-risk pattern for triggering a Sentinel block than the single-shot
case that was tested. The CLI/backend's full session flow additionally
replays an ov-df.microsoft.com fingerprint; add that too if this alone
turns out not to be enough under real scanning load.
"""
session_id = str(uuid.uuid4())
try:
requests.get(
"https://vlscppe.microsoft.com/tags",
params={"org_id": "y6jn8c31", "session_id": session_id},
headers={"User-Agent": UA},
timeout=10,
)
except Exception as e:
logging.warning(f"Session permit call failed (continuing anyway): {e}")
return session_id
def get_product(product_id, session_id):
@ -66,13 +85,15 @@ def scan_id(product_id):
# MS usually puts the release name in the first SKU
skus = data.get("Skus", [])
if skus:
# Check multiple possible name keys
s = skus[0]
# ReleaseName is often "Windows 10 Version 22H2 (Updated Oct 2025)"
name = s.get("EditionName") or s.get("ReleaseName") or s.get("FriendlyName")
# Confirmed live 2026-07-14: the real field is "ProductDisplayName"
# (e.g. "Windows 11 25H2"), not EditionName/ReleaseName/FriendlyName --
# those don't exist in the actual response and always fell through to
# the generic fallback below, silently.
name = s.get("ProductDisplayName") or s.get("LocalizedProductDisplayName")
if name:
return name
return f"Windows Product {product_id}"
if __name__ == "__main__":