Primary Concerns
As long as the stash is not within scope, globals such as window/global would effectively be sanitized of any risky methods, such as eval
The API feels complete, am I missing anything?
- Are there any "risky" aspects to the sever method.
- Any thoughts with regard to performance or security.
- I don't think there is any performance implications from maintaining a stash of the extracted methods, as they would exist already, we are just moving them around.
- is the layout below easy to read and understand, should I document these things in the future or should I just stick to the code?
Background Info
Scalpel (s5l) is a small class to shuffle properties between objects.
Initially developed for some work being done within the Electron framework, where "window" and "global" needed to be pruned of insecure methods.
Scalpel will allow you to remove any methods from these "global" type objects, and then restore them, when/if you want.
Here is the condensed version:
class S5l /* S{calpe}(5)l*/ {
constructor (){ this.stash = {}; }
sever ( methods, contexts, message, stash ){
if(! Array.isArray(methods)) methods = [methods];
message = message ? message : ' has been disabled for security reasons.';
this.stash = stash ? stash : this.stash;
stash = this.stash;
methods.forEach(function(method){
contexts.forEach(function(tuple){
let label = Object.keys(tuple)[0];
if( Object.prototype.hasOwnProperty.call( tuple[label], method )){
stash[label] = stash[label] || {};
stash[label][method] = {
_f: tuple[label][method], // the function/method itself.
_r: tuple[label], // a parent reference, where the method was.
_m: method // the method/property name
};
tuple[label][method] = function(){ throw new Error( method + message); };
}
});
});
return this;
}
purge(dotPath){
delete this.stash;
this.stash = {};
return this;
}
_dotToSteps(dotPath){
return dotPath.split('.');
}
_dotWalker(dotPath, context, _c){
var steps = this._dotToSteps(dotPath);
for( var i=0; i < steps.length; i++){
if( Object.prototype.hasOwnProperty.call( context, steps[i] ) ){
_c = context, context = context[steps[i]];
if(i === steps.length -1)
return { at: steps[i], context: context, parent: _c };
} else {
return undefined;
}
}
}
_retrieve(dotPath, context){
context = context ? context : this.stash;
return dotPath ? this._dotWalker(dotPath, context ) : context;
}
use(dotPath, context){
return this._retrieve(dotPath, context).context._f || this._retrieve(dotPath, context).parent;
}
attach(dotPath, to, as){
var tmp = this._retrieve(dotPath, this.stash);
to[ as || tmp.at ] = tmp.context._f;
return to;
}
}
Here is the commented version:
class S5l /* S{calpe}(5)l*/ {
constructor (){
this.stash = {};
}
/**
methods: a string or array of method names
contexts: an tuple or array of tuples.
tuple: { prop: object }
message (optional): a message used to make the stub.
stash (optional): the context used for the stash.
*/
sever ( methods, contexts, message, stash ){
// Ensure we have an array, from which to pull methods from.
if(! Array.isArray(methods)) methods = [methods];
// Prepair or Incorperate a supplied message
message = message ? message : ' has been disabled for security reasons.';
// Swap out stashes, or continue using this.stash
this.stash = stash ? stash : this.stash;
// Make link for use below, ES6, would prevent us from needing this.
stash = this.stash;
// Process all the method.
methods.forEach(function(method){
/* TODO: we should responde to all types,
but for now we expect all values
to be objects.
*/
/**
We loop through the contexts, the use case
here is that we have multple "globals" that
need to be stripped of a method(s).
*/
contexts.forEach(function(tuple){
let label = Object.keys(tuple)[0];
// We only want to work with existing props/methods
if( Object.prototype.hasOwnProperty.call( tuple[label], method )){
// Initialize a new sub object, or use existing.
stash[label] = stash[label] || {};
// Archive/Stash our source method/prop and info.
stash[label][method] = {
_f: tuple[label][method], // the function/method itself.
_r: tuple[label], // a parent reference, where the method was.
_m: method // the method/property name
};
// Assign stub to original context property/method.
tuple[label][method] = function(){ throw new Error( method + message); };
}
});
});
return this;
}
/**
Will drop all link/references in the stash,
to allow garbage collection, if applicable.
*/
purge(dotPath){
delete this.stash; // to ensure garbage collection
this.stash = {}; // reinitialize store
return this;
}
/**
Converts 'dot.path.string' into an array of successive props
*/
_dotToSteps(dotPath){
return dotPath.split('.');
}
/**
Simple traverser, that uses the
*/
_dotWalker(dotPath, context, _c){
var steps = this._dotToSteps(dotPath);
for( var i=0; i < steps.length; i++){
if( Object.prototype.hasOwnProperty.call( context, steps[i] ) ){
_c = context, context = context[steps[i]];
if(i === steps.length -1)
/**
To be able to restore things, we utilize a tuple literal
to maintain reference and other vital information.
*/
return { at: steps[i], context: context, parent: _c };
} else {
/**
Will return on first occurence of an invalid path.
*/
return undefined;
}
}
// Implicit undefined is returned.
}
/**
Get's the context as it was when stashed.
returns undefined if no such context exists within the stash, or it was falsy
*/
_retrieve(dotPath, context){
context = context ? context : this.stash;
return dotPath ? this._dotWalker(dotPath, context ) : context;
}
/**
Returns a reference to the method in the Stash, you can't update the stash's
reference here, but you can use it.
*/
use(dotPath, context){
return this._retrieve(dotPath, context).context._f || this._retrieve(dotPath, context).parent;
}
/**
Will assign a stashed value to an object/context supplied.
*/
attach(dotPath, to, as){
var tmp = this._retrieve(dotPath, this.stash);
to[ as || tmp.at ] = tmp.context._f; //TODO: confirm everything works.
return to;
}
}
API Examples
Construct a new Scalpel Stash object.;
var s = new S5l();
Remove methods from an object/context, can accept an array of methods to be removed.
Expects an array of tuples { prop: object }, where prop will become the name used within the stash archive for the object/value.
When replaced with a value, the initial context will become a stub funtion which throws an error when called.
s.sever(['eval','alert'], [{window: window}]);
// window.alert && window.eval have been replaced with
// stub functions.
Access the original method/prop, uses dotPath notation
window.alert('hi there');
// Error is thrown "alert has been disabled for security reasons."
s.use('window.alert')('Hi there.');
// calls the original alert function as it was initially
Attach a severed property/method to context, accepts a dotPaths as the first argument.
s.attach('window.alert', window);
// window.alert, now equals the original alert method.
s.attach('window.alert', window, 'scream');
// window.scream, now equals the original alert method.
Retrieve the stash, or parts of it.
s._retrieve(window.alert)
// stash.window.alert
or get the whole stash
s._retrieve()
// stash {...}
Release the stash, and reinitialize it.
s.purge();
// stash is now blank, {}
There is also a _dotToSteps and _dotWalker methods, these are not intended for public use and so are not documented here.