Skip to content

JS-1790 Restrict S5725 to statically versioned URLs#7134

Open
pierre-loup-tristant-sonarsource wants to merge 2 commits into
masterfrom
JS-1790-restrict-S5725-to-versioned-urls
Open

JS-1790 Restrict S5725 to statically versioned URLs#7134
pierre-loup-tristant-sonarsource wants to merge 2 commits into
masterfrom
JS-1790-restrict-S5725-to-versioned-urls

Conversation

@pierre-loup-tristant-sonarsource
Copy link
Copy Markdown

Summary

  • Rule S5725 now only raises issues for remote scripts whose URL contains a recognizable version path segment (/3.7.1/, /jquery@3.7.1/, /bootstrap@5.3.0, etc.), eliminating false positives on dynamic CDN URLs (Google Tag Manager, Stripe, etc.)
  • Checks both integrity and crossorigin="anonymous" attributes with distinct messages for each missing case
  • Conservatively suppresses the crossorigin check when the assigned value cannot be statically resolved, avoiding false positives

Test plan

  • Unit tests updated with versioned/non-versioned URL cases, unresolved crossOrigin, and all three message variants
  • Ruling ITs pass (59/59): custom-jsts and ant-design expected files updated

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pierre-loup-tristant-sonarsource pierre-loup-tristant-sonarsource requested a review from a team May 29, 2026 13:29
@sonarqubecloud
Copy link
Copy Markdown

Agentic Analysis: Early Results

Agentic Analysis and Context Augmentation are available on your project. Here are some issues that could have been prevented. Follow the links to learn how to put them into action.

10 issue(s) found across 1 file(s):

Rule File Line Message
javascript:S3504 its/sources/custom/jsts/S5725.js 2 Unexpected var, use let or const instead.
javascript:S5725 its/sources/custom/jsts/S5725.js 2 Make sure not using resource integrity feature is safe here.
javascript:S3504 its/sources/custom/jsts/S5725.js 7 Unexpected var, use let or const instead.
javascript:S5725 its/sources/custom/jsts/S5725.js 7 Make sure not using resource integrity feature is safe here.
javascript:S3504 its/sources/custom/jsts/S5725.js 12 Unexpected var, use let or const instead.
javascript:S5725 its/sources/custom/jsts/S5725.js 12 Make sure not using resource integrity feature is safe here.
javascript:S3504 its/sources/custom/jsts/S5725.js 18 Unexpected var, use let or const instead.
javascript:S3504 its/sources/custom/jsts/S5725.js 24 Unexpected var, use let or const instead.
javascript:S3504 its/sources/custom/jsts/S5725.js 32 Unexpected var, use let or const instead.
javascript:S3504 its/sources/custom/jsts/S5725.js 40 Unexpected var, use let or const instead.

Analyzed by SonarQube Agentic Analysis in 2.5 s

@hashicorp-vault-sonar-prod
Copy link
Copy Markdown

hashicorp-vault-sonar-prod Bot commented May 29, 2026

JS-1790

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

Ruling Report

Code no longer flagged (4 issues)

S1441

custom-jsts/S5725.js:1

>    1 | // No issue - URL has no version path segment
     2 | var script1 = document.createElement("script");
     3 | script1.src = "https://www.googletagmanager.com/gtag/js?id=UA-XXXXX-X";

S3504

custom-jsts/S5725.js:1

>    1 | // No issue - URL has no version path segment
     2 | var script1 = document.createElement("script");
     3 | script1.src = "https://www.googletagmanager.com/gtag/js?id=UA-XXXXX-X";

S3798

custom-jsts/S5725.js:1

>    1 | // No issue - URL has no version path segment
     2 | var script1 = document.createElement("script");
     3 | script1.src = "https://www.googletagmanager.com/gtag/js?id=UA-XXXXX-X";

S5725

custom-jsts/S5725.js:1

>    1 | // No issue - URL has no version path segment
     2 | var script1 = document.createElement("script");
     3 | script1.src = "https://www.googletagmanager.com/gtag/js?id=UA-XXXXX-X";

New issues flagged (39 issues)

S5725

ant-design/site/theme/template/IconDisplay/IconPicSearcher.tsx:62

    60 | 
    61 |   loadModel = () => {
>   62 |     const script = document.createElement('script');
    63 |     script.onload = async () => {
    64 |       await window.antdIconClassifier.load();

custom-jsts/S5725.js:12

    10 | 
    11 | // Sensitive - versioned URL (semver path), missing integrity
>   12 | var script3 = document.createElement("script");
    13 | script3.src = "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js";
    14 | script3.crossOrigin = "anonymous";

custom-jsts/S5725.js:18

    16 | 
    17 | // Sensitive - versioned URL (package@version), missing crossorigin
>   18 | var script4 = document.createElement("script");
    19 | script4.src = "https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js";
    20 | script4.integrity = "sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut";

custom-jsts/S5725.js:40

    38 | 
    39 | // Sensitive - versioned URL with both integrity but crossOrigin is not "anonymous";
>   40 | var script7 = document.createElement("script");
    41 | script7.src = "https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js";
    42 | script7.integrity = "sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut";

S1192

custom-jsts/S5725.js:19

    17 | // Sensitive - versioned URL (package@version), missing crossorigin
    18 | var script4 = document.createElement("script");
>   19 | script4.src = "https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js";
    20 | script4.integrity = "sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut";
    21 | document.head.appendChild(script4);

custom-jsts/S5725.js:20

    18 | var script4 = document.createElement("script");
    19 | script4.src = "https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js";
>   20 | script4.integrity = "sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut";
    21 | document.head.appendChild(script4);
    22 | 

S1441

custom-jsts/S5725.js:7

     5 | 
     6 | // No issue - only API major version (v3 has no minor component)
>    7 | var script2 = document.createElement("script");
     8 | script2.src = "https://js.stripe.com/v3/stripe.js";
     9 | document.head.appendChild(script2);

custom-jsts/S5725.js:8

     6 | // No issue - only API major version (v3 has no minor component)
     7 | var script2 = document.createElement("script");
>    8 | script2.src = "https://js.stripe.com/v3/stripe.js";
     9 | document.head.appendChild(script2);
    10 | 

custom-jsts/S5725.js:12

    10 | 
    11 | // Sensitive - versioned URL (semver path), missing integrity
>   12 | var script3 = document.createElement("script");
    13 | script3.src = "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js";
    14 | script3.crossOrigin = "anonymous";

custom-jsts/S5725.js:13

    11 | // Sensitive - versioned URL (semver path), missing integrity
    12 | var script3 = document.createElement("script");
>   13 | script3.src = "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js";
    14 | script3.crossOrigin = "anonymous";
    15 | document.head.appendChild(script3);

...and 29 more

📋 View full report

Code no longer flagged (4)

S1441

S3504

S3798

S5725

New issues flagged (39)

S5725

S1192

S1441

S3504

S3798

Comment thread packages/analysis/src/jsts/rules/S5725/rule.ts
Comment thread packages/analysis/src/jsts/rules/S5725/rule.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqube-next
Copy link
Copy Markdown

@gitar-bot
Copy link
Copy Markdown

gitar-bot Bot commented May 29, 2026

Code Review 👍 Approved with suggestions 0 resolved / 2 findings

Restricts rule S5725 to statically versioned URLs to reduce false positives. Consider revising the empty string handling for crossOrigin and tightening the ALIAS_PATH_REGEX to avoid unintended pattern matches.

💡 Edge Case: crossOrigin="" (empty string) treated as missing crossOrigin

📄 packages/analysis/src/jsts/rules/S5725/rule.ts:107-113

In the HTML DOM spec, setting element.crossOrigin = "" is equivalent to "anonymous" (the empty string maps to the anonymous keyword). The isCrossOriginAnonymousAssignment function only checks for right.value === 'anonymous', so a valid script.crossOrigin = "" assignment would trigger a false positive missingCrossOrigin error.

This is an edge case that could affect real-world code where developers use the empty string form.

Also accept empty string as equivalent to 'anonymous' per the HTML spec
function isCrossOriginAnonymousAssignment(memberExpression: TSESTree.Node): boolean {
  if (!isCrossOriginAssignment(memberExpression)) {
    return false;
  }
  const right = (memberExpression.parent as estree.AssignmentExpression).right;
  return right.type === 'Literal' && (right.value === 'anonymous' || right.value === '');
}
💡 Edge Case: ALIAS_PATH_REGEX may match non-version @ patterns in URLs

📄 packages/analysis/src/jsts/rules/S5725/rule.ts:32

The regex /\/[^/@]*@[\d.]+(?=[/?#]|$)/ uses [\d.]+ which has two weaknesses:

  1. It matches degenerate inputs like /pkg@. or /pkg@... (only dots, no actual digits).
  2. It could match non-package patterns like /user@123/resource in URLs that use @ for other purposes (e.g., scoped auth tokens in path).

While unlikely in practice for CDN URLs, requiring at least one digit would make the regex more robust: [\d][\d.]* instead of [\d.]+.

Require at least one leading digit after @ to avoid matching dot-only or non-version patterns
const ALIAS_PATH_REGEX = /\/[^/@]*@\d[\d.]*(?=[/?#]|$)/;
🤖 Prompt for agents
Code Review: Restricts rule S5725 to statically versioned URLs to reduce false positives. Consider revising the empty string handling for crossOrigin and tightening the ALIAS_PATH_REGEX to avoid unintended pattern matches.

1. 💡 Edge Case: crossOrigin="" (empty string) treated as missing crossOrigin
   Files: packages/analysis/src/jsts/rules/S5725/rule.ts:107-113

   In the HTML DOM spec, setting `element.crossOrigin = ""` is equivalent to `"anonymous"` (the empty string maps to the anonymous keyword). The `isCrossOriginAnonymousAssignment` function only checks for `right.value === 'anonymous'`, so a valid `script.crossOrigin = ""` assignment would trigger a false positive `missingCrossOrigin` error.
   
   This is an edge case that could affect real-world code where developers use the empty string form.

   Fix (Also accept empty string as equivalent to 'anonymous' per the HTML spec):
   function isCrossOriginAnonymousAssignment(memberExpression: TSESTree.Node): boolean {
     if (!isCrossOriginAssignment(memberExpression)) {
       return false;
     }
     const right = (memberExpression.parent as estree.AssignmentExpression).right;
     return right.type === 'Literal' && (right.value === 'anonymous' || right.value === '');
   }

2. 💡 Edge Case: ALIAS_PATH_REGEX may match non-version @ patterns in URLs
   Files: packages/analysis/src/jsts/rules/S5725/rule.ts:32

   The regex `/\/[^/@]*@[\d.]+(?=[/?#]|$)/` uses `[\d.]+` which has two weaknesses:
   1. It matches degenerate inputs like `/pkg@.` or `/pkg@...` (only dots, no actual digits).
   2. It could match non-package patterns like `/user@123/resource` in URLs that use `@` for other purposes (e.g., scoped auth tokens in path).
   
   While unlikely in practice for CDN URLs, requiring at least one digit would make the regex more robust: `[\d][\d.]*` instead of `[\d.]+`.

   Fix (Require at least one leading digit after @ to avoid matching dot-only or non-version patterns):
   const ALIAS_PATH_REGEX = /\/[^/@]*@\d[\d.]*(?=[/?#]|$)/;

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Copy link
Copy Markdown
Contributor

@vdiez vdiez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall.

Non-blocking follow-ups if we want to tighten a couple of edge cases later:

  • crossOrigin is currently treated order-insensitively, so script.crossOrigin = anonymous; script.crossOrigin = use-credentials; no longer raises even though the final value is not compliant.
  • The new version regexes do not match prerelease semver URLs such as https://cdn.jsdelivr.net/npm/react@19.0.0-rc.1/umd/react.production.min.js, so those pinned URLs are currently skipped.

I would keep UUID-shaped URLs out of the heuristic for now unless we can point to a concrete JS/CDN pattern we want to support. In the current ruling corpus the UUID-looking URLs I checked are mostly opaque asset paths rather than versioned script artifacts, so matching them generically looks likely to reintroduce false positives.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants