Payments Data Platform | Modernbanc
Vault

Relay proxy

Using a proxy helps send sensitive data to the destination without exposing it to your servers, for example initiating a payment via Stripe API while ensuring PCI-compliance.

Modernbanc's low-latency and high-throughput workflow engine allows us to build and deploy a proxy fit for our use-case in minutes.

In this guide we'll build two examples of proxies: Static Proxy and Dynamic Proxy.

Static proxy

A static proxy is when we know exactly which fields need to be decrypted when forwarding your request to a third-party.

Let's assume we already know that this proxy will be used to call a specific Stripe API endpoint with a specific request body. The workflow will consist of the following steps:

Retrieve secrets

This step retrieve the secrets by ids that were passed in the request body. Add a find step with following parameters:

ParameterValue
TypeMany
ModelSecret
Filtersin: [[steps[0].result.secret_in_filter_value]]
Includevalue (Our decrypted value)
Make an API call to Stripe.

This step will actually make an API call to Stripe.

ParameterValue
TypeHTTP Query
MethodPOST
URLapi.stripe.com/v1

Dynamic Proxy

A dynamic proxy is when we dynamically detect presence of tokens in the payload and then replace it in the body - allowing for flexibility without a need to redeploy.

Our proxy workflow will contain the following steps.

Detect secrets

This function step will run custom Javascript code that looks through the specified object (in our case a request body) and scans for presence of tokens.

In this example we mark tokens by surrounding them with || , for example you might make a POST request with the following body:

{
  "card_number": "||SECRET_MAR24_LhkYjGQ3XAsyOV6pMkb||",
  "card_expiry_month": "||SECRET_MAR24_5XGYKsfmmtToNUw9ezb||",
  "card_expiry_year": "||SECRET_MAR24_JeebDVhj9Sify463jt7||",
  "card_cvv": "||SECRET_MAR24_cwH5m18GrBigoCDutnn||"
}

Step body

Paste the following code in the workflow step code:

Find tokens in the payload
function findWrappedSubstrings(obj, path = '') {
  let results = [];
  let detectedTexts = []; // Array to store all detected texts for substitutions
 
  const regex = /\|\|([^|]+)\|\|/g;
 
  const traverse = (element, currentPath) => {
    if (typeof element === 'object' && element !== null) {
      for (const key in element) {
        if (element.hasOwnProperty(key)) {
          traverse(element[key], `${currentPath}${currentPath ? '.' : ''}${key}`);
        }
      }
    } else if (typeof element === 'string') {
      const positions = [];
      let match;
 
      while ((match = regex.exec(element)) !== null) {
        const originalText = match[1];
        positions.push({
          start: match.index,
          end: regex.lastIndex - 1,
          length: originalText.length,
          originalText: originalText
        });
        if (!detectedTexts.includes(originalText)) {
          detectedTexts.push(originalText); // Check for missing parentheses here
        }
      }
 
 
      if (positions.length > 0) {
        results.push({
          path: currentPath,
          positions: positions
        });
      }
    }
  };
 
  traverse(obj, path);
 
  return { results, detectedTexts };
}
 
 
try {
  const { results, detectedTexts } = findWrappedSubstrings(_trigger_version._input.body);
  const secret_in_filter_value = detectedTexts.join(",")
  callback({results, secret_in_filter_value}, null)
}
catch (err) {
  callback(null, err)
}
Get underlying secret values

This step will get comma-separated secret ids from the step above and retrieve underlying secrets and their values.

Add a find step with following parameters:

ParameterValue
TypeMany
ModelSecret
Filtersin: [[steps[0].result.secret_in_filter_value]]
Includevalue (Our decrypted value)
Substitute secrets back into payload

In this step we'll use decrypted values found in previous step and create a final payload. Paste the following code in the workflow step code:

function substituteSecrets(obj, secretValues) {
  const regex = /\|\|([^|]+)\|\|/g; // Regular expression to find secret placeholders
 
  const traverseAndReplace = (element) => {
    if (typeof element === 'object' && element !== null) {
      for (const key in element) {
        if (element.hasOwnProperty(key)) {
          element[key] = traverseAndReplace(element[key]); // Recursive replacement
        }
      }
    } else if (typeof element === 'string') {
      // Check if the string exactly matches a secret ID pattern
      const match = element.match(/^(\|\|([^|]+)\|\|)$/);
      if (match && secretValues.hasOwnProperty(match[2])) {
        return secretValues[match[2]]; // Directly return the value, preserving its type
      }
      // Replace all occurrences in strings without changing the entire string's nature
      return element.replace(regex, (match, secretId) => {
        return secretValues[secretId] !== undefined ? secretValues[secretId].toString() : match;
      });
    }
    return element;
  };
 
  return traverseAndReplace(obj);
}
 
const found_secret_map = steps[1].result?.items.reduce((acc, item) => {
  acc[item.id] = item.value;
  return acc;
}, {});
 
const final_body = substituteSecrets(_trigger_version._input.body, found_secret_map);
 
callback(final_body, null)
Send decrypted payload.

Add any step to send a request either API or a database with a final decrypted payload from previous step.

In this example we'll send a POST request to Stripe. Create a connection_query step.

ParameterValue
TypeHTTP Query
MethodPOST
URLapi.example.com/v1
Body[[steps[3].result ?? {}]]

Removing decrypted data from execution logs.

After testing the workflow in test environment ensure you redacted all sensitive data from executions before deploying to production.

For example we could provide a replacements array in Version tab as follows:

[
  { 
    "paths": ["data[1].result, data[2].result, data[3].result"], 
    "with": "REDACTED" 
  }
]

Conclusion

That's it - this is just an example of how to deploy a proxy on Modernbanc. Our platform offers unmatched flexibility so you can tailor the steps to your use-case.

If you have any confusion on how to proceed or need help - we're just a .