Socket
Socket
Sign inDemoInstall

@contrast/rewriter

Package Overview
Dependencies
Maintainers
9
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@contrast/rewriter - npm Package Compare versions

Comparing version 1.4.2 to 1.5.0

lib/cache.js

232

lib/index.js
/*
* Copyright: 2022 Contrast Security, Inc
* Copyright: 2024 Contrast Security, Inc
* Contact: support@contrastsecurity.com

@@ -16,10 +16,26 @@ * License: Commercial

// @ts-check
'use strict';
const { transformSync } = require('@swc/core');
const Module = require('module');
const { transform, transformSync } = require('@swc/core');
const Module = require('node:module');
const { Cache } = require('./cache');
const rewriterPath = require.resolve('@contrast/agent-swc-plugin');
const unwriterPath = require.resolve('@contrast/agent-swc-plugin-unwrite');
/**
* @typedef {Object} Core
* @prop {import('@contrast/common').AppInfo} appInfo
* @prop {string} agentVersion
* @prop {import('@contrast/config').Config} config
* @prop {import('@contrast/logger').Logger} logger
*/
/**
* @typedef {'assess' | 'protect'} Mode
*/
/**
* @typedef {Object} RewriteOpts
* @prop {string=} filename e.g. 'index.js'
* @prop {boolean=} isModule if true, file is parsed as an ES module instead of a CJS script
* @prop {boolean=} inject if true, injects ContrastMethods on the global object
* @prop {boolean=} wrap if true, wraps the content with a modified module wrapper IIFE
* @prop {boolean=} trim if true, removes added characters from the end of the generated code
*/

@@ -31,9 +47,68 @@ // @ts-expect-error `wrapper` is missing from @types/node

/** @typedef {'assess' | 'protect'} Mode */
const rewriterPath = require.resolve('@contrast/agent-swc-plugin');
const unwriterPath = require.resolve('@contrast/agent-swc-plugin-unwrite');
const rewriter = {
/** @type {Set<Mode>} */
modes: new Set(),
/**
* Wraps the source content as necessary to support rewriting.
* Wrapping must occur before rewriting since the underlying rewriter cannot
* parse certain valid statements such as `return` statements in a CJS script.
* @param {string} content
* @returns {string}
*/
const wrap = (content) => {
let shebang = '';
// The shebang will be commented out since it cannot be present in a
// function body. swc doesn't include the commented shebang in the generated
// code despite including comments otherwise.
if (content.charAt(0) === '#') {
shebang = content.substring(0, content.indexOf('\n') + 1);
content = `//${content}`;
}
content = `${shebang}${prefix}${content}${suffix}`;
return content;
};
/**
* Trims extraneous characters that may have been added by the rewriter.
* Handles newline or semicolon insertion, removing the added characters if they
* were not present in the original source content.
* @param {string} content
* @param {import('@swc/core').Output} result
* @returns {import('@swc/core').Output}
*/
const trim = (content, result) => {
let carriageReturn = 0;
// swc always adds a newline, so we only need to check the input
if (!content.endsWith('\n')) {
result.code = result.code.substring(0, result.code.length - 1);
} else if (content.endsWith('\r\n')) {
// if EOL is \r\n, then we need to account for that when we check the
// negative index of the last semicolon below
carriageReturn = 1;
}
const resultSemicolonIdx = result.code.lastIndexOf(';');
const contentSemicolonIdx = content.lastIndexOf(';');
if (contentSemicolonIdx === -1 || resultSemicolonIdx - result.code.length !== contentSemicolonIdx - content.length + carriageReturn) {
result.code = result.code.substring(0, resultSemicolonIdx) + result.code.substring(resultSemicolonIdx + 1, result.code.length);
}
return result;
};
class Rewriter {
/**
* @param {Core} core
*/
constructor(core) {
this.core = core;
this.logger = core.logger.child({ name: 'contrast:rewriter' });
/** @type {Set<Mode>} */
this.modes = new Set();
this.cache = new Cache(core);
}
/**
* Sets the rewriter to 'assess' or 'protect' mode, enabling different

@@ -44,75 +119,93 @@ * transforms.

install(mode) {
this.logger.trace('installing rewriter mode: %s', mode);
this.modes.add(mode);
},
this.cache.install(mode);
}
/**
* @param {string} content the source code
* @param {object} opts
* @param {string=} opts.filename e.g. 'index.js'
* @param {boolean=} opts.isModule if true, file is parsed as an ES module instead of a CJS script
* @param {boolean=} opts.inject if true, injects ContrastMethods on the global object
* @param {boolean=} opts.wrap if true, wraps the content with a modified module wrapper IIFE
* @returns {import("@swc/core").Output}
* @param {RewriteOpts} opts
* @returns {import('@swc/core').Options}
*/
rewrite(content, opts = {}) {
let shebang = '';
if (content.charAt(0) === '#') {
shebang = content.substring(0, content.indexOf('\n') + 1);
// see the test output: swc doesn't include the commented shebang in the generated code despite including comments otherwise
content = `//${content}`;
}
if (opts.wrap) {
content = `${shebang}${prefix}${content}${suffix}`;
}
const result = transformSync(content, {
rewriteConfig(opts) {
return {
filename: opts.filename,
isModule: opts.isModule,
env: {
targets: {
node: process.versions.node
}
},
jsc: {
target: 'es2019', // should work for node >14
experimental: {
plugins: [
[
rewriterPath,
{
assess: this.modes.has('assess'),
inject: opts.inject,
},
],
],
plugins: [[rewriterPath, {
assess: this.modes.has('assess'),
inject: opts.inject,
}]],
},
},
sourceMaps: true,
});
// if we're trimming the output we're not rewriting an entire file, which
// means source maps are not relevant.
sourceMaps: !opts.trim && this.core.config.agent.node.source_maps.enable,
};
}
if (!opts.wrap) {
let carriageReturn = 0;
// swc always adds a newline, so we only need to check the input
if (!content.endsWith('\n')) {
result.code = result.code.substring(0, result.code.length - 1);
} else if (content.endsWith('\r\n')) {
// if EOL is \r\n, then we need to account for that when we check the
// negative index of the last semicolon below
carriageReturn = 1;
}
const resultSemicolonIdx = result.code.lastIndexOf(';');
const contentSemicolonIdx = content.lastIndexOf(';');
if (contentSemicolonIdx === -1 || resultSemicolonIdx - result.code.length !== contentSemicolonIdx - content.length + carriageReturn) {
result.code = result.code.substring(0, resultSemicolonIdx) + result.code.substring(resultSemicolonIdx + 1, result.code.length);
}
/**
* Rewrites the provided source code string asynchronously to be consumed by
* ESM hooks.
* @param {string} content
* @param {RewriteOpts=} opts
* @returns {Promise<import('@swc/core').Output>}
*/
async rewrite(content, opts = {}) {
this.logger.trace({ opts }, 'rewriting %s', opts.filename);
if (opts.wrap) {
content = wrap(content);
}
let result = await transform(content, this.rewriteConfig(opts));
if (opts.trim) {
result = trim(content, result);
}
return result;
},
}
/**
* Rewrites the provided source code string synchronously to be consumed by
* CJS hooks.
* @param {string} content
* @param {RewriteOpts=} opts
* @returns {import('@swc/core').Output}
*/
rewriteSync(content, opts = {}) {
this.logger.trace({ opts }, 'rewriting %s', opts.filename);
if (opts.wrap) {
content = wrap(content);
}
let result = transformSync(content, this.rewriteConfig(opts));
if (opts.trim) {
result = trim(content, result);
}
return result;
}
/**
* Removes contrast-related rewritten code from provided source code string.
* @param {string} content
* @returns {string}
*/
unwrite(content) {
unwriteSync(content) {
return transformSync(content, {
env: {
targets: {
node: process.versions.node
}
},
jsc: {
target: 'es2019', // should work for node >14
experimental: {

@@ -122,15 +215,16 @@ plugins: [[unwriterPath, {}]],

},
sourceMaps: false,
}).code;
},
};
}
}
/** @typedef {typeof rewriter} Rewriter */
/**
* @param {{ rewriter: Rewriter }} core
* @param {Core & { rewriter?: Rewriter; }} core
* @returns {Rewriter}
*/
module.exports = function init(core) {
core.rewriter = rewriter;
return rewriter;
core.rewriter = new Rewriter(core);
return core.rewriter;
};
module.exports.Rewriter = Rewriter;
/*
* Copyright: 2022 Contrast Security, Inc
* Copyright: 2024 Contrast Security, Inc
* Contact: support@contrastsecurity.com

@@ -22,7 +22,7 @@ * License: Commercial

module.exports = function(deps) {
module.exports = function (deps) {
const sourceMaps = deps.rewriter.sourceMaps = {};
const consumerCache = sourceMaps.consumerCache = {};
sourceMaps.cacheConsumerMap = function(filename, map) {
sourceMaps.cacheConsumerMap = function (filename, map) {
consumerCache[filename] = new SourceMapConsumer(map);

@@ -33,3 +33,3 @@ };

*/
sourceMaps.chain = function(filename, map) {
sourceMaps.chain = function (filename, map) {
let ret;

@@ -41,6 +41,5 @@

ret = transfer({ fromSourceMap: map, toSourceMap: existingMap });
deps.logger.trace(`Merged sourcemap from ${filename}.map`);
} catch (e) {
deps.logger.debug(`Unable to read ${filename}.map.js`);
deps.logger.debug(`${e}`);
deps.logger.trace('Merged sourcemap from %s.map', filename);
} catch (err) {
deps.logger.debug({ err }, 'Unable to read %s.map.js', filename);
}

@@ -47,0 +46,0 @@ }

{
"name": "@contrast/rewriter",
"version": "1.4.2",
"version": "1.5.0",
"description": "A transpilation tool mainly used for instrumentation",
"license": "SEE LICENSE IN LICENSE",
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
"files": [
"lib/"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"types": "types/index.d.ts",
"engines": {

@@ -20,4 +17,5 @@ "npm": ">=6.13.7 <7 || >= 8.3.1",

"dependencies": {
"@contrast/agent-swc-plugin": "^1.2.0",
"@contrast/agent-swc-plugin-unwrite": "^1.2.0",
"@contrast/agent-swc-plugin": "^1.3.0",
"@contrast/agent-swc-plugin-unwrite": "^1.3.0",
"@contrast/common": "^1.16.0",
"@contrast/synchronous-source-maps": "^1.1.3",

@@ -27,2 +25,2 @@ "@swc/core": "1.3.39",

}
}
}

@@ -5,30 +5,3 @@ ## `@contrast/rewriter`

For example, Assess will register transforms for `+` -> `contrast_add()` so that it can perform propagation
via instrumentation of `contrast_add()`.
#### Example Service Usage
```typescript
const { Rewriter } = require('.');
const rewriter = new Rewriter({ logger });
rewriter.addTransforms({
BinaryExpression(path) {
const method = methodLookups[path.node.operator];
if (method) {
path.replaceWith(
t.callExpression(
expression('ContrastMethods.%%method%%')({ method }), [
path.node.left,
path.node.right
]
)
);
}
}
});
const result = rewriter.rewrite('function add(x, y) { return x + y; }');
```
For example, Assess will register transforms for `+` -> `ContrastMethods.add()`
so that it can perform propagation via instrumentation of `ContrastMethods.add()`.

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc