Skip to content

Fix: Uses unsafe JSON.load and injects unescaped external data into...#2650

Open
shanecodezzz wants to merge 1 commit intofreeCodeCamp:mainfrom
shanecodezzz:fix/code-quality-1771144082
Open

Fix: Uses unsafe JSON.load and injects unescaped external data into...#2650
shanecodezzz wants to merge 1 commit intofreeCodeCamp:mainfrom
shanecodezzz:fix/code-quality-1771144082

Conversation

@shanecodezzz
Copy link

If you’re adding a new scraper, please ensure that you have:

  • Tested the scraper on a local copy of DevDocs
  • Ensured that the docs are styled similarly to other docs on DevDocs
  • Added these files to the public/icons/your_scraper_name/ directory:
    • 16.png: a 16×16 pixel icon for the doc
    • 16@2x.png: a 32×32 pixel icon for the doc
    • SOURCE: A text file containing the URL to the page the image can be found on or the URL of the original image itself

If you're updating existing documentation to its latest version, please ensure that you have:

  • Updated the versions and releases in the scraper file
  • Ensured the license is up-to-date
  • Ensured the icons and the SOURCE file in public/icons/your_scraper_name/ are up-to-date if the documentation has a custom icon
  • Ensured self.links contains up-to-date urls if self.links is defined
  • Tested the changes locally to ensure:
    • The scraper still works without errors
    • The scraped documentation still looks consistent with the rest of DevDocs
    • The categorization of entries is still good

Replaced JSON.load with JSON.parse, added HTML escaping to all interpolated values, added nil-safe access guards, and wrapped HTTP/parse calls in error handling across every method.
@shanecodezzz shanecodezzz requested a review from a team as a code owner February 15, 2026 08:28
Copy link

@paragon-review paragon-review left a comment

Choose a reason for hiding this comment

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

Review Summary

This review identified 3 issues in 1 file.

Confidence score: 2/5

  • High risk - 1 critical issue require immediate attention

Severity breakdown: Critical: 1, Medium: 2

1 file reviewed, 3 comments


Tip: @paragon-run <instructions> to chat with our agent or push fixes!

Dashboard

if key == '__compat'
tmp = slug.split('/')
last_table_entry.add_child("<th><code>#{tmp.last}")
last_table_entry.add_child("<th><code>#{CGI.escapeHTML(tmp.last.to_s)}")

Choose a reason for hiding this comment

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

Bug: CGI

CGI.escapeHTML used 6 times but require 'cgi' is missing. Every compat table call raises NameError. Add require 'cgi' at the top of the file.

View Details

Location: lib/docs/filters/mdn/compat_tables.rb (lines 122)

Analysis

CGI.escapeHTML used 6 times but require 'cgi' is missing

What fails Every call to generate_compatibility_table_wrapper raises NameError: uninitialized constant CGI because require 'cgi' is never loaded in this file or its ancestors.
Result NameError: uninitialized constant CGI — all MDN compat tables silently break
Expected CGI module is loaded and escapeHTML works correctly for all 6 call sites
Impact Complete breakage of MDN compatibility table generation. No compat tables render at all.
How to reproduce
Run the doc scraper against any MDN page with a compat table, or: ruby -e "require './lib/docs/filters/mdn/compat_tables'; # triggers NameError on CGI.escapeHTML"
Patch Details
-        @json_data = parsed['data']
+        require 'cgi' unless defined?(CGI)
+        @json_data = parsed['data']
AI Fix Prompt
Fix this issue: CGI.escapeHTML used 6 times but `require 'cgi'` is missing. Every compat table call raises NameError. Add `require 'cgi'` at the top of the file.

Location: lib/docs/filters/mdn/compat_tables.rb (lines 122)
Problem: Every call to generate_compatibility_table_wrapper raises NameError: uninitialized constant CGI because `require 'cgi'` is never loaded in this file or its ancestors.
Current behavior: NameError: uninitialized constant CGI — all MDN compat tables silently break
Expected: CGI module is loaded and escapeHTML works correctly for all 6 call sites
Steps to reproduce: Run the doc scraper against any MDN page with a compat table, or: ruby -e "require './lib/docs/filters/mdn/compat_tables'; # triggers NameError on CGI.escapeHTML"

Provide a code fix.

Tip: Reply with @paragon-run to automatically fix this issue

end
version_removed.push(element['version_removed'] || false)
version_removed.push(element['version_removed'] ? CGI.escapeHTML(element['version_removed'].to_s) : false)
notes.push(element['notes'] || false)

Choose a reason for hiding this comment

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

Security: The notes field is interpolated into HTML unescaped

The notes field is interpolated into HTML unescaped. This is inconsistent with escaping applied elsewhere. Add a comment or sanitize notes.

View Details

Location: lib/docs/filters/mdn/compat_tables.rb (lines 170)

Analysis

The notes field is interpolated into HTML unescaped

What fails The notes field from BCD JSON is pushed raw (line 170) and interpolated directly into HTML (line 181) without CGI.escapeHTML, unlike all other fields in this PR.
Result Raw HTML from the BCD API notes field is injected directly into output without sanitization
Expected Either sanitize notes consistently with other fields, or document the intentional exception with a code comment explaining notes contains trusted HTML
Impact XSS injection point if upstream BCD API is compromised or returns unexpected content. Inconsistent security posture within the same PR.
How to reproduce
1. Examine line 170: notes.push(element['notes'] || false)
2. Examine line 181: "<details><summary>#{version_string}</summary>#{notes[value]}</details>"
3. Note that version, release_date, version_removed are all escaped but notes is not.
AI Fix Prompt
Fix this issue: The `notes` field is interpolated into HTML unescaped. This is inconsistent with escaping applied elsewhere. Add a comment or sanitize notes.

Location: lib/docs/filters/mdn/compat_tables.rb (lines 170)
Problem: The `notes` field from BCD JSON is pushed raw (line 170) and interpolated directly into HTML (line 181) without CGI.escapeHTML, unlike all other fields in this PR.
Current behavior: Raw HTML from the BCD API notes field is injected directly into output without sanitization
Expected: Either sanitize notes consistently with other fields, or document the intentional exception with a code comment explaining notes contains trusted HTML
Steps to reproduce: 1. Examine line 170: notes.push(element['notes'] || false)
2. Examine line 181: "<details><summary>#{version_string}</summary>#{notes[value]}</details>"
3. Note that version, release_date, version_removed are all escaped but notes is not.

Provide a code fix.

Tip: Reply with @paragon-run to automatically fix this issue

response = Request.run url
return "" unless response.success?
@json_data = JSON.load(response.body)['data']
parsed = JSON.parse(response.body)

Choose a reason for hiding this comment

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

Bug: JSON

JSON.parse raises ParserError on malformed responses. This crashes the entire scraper. Wrap parse in rescue JSON::ParserError.

View Details

Location: lib/docs/filters/mdn/compat_tables.rb (lines 74)

Analysis

JSON.parse raises ParserError on malformed responses. This crashes the entire scraper

What fails JSON.parse(response.body) on line 74 raises JSON::ParserError if the BCD API returns a 200 with non-JSON body (maintenance page, HTML error page, etc.).
Result Unhandled JSON::ParserError crashes the scraper for the entire doc set
Expected Malformed JSON is caught gracefully with rescue JSON::ParserError and returns empty string, consistent with the existing error-handling pattern on line 73
Impact A transient upstream API issue crashes the entire doc generation pipeline instead of gracefully degrading one table.
How to reproduce
1. Simulate a BCD API returning HTML on a 200 response
2. The scraper hits JSON.parse which throws JSON::ParserError
3. Entire doc generation crashes instead of skipping one table
Patch Details
-        parsed = JSON.parse(response.body)
+        parsed = JSON.parse(response.body) rescue nil
+        return "" unless parsed.is_a?(Hash) && parsed['data'].is_a?(Hash)
AI Fix Prompt
Fix this issue: JSON.parse raises ParserError on malformed responses. This crashes the entire scraper. Wrap parse in rescue JSON::ParserError.

Location: lib/docs/filters/mdn/compat_tables.rb (lines 74)
Problem: JSON.parse(response.body) on line 74 raises JSON::ParserError if the BCD API returns a 200 with non-JSON body (maintenance page, HTML error page, etc.).
Current behavior: Unhandled JSON::ParserError crashes the scraper for the entire doc set
Expected: Malformed JSON is caught gracefully with `rescue JSON::ParserError` and returns empty string, consistent with the existing error-handling pattern on line 73
Steps to reproduce: 1. Simulate a BCD API returning HTML on a 200 response
2. The scraper hits JSON.parse which throws JSON::ParserError
3. Entire doc generation crashes instead of skipping one table

Provide a code fix.

Tip: Reply with @paragon-run to automatically fix this issue

@shanecodezzz
Copy link
Author

Hey @freeCodeCamp!

We ran Paragon on this PR and it caught some real issues — check the inline comments above.

Found 1 issues including bugs and security fixes.

We know maintaining OSS is tough, especially without automated code review catching things before they hit prod. We set up open source projects with Paragon so you don't have to worry about that stuff. Shoot me an email at shane@polarity.so if you're interested, happy to get you set up!

polarity.so/paragon

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