webstore-lint 1.0.31 → 1.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -4
- package/bin/webstore-lint.mjs +1 -0
- package/data/policy.json +23 -29
- package/package.json +2 -2
- package/src/privacy.mjs +12 -12
- package/src/rules.mjs +4 -1
package/README.md
CHANGED
|
@@ -183,10 +183,13 @@ the tool prints when relevant:
|
|
|
183
183
|
1. **The disclosure rule got much wider.** It used to bite only when the data was
|
|
184
184
|
not closely related to your single purpose. The live policy now covers any
|
|
185
185
|
user data, disclosed before installation. Meanwhile Google's own
|
|
186
|
-
troubleshooting page for `Purple Nickel`
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
troubleshooting page for `Purple Nickel` now also covers any user data, but
|
|
187
|
+
still says before collection rather than before installation. It also still
|
|
188
|
+
conditionally allows collection not disclosed in the listing. The
|
|
189
|
+
[User Data FAQ](https://developer.chrome.com/docs/webstore/program-policies/user-data-faq)
|
|
190
|
+
separately requires disclosure and consent within the product before collection;
|
|
191
|
+
a listing disclosure alone does not satisfy that requirement. Check both the
|
|
192
|
+
listing and the consent flow. These differences were rechecked on 9 October 2026.
|
|
190
193
|
2. **The prediction markets ban has a carve-out the announcement omits entirely.**
|
|
191
194
|
Simulated markets with no real money winnings may be allowed, if you clearly
|
|
192
195
|
say no real money is involved.
|
|
@@ -347,6 +350,10 @@ What it reports:
|
|
|
347
350
|
It checks *reachability*. It does not judge whether your policy is adequate,
|
|
348
351
|
which is the thing a reviewer actually reads it for.
|
|
349
352
|
|
|
353
|
+
The request has a ten-second deadline, including downloading the page body.
|
|
354
|
+
A timeout or interrupted download produces a network warning, with no verdict
|
|
355
|
+
on the unread page's content.
|
|
356
|
+
|
|
350
357
|
## What it cannot do
|
|
351
358
|
|
|
352
359
|
It reads your package. It cannot see your store listing, your screenshots or your
|
|
@@ -401,6 +408,13 @@ fetch of Google's pages, parsed from raw HTML, with 278 verbatim fields
|
|
|
401
408
|
re-checked programmatically against the raw bytes. Nothing in `data/policy.json`
|
|
402
409
|
is admitted unless it is marked `VERIFIED_GOOGLE`.
|
|
403
410
|
|
|
411
|
+
The citations published in `data/policy.json` were rechecked against 23 official
|
|
412
|
+
Google pages on 2026-10-09. This refresh includes the affiliate policy's direct
|
|
413
|
+
user benefit requirement and the installation button disclosure requirement. It
|
|
414
|
+
also removes outdated claims that Google's troubleshooting page still carries
|
|
415
|
+
the old personal-or-sensitive-data scope. Historical research fields in the
|
|
416
|
+
source dataset retain their original measurement dates.
|
|
417
|
+
|
|
404
418
|
That method was not paranoia. The first pass over the troubleshooting page, done
|
|
405
419
|
through a summarising model, produced a fabricated policy quote and an invented
|
|
406
420
|
source structure. A second independent sweep invented a codename that does not
|
package/bin/webstore-lint.mjs
CHANGED
|
@@ -281,6 +281,7 @@ const wrap = (s, indent = " ", width = 92) => {
|
|
|
281
281
|
|
|
282
282
|
console.log(`\nwebstore-lint ${result.root}`);
|
|
283
283
|
console.log(`policy data pulled ${POLICY.datasetPulledAt}, ${POLICY.enforcedFrom} updates included\n`);
|
|
284
|
+
if (POLICY.citationsVerifiedAt) console.log(`policy citations checked ${POLICY.citationsVerifiedAt}\n`);
|
|
284
285
|
|
|
285
286
|
if (!result.findings.length) {
|
|
286
287
|
console.log("No findings.\n");
|
package/data/policy.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"generatedFrom": "research/data/chrome-rejection-reasons.json",
|
|
4
4
|
"datasetPulledAt": "2026-07-29T00:08:05Z",
|
|
5
|
+
"citationsVerifiedAt": "2026-10-09",
|
|
5
6
|
"enforcedFrom": "2026-08-01",
|
|
6
7
|
"enforcementQuote": "Enforcement for these updated policies will begin on August 1, 2026. Extensions found out of compliance after this date may face enforcement action from the Chrome Web Store.",
|
|
7
8
|
"announcementUrl": "https://developer.chrome.com/blog/cws-policy-updates-2026",
|
|
@@ -152,11 +153,7 @@
|
|
|
152
153
|
"Visit the Privacy tab for your extension to verify that a privacy policy link appears in the \"Privacy Policy\" box (located at the bottom of the Privacy tab) and that the link works as expected.",
|
|
153
154
|
"Ensure the privacy policy talks about data collection, usage, handling, and sharing.",
|
|
154
155
|
"If you have done the above but are still encountering review issues, use the appeal button on the item detail page to appeal the verdict or ask for clarification."
|
|
155
|
-
]
|
|
156
|
-
"supersededStillOnTroubleshooting": [
|
|
157
|
-
"If your Product handles personal or sensitive user data (including personally identifiable information, financial and payment information, health information, authentication information, website content and resources, form data, web browsing activity, user-provided content and personal communications), then your Product must: Post a privacy policy, and Handle the user data securely, including transmitting it via modern cryptography."
|
|
158
|
-
],
|
|
159
|
-
"supersededMeasuredAt": "2026-07-31T13:50:08.088Z"
|
|
156
|
+
]
|
|
160
157
|
},
|
|
161
158
|
"illegal-activities": {
|
|
162
159
|
"title": "Illegal activities",
|
|
@@ -198,7 +195,7 @@
|
|
|
198
195
|
"supersededStillOnTroubleshooting": [
|
|
199
196
|
"We don't allow content or services that facilitate online gambling or prediction markets, including but not limited to online casinos, sports betting, lotteries, or games of skill that offer prizes of cash or other value."
|
|
200
197
|
],
|
|
201
|
-
"supersededMeasuredAt": "2026-
|
|
198
|
+
"supersededMeasuredAt": "2026-10-09"
|
|
202
199
|
},
|
|
203
200
|
"pornography": {
|
|
204
201
|
"title": "Pornographic content",
|
|
@@ -225,7 +222,7 @@
|
|
|
225
222
|
"If we become aware of content with child pornography, we will report it to the appropriate authorities and delete the Google Accounts of those involved with the distribution.",
|
|
226
223
|
"Content which contains non-sexual nudity - such as artistic, educational, scientific, or cultural nudity - is generally allowed, but may impact the visibility of your Product."
|
|
227
224
|
],
|
|
228
|
-
"supersededMeasuredAt": "2026-
|
|
225
|
+
"supersededMeasuredAt": "2026-10-09"
|
|
229
226
|
},
|
|
230
227
|
"hate": {
|
|
231
228
|
"title": "Hate content",
|
|
@@ -248,7 +245,7 @@
|
|
|
248
245
|
"We don't allow content advocating against or inciting hatred towards groups of people based on their race or ethnic origin, religion, disability, gender, age, veteran status, nationality, sexual orientation, gender, gender identity, or any other characteristic that is associated with systematic discrimination or marginalization.",
|
|
249
246
|
"Additionally, the visibility of your Product may be impacted if it contains generally hateful content not covered by the above definition."
|
|
250
247
|
],
|
|
251
|
-
"supersededMeasuredAt": "2026-
|
|
248
|
+
"supersededMeasuredAt": "2026-10-09"
|
|
252
249
|
},
|
|
253
250
|
"not-family-safe": {
|
|
254
251
|
"title": "Not family safe",
|
|
@@ -269,7 +266,7 @@
|
|
|
269
266
|
"Our content policies apply to your Product's content, including any ads it shows to users and any user-generated content it hosts or links to.",
|
|
270
267
|
"Further, they apply to any content from your developer account that is publicly displayed in Chrome Web Store, including your developer name and the landing page of your listed developer website."
|
|
271
268
|
],
|
|
272
|
-
"supersededMeasuredAt": "2026-
|
|
269
|
+
"supersededMeasuredAt": "2026-10-09"
|
|
273
270
|
},
|
|
274
271
|
"violence": {
|
|
275
272
|
"title": "Violent content",
|
|
@@ -297,7 +294,7 @@
|
|
|
297
294
|
"Content that results in the unwanted sexualization of a person, including malicious claims about a person's sexual activities, sexual orientation, or gender identity.",
|
|
298
295
|
"A series of posts/comments/photos that, taken together, clearly have the primary intention of harassment, even if each individual piece of content is not severe."
|
|
299
296
|
],
|
|
300
|
-
"supersededMeasuredAt": "2026-
|
|
297
|
+
"supersededMeasuredAt": "2026-10-09"
|
|
301
298
|
},
|
|
302
299
|
"single-use": {
|
|
303
300
|
"title": "Single purpose",
|
|
@@ -314,14 +311,14 @@
|
|
|
314
311
|
"triggers": [
|
|
315
312
|
"The extension provides two or more purposes in the same extension (for example an extension providing image format conversion and bibliography generation).",
|
|
316
313
|
"The extension provides one or more unrelated additional functionalities using the extension's action icon.",
|
|
317
|
-
"The extension provides a modified search experience on a new tab page which does not respect the user
|
|
314
|
+
"The extension provides a modified search experience on a new tab page which does not respect the user’s choice of search provider or fails to utilize official Chrome search APIs (e.g., chrome.search)."
|
|
318
315
|
],
|
|
319
316
|
"fixes": [
|
|
320
317
|
"Narrow the functionality of your extension to clearly fit within one well-defined purpose and ensure that purpose is clearly described in your extension's metadata.",
|
|
321
318
|
"If your extension is offering some functionality and also injecting ads, then either stop injecting ads or remove all other functionality besides ad injection.",
|
|
322
319
|
"Chrome does not support optional new tab pages. If you wish to provide some functionality and an optional new tab page, you should separate the new tab page into a standalone extension.",
|
|
323
320
|
"Don't offer another unrelated functionality in the extension's action icon.",
|
|
324
|
-
"If your new tab page extension includes a search experience, use the Chrome Search API to ensure it respects the user
|
|
321
|
+
"If your new tab page extension includes a search experience, use the Chrome Search API to ensure it respects the user’s choice of search provider."
|
|
325
322
|
]
|
|
326
323
|
},
|
|
327
324
|
"udp-prominent-disclosure": {
|
|
@@ -346,9 +343,10 @@
|
|
|
346
343
|
"Consider providing users with an \"offline mode\" that only stores user data locally."
|
|
347
344
|
],
|
|
348
345
|
"supersededStillOnTroubleshooting": [
|
|
349
|
-
"If your Product handles
|
|
346
|
+
"Prominent Disclosure Requirement: If your Product handles any user data, then prior to the collection, it must: Prominently disclose how the user data will be used. Obtain the user's affirmative consent for such use.",
|
|
347
|
+
"Collecting data that is not prominently disclosed in the Chrome Web Store listing is allowed so long as this data collection is consistent with the extension's single purpose, the user is informed of the data collection before it begins, and the user consents to the data collection."
|
|
350
348
|
],
|
|
351
|
-
"supersededMeasuredAt": "2026-
|
|
349
|
+
"supersededMeasuredAt": "2026-10-09"
|
|
352
350
|
},
|
|
353
351
|
"udp-secure": {
|
|
354
352
|
"title": "User data policy - secure transmission",
|
|
@@ -368,11 +366,7 @@
|
|
|
368
366
|
"Don't transmit user data over HTTP. If possible, use secure protocols for all requests.",
|
|
369
367
|
"Don't encode data in request headers or query parameters, even over HTTPS. Headers and request URLs often appear in server logs, which can unintentionally leak this information.",
|
|
370
368
|
"Use the Chrome DevTools or other network monitoring tools to watch the network requests the extension makes."
|
|
371
|
-
]
|
|
372
|
-
"supersededStillOnTroubleshooting": [
|
|
373
|
-
"If your Product handles personal or sensitive user data (including personally identifiable information, financial and payment information, health information, authentication information, website content and resources, form data, web browsing activity, user-provided content and personal communications), then your Product must: Post a privacy policy, and Handle the user data securely, including transmitting it via modern cryptography."
|
|
374
|
-
],
|
|
375
|
-
"supersededMeasuredAt": "2026-07-31T13:50:08.088Z"
|
|
369
|
+
]
|
|
376
370
|
},
|
|
377
371
|
"udp-other-requirements": {
|
|
378
372
|
"title": "User data policy - other requirements",
|
|
@@ -385,11 +379,11 @@
|
|
|
385
379
|
"troubleshootingUrl": "https://developer.chrome.com/docs/webstore/troubleshooting#udp-other-requirements",
|
|
386
380
|
"triggers": [
|
|
387
381
|
"The extension is collecting Web Browsing Activity when it is not needed for a user facing feature.",
|
|
388
|
-
"
|
|
382
|
+
"User data collected using the extension is being disclosed publicly."
|
|
389
383
|
],
|
|
390
384
|
"fixes": [
|
|
391
385
|
"Don't collect Web Browsing Activity unless it is required for a user-facing feature.",
|
|
392
|
-
"Ensure user
|
|
386
|
+
"Ensure user data is not being disclosed publicly."
|
|
393
387
|
]
|
|
394
388
|
},
|
|
395
389
|
"cryptocurrency-mining": {
|
|
@@ -439,7 +433,7 @@
|
|
|
439
433
|
"Yellow Argon"
|
|
440
434
|
],
|
|
441
435
|
"plainMeaning": "Your description is padded with keywords, site lists or locations to game search ranking.",
|
|
442
|
-
"policyQuote": "Keyword Spam is the practice of including irrelevant or excessive keywords in an extensions description in an attempt to manipulate its ranking, resulting in a spammy, negative user experience. We don't allow extensions with irrelevant or excessive metadata. Developers should focus on providing a clear and well-written description that uses keywords appropriately and in context. Some examples of Keyword Spam include: Lists of sites/brands/keywords without substantial added value Lists of regional locations Unnatural repetition of the same keyword more than 5 times We don
|
|
436
|
+
"policyQuote": "Keyword Spam is the practice of including irrelevant or excessive keywords in an extensions description in an attempt to manipulate its ranking, resulting in a spammy, negative user experience. We don't allow extensions with irrelevant or excessive metadata. Developers should focus on providing a clear and well-written description that uses keywords appropriately and in context. Some examples of Keyword Spam include: Lists of sites/brands/keywords without substantial added value Lists of regional locations Unnatural repetition of the same keyword more than 5 times We don’t allow unattributed or anonymous user testimonials in the product's description.",
|
|
443
437
|
"policyUrl": "https://developer.chrome.com/docs/webstore/program-policies/listing-requirements",
|
|
444
438
|
"troubleshootingUrl": "https://developer.chrome.com/docs/webstore/troubleshooting#keyword-stuffing",
|
|
445
439
|
"triggers": [
|
|
@@ -452,7 +446,7 @@
|
|
|
452
446
|
"We don't allow extensions with misleading, improperly formatted, non-descriptive, irrelevant, excessive, or inappropriate metadata, including but not limited to the extension's description, developer name, title, icon, screenshots, and promotional images.",
|
|
453
447
|
"Unattributed or anonymous user testimonials in the product's description."
|
|
454
448
|
],
|
|
455
|
-
"supersededMeasuredAt": "2026-
|
|
449
|
+
"supersededMeasuredAt": "2026-10-09"
|
|
456
450
|
},
|
|
457
451
|
"redirection": {
|
|
458
452
|
"title": "Redirection",
|
|
@@ -518,7 +512,7 @@
|
|
|
518
512
|
"Red Zinc"
|
|
519
513
|
],
|
|
520
514
|
"plainMeaning": "How you got users to install the extension was misleading, regardless of what the extension itself does.",
|
|
521
|
-
"policyQuote": "Extensions must be marketed responsibly. The set of functionalities promised by the extension must be stated clearly and in a transparent manner. The outcome of any user interaction should match the reasonable expectations that were set with the user. Extensions that use or benefit from deceptive installation tactics will be removed from the Chrome Web Store. Deceptive installation tactics include: Confusing or deceptive advertisements or marketing materials preceding the installation of your extension. The features of your extension should be clear when marketing your product. Misleading interactive elements as part of your distribution flow. This includes misleading call-to-action buttons or forms that imply an outcome other than the installation of an extension. Adjusting the Chrome Web Store product listing window with the effect of withholding or hiding extension metadata from the user. Bundling other extensions or offers within the same installation flow. Requiring unrelated user action to access advertised functionality.",
|
|
515
|
+
"policyQuote": "Extensions must be marketed responsibly. The set of functionalities promised by the extension must be stated clearly and in a transparent manner. The outcome of any user interaction should match the reasonable expectations that were set with the user. Extensions that use or benefit from deceptive installation tactics will be removed from the Chrome Web Store. Deceptive installation tactics include: Confusing or deceptive advertisements or marketing materials preceding the installation of your extension. The features of your extension should be clear when marketing your product. Misleading interactive elements as part of your distribution flow. This includes misleading call-to-action buttons or forms that imply an outcome other than the installation of an extension. Any call-to-action buttons preceding the installation of your extension must state that an extension will be installed. Adjusting the Chrome Web Store product listing window with the effect of withholding or hiding extension metadata from the user. Bundling other extensions or offers within the same installation flow. Requiring unrelated user action to access advertised functionality.",
|
|
522
516
|
"policyUrl": "https://developer.chrome.com/docs/webstore/program-policies/deceptive-installation-tactics",
|
|
523
517
|
"troubleshootingUrl": "https://developer.chrome.com/docs/webstore/troubleshooting#uws-distribution",
|
|
524
518
|
"triggers": [
|
|
@@ -552,7 +546,7 @@
|
|
|
552
546
|
"Yellow Potassium"
|
|
553
547
|
],
|
|
554
548
|
"plainMeaning": "The extension is too thin to be worth listing, or it just links out to a service that does the actual work.",
|
|
555
|
-
"policyQuote": "Extensions must provide a basic degree of functionality and utility that provide value to the catalog of the Chrome Web Store. Some examples of common violations include: Extensions with no functionality or utility. Extensions with functionality that is not directly provided by the extension (e.g. file converters which only link to other file conversion services). Click-baity template extensions that only vary slightly in functionality with negligible utility (e.g. a
|
|
549
|
+
"policyQuote": "Extensions must provide a basic degree of functionality and utility that provide value to the catalog of the Chrome Web Store. Some examples of common violations include: Extensions with no functionality or utility. Extensions with functionality that is not directly provided by the extension (e.g. file converters which only link to other file conversion services). Click-baity template extensions that only vary slightly in functionality with negligible utility (e.g. a “Word of the Day” extension and a “Daily Inspirational Quotes” extension, which use the same general extension template).",
|
|
556
550
|
"policyUrl": "https://developer.chrome.com/docs/webstore/program-policies/minimum-functionality",
|
|
557
551
|
"troubleshootingUrl": "https://developer.chrome.com/docs/webstore/troubleshooting#minimum_functionality",
|
|
558
552
|
"triggers": [
|
|
@@ -571,8 +565,8 @@
|
|
|
571
565
|
"notificationIds": [
|
|
572
566
|
"Grey Titanium"
|
|
573
567
|
],
|
|
574
|
-
"plainMeaning": "
|
|
575
|
-
"policyQuote": "Any affiliate program must be described prominently in the product's Chrome Web Store page, user interface, and before installation. Related user action is required before the inclusion of each affiliate code, link, or cookie. Some example violations include: An extension that updates a shopping-related cookie without the user's knowledge while the user is browsing shopping sites. An extension that appends an affiliate code to the URL or replaces an existing affiliate code in the URL without the user's explicit knowledge or related user action. An extension that applies or replaces affiliate promo codes without the user's explicit knowledge or related user action.",
|
|
568
|
+
"plainMeaning": "Affiliate links, codes or cookies need prominent disclosure, a direct user benefit related to the core functionality, and related user action before each inclusion.",
|
|
569
|
+
"policyQuote": "Any affiliate program must be described prominently in the product's Chrome Web Store page, user interface, and before installation. Affiliate links, codes, or cookies must only be included when the extension provides a direct and transparent user benefit related to the extension's core functionality. It is not permitted to inject affiliate links without related user action and without providing a tangible benefit to users. Some common violations include: Inserting affiliate links when no discount, cashback, or donation is provided. An extension that continuously injects affiliate links in the background without related user action. Related user action is required before the inclusion of each affiliate code, link, or cookie. Some example violations include: An extension that updates a shopping-related cookie without the user's knowledge while the user is browsing shopping sites. An extension that appends an affiliate code to the URL or replaces an existing affiliate code in the URL without the user's explicit knowledge or related user action. An extension that applies or replaces affiliate promo codes without the user's explicit knowledge or related user action.",
|
|
576
570
|
"policyUrl": "https://developer.chrome.com/docs/webstore/program-policies/affiliate-ads",
|
|
577
571
|
"troubleshootingUrl": "https://developer.chrome.com/docs/webstore/troubleshooting#affiliate_ads",
|
|
578
572
|
"triggers": [
|
|
@@ -598,7 +592,7 @@
|
|
|
598
592
|
"Leveraging techniques in an attempt to manipulate the distribution of updates to users and keep them on versions with previous violations"
|
|
599
593
|
],
|
|
600
594
|
"fixes": [
|
|
601
|
-
"Don
|
|
595
|
+
"Don’t attempt any further actions to circumvent intended limitations or enforcement actions.",
|
|
602
596
|
"Continued attempts at enforcement circumvention will result in suspension of your developer account."
|
|
603
597
|
]
|
|
604
598
|
}
|
|
@@ -633,7 +627,7 @@
|
|
|
633
627
|
"blogQuote": "Disclosure Requirements Policy: We are updating our transparency standards to require that all data collection be prominently disclosed to the user—regardless of whether the data is closely related to the extension's single purpose. Additionally, developers must proactively disclose to users if their data handling practices change at any point after the initial installation.",
|
|
634
628
|
"announcementUrl": "https://developer.chrome.com/blog/cws-policy-updates-2026",
|
|
635
629
|
"inLivePolicyText": true,
|
|
636
|
-
"discrepancy": "
|
|
630
|
+
"discrepancy": "Rechecked 2026-10-09: the troubleshooting quote now covers any user data, so the old scope mismatch is gone. It still says prior to collection, while the live policy says prior to installation. The troubleshooting fixes still allow collection not disclosed in the listing under stated conditions. The User Data FAQ separately says disclosure and consent must occur within the product UI, and that listing disclosures alone do not satisfy that requirement.",
|
|
637
631
|
"carveOut": null,
|
|
638
632
|
"notificationId": "Purple Nickel"
|
|
639
633
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webstore-lint",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.33",
|
|
4
4
|
"description": "Check an unpacked Chrome extension against the Chrome Web Store program policies before you submit it. Every finding cites Google's own notification ID and verbatim policy text. Covers the 1 August 2026 policy updates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chrome-extension",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"node": ">=18"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
-
"test": "node test/lint.test.mjs && node test/action.test.mjs && node --test test/permissions-output.test.mjs test/cli-modes.test.mjs"
|
|
41
|
+
"test": "node test/lint.test.mjs && node test/action.test.mjs && node --test test/permissions-output.test.mjs test/cli-modes.test.mjs test/privacy-timeout.test.mjs"
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
package/src/privacy.mjs
CHANGED
|
@@ -118,6 +118,7 @@ export async function checkPolicyUrl(url, { timeoutMs = 10000, fetchImpl = fetch
|
|
|
118
118
|
const ctl = new AbortController();
|
|
119
119
|
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
120
120
|
let res;
|
|
121
|
+
let body;
|
|
121
122
|
try {
|
|
122
123
|
res = await fetchImpl(u.href, {
|
|
123
124
|
signal: ctl.signal,
|
|
@@ -129,24 +130,30 @@ export async function checkPolicyUrl(url, { timeoutMs = 10000, fetchImpl = fetch
|
|
|
129
130
|
"user-agent": "webstore-lint (privacy policy reachability check)",
|
|
130
131
|
},
|
|
131
132
|
});
|
|
133
|
+
// fetch resolves at headers. Keep the deadline active until the body is
|
|
134
|
+
// complete too, or a stalled page can hang the CLI and Action indefinitely.
|
|
135
|
+
if (res.ok) body = await res.text();
|
|
136
|
+
else ctl.abort(); // The status is enough evidence; do not download its body.
|
|
132
137
|
} catch (e) {
|
|
133
|
-
clearTimeout(timer);
|
|
134
138
|
// COULD NOT LOOK IS NOT THE SAME FACT AS NOT THERE. A DNS failure on the
|
|
135
139
|
// developer's own machine, an offline CI runner or a proxy is not evidence
|
|
136
140
|
// about the URL, so this warns and says which it is rather than failing.
|
|
137
|
-
const aborted = e.name === "AbortError";
|
|
141
|
+
const aborted = ctl.signal.aborted || e.name === "AbortError";
|
|
138
142
|
return [...findings, f(
|
|
139
143
|
"warn",
|
|
140
144
|
aborted
|
|
141
|
-
? `The privacy policy URL did not
|
|
142
|
-
:
|
|
145
|
+
? `The privacy policy URL did not finish loading within ${timeoutMs / 1000}s`
|
|
146
|
+
: res
|
|
147
|
+
? `The privacy policy response could not be read from here: ${e.message}`
|
|
148
|
+
: `The privacy policy URL could not be reached from here: ${e.message}`,
|
|
143
149
|
"This is not the same as the address being broken. It may be your network, a proxy, or an offline " +
|
|
144
150
|
"runner. Open it yourself in a private browser window before you trust either answer. " +
|
|
145
151
|
"A reviewer will be doing exactly that.",
|
|
146
152
|
[{ file: "--privacy-policy", text: url }],
|
|
147
153
|
)];
|
|
154
|
+
} finally {
|
|
155
|
+
clearTimeout(timer);
|
|
148
156
|
}
|
|
149
|
-
clearTimeout(timer);
|
|
150
157
|
|
|
151
158
|
const finalUrl = res.url || u.href;
|
|
152
159
|
const redirected = finalUrl.replace(/\/$/, "") !== u.href.replace(/\/$/, "");
|
|
@@ -163,13 +170,6 @@ export async function checkPolicyUrl(url, { timeoutMs = 10000, fetchImpl = fetch
|
|
|
163
170
|
)];
|
|
164
171
|
}
|
|
165
172
|
|
|
166
|
-
let body = "";
|
|
167
|
-
try {
|
|
168
|
-
body = await res.text();
|
|
169
|
-
} catch {
|
|
170
|
-
body = "";
|
|
171
|
-
}
|
|
172
|
-
|
|
173
173
|
// Strip tags and scripts before counting words, or a single-page app's bundle
|
|
174
174
|
// reads as a wall of text that mentions everything.
|
|
175
175
|
const text = body
|
package/src/rules.mjs
CHANGED
|
@@ -1744,7 +1744,10 @@ export const RULES = [
|
|
|
1744
1744
|
detail:
|
|
1745
1745
|
"This changed on 1 August 2026 and the change is easy to miss. The old rule only bit when the data was " +
|
|
1746
1746
|
"NOT closely related to the extension's single purpose. The live policy now covers ANY user data. " +
|
|
1747
|
-
"
|
|
1747
|
+
"Verify the store listing and consent flow against the live policy. Google's User Data FAQ also " +
|
|
1748
|
+
"requires prominent disclosure and consent within the product before collection; a listing or " +
|
|
1749
|
+
"privacy policy alone does not satisfy that requirement. See " +
|
|
1750
|
+
"https://developer.chrome.com/docs/webstore/program-policies/user-data-faq. " +
|
|
1748
1751
|
"READ THE WARNING BELOW ABOUT GOOGLE'S OWN TROUBLESHOOTING PAGE.",
|
|
1749
1752
|
evidence: [
|
|
1750
1753
|
...dataPerms.map((p) => ({ file: "manifest.json", line: 1, text: `"${p}"` })),
|