Regex pattern corruption in Apigee Edge (Rhino JavaScript engine)

I am encountering an issue in Apigee Edge where regex pattern values retrieved from a JSON Schema inside a JavaScript policy appear to be altered by the Rhino JavaScript engine, resulting in incorrect validation behavior at runtime.

For example, the following JSON Schema definition:

{
  "x": {
    "maxLength": 10,
    "pattern": "^7\\d{9}$",
    "type": "string",
    "nullable": true
  }
}

When the pattern value is read and evaluated inside the JavaScript policy, it is interpreted as:

^7d{9}$

The pattern is correctly defined in the JSON Schema using a double backslash (\\d) to represent an escaped digit token (\d). However, when the pattern is read inside the JavaScript policy, the escape sequence is lost and interpreted as a literal d.

Is this a known limitation of Rhino in Apigee Edge? If so, what is the recommended or supported approach for safely handling and validating schema-based regex patterns in this environment?

1 Like

Hey @Abdulkarim, could you please share the full code (or at least the part where the JSON Schema comes from and where the RegExp is constructed)? Right now it’s not clear how this schema is being obtained.

The reason I’m asking is the following. If the schema is used as a JavaScript string, for example:

var schemaJsonString = '{"x": { "pattern": "^7\\d{9}$", "maxLength": 10, "type": "string", "nullable": true }}';
var obj = JSON.parse(schemaJsonString);

This would actually lead to a JSON parsing error, because inside a JS string \\ becomes \, and \d is not a valid escape sequence in JSON.

On the other hand, if the schema is already a JavaScript object, for example:

var obj = {
  x: {
    maxLength: 10,
    pattern: "^7\\d{9}$",
    type: "string",
    nullable: true
  }
};

Then this should work fine, and new RegExp(obj.x.pattern) should preserve \d correctly.

1 Like

Ahh, so it seems the problem is with escaping, and how the pattern expression “gets into” the RegExp object.

Thanks @nmarkevich for the explanation.

In our case, the schema is not defined as a JavaScript string literal and is not parsed using JSON.parse(). It is loaded dynamically from Apigee propertysets / flow variables and materialized directly as a JavaScript object (e.g. via eval()), not embedded in JS source code.

We verified this by logging the pattern value after the schema object is created and before calling new RegExp(), and at that point the pattern is already observed as ^7d{9}$. This indicates the escape sequence is lost earlier, during how Rhino processes the schema retrieved from the flow variable, rather than due to JSON string escaping in JavaScript code.

Any guidance on known Rhino limitations or best practices when handling regex patterns from flow variables would be appreciated.

Can you be a bit more specific about how the schema is being sourced and “materialised”? Saying “propertysets / flow variables (e.g. via eval)” is still pretty vague — the exact retrieval path matters a lot here.

I tried to reproduce this in Apigee Edge using a JS policy <Properties> (available via the built-in properties object). I tested both materialisation approaches: eval() and JSON.parse() (and yes — please don’t use eval in real code). In both cases, the backslash is preserved before new RegExp(), and the regex works as expected, so this doesn’t look like a Rhino limitation.

Properties:

    <Properties>
        <Property name="schema">{"x": { "pattern": "^7\\d{9}$", "maxLength": 10, "type": "string", "nullable": true }}</Property>
    </Properties>

Test code:

// JavaScript-1.js (Apigee Edge / Rhino)
// Compare eval() materialisation vs JSON.parse() materialisation

context.setVariable('debug.started', true);

var schemaJsonString = properties.schema;

context.setVariable('debug.schema.present', !!schemaJsonString);
context.setVariable('debug.schema.raw', schemaJsonString);

// ---------- Path A: eval() ----------
var objEval = eval('(' + schemaJsonString + ')');
var patternEval = objEval.x.pattern;

context.setVariable('debug.eval.pattern.raw', patternEval);
context.setVariable(
  'debug.eval.pattern.charcodes',
  patternEval.split('').map(function (c) { return c.charCodeAt(0); }).join(',')
);

var reEval = new RegExp(patternEval);

// ---------- Path B: JSON.parse() ----------
var objJson = JSON.parse(schemaJsonString);
var patternJson = objJson.x.pattern;

context.setVariable('debug.json.pattern.raw', patternJson);
context.setVariable(
  'debug.json.pattern.charcodes',
  patternJson.split('').map(function (c) { return c.charCodeAt(0); }).join(',')
);

var reJson = new RegExp(patternJson);

// ---------- Test ----------
var value = context.getVariable('request.queryparam.value');

context.setVariable('debug.value', value);

context.setVariable('debug.eval.match', reEval.test(value));
context.setVariable('debug.json.match', reJson.test(value));

Output:

debug.started  true
debug.schema.present  true
debug.schema.raw  {"x": { "pattern": "^7\\d{9}$", "maxLength": 10, "type": "string", "nullable": true }}
debug.eval.pattern.raw  ^7\d{9}$
debug.eval.pattern.charcodes  94,55,92,100,123,57,125,36
debug.json.pattern.raw  ^7\d{9}$
debug.json.pattern.charcodes  94,55,92,100,123,57,125,36
request.queryparam.value  7123456789
debug.value  7123456789
debug.eval.match  true
debug.json.match  true