diff --git a/README.md b/README.md index 0b6a232..8dc681d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Sec-literal + ![version](https://img.shields.io/badge/dynamic/json.svg?url=https://raw.githubusercontent.com/NodeSecure/sec-literal/master/package.json&query=$.version&label=Version) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/NodeSecure/sec-literal/commit-activity) [![OpenSSF @@ -29,47 +30,139 @@ $ yarn add @nodesecure/sec-literal ## Hex ### isHex(anyValue): boolean + Detect if the given string is an Hexadecimal value +```js +Hex.isHex('4e20') // true +Hex.isHex(20) // false +``` + ### isSafe(anyValue): boolean + Detect if the given string is a safe Hexadecimal value. The goal of this method is to eliminate false-positive. ```js -Hex.isSafe("1234"); // true -Hex.isSafe("abcdef"); // true +Hex.isSafe('393d8') // true +Hex.isSafe('7f196a64a870440000') // false ``` ## Literal ### isLiteral(anyValue): boolean + +Detect if the given literal is a ESTree literal. + +```js +const literalSample = createLiteral("hello world"); +Literal.isLiteral(literalSample); // true +Literal.isLiteral("hello world!"); // false +``` + ### toValue(anyValue): string + +Returns the value of the literal if the input is an ESTree literal else it returns the original input + +```js +const literalSample = createLiteral("hello world"); +Literal.toValue(literalSample); // returns "hello world" +``` + ### toRaw(anyValue): string + +Returns the raw value of literal if the literal is an ESTree literal else it returns the original input + +```js +const literalSample = createLiteral("hello world", true); +Literal.toRaw(literalSample); // returns "hello world" +``` + ### defaultAnalysis(literalValue) +Returns an object which indicates if the literal contains hexadecimal, unicode or base64 sequence if the input is an ESTree literal else it returns null + +```js +const literalSample = createLiteral("hello world"); +Literal.toRaw(literalSample); // returns {hasHexadecimalSequence: null, hasUnicodeSequence: null, isBase64: null} +``` + ## Utils ### isSvg(strValue): boolean +Detect if a given string is an SVG. + +```js +const SVG_HTML = ` + + `; +Utils.isSvg(SVG_HTML); // true +``` + ### isSvgPath(strValue): boolean -Detect if a given string is a svg path or not. + +Detect if a given string is a svg path. + +```js +Utils.isSvgPath("M150 0 L75 200 L225 200 Z"); // true +Utils.isSvgPath("hi there!"); // false +``` ### stringCharDiversity(str): number -Get the number of unique chars in a given string + +Get the number of unique chars in a given string. + +```js +Utils.stringCharDiversity("hello"); // returns 4 +Utils.stringCharDiversity("hello", ["l"]); // returns 3 +Utils.stringCharDiversity("syntax"); // returns 6 +``` ### stringSuspicionScore(str): number -Analyze a given string an give it a suspicion score (higher than 1 or 2 mean that the string is highly suspect). + +Analyze a given string and give it a suspicion score (higher than 1 or 2 mean that the string is highly suspect). + +```js +Utils.stringSuspicionScore("hello world"); // returns 0 +Utils.stringSuspicionScore( + "XoMFrxuRvgb6a7lip6uYd6sz13E4KooQYqiIL0ZQReukg8BqZwsjCeay" +); // returns 1 +``` ## Patterns ### commonStringPrefix(leftStr, rightStr): string | null + +Get the common string prefix (at the start) pattern + +```js +Patterns.commonStringPrefix("boo", "foo"); // null +Patterns.commonStringPrefix("bromance", "brother"); // "bro" +``` + ### commonStringSuffix(leftStr, rightStr): string | null + +Get the common string suffixes (at the end) pattern. + +```js +Patterns.commonStringSuffix("boo", "foo"); // oo +Patterns.commonStringSuffix("bromance", "brother"); // null +``` + ### commonHexadecimalPrefix(identifiersArray: string[]) +Return the number of one time occurences of hexadecimal prefixes and an object containing the list of prefixes and the number of occurences in a given array of hexadecimals. + +```js +Patterns.commonHexadecimalPrefix(["_0x33bb79", "foo", "_0x3c0c55", "_0x1185d5"]); // returns { oneTimeOccurence: 1, prefix: { _0x: 3 } } +``` ## Contributors ✨ + [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) + Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): @@ -90,4 +183,5 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d ## License + MIT diff --git a/src/hex.js b/src/hex.js index cb0988c..c0ba0a7 100644 --- a/src/hex.js +++ b/src/hex.js @@ -25,7 +25,11 @@ export const CONSTANTS = Object.freeze({ /** * @description detect if the given string is an Hexadecimal value * @param {SecLiteral.Literal | string} anyValue - * @returns {boolean} + * @returns boolean + * + * @example + * isHex('4e20') // true + * isHex(20) // false */ export function isHex(anyValue) { const value = Literal.toValue(anyValue); @@ -36,7 +40,11 @@ export function isHex(anyValue) { /** * @description detect if the given string is a safe Hexadecimal value * @param {SecLiteral.Literal | string} anyValue - * @returns {boolean} + * @returns boolean + * + * @example + * isSafe('393d8') // true + * isSafe('7f196a64a870440000') // false */ export function isSafe(anyValue) { const rawValue = Literal.toRaw(anyValue); diff --git a/src/literal.js b/src/literal.js index 1937c07..30d213e 100644 --- a/src/literal.js +++ b/src/literal.js @@ -2,32 +2,58 @@ import isStringBase64 from "is-base64"; /** + * @description detect if the given literal is a ESTree literal. * @param {SecLiteral.Literal | string} anyValue - * @returns {string} + * @returns boolean + * + * @example + * const literalSample = createLiteral("hello world"); + * Literal.isLiteral(literalSample); // true + * Literal.isLiteral("hello world!"); // false */ export function isLiteral(anyValue) { - return typeof anyValue === "object" && "type" in anyValue && anyValue.type === "Literal"; + return ( + typeof anyValue === "object" && + "type" in anyValue && + anyValue.type === "Literal" + ); } /** + * @description returns the value of the literal if the input is an ESTree literal else returns the original input. * @param {SecLiteral.Literal | string} strOrLiteral - * @returns {string} + * @returns string + * + * @example + * const literalSample = createLiteral("hello world"); + * Literal.toValue(literalSample); // returns "hello world" */ export function toValue(strOrLiteral) { return isLiteral(strOrLiteral) ? strOrLiteral.value : strOrLiteral; } /** + * @description returns the raw value of literal if the literal is an ESTree literal else returns the original input * @param {SecLiteral.Literal | string} strOrLiteral - * @returns {string} + * @returns string + * + * @example + * const literalSample = createLiteral("hello world", true); + * Literal.toRaw(literalSample); // returns "hello world" + * */ export function toRaw(strOrLiteral) { return isLiteral(strOrLiteral) ? strOrLiteral.raw : strOrLiteral; } /** + * @description returns an object which indicates if the literal contains hexadecimal, unicode or base64 sequence if the input is an ESTree literal else it returns null * @param {!SecLiteral.Literal} literalValue * @returns {SecLiteral.LiteralDefaultAnalysis} + * + * @example + * const literalSample = createLiteral("hello world"); + * Literal.toRaw(literalSample); // returns { hasHexadecimalSequence: null, hasUnicodeSequence: null, isBase64: null} */ export function defaultAnalysis(literalValue) { if (!isLiteral(literalValue)) { @@ -35,8 +61,12 @@ export function defaultAnalysis(literalValue) { } const hasRawValue = "raw" in literalValue; - const hasHexadecimalSequence = hasRawValue ? /\\x[a-fA-F0-9]{2}/g.exec(literalValue.raw) !== null : null; - const hasUnicodeSequence = hasRawValue ? /\\u[a-fA-F0-9]{4}/g.exec(literalValue.raw) !== null : null; + const hasHexadecimalSequence = hasRawValue + ? /\\x[a-fA-F0-9]{2}/g.exec(literalValue.raw) !== null + : null; + const hasUnicodeSequence = hasRawValue + ? /\\u[a-fA-F0-9]{4}/g.exec(literalValue.raw) !== null + : null; const isBase64 = isStringBase64(literalValue.value, { allowEmpty: false }); return { hasHexadecimalSequence, hasUnicodeSequence, isBase64 }; diff --git a/src/patterns.js b/src/patterns.js index 034654c..06db512 100644 --- a/src/patterns.js +++ b/src/patterns.js @@ -11,8 +11,8 @@ import * as Literal from "./literal.js"; * @returns {string | null} * * @example - * commonStringPrefix("boo", "foo"); // null - * commonStringPrefix("bromance", "brother"); // "bro" + * Patterns.commonStringPrefix("boo", "foo"); // null + * Patterns.commonStringPrefix("bromance", "brother"); // "bro" */ export function commonStringPrefix(leftAnyValue, rightAnyValue) { const leftStr = Literal.toValue(leftAnyValue); @@ -44,8 +44,8 @@ function reverseString(string) { * @returns {string | null} * * @example - * commonStringSuffix("boo", "foo"); // oo - * commonStringSuffix("bromance", "brother"); // null + * Patterns.commonStringSuffix("boo", "foo"); // oo + * Patterns.commonStringSuffix("bromance", "brother"); // null */ export function commonStringSuffix(leftStr, rightStr) { const commonPrefix = commonStringPrefix( @@ -56,6 +56,15 @@ export function commonStringSuffix(leftStr, rightStr) { return commonPrefix === null ? null : reverseString(commonPrefix); } +/** + * @description returns the number of one time occurences of hexadecimal prefixes and an object containing the prefixes and the number of occurences in a given array of hexadecimals. + * @param {!string} leftStr + * @param {!string} rightStr + * @returns {string | null} + * + * @example + * Patterns.commonHexadecimalPrefix(["_0x33bb79", "foo", "_0x3c0c55", "_0x1185d5"]); // returns { oneTimeOccurence: 1, prefix: { _0x: 3 } } + */ export function commonHexadecimalPrefix(identifiersArray) { if (!Array.isArray(identifiersArray)) { throw new TypeError("identifiersArray must be an Array"); @@ -71,8 +80,7 @@ export function commonHexadecimalPrefix(identifiersArray) { if (commonStr === cp || commonStr.startsWith(cp)) { prefix.add(cp); - } - else if (cp.startsWith(commonStr)) { + } else if (cp.startsWith(commonStr)) { prefix.delete(cp); prefix.add(commonStr, count + 1); } @@ -93,6 +101,6 @@ export function commonHexadecimalPrefix(identifiersArray) { return { oneTimeOccurence, - prefix: Object.fromEntries(prefix) + prefix: Object.fromEntries(prefix), }; } diff --git a/src/utils.js b/src/utils.js index e77be2d..bd3df82 100644 --- a/src/utils.js +++ b/src/utils.js @@ -6,24 +6,34 @@ import stringWidth from "string-width"; import { toValue } from "./literal.js"; /** + * @description detect if a given string is an SVG. * @param {SecLiteral.Literal | string} strOrLiteral - * @returns {boolean} + * @returns boolean + * + * @example + * const SVG_HTML = ` + * + * `; + * Utils.isSvg(SVG_HTML); // true */ export function isSvg(strOrLiteral) { try { const value = toValue(strOrLiteral); return isStringSvg(value) || isSvgPath(value); - } - catch { + } catch { return false; } } /** - * @description detect if a given string is a svg path or not. + * @description detect if a given string is a svg path. * @param {!string} str svg path literal - * @returns {boolean} + * @returns boolean + * + * @example + * Utils.isSvgPath("M150 0 L75 200 L225 200 Z"); // true + * Utils.isSvgPath("hi there!"); // false */ export function isSvgPath(str) { if (typeof str !== "string") { @@ -31,13 +41,17 @@ export function isSvgPath(str) { } const trimStr = str.trim(); - return trimStr.length > 4 && /^[mzlhvcsqta]\s*[-+.0-9][^mlhvzcsqta]+/i.test(trimStr) && /[\dz]$/i.test(trimStr); + return ( + trimStr.length > 4 && + /^[mzlhvcsqta]\s*[-+.0-9][^mlhvzcsqta]+/i.test(trimStr) && + /[\dz]$/i.test(trimStr) + ); } /** * @description detect if a given string is a morse value. * @param {!string} str any string value - * @returns {boolean} + * @returns boolean */ export function isMorse(str) { return /^[.-]{1,5}(?:[\s\t]+[.-]{1,5})*(?:[\s\t]+[.-]{1,5}(?:[\s\t]+[.-]{1,5})*)*$/g.test(str); @@ -45,7 +59,7 @@ export function isMorse(str) { /** * @param {!string} str any string value - * @returns {string} + * @returns string */ export function escapeRegExp(str) { return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); @@ -55,7 +69,12 @@ export function escapeRegExp(str) { * @description Get the number of unique chars in a given string * @param {!string} str string * @param {string[]} [charsToExclude=[]] - * @returns {number} + * @returns number + * + * @example + * Utils.stringCharDiversity("hello"); // returns 4 + * Utils.stringCharDiversity("hello", ["l"]); // returns 3 + * Utils.stringCharDiversity("syntax"); // returns 6 */ export function stringCharDiversity(str, charsToExclude = []) { const data = new Set(str); @@ -71,9 +90,15 @@ const kMinUnsafeStringLenThreshold = 200; const kScoreStringLengthThreshold = 750; /** - * @description Analyze a given string an give it a suspicion score (higher than 1 or 2 mean that the string is highly suspect). + * @description Analyze a given string and give it a suspicion score (higher than 1 or 2 mean that the string is highly suspect). * @param {!string} str string to analyze - * @returns {number} + * @returns number + * + * @example + * Utils.stringSuspicionScore("hello world"); // returns 0 + * Utils.stringSuspicionScore( + * "XoMFrxuRvgb6a7lip6uYd6sz13E4KooQYqiIL0ZQReukg8BqZwsjCeay" + * ); // returns 1 */ export function stringSuspicionScore(str) { const strLen = stringWidth(str); @@ -82,12 +107,16 @@ export function stringSuspicionScore(str) { } const includeSpace = str.includes(" "); - const includeSpaceAtStart = includeSpace ? str.slice(0, kMaxSafeStringLen).includes(" ") : false; + const includeSpaceAtStart = includeSpace + ? str.slice(0, kMaxSafeStringLen).includes(" ") + : false; let suspectScore = includeSpaceAtStart ? 0 : 1; if (strLen > kMinUnsafeStringLenThreshold) { suspectScore += Math.ceil(strLen / kScoreStringLengthThreshold); } - return stringCharDiversity(str) >= kMaxSafeStringCharDiversity ? suspectScore + 2 : suspectScore; + return stringCharDiversity(str) >= kMaxSafeStringCharDiversity + ? suspectScore + 2 + : suspectScore; }