@datadayrepos/js-lexer
v0.0.1-beta.14
Published
lexer for safe eval of js expressions
Downloads
2
Readme
js-lexer
JS Lexer is a bespoke, security-conscious JavaScript expression parser designed for specific contexts.
🚀 Features
- Security first: It allows only execution of safe expressions.
- CSP Compliant: Fully compatible with the most stringent Content Security Policy (CSP) settings enforced by web servers.
- Lightweight: A minimal footprint of just 3kb.
- Its fast and optimized: Offers speed and optimization comparable to native JavaScript solutions.
Purpose and context
The primary goal of JS Lexer is to provide a secure parsing mechanism for JavaScript expressions. This necessity stems from the requirement to execute dynamically generated code within our editors, particularly focusing on logical operators and value comparisons within objects.
Traditional approaches in native JavaScript often clash with stringent CSP directives. The scarcity of secure parsers capable of replacing eval or new Function() constructors was notably surprising. These traditional methods are alarmingly prone to injection attacks and hijacking vulnerabilities. Although there is a debate among experts about the relative safety of modern new Function() constructs compared to the notoriously unsafe eval function, browser standard authorities deem them risky enough to warrant caution.
In our quest to deliver high-security applications, we recognized the need for a parser that not only supports dynamic services but also adheres to strict security standards. JS Lexer is our solution to this challenge, striking a balance between dynamic code execution and robust security measures.
Technically, the expression parser is based on the principles of the Shunting yard algorithm.
🛠 Usage
import { safeFunction } from '@datadayrepos/js-lexer'
// execute the function here and pass in necesarry arguments
const result = safeFunction(
expression, // js like expression as string
context, // main object
subcontext, // sub object of parent
langObject // special case for i18n translation parser
)
1. The objects
The objects in this context refer to any valid JavaScript object structure, with the exception of functions. This primarily includes object structures and arrays.
The term "subobject" is used to describe a subset of the main object, although it can, in principle, be an independent entity.
The expressions
Expressions within this framework are subject to certain restrictions:
Scope Limitation: Expressions can only reference the two objects passed into the function. They do not have access to global variables or any other external data. This limitation is central to the security model.
Syntax and Policies: First Context Object: The mandatory context object can be referred to in one of two ways:
- Using the notation '$.path.to.something', with "$." as the initial path marker.
- Alternatively, as "rootFormData."
Second Context Object: This object can be referenced as:
- '$$$.path.to.something', with "$$$." as the initial path marker.
- Or, as "parentFormData."
Expressions Involvement: It is feasible to construct expressions that reference either one or both of these objects.
Language Context Object: This is a special case. It will traverse the path of an i18n-style language object passed in: Using the expression marker 't()'.
Examples:
Here are some examples of expressions that conform to the specified syntax and policies:
Accessing an Array Element and Checking for a Value:
'rootFormData.array_1[0].multicheckbox_1.includes(\'value2\')'
This expression accesses the first element of array_1 in rootFormData and checks if multicheckbox_1 includes the value 'value2'.
Negating a Property Value:
'!rootFormData.c.bb'
Here, the expression negates the boolean value of property bb within the c object of rootFormData.
Comparing a Property Value to a Number:
'rootFormData.ll[3].tt < 5'
In this case, the expression checks if the tt property in the third index of the ll array in rootFormData is less than 5.
Ternary Expression:
rootFormData.status === 'active' ? 'Status is active' : 'Status is inactive'
The expression checks if rootFormData.status is equal to 'active'. If true, it evaluates to 'Status is active'. If false, it evaluates to 'Status is inactive'.
Language Parser Usage:
't(myNameSpace.c)'
This expression uses the language context object to retrieve a translated string, accessing the c property within the myNameSpace namespace.
Each of these examples demonstrates how to construct expressions within the defined syntax rules, enabling the manipulation and evaluation of JavaScript object properties in a secure manner.
Supported operations
These are the currently supported operations:
Comparison Operations These operations are used for comparing values:
!: Not, for negation or inequality checks.
: Greater than. <: Less than.
JavaScript Specific Operations Operations that are specific to JavaScript functionality:
.: Dot operator, currently supports .includes() method for array and string inclusion checks. t: Internationalization function, supports t() syntax for language translations.
Logical Operations These are used for logical reasoning and flow control:
&: Logical AND. :: Used in conditional (ternary) expressions. =: Assignment or equality. ?: Used in conditional (ternary) expressions. |: Logical OR.
Mathematical Operations Operations for performing mathematical calculations:
%: Modulus, for finding the remainder. (: Opening parenthesis for grouping expressions. ): Closing parenthesis for grouping expressions. *: Multiplication. +: Addition. -: Subtraction. /: Division.
String Operations Operations specifically for handling strings:
': Single quotes for defining string literals. `: Backticks for defining string literals. ": Double quotes for defining string literals.
Each of these operations plays a specific role in manipulating and evaluating expressions within your system, ensuring a structured and secure way to handle JavaScript expressions.
2. Install
npm install @datadayrepos/js-lexer
3. Develop
Your src directory is where your TypeScript source files reside. Transpiled files are output to the dist directory. The dist-test dir holds a larger output with test functions and objects.
💻 DEV Commands
Build: Transpile TypeScript to JavaScript
pnpm run build
Linting: Check and fix code style
pnpm run lint
pnpm run lint:fix
Release: Bump version and publish
pnpm run release
Testing: Run tests (tbd)
pnpm run test
Type Checking: Validate TypeScript
pnpm run typecheck
Publish: Publish package publicly
pnpm run pub
Test e2e: Builds tests-build and runs the tests of the parser and compiler
pnpm run test:e2e
Test e2e: Builds tests-build and runs the tests of the getvalue parser
pnpm run test:getPathVal
4. Todo
This is Beta 1. We expect some issues.
The reverser is nearly developed but not accessible in this api. This constructs expressions from AST trees. It is usefull for integration with UIX based expression builders.
We expect some issues with using expressions that uses js "includes"
📦 Template Structure
{
"name": "__PACKAGE_NAME__",
"type": "module",
"version": "0.0.1",
"private": true,
"description": "__PACKAGE_DESCRIPTION__",
"scripts": {
"lint": "eslint --cache .",
"lint:fix": "eslint . --fix",
"release": "bumpp -r && pnpm -r publish",
"test": "echo \"Error: no test specified\" && exit 1",
"typecheck": "tsc --noEmit",
"build": "tsc",
"pub": "npm publish --access public"
}
}
🗂️ File Structure
- src/: Source files written in TypeScript.
- dist/: Transpiled source files in JavaScript.
- dist-test/: Transpiled source files in JavaScript in test mode.
🔗 Links
📄 License
Proprietary License © 2023 Ivar Strand