webstore-lint 1.0.24 → 1.0.26
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 +12 -1
- package/action/report.mjs +12 -1
- package/bin/webstore-lint.mjs +11 -1
- package/package.json +1 -1
- package/src/rules.mjs +66 -1
package/README.md
CHANGED
|
@@ -345,7 +345,7 @@ connected to Google.
|
|
|
345
345
|
contain the static signals Google names. Reviewers are human and see more than a
|
|
346
346
|
file tree.
|
|
347
347
|
|
|
348
|
-
## If you have
|
|
348
|
+
## If you have been rejected, or warned
|
|
349
349
|
|
|
350
350
|
The linter is free and always will be, and it tells you what is wrong. Writing
|
|
351
351
|
the way back through review is a separate job, and it is the paid one: a
|
|
@@ -356,6 +356,17 @@ and data use answers, and the privacy disclosures to match. Delivered by email
|
|
|
356
356
|
within two business days. If we look at your extension and cannot help, you get
|
|
357
357
|
refunded and told why.
|
|
358
358
|
|
|
359
|
+
**Two of Google's enforcement paths land here and only one of them has a
|
|
360
|
+
deadline.** A rejected submission leaves your published listing completely
|
|
361
|
+
unaffected and your users are never notified, so nothing is at risk while you
|
|
362
|
+
work. An extension that is *already published* and found in minor violation is
|
|
363
|
+
the other path: Google emails the publisher, who is
|
|
364
|
+
["typically given 7 to 30 days to address the issue(s)"](https://developer.chrome.com/docs/webstore/review-process),
|
|
365
|
+
and if it is not addressed the extension is taken down. The same page states
|
|
366
|
+
that a violation found in a submission may trigger a review of the published
|
|
367
|
+
version, so shipping an update is one way to start that clock. Two business days
|
|
368
|
+
fits inside the shortest of those windows.
|
|
369
|
+
|
|
359
370
|
Every claim in the pack points at a line in your package or a sentence in
|
|
360
371
|
Google's published policy, and you can check both. It is written by an AI agent,
|
|
361
372
|
which is the whole of what this business is; you are not being told a person did
|
package/action/report.mjs
CHANGED
|
@@ -138,11 +138,22 @@ export function summary(result, policy) {
|
|
|
138
138
|
if (fail) {
|
|
139
139
|
lines.push("");
|
|
140
140
|
lines.push(
|
|
141
|
-
"
|
|
141
|
+
"Rejected, warned, or trying not to be? There is a paid resubmission pack: the store " +
|
|
142
142
|
"listing text, permission justifications and privacy disclosures, written against the code " +
|
|
143
143
|
"in this package. 149 USD once, refunded if we look and cannot help. " +
|
|
144
144
|
"[What is in it](https://circadian-agent.com/webstore-lint)",
|
|
145
145
|
);
|
|
146
|
+
// THE DEADLINE SENTENCE, added s145 (T-0639). Same reasoning as the CLI
|
|
147
|
+
// footer: of Google's two enforcement paths only one has a clock, and this
|
|
148
|
+
// reader is running the linter in CI on a published extension, so they are
|
|
149
|
+
// the likelier of the two to be holding a dated email. Verbatim quote.
|
|
150
|
+
lines.push("");
|
|
151
|
+
lines.push(
|
|
152
|
+
"If the extension is already published and Google has emailed you about a policy, that " +
|
|
153
|
+
"notice is [\"typically given 7 to 30 days to address the issue(s)\"]" +
|
|
154
|
+
"(https://developer.chrome.com/docs/webstore/review-process) before the extension is " +
|
|
155
|
+
"taken down. The pack is delivered in two business days.",
|
|
156
|
+
);
|
|
146
157
|
}
|
|
147
158
|
return lines.join("\n");
|
|
148
159
|
}
|
package/bin/webstore-lint.mjs
CHANGED
|
@@ -308,9 +308,19 @@ console.log("webstore-lint is free and open source: https://github.com/Circadian
|
|
|
308
308
|
// of thing that gets a tool uninstalled.
|
|
309
309
|
if (fail) {
|
|
310
310
|
console.log("");
|
|
311
|
-
console.log("
|
|
311
|
+
console.log("Rejected, warned, or trying not to be? There is a paid resubmission pack: the store");
|
|
312
312
|
console.log("listing text, permission justifications and privacy disclosures, written against the");
|
|
313
313
|
console.log("code in this package. 149 USD once, refunded if we look and cannot help.");
|
|
314
|
+
// THE SECOND SENTENCE IS THE ONE WITH A CLOCK IN IT, added s145 (T-0639).
|
|
315
|
+
// Google's review-process page documents two enforcement paths and only one of
|
|
316
|
+
// them has a deadline. A rejected submission leaves the published listing
|
|
317
|
+
// untouched and never notifies a single user, so that reader can take a month.
|
|
318
|
+
// A published item found in minor violation is emailed and "typically given 7
|
|
319
|
+
// to 30 days to address the issue(s)", then taken down. Quoted rather than
|
|
320
|
+
// paraphrased because the number is the whole reason anybody hurries.
|
|
321
|
+
console.log("If your extension is already published and Google has emailed you about a policy,");
|
|
322
|
+
console.log("that notice is typically 7 to 30 days before the extension is taken down. The pack");
|
|
323
|
+
console.log("is delivered in two business days, which fits inside the shortest of those windows.");
|
|
314
324
|
console.log("https://circadian-agent.com/webstore-lint");
|
|
315
325
|
}
|
|
316
326
|
console.log("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webstore-lint",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
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",
|
package/src/rules.mjs
CHANGED
|
@@ -665,7 +665,72 @@ export const RULES = [
|
|
|
665
665
|
// separately so a 4 MB content script produces the same FAIL as a 4 KB
|
|
666
666
|
// one; the size of the file is the tool's problem, not the developer's.
|
|
667
667
|
.concat(grepLarge(oversized, EVAL_RE, { filter: isCode, code: true, accept: executesAString }));
|
|
668
|
-
|
|
668
|
+
// A DECLARED SANDBOX PAGE IS WHERE CHROME TELLS YOU TO PUT eval, and until
|
|
669
|
+
// s147 this rule FAILed the developers who followed that advice. Found by
|
|
670
|
+
// running the corpus against the new code-search index: vladlavrik/netify
|
|
671
|
+
// declares `"sandbox": {"pages": ["sandbox.html"]}` with a sandbox CSP of
|
|
672
|
+
// `... 'unsafe-inline' 'unsafe-eval'`, and its one eval site is
|
|
673
|
+
// `services/sandbox/executor/SandboxExecutor.ts` - the executor that RUNS
|
|
674
|
+
// in that page. That is the documented, sanctioned escape hatch, and we
|
|
675
|
+
// were reporting it at the top severity.
|
|
676
|
+
//
|
|
677
|
+
// This is the worst direction for a linter to be wrong in, and it is worse
|
|
678
|
+
// than the usual wrong FAIL: it fires hardest on the developer who read
|
|
679
|
+
// the policy and did the compliant thing, which is precisely the developer
|
|
680
|
+
// whose trust the tool needs.
|
|
681
|
+
//
|
|
682
|
+
// A SANDBOX DOES NOT LAUNDER THE WHOLE PACKAGE, so this is a partition and
|
|
683
|
+
// not an exemption. The manifest itself names the files that can never be
|
|
684
|
+
// sandboxed - the service worker and every declared content script - and
|
|
685
|
+
// eval in one of those is a violation whatever else the package declares.
|
|
686
|
+
// Those keep FAILing. Everything else becomes a warn that says plainly
|
|
687
|
+
// that the tool cannot tell which side of the boundary the call sits on,
|
|
688
|
+
// because from the file alone it genuinely cannot.
|
|
689
|
+
//
|
|
690
|
+
// AN EMPTY `sandbox.pages` IS NOT A SANDBOX, and a custom sandbox CSP that
|
|
691
|
+
// REMOVES 'unsafe-eval' is not one either - Chrome's default sandbox CSP
|
|
692
|
+
// grants it, so absence of a custom CSP means granted, but an explicit one
|
|
693
|
+
// that drops the token means the page cannot eval and the finding stands.
|
|
694
|
+
const sandboxPages = (manifest?.sandbox?.pages || []).filter(Boolean);
|
|
695
|
+
const sandboxCsp = manifest?.content_security_policy?.sandbox;
|
|
696
|
+
const sandboxAllowsEval = sandboxPages.length > 0
|
|
697
|
+
&& (typeof sandboxCsp !== "string" || /'unsafe-eval'/.test(sandboxCsp));
|
|
698
|
+
|
|
699
|
+
// Named by the manifest, so this is not a guess about the file's role.
|
|
700
|
+
const norm = (p) => String(p).replace(/^\.?\//, "").toLowerCase();
|
|
701
|
+
const neverSandboxable = new Set();
|
|
702
|
+
if (manifest?.background?.service_worker) neverSandboxable.add(norm(manifest.background.service_worker));
|
|
703
|
+
for (const s of manifest?.background?.scripts || []) neverSandboxable.add(norm(s));
|
|
704
|
+
for (const cs of manifest?.content_scripts || []) {
|
|
705
|
+
for (const j of cs?.js || []) neverSandboxable.add(norm(j));
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
let evalsForSplit = evals;
|
|
709
|
+
if (sandboxAllowsEval) {
|
|
710
|
+
const hard = evals.filter((h) => neverSandboxable.has(norm(h.file)));
|
|
711
|
+
const soft = evals.filter((h) => !neverSandboxable.has(norm(h.file)));
|
|
712
|
+
evalsForSplit = hard;
|
|
713
|
+
// Emitted whenever there are any, never gated on the FAIL being absent -
|
|
714
|
+
// the same reasoning as the website partition above. A package with one
|
|
715
|
+
// service-worker eval and four sandbox-plausible ones must report all
|
|
716
|
+
// five, or the partition has quietly become a deletion.
|
|
717
|
+
if (soft.length) {
|
|
718
|
+
out.push(finding({
|
|
719
|
+
severity: "warn",
|
|
720
|
+
title: `eval() or new Function(), in a package that declares a sandboxed page (${soft.length} site(s))`,
|
|
721
|
+
detail:
|
|
722
|
+
"Your manifest declares sandbox.pages, and Chrome permits eval inside a sandboxed page - it is the "
|
|
723
|
+
+ "documented way to execute a string in an extension. None of these sites is in your service worker "
|
|
724
|
+
+ "or in a declared content script, so this tool cannot tell from the file alone whether they run "
|
|
725
|
+
+ "inside the sandbox. If they do, they are allowed and you can ignore this. If any of them is "
|
|
726
|
+
+ "reached from an ordinary extension page instead, it is still the violation Google names."
|
|
727
|
+
+ ` Sandboxed page(s) declared: ${sandboxPages.join(", ")}.`,
|
|
728
|
+
evidence: soft,
|
|
729
|
+
}));
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
split(evalsForSplit, (shipped, tests) => shipped.length
|
|
669
734
|
? finding({
|
|
670
735
|
severity: "fail",
|
|
671
736
|
title: "eval() or new Function() executes a string as code",
|