VYPR
High severityNVD Advisory· Published Oct 24, 2024· Updated Oct 25, 2024

OpenRefine's PreviewExpressionCommand, which is eval, lacks protection against cross-site request forgery (CSRF)

CVE-2024-47879

Description

OpenRefine is a free, open source tool for working with messy data. Prior to version 3.8.3, lack of cross-site request forgery protection on the preview-expression command means that visiting a malicious website could cause an attacker-controlled expression to be executed. The expression can contain arbitrary Clojure or Python code. The attacker must know a valid project ID of a project that contains at least one row, and the attacker must convince the victim to open a malicious webpage. Version 3.8.3 fixes the issue.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

OpenRefine < 3.8.3 lacks CSRF protection on the preview-expression endpoint, enabling remote code execution via crafted Clojure/Python expressions.

Vulnerability

Overview

CVE-2024-47879 describes a missing cross-site request forgery (CSRF) protection in OpenRefine versions prior to 3.8.3. The preview-expression command, which evaluates user-provided expressions, did not validate a CSRF token. This allows an attacker to craft a malicious webpage that, when visited by an authenticated OpenRefine user, can submit a forged request containing an arbitrary expression. The expression can be written in Clojure or Python and is executed without restrictions on the server [1][4].

Exploitation

Prerequisites

To exploit this vulnerability, an attacker must know a valid project ID of a project that contains at least one row. They then need to convince the victim to open a malicious web page. No other authentication or user interaction beyond visiting the page is required — the victim's browser automatically sends the attacker's crafted POST request to the OpenRefine instance if the victim is logged in. The root cause is an incorrect assumption that the /command/core/preview-expression/ endpoint does not modify state and therefore does not need CSRF protection, as noted in the code comments [1][4].

Impact

Successful exploitation yields remote code execution (RCE) as the OpenRefine user. Since the expression can call arbitrary system commands (e.g., via clojure:(.exec (Runtime/getRuntime) ...) or Python's os.system), an attacker can execute arbitrary code on the host machine. This goes beyond simple data manipulation and can lead to full compromise of the OpenRefine server and its data [1][4].

Mitigation

The vulnerability is fixed in OpenRefine version 3.8.3 by adding CSRF token validation to the preview-expression command and other expression-evaluating endpoints [1]. Users are strongly advised to upgrade immediately. No workarounds are documented; however, restricting network access to the OpenRefine web interface can limit exposure until patching is possible.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.openrefine:mainMaven
< 3.8.33.8.3

Affected products

3

Patches

1
090924ca9234

Add CSRF protection to commands that evaluate expressions

https://github.com/OpenRefine/OpenRefineAntonin DelpeuchOct 8, 2024via ghsa
21 files changed · +357 256
  • extensions/wikibase/module/scripts/menu-bar-extension.js+3 1 modified
    @@ -35,8 +35,10 @@ WikibaseExporterMenuBar.exportTo = function (format) {
       var form = document.createElement("form");
       $(form).css("display", "none")
           .attr("method", "post")
    -      .attr("action", "command/core/export-rows/" + targetUrl)
           .attr("target", "openrefine-export-" + format);
    +  Refine.wrapCSRF(function(csrfToken) {
    +    $(form).attr("action", "command/core/export-rows/" + targetUrl + "?" + $.attr({csrf_token: csrfToken}))
    +  });
       $('<input />')
           .attr("name", "engine")
           .val(JSON.stringify(ui.browsingEngine.getJSON()))
    
  • main/src/com/google/refine/commands/browsing/ComputeClustersCommand.java+5 4 modified
    @@ -58,13 +58,14 @@ public class ComputeClustersCommand extends Command {
     
         final static Logger logger = LoggerFactory.getLogger("compute-clusters_command");
     
    -    /**
    -     * This command uses POST (probably to allow for larger parameters) but does not actually modify any state so we do
    -     * not add CSRF protection to it.
    -     */
         @Override
         public void doPost(HttpServletRequest request, HttpServletResponse response)
                 throws ServletException, IOException {
    +        // This command triggers evaluation expression and therefore requires CSRF-protection
    +        if (!hasValidCSRFToken(request)) {
    +            respondCSRFError(response);
    +            return;
    +        }
     
             try {
                 long start = System.currentTimeMillis();
    
  • main/src/com/google/refine/commands/browsing/ComputeFacetsCommand.java+5 4 modified
    @@ -45,13 +45,14 @@ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     
     public class ComputeFacetsCommand extends Command {
     
    -    /**
    -     * This command uses POST (probably to allow for larger parameters) but does not actually modify any state so we do
    -     * not add CSRF protection to it.
    -     */
         @Override
         public void doPost(HttpServletRequest request, HttpServletResponse response)
                 throws ServletException, IOException {
    +        // This command triggers evaluation expression and therefore requires CSRF-protection
    +        if (!hasValidCSRFToken(request)) {
    +            respondCSRFError(response);
    +            return;
    +        }
     
             try {
                 Project project = getProject(request);
    
  • main/src/com/google/refine/commands/browsing/GetScatterplotCommand.java+5 0 modified
    @@ -69,6 +69,11 @@ public class GetScatterplotCommand extends Command {
         @Override
         public void doGet(HttpServletRequest request, HttpServletResponse response)
                 throws ServletException, IOException {
    +        // This command triggers evaluation expression and therefore requires CSRF-protection.
    +        if (!hasValidCSRFTokenAsGET(request)) {
    +            respondCSRFError(response);
    +            return;
    +        }
     
             try {
                 long start = System.currentTimeMillis();
    
  • main/src/com/google/refine/commands/expr/PreviewExpressionCommand.java+6 5 modified
    @@ -121,15 +121,16 @@ public PreviewResult(List<ExpressionValue> evaluated) {
             }
         }
     
    -    /**
    -     * The command uses POST but does not actually modify any state so it does not require CSRF.
    -     */
    -
         @Override
         public void doPost(HttpServletRequest request, HttpServletResponse response)
                 throws ServletException, IOException {
    -
             try {
    +            // This command triggers evaluation expression and therefore requires CSRF-protection
    +            if (!hasValidCSRFToken(request)) {
    +                respondCSRFError(response);
    +                return;
    +            }
    +
                 Project project = getProject(request);
     
                 int cellIndex = Integer.parseInt(request.getParameter("cellIndex"));
    
  • main/src/com/google/refine/commands/project/ExportRowsCommand.java+5 0 modified
    @@ -86,6 +86,11 @@ static public Properties getRequestParameters(HttpServletRequest request) {
         @Override
         public void doPost(HttpServletRequest request, HttpServletResponse response)
                 throws ServletException, IOException {
    +        // This command triggers evaluation expression and therefore requires CSRF-protection
    +        if (!hasValidCSRFToken(request)) {
    +            respondCSRFError(response);
    +            return;
    +        }
     
             ProjectManager.singleton.setBusy(true);
     
    
  • main/tests/server/src/com/google/refine/commands/browsing/ComputeClustersCommandTests.java+25 0 added
    @@ -0,0 +1,25 @@
    +
    +package com.google.refine.commands.browsing;
    +
    +import java.io.IOException;
    +
    +import javax.servlet.ServletException;
    +
    +import org.testng.annotations.BeforeMethod;
    +import org.testng.annotations.Test;
    +
    +import com.google.refine.commands.CommandTestBase;
    +
    +public class ComputeClustersCommandTests extends CommandTestBase {
    +
    +    @BeforeMethod
    +    public void setUpCommand() {
    +        command = new ComputeClustersCommand();
    +    }
    +
    +    @Test
    +    public void testCSRFProtection() throws ServletException, IOException {
    +        command.doPost(request, response);
    +        assertCSRFCheckFailed();
    +    }
    +}
    
  • main/tests/server/src/com/google/refine/commands/browsing/ComputeFacetsCommandTests.java+25 0 added
    @@ -0,0 +1,25 @@
    +
    +package com.google.refine.commands.browsing;
    +
    +import java.io.IOException;
    +
    +import javax.servlet.ServletException;
    +
    +import org.testng.annotations.BeforeMethod;
    +import org.testng.annotations.Test;
    +
    +import com.google.refine.commands.CommandTestBase;
    +
    +public class ComputeFacetsCommandTests extends CommandTestBase {
    +
    +    @BeforeMethod
    +    public void setUpCommand() {
    +        command = new ComputeClustersCommand();
    +    }
    +
    +    @Test
    +    public void testCSRFProtection() throws ServletException, IOException {
    +        command.doPost(request, response);
    +        assertCSRFCheckFailed();
    +    }
    +}
    
  • main/tests/server/src/com/google/refine/commands/browsing/ScatterplotDrawCommandTests.java+10 19 modified
    @@ -1,15 +1,9 @@
     
     package com.google.refine.commands.browsing;
     
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.when;
    -
     import java.io.IOException;
    -import java.io.PrintWriter;
    -import java.io.StringWriter;
     
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    +import javax.servlet.ServletException;
     
     import com.fasterxml.jackson.core.JsonParseException;
     import com.fasterxml.jackson.databind.JsonMappingException;
    @@ -18,23 +12,14 @@
     import org.testng.annotations.Test;
     
     import com.google.refine.browsing.facets.ScatterplotFacet;
    -import com.google.refine.commands.Command;
    +import com.google.refine.commands.CommandTestBase;
     import com.google.refine.util.ParsingUtilities;
     
    -public class ScatterplotDrawCommandTests {
    -
    -    protected HttpServletRequest request = null;
    -    protected HttpServletResponse response = null;
    -    protected StringWriter writer = null;
    -    protected Command command = null;
    +public class ScatterplotDrawCommandTests extends CommandTestBase {
     
         @BeforeMethod
    -    public void setUp() throws IOException {
    -        request = mock(HttpServletRequest.class);
    -        response = mock(HttpServletResponse.class);
    +    public void setUpCommand() {
             command = new GetScatterplotCommand();
    -        writer = new StringWriter();
    -        when(response.getWriter()).thenReturn(new PrintWriter(writer));
         }
     
         public static String configJson = "{"
    @@ -109,6 +94,12 @@ public void setUp() throws IOException {
                 + "\"to_y\":0,"
                 + "\"color\":\"ff6a00\"}";
     
    +    @Test
    +    public void testCSRFProtection() throws ServletException, IOException {
    +        command.doGet(request, response);
    +        assertCSRFCheckFailed();
    +    }
    +
         @Test
         public void testParseConfig() throws JsonParseException, JsonMappingException, IOException {
             GetScatterplotCommand.PlotterConfig config = ParsingUtilities.mapper.readValue(configJson,
    
  • main/tests/server/src/com/google/refine/commands/expr/PreviewExpressionCommandTests.java+11 22 modified
    @@ -27,47 +27,30 @@
     
     package com.google.refine.commands.expr;
     
    -import static org.mockito.Mockito.mock;
     import static org.mockito.Mockito.when;
     
     import java.io.IOException;
    -import java.io.PrintWriter;
     import java.io.Serializable;
    -import java.io.StringWriter;
     
     import javax.servlet.ServletException;
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
     
     import org.testng.annotations.AfterMethod;
     import org.testng.annotations.BeforeMethod;
     import org.testng.annotations.Test;
     
    -import com.google.refine.RefineTest;
     import com.google.refine.commands.Command;
    +import com.google.refine.commands.CommandTestBase;
     import com.google.refine.expr.MetaParser;
     import com.google.refine.grel.Parser;
     import com.google.refine.model.Project;
     import com.google.refine.util.TestUtils;
     
    -public class PreviewExpressionCommandTests extends RefineTest {
    +public class PreviewExpressionCommandTests extends CommandTestBase {
     
         protected Project project = null;
    -    protected HttpServletRequest request = null;
    -    protected HttpServletResponse response = null;
    -    protected Command command = null;
    -    protected StringWriter writer = null;
     
         @BeforeMethod
    -    public void setUpRequestResponse() {
    -        request = mock(HttpServletRequest.class);
    -        response = mock(HttpServletResponse.class);
    -        writer = new StringWriter();
    -        try {
    -            when(response.getWriter()).thenReturn(new PrintWriter(writer));
    -        } catch (IOException e) {
    -            e.printStackTrace();
    -        }
    +    public void setUpCommandAndProject() {
             command = new PreviewExpressionCommand();
             project = createProject(
                     new String[] { "a", "b" },
    @@ -89,8 +72,14 @@ public void unregisterGRELParser() {
         }
     
         @Test
    -    public void testJsonResponse() throws ServletException, IOException {
    +    public void testCSRFProtection() throws ServletException, IOException {
    +        command.doPost(request, response);
    +        assertCSRFCheckFailed();
    +    }
     
    +    @Test
    +    public void testJsonResponse() throws ServletException, IOException {
    +        when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken());
             when(request.getParameter("project")).thenReturn(Long.toString(project.id));
             when(request.getParameter("cellIndex")).thenReturn("1");
             when(request.getParameter("expression")).thenReturn("grel:value + \"_u\"");
    @@ -106,7 +95,7 @@ public void testJsonResponse() throws ServletException, IOException {
     
         @Test
         public void testParseError() throws ServletException, IOException {
    -
    +        when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken());
             when(request.getParameter("project")).thenReturn(Long.toString(project.id));
             when(request.getParameter("cellIndex")).thenReturn("1");
             when(request.getParameter("expression")).thenReturn("grel:value +");
    
  • main/tests/server/src/com/google/refine/commands/project/ExportRowsCommandTests.java+25 0 added
    @@ -0,0 +1,25 @@
    +
    +package com.google.refine.commands.project;
    +
    +import java.io.IOException;
    +
    +import javax.servlet.ServletException;
    +
    +import org.testng.annotations.BeforeMethod;
    +import org.testng.annotations.Test;
    +
    +import com.google.refine.commands.CommandTestBase;
    +
    +public class ExportRowsCommandTests extends CommandTestBase {
    +
    +    @BeforeMethod
    +    public void setUpCommand() {
    +        command = new ExportRowsCommand();
    +    }
    +
    +    @Test
    +    public void testCSRFProtection() throws ServletException, IOException {
    +        command.doPost(request, response);
    +        assertCSRFCheckFailed();
    +    }
    +}
    
  • main/webapp/modules/core/scripts/dialogs/clustering-dialog.js+1 1 modified
    @@ -444,7 +444,7 @@ ClusteringDialog.prototype._cluster = function() {
     
         this._elmts.resultSummary.empty();
     
    -    $.post(
    +    Refine.postCSRF(
             "command/core/compute-clusters?" + $.param({ project: theProject.id }),
             {
                 engine: JSON.stringify(ui.browsingEngine.getJSON()),
    
  • main/webapp/modules/core/scripts/dialogs/custom-tabular-exporter-dialog.js+19 17 modified
    @@ -309,32 +309,34 @@ CustomTabularExporterDialog.prototype._postExport = function(preview) {
       }
       
       var ext = CustomTabularExporterDialog.formats[format].extension;
    -  var form = ExporterManager.prepareExportRowsForm(format, !exportAllRowsCheckbox, ext);
    +  Refine.wrapCSRF(function(csrfToken) {
    +    var form = ExporterManager.prepareExportRowsForm(format, !exportAllRowsCheckbox, ext, csrfToken);
     
    -  if (preview) {
    -    $(form).attr("target", "refine-export");
    -  }
    -  $('<input />')
    -  .attr("name", "options")
    -  .val(JSON.stringify(options))
    -  .appendTo(form);
    -  if (encoding) {
    +    if (preview) {
    +      $(form).attr("target", "refine-export");
    +    }
         $('<input />')
    -    .attr("name", "encoding")
    -    .val(encoding)
    -    .appendTo(form);
    -  }
    -  $('<input />')
    -  .attr("name", "preview")
    -  .val(preview)
    -  .appendTo(form);
    +      .attr("name", "options")
    +      .val(JSON.stringify(options))
    +      .appendTo(form);
    +    if (encoding) {
    +      $('<input />')
    +        .attr("name", "encoding")
    +        .val(encoding)
    +        .appendTo(form);
    +    }
    +    $('<input />')
    +      .attr("name", "preview")
    +      .val(preview)
    +      .appendTo(form);
     
         document.body.appendChild(form);
         if (preview) {
           window.open(" ", "refine-export");
         }
         form.submit();
         document.body.removeChild(form);
    +  });
     }
     
     CustomTabularExporterDialog.prototype._selectColumn = function(columnName) {
    
  • main/webapp/modules/core/scripts/dialogs/expression-preview-dialog.js+1 1 modified
    @@ -444,7 +444,7 @@ ExpressionPreviewDialog.Widget.prototype.update = function() {
         if(activeTabName === "Distance"){
             self._renderDistancePreview(this._values[0]);
         } else {
    -        $.post(
    +        Refine.postCSRF(
                 "command/core/preview-expression?" + $.param(params), 
                 {
                     expression: this._getLanguage() + ":" + expression,
    
  • main/webapp/modules/core/scripts/dialogs/scatterplot-dialog.js+118 115 modified
    @@ -99,123 +99,126 @@ ScatterplotDialog.prototype._renderMatrix = function() {
             var params = {
                 project: theProject.id
             };
    -        $.getJSON("command/core/get-columns-info?" + $.param(params),function(data) {
    -            if (data === null || typeof data.length == 'undefined') {
    -                container.html($.i18n('core-dialogs/error-getColumnInfo'));
    -                return;
    -            }
    -                
    -            var columns = [];
    -            for (var i = 0; i < data.length; i++) {
    -                if (data[i].is_numeric) {
    -                    columns.push(data[i]);
    -                }
    -            }
    -                
    -            if (typeof self._plot_size == 'undefined') {
    -                self._plot_size = Math.max(Math.floor(500 / columns.length / 5) * 5,20);
    -                self._dot_size = 0.8;
    -            }
    -            
    -            var table = '<table class="scatterplot-matrix-table"><tbody>';
    -            
    -            var createScatterplot = function(cx, cy) {
    -                var title = cx + ' (x) vs. ' + cy + ' (y)';
    -                var link = '<a href="javascript:{}" title="' + title + '" cx="' + cx + '" cy="' + cy + '">';
    -                var plotter_params = { 
    -                    'cx' : cx, 
    -                    'cy' : cy,
    -                    'l' : self._plot_size,
    -                    'dot': self._dot_size,
    -                    'dim_x': self._plot_method,
    -                    'dim_y': self._plot_method,
    -                    'r': self._rotation
    -                };
    -                var params = {
    -                    project: theProject.id,
    -                    engine: JSON.stringify(ui.browsingEngine.getJSON()), 
    -                    plotter: JSON.stringify(plotter_params) 
    -                };
    -                var url = "command/core/get-scatterplot?" + $.param(params);
    +        Refine.wrapCSRF(function(csrfToken) {
    +          $.getJSON("command/core/get-columns-info?" + $.param(params),function(data) {
    +              if (data === null || typeof data.length == 'undefined') {
    +                  container.html($.i18n('core-dialogs/error-getColumnInfo'));
    +                  return;
    +              }
    +                  
    +              var columns = [];
    +              for (var i = 0; i < data.length; i++) {
    +                  if (data[i].is_numeric) {
    +                      columns.push(data[i]);
    +                  }
    +              }
    +                  
    +              if (typeof self._plot_size == 'undefined') {
    +                  self._plot_size = Math.max(Math.floor(500 / columns.length / 5) * 5,20);
    +                  self._dot_size = 0.8;
    +              }
    +              
    +              var table = '<table class="scatterplot-matrix-table"><tbody>';
    +              
    +              var createScatterplot = function(cx, cy) {
    +                  var title = cx + ' (x) vs. ' + cy + ' (y)';
    +                  var link = '<a href="javascript:{}" title="' + title + '" cx="' + cx + '" cy="' + cy + '">';
    +                  var plotter_params = { 
    +                      'cx' : cx, 
    +                      'cy' : cy,
    +                      'l' : self._plot_size,
    +                      'dot': self._dot_size,
    +                      'dim_x': self._plot_method,
    +                      'dim_y': self._plot_method,
    +                      'r': self._rotation
    +                  };
    +                  var params = {
    +                      project: theProject.id,
    +                      engine: JSON.stringify(ui.browsingEngine.getJSON()), 
    +                      plotter: JSON.stringify(plotter_params),
    +                      csrf_token: csrfToken
    +                  };
    +                  var url = "command/core/get-scatterplot?" + $.param(params);
     
    -                var attrs = [
    -                    'width="' + self._plot_size + '"',
    -                    'height="' + self._plot_size + '"',
    -                    'src2="' + url + '"'
    -                ];
    -                
    -                return link + '<img ' + attrs.join(' ') + ' /></a>';
    -            };
    -    
    -            for (var i = 0; i < columns.length; i++) {
    -                table += '<tr>';
    -                var div_class = "column_header";
    -                if (columns[i].name == self._column) div_class += " current_column";
    -                table += '<td class="' + div_class + '" colspan="' + (i + 1) + '">' + columns[i].name + '</td>';
    -                for (var j = i + 1; j < columns.length; j++) {
    -                    var cx = columns[i].name;
    -                    var cy = columns[j].name;
    -                    
    -                    var div_class = "scatterplot";
    -                    var current = cx == self._column || cy == self._column;
    -                    if (current) div_class += " current_column";
    -                    table += '<td><div class="' + div_class + '">' + createScatterplot(cx,cy) + '</div></td>';
    -                }
    -                table += '</tr>';
    -            }
    -    
    -            table += "</tbody></table>";
    -            
    -            var width = container.width();
    -            container.empty().css("width", width + "px").html(table);
    -            
    -            container.find("a").on('click',function() {
    -                var options = {
    -                    "name" : $(this).attr("title"),
    -                    "cx" : $(this).attr("cx"), 
    -                    "cy" : $(this).attr("cy"), 
    -                    "l" : 150,
    -                    "ex" : "value",
    -                    "ey" : "value",
    -                    "dot" : self._dot_size,
    -                    "dim_x" : self._plot_method,
    -                    "dim_y" : self._plot_method,
    -                    'r': self._rotation
    -                };
    -                ui.browsingEngine.addFacet("scatterplot", options);
    -                self._dismiss();
    -            });
    +                  var attrs = [
    +                      'width="' + self._plot_size + '"',
    +                      'height="' + self._plot_size + '"',
    +                      'src2="' + url + '"'
    +                  ];
    +                  
    +                  return link + '<img ' + attrs.join(' ') + ' /></a>';
    +              };
    +      
    +              for (var i = 0; i < columns.length; i++) {
    +                  table += '<tr>';
    +                  var div_class = "column_header";
    +                  if (columns[i].name == self._column) div_class += " current_column";
    +                  table += '<td class="' + div_class + '" colspan="' + (i + 1) + '">' + columns[i].name + '</td>';
    +                  for (var j = i + 1; j < columns.length; j++) {
    +                      var cx = columns[i].name;
    +                      var cy = columns[j].name;
    +                      
    +                      var div_class = "scatterplot";
    +                      var current = cx == self._column || cy == self._column;
    +                      if (current) div_class += " current_column";
    +                      table += '<td><div class="' + div_class + '">' + createScatterplot(cx,cy) + '</div></td>';
    +                  }
    +                  table += '</tr>';
    +              }
    +      
    +              table += "</tbody></table>";
    +              
    +              var width = container.width();
    +              container.empty().css("width", width + "px").html(table);
    +              
    +              container.find("a").on('click',function() {
    +                  var options = {
    +                      "name" : $(this).attr("title"),
    +                      "cx" : $(this).attr("cx"), 
    +                      "cy" : $(this).attr("cy"), 
    +                      "l" : 150,
    +                      "ex" : "value",
    +                      "ey" : "value",
    +                      "dot" : self._dot_size,
    +                      "dim_x" : self._plot_method,
    +                      "dim_y" : self._plot_method,
    +                      'r': self._rotation
    +                  };
    +                  ui.browsingEngine.addFacet("scatterplot", options);
    +                  self._dismiss();
    +              });
     
    -            var load_images = function(data) {
    -                if (self._active) {
    -                    data.batch = 0;
    -                    var end = Math.min(data.index + data.batch_size,data.images.length);
    -                    for (; data.index < end; data.index++) {
    -                        load_image(data);
    -                    }
    -                }
    -            };
    -            
    -            var load_image = function(data) {
    -                var img = $(data.images[data.index]);
    -                var src2 = img.attr("src2");
    -                if (src2) {
    -                    img.attr("src", src2);
    -                    img.removeAttr("src2");
    -                    img.on("load", function() {
    -                        data.batch++;
    -                        if (data.batch == data.batch_size) {
    -                            load_images(data);
    -                        }
    -                    });
    -                }
    -            };
    -            
    -            load_images({
    -                index : 0,
    -                batch_size: 4,
    -                images : container.find(".scatterplot img")
    -            });
    +              var load_images = function(data) {
    +                  if (self._active) {
    +                      data.batch = 0;
    +                      var end = Math.min(data.index + data.batch_size,data.images.length);
    +                      for (; data.index < end; data.index++) {
    +                          load_image(data);
    +                      }
    +                  }
    +              };
    +              
    +              var load_image = function(data) {
    +                  var img = $(data.images[data.index]);
    +                  var src2 = img.attr("src2");
    +                  if (src2) {
    +                      img.attr("src", src2);
    +                      img.removeAttr("src2");
    +                      img.on("load", function() {
    +                          data.batch++;
    +                          if (data.batch == data.batch_size) {
    +                              load_images(data);
    +                          }
    +                      });
    +                  }
    +              };
    +              
    +              load_images({
    +                  index : 0,
    +                  batch_size: 4,
    +                  images : container.find(".scatterplot img")
    +              });
    +          });
             });
         } else {
             container.html(
    
  • main/webapp/modules/core/scripts/dialogs/sql-exporter-dialog.js+39 34 modified
    @@ -347,38 +347,39 @@ function SqlExporterDialog(options) {
           options.limit = 10;
         }
     
    -  // var ext = SqlExporterDialog.formats[format].extension;
    -    var form = self._prepareSqlExportRowsForm(format, !exportAllRowsCheckbox, "sql");
    -    if (preview) {
    -      $(form).attr("target", "refine-export");
    -    }
    -    $('<input />')
    -    .attr("name", "options")
    -    .val(JSON.stringify(options))
    -    .appendTo(form);
    -    if (encoding) {
    +    Refine.wrapCSRF(function(csrfToken) { 
    +      var form = self._prepareSqlExportRowsForm(format, !exportAllRowsCheckbox, "sql", csrfToken);
    +      if (preview) {
    +        $(form).attr("target", "refine-export");
    +      }
           $('<input />')
    -      .attr("name", "encoding")
    -      .val(encoding)
    -      .appendTo(form);
    -    }
    -    $('<input />')
    -    .attr("name", "preview")
    -    .val(preview)
    -    .appendTo(form);
    +        .attr("name", "options")
    +        .val(JSON.stringify(options))
    +        .appendTo(form);
    +      if (encoding) {
    +        $('<input />')
    +          .attr("name", "encoding")
    +          .val(encoding)
    +          .appendTo(form);
    +      }
    +      $('<input />')
    +        .attr("name", "preview")
    +        .val(preview)
    +        .appendTo(form);
     
    -    document.body.appendChild(form);
    +      document.body.appendChild(form);
     
    -    if (preview) {
    -      window.open(" ", "refine-export");
    -    }
    -    form.submit();
    +      if (preview) {
    +        window.open(" ", "refine-export");
    +      }
    +      form.submit();
     
    -    document.body.removeChild(form);
    +      document.body.removeChild(form);
    +    });
         return true;
       };
     
    -  SqlExporterDialog.prototype._prepareSqlExportRowsForm = function(format, includeEngine, ext) {
    +  SqlExporterDialog.prototype._prepareSqlExportRowsForm = function(format, includeEngine, ext, csrfToken) {
           var name = ExporterManager.stripNonFileChars(theProject.metadata.name);
           var form = document.createElement("form");
           $(form)
    @@ -387,18 +388,22 @@ function SqlExporterDialog(options) {
           .attr("action", "command/core/export-rows/" + name + ((ext) ? ("." + ext) : ""));
     
           $('<input />')
    -      .attr("name", "project")
    -      .val(theProject.id)
    -      .appendTo(form);
    +        .attr('name', 'csrf_token')
    +        .attr('value', csrfToken)
    +        .appendTo(form);
           $('<input />')
    -      .attr("name", "format")
    -      .val(format)
    -      .appendTo(form);
    +        .attr("name", "project")
    +        .val(theProject.id)
    +        .appendTo(form);
    +      $('<input />')
    +        .attr("name", "format")
    +        .val(format)
    +        .appendTo(form);
           if (includeEngine) {
             $('<input />')
    -        .attr("name", "engine")
    -        .val(JSON.stringify(ui.browsingEngine.getJSON()))
    -        .appendTo(form);
    +          .attr("name", "engine")
    +          .val(JSON.stringify(ui.browsingEngine.getJSON()))
    +          .appendTo(form);
           }
     
           return form;
    
  • main/webapp/modules/core/scripts/dialogs/templating-exporter-dialog.js+9 3 modified
    @@ -53,7 +53,12 @@ TemplatingExporterDialog.prototype._createDialog = function() {
         this._elmts.cancelButton.html($.i18n('core-buttons/cancel'));
         this._elmts.previewTextarea.attr('aria-label',$.i18n('core-dialogs/template-preview'))
         
    -    this._elmts.exportButton.on('click',function() { self._export(); self._dismiss(); });
    +    this._elmts.exportButton.on('click',function() {
    +      Refine.wrapCSRF(function(csrfToken) {
    +        self._export(csrfToken);
    +        self._dismiss();
    +      });
    +    });
         this._elmts.cancelButton.on('click',function() { self._dismiss(); });
         this._elmts.resetButton.on('click',function() {
             self._fillInTemplate(self._createDefaultTemplate());
    @@ -122,7 +127,7 @@ TemplatingExporterDialog.prototype._dismiss = function() {
     
     TemplatingExporterDialog.prototype._updatePreview = function() {
         var self = this;
    -    $.post(
    +    Refine.postCSRF(
             "command/core/export-rows/preview.txt",
             {
                 "project" : theProject.id, 
    @@ -147,7 +152,7 @@ TemplatingExporterDialog.prototype._updatePreview = function() {
         });
     };
     
    -TemplatingExporterDialog.prototype._export = function() {
    +TemplatingExporterDialog.prototype._export = function(csrfToken) {
         var name = ExporterManager.stripNonFileChars(theProject.metadata.name);
         var form = document.createElement("form");
         $(form)
    @@ -162,6 +167,7 @@ TemplatingExporterDialog.prototype._export = function() {
                 .appendTo(form);
         };
     
    +    appendField("csrf_token", csrfToken);
         appendField("engine", JSON.stringify(ui.browsingEngine.getJSON()));
         appendField("project", theProject.id);
         appendField("format", "template");
    
  • main/webapp/modules/core/scripts/facets/scatterplot-facet.js+28 19 modified
    @@ -146,12 +146,14 @@ class ScatterplotFacet extends Facet {
         });
     
         this._elmts.plotDiv.width(this._config.l + "px").height(this._config.l + "px");
    -    this._elmts.plotBaseImg.attr("src", this._formulateBaseImageUrl())
    -    .attr("width", this._config.l)
    -    .attr("height", this._config.l);
    -    this._elmts.plotImg.attr("src", this._formulateCurrentImageUrl())
    -    .attr("width", this._config.l)
    -    .attr("height", this._config.l);
    +    Refine.wrapCSRF(function(csrfToken) {
    +      self._elmts.plotBaseImg.attr("src", self._formulateBaseImageUrl(csrfToken))
    +        .attr("width", self._config.l)
    +        .attr("height", self._config.l);
    +      self._elmts.plotImg.attr("src", self._formulateCurrentImageUrl(csrfToken))
    +        .attr("width", self._config.l)
    +        .attr("height",self._config.l);
    +    });
     
         var ops = {
             instance: true,        
    @@ -252,19 +254,19 @@ class ScatterplotFacet extends Facet {
         }
       };
     
    -  _formulateCurrentImageUrl() {
    -    return this._formulateImageUrl(ui.browsingEngine.getJSON(false, this), { color: "ff6a00" });
    +  _formulateCurrentImageUrl(csrfToken) {
    +    return this._formulateImageUrl(ui.browsingEngine.getJSON(false, this), { color: "ff6a00" }, csrfToken);
       };
     
    -  _formulateBaseImageUrl() {
    -    return this._formulateImageUrl({},{ color: "888888", dot : this._config.dot * 0.9 });
    +  _formulateBaseImageUrl(csrfToken) {
    +    return this._formulateImageUrl({},{ color: "888888", dot : this._config.dot * 0.9 }, csrfToken);
       };
     
    -  _formulateExportImageUrl() {
    -    return this._formulateImageUrl(ui.browsingEngine.getJSON(false, this), { dot : this._config.dot * 5, l: 500, base_color: "888888" });
    +  _formulateExportImageUrl(csrfToken) {
    +    return this._formulateImageUrl(ui.browsingEngine.getJSON(false, this), { dot : this._config.dot * 5, l: 500, base_color: "888888" }, csrfToken);
       };
     
    -  _formulateImageUrl(engineConfig, conf) {
    +  _formulateImageUrl(engineConfig, conf, csrfToken) {
         var options = {};
         for (var p in this._config) {
           if (this._config.hasOwnProperty(p)) {        
    @@ -279,7 +281,8 @@ class ScatterplotFacet extends Facet {
         var params = {
             project: theProject.id,
             engine: JSON.stringify(engineConfig), 
    -        plotter: JSON.stringify(options) 
    +        plotter: JSON.stringify(options),
    +        csrf_token: csrfToken 
         };
         return "command/core/get-scatterplot?" + $.param(params);
       };
    @@ -337,9 +340,12 @@ class ScatterplotFacet extends Facet {
       };
     
       changePlot() {
    -    this._elmts.plotBaseImg.attr("src", this._formulateBaseImageUrl());
    -    this._elmts.plotImg.attr("src", this._formulateCurrentImageUrl());
    -    this._elmts.exportPlotLink.attr("href", this._formulateExportImageUrl());
    +    let self = this;
    +    Refine.wrapCSRF(function(csrfToken) {
    +      self._elmts.plotBaseImg.attr("src", self._formulateBaseImageUrl(csrfToken));
    +      self._elmts.plotImg.attr("src", self._formulateCurrentImageUrl(csrfToken));
    +      self._elmts.exportPlotLink.attr("href", self._formulateExportImageUrl(csrfToken));
    +    });
       };
     
       render() {
    @@ -359,8 +365,11 @@ class ScatterplotFacet extends Facet {
         this._elmts.plotDiv.show();
         this._elmts.statusDiv.show();
     
    -    this._elmts.plotImg.attr("src", this._formulateCurrentImageUrl());
    -    this._elmts.exportPlotLink.attr("href", this._formulateExportImageUrl());
    +    let self = this;
    +    Refine.wrapCSRF(function(csrfToken) {
    +      self._elmts.plotImg.attr("src", self._formulateCurrentImageUrl(csrfToken));
    +      self._elmts.exportPlotLink.attr("href", self._formulateExportImageUrl(csrfToken));
    +    });
       };
     
       _updateRest() {
    
  • main/webapp/modules/core/scripts/project/browsing-engine.js+1 1 modified
    @@ -265,7 +265,7 @@ BrowsingEngine.prototype.update = function(onDone) {
       this._elmts.controls.css("display", "none");
       this._elmts.indicator.css("display", "block");
     
    -  $.post(
    +  Refine.postCSRF(
         "command/core/compute-facets?" + $.param({ project: theProject.id }),
         { engine: JSON.stringify(this.getJSON(true)) },
         function(data) {
    
  • main/webapp/modules/core/scripts/project/exporters.js+10 8 modified
    @@ -112,19 +112,21 @@ ExporterManager.stripNonFileChars = function(name) {
     };
     
     ExporterManager.handlers.exportRows = function(format, ext) {
    -  let form = ExporterManager.prepareExportRowsForm(format, true, ext);
    -  document.body.appendChild(form);
    -  form.submit();
    -  document.body.removeChild(form);
    +  Refine.wrapCSRF(function(csrfToken) {
    +    let form = ExporterManager.prepareExportRowsForm(format, true, ext, csrfToken);
    +    document.body.appendChild(form);
    +    form.submit();
    +    document.body.removeChild(form);
    +  });
     };
     
    -ExporterManager.prepareExportRowsForm = function(format, includeEngine, ext) {
    +ExporterManager.prepareExportRowsForm = function(format, includeEngine, ext, csrfToken) {
       let name = encodeURI(ExporterManager.stripNonFileChars(theProject.metadata.name));
       let form = document.createElement("form");
       $(form)
    -  .css("display", "none")
    -  .attr("method", "post")
    -  .attr("action", "command/core/export-rows/" + name + ((ext) ? ("." + ext) : ""));
    +   .css("display", "none")
    +   .attr("method", "post")
    +   .attr("action", "command/core/export-rows/" + name + ((ext) ? ("." + ext) : "") + "?" + $.param({csrf_token: csrfToken}));
     
       $('<input />')
       .attr("name", "project")
    
  • main/webapp/modules/core/scripts/util/csrf.js+6 2 modified
    @@ -3,7 +3,7 @@ CSRFUtil = {};
     // Requests a CSRF token and calls the supplied callback
     // with the token
     CSRFUtil.wrapCSRF = function(onCSRF) {
    -    $.get(
    +    return $.get(
             "command/core/get-csrf-token",
             {},
             function(response) {
    @@ -20,7 +20,11 @@ CSRFUtil.postCSRF = function(url, data, success, dataType, failCallback) {
         return CSRFUtil.wrapCSRF(function(token) {
             var fullData = data || {};
             if (typeof fullData == 'string') {
    -            fullData = fullData + "&" + $.param({csrf_token: token});
    +            if (fullData.includes('?')) {
    +              fullData = fullData + "&" + $.param({csrf_token: token});
    +            } else {
    +              fullData = fullData + "?" + $.param({csrf_token: token});
    +            }
             } else {
                 fullData['csrf_token'] = token;
             }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.