Ruby JSON Entity Escape Vulnerability

Medium Risk Cross-Site Scripting (XSS)
RubyJSONEntity EscapeXSSInput ValidationOutput Encoding

What it is

Application fails to properly escape JSON entities when rendering user input in Ruby applications, potentially leading to JSON injection or cross-site scripting attacks.

require 'json' require 'sinatra' get '/user_profile' do username = params[:username] bio = params[:bio] # Vulnerable: Direct interpolation without escaping json_response = "{\"username\": \"#{username}\", \"bio\": \"#{bio}\"}" content_type :json json_response end post '/update_profile' do user_data = JSON.parse(request.body.read) # Vulnerable: Unescaped output response_data = { message: "Profile updated for #{user_data['name']}", timestamp: Time.now.to_s } # Dangerous: Manual JSON construction "{\"status\": \"success\", \"data\": \"#{response_data}\"}" end
require 'json' require 'sinatra' require 'cgi' def validate_string_input(input, max_length = 100) return '' if input.nil? # Basic validation clean_input = input.to_s.strip # Length check raise ArgumentError, 'Input too long' if clean_input.length > max_length # Check for suspicious characters if clean_input.match?(/[<>"'&]/) raise ArgumentError, 'Invalid characters in input' end clean_input end def safe_json_response(data) # Secure: Use JSON.generate for proper escaping content_type :json JSON.generate(data) end get '/user_profile' do begin username = validate_string_input(params[:username], 50) bio = validate_string_input(params[:bio], 500) # Secure: Proper JSON generation with escaping response_data = { username: username, bio: bio, timestamp: Time.now.iso8601 } safe_json_response(response_data) rescue ArgumentError => e status 400 safe_json_response({ error: 'Invalid input', details: e.message }) rescue => e status 500 safe_json_response({ error: 'Server error' }) end end post '/update_profile' do begin # Validate content type halt 400, safe_json_response({ error: 'Content-Type must be application/json' }) unless request.content_type == 'application/json' # Parse JSON safely user_data = JSON.parse(request.body.read) # Validate required fields halt 400, safe_json_response({ error: 'Name is required' }) unless user_data['name'] # Validate and sanitize input safe_name = validate_string_input(user_data['name'], 100) safe_email = validate_string_input(user_data['email'], 200) if user_data['email'] # Secure response construction response_data = { status: 'success', message: "Profile updated for user", user_id: safe_name, timestamp: Time.now.iso8601 } safe_json_response(response_data) rescue JSON::ParserError status 400 safe_json_response({ error: 'Invalid JSON format' }) rescue ArgumentError => e status 400 safe_json_response({ error: 'Validation failed', details: e.message }) rescue => e status 500 safe_json_response({ error: 'Server error' }) end end

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Ruby code renders JSON in HTML: <%= @data.to_json %>. to_json doesn't HTML-escape output. JSON containing </script> tags can break out of script context. XSS through JSON injection in HTML. JSON values with HTML special characters not escaped when embedded in pages.

Root causes

Using to_json Without HTML Escaping in Views

Ruby code renders JSON in HTML: <%= @data.to_json %>. to_json doesn't HTML-escape output. JSON containing </script> tags can break out of script context. XSS through JSON injection in HTML. JSON values with HTML special characters not escaped when embedded in pages.

Embedding JSON in HTML Script Tags Without Escaping

Inline JavaScript with JSON: <script>var data = <%= user_data.to_json %>;</script>. If user_data contains </script>, breaks out of script context. User-controlled JSON values can inject JavaScript. Need HTML entity escaping for safe embedding. JSON in HTML requires double escaping.

Not Using json_escape or j Helper in Rails Views

Missing escape helper: <%= raw @data.to_json %>. Rails provides json_escape (alias j): <%= j @data.to_json %>. Escapes HTML special characters. raw bypasses escaping. Without json_escape, HTML metacharacters in JSON enable XSS.

User-Controlled Data in JSON Responses Rendered in HTML

User input in JSON embedded in pages: data = {message: params[:message]}; <%= data.to_json %>. Message containing <script> or </script> tags. JSON serialization doesn't prevent HTML injection. Context-specific escaping required for safe rendering.

Mixing Content-Type application/json with HTML Rendering

JSON API endpoints rendered in HTML views: respond_to do |format| format.html { render inline: @data.to_json }. Confusing JSON responses with HTML embedding. Different escaping requirements. API responses shouldn't render in HTML. Proper content type separation needed.

Fixes

1

Always Use json_escape (j) Helper in Rails Views

Use Rails escape helper: <script>var data = <%= j @data.to_json %>;</script>. json_escape (alias j) HTML-escapes JSON. Escapes <, >, &. Safe for embedding in HTML. Use j() for all JSON in views. Prevents script injection through JSON.

2

Use content_for with JSON Script Tags

Structured approach: <% content_for :javascript do %><script>var data = <%= j @data.to_json %>;</script><% end %>. Centralized JSON injection. Helper methods for JSON embedding. Consistent escaping across application. Template structure enforcing security.

3

Return JSON Responses with Proper Content-Type

Use render json: render json: @data. Rails sets Content-Type: application/json. Browser handles as JSON, not HTML. No HTML rendering of JSON. Proper API responses. Separation of JSON API from HTML views.

4

Use JavaScript-Safe JSON Serialization Libraries

Oj gem with safe mode: Oj.dump(data, mode: :rails). Handles escaping for HTML embedding. ActiveSupport::JSON with escape_html_entities_in_json = true. Configuration-level escaping. Library-level protection against JSON XSS.

5

Implement Content Security Policy Headers

CSP for defense-in-depth: response.headers['Content-Security-Policy'] = "script-src 'self' 'nonce-#{nonce}'". Inline scripts require nonce. External scripts from trusted domains. CSP prevents injected scripts even if escaping bypassed.

6

Validate and Sanitize Data Before JSON Serialization

Input validation: params.require(:message).permit(:content); sanitize(params[:message]). Allowlist attributes. Schema validation. Sanitize HTML before JSON. Defense-in-depth with validation, sanitization, and escaping. Multiple layers prevent JSON injection.

Detect This Vulnerability in Your Code

Sourcery automatically identifies ruby json entity escape vulnerability and many other security issues in your codebase.