@wordpress/eslint-plugin
Advanced tools
| import { RuleTester } from 'eslint'; | ||
| import rule from '../no-ds-tokens'; | ||
| const ruleTester = new RuleTester( { | ||
| parserOptions: { | ||
| ecmaVersion: 6, | ||
| ecmaFeatures: { | ||
| jsx: true, | ||
| }, | ||
| }, | ||
| } ); | ||
| ruleTester.run( 'no-ds-tokens', rule, { | ||
| valid: [ | ||
| { | ||
| code: `const style = 'color: var(--my-custom-prop)';`, | ||
| }, | ||
| { | ||
| code: `const style = 'color: blue';`, | ||
| }, | ||
| { | ||
| code: 'const style = `border: 1px solid var(--other-prefix-token)`;', | ||
| }, | ||
| { | ||
| code: `const name = 'something--wpds-color';`, | ||
| }, | ||
| { | ||
| code: `<div style={ { color: 'var(--my-custom-prop)' } } />`, | ||
| }, | ||
| ], | ||
| invalid: [ | ||
| { | ||
| code: `const style = 'color: var(--wpds-color-fg-content-neutral)';`, | ||
| errors: [ | ||
| { | ||
| messageId: 'disallowed', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: 'const style = `color: var(--wpds-color-fg-content-neutral)`;', | ||
| errors: [ | ||
| { | ||
| messageId: 'disallowed', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: `<div style={ { color: 'var(--wpds-color-fg-content-neutral)' } } />`, | ||
| errors: [ | ||
| { | ||
| messageId: 'disallowed', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: 'const style = `border: 1px solid var(--wpds-border-color, var(--wpds-border-fallback))`;', | ||
| errors: [ | ||
| { | ||
| messageId: 'disallowed', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: `const token = '--wpds-color-fg';`, | ||
| errors: [ | ||
| { | ||
| messageId: 'disallowed', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: 'const style = `--wpds-color-fg: red`;', | ||
| errors: [ | ||
| { | ||
| messageId: 'disallowed', | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| } ); |
| /** | ||
| * External dependencies | ||
| */ | ||
| import { RuleTester } from 'eslint'; | ||
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import rule from '../no-i18n-in-save'; | ||
| const ruleTester = new RuleTester( { | ||
| parserOptions: { | ||
| ecmaVersion: 6, | ||
| sourceType: 'module', | ||
| ecmaFeatures: { | ||
| jsx: true, | ||
| }, | ||
| }, | ||
| } ); | ||
| ruleTester.run( 'no-i18n-in-save', rule, { | ||
| valid: [ | ||
| { | ||
| code: ` | ||
| function edit() { | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| }, | ||
| { | ||
| code: ` | ||
| const edit = () => { | ||
| return __( 'Hello World' ); | ||
| }; | ||
| `, | ||
| }, | ||
| { | ||
| code: ` | ||
| const settings = { | ||
| edit() { | ||
| return __( 'Hello World' ); | ||
| }, | ||
| }; | ||
| `, | ||
| }, | ||
| { | ||
| code: ` | ||
| // Translation functions are fine in non-save files | ||
| function render() { | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| }, | ||
| { | ||
| code: ` | ||
| // Translation in edit function | ||
| export default function Edit() { | ||
| return <div>{ __( 'Hello World' ) }</div>; | ||
| } | ||
| `, | ||
| }, | ||
| { | ||
| code: ` | ||
| // Translation in deprecated save function is allowed | ||
| function save() { | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| filename: '/path/to/block/deprecated.js', | ||
| }, | ||
| { | ||
| code: ` | ||
| // Translation in deprecated save function with Windows path | ||
| function save() { | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| filename: 'D:\\path\\to\\block\\deprecated.js', | ||
| }, | ||
| ], | ||
| invalid: [ | ||
| { | ||
| code: ` | ||
| // Translation in save file outside save function | ||
| function render() { | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| filename: '/path/to/block/save.js', | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| function save() { | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| const save = () => { | ||
| return __( 'Hello World' ); | ||
| }; | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| const save = function() { | ||
| return __( 'Hello World' ); | ||
| }; | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| export default function save() { | ||
| return <span>{ __( 'Hello World' ) }</span>; | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| const settings = { | ||
| save() { | ||
| return __( 'Hello World' ); | ||
| }, | ||
| }; | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| const settings = { | ||
| save: () => __( 'Hello World' ), | ||
| }; | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| function save() { | ||
| return _x( 'Hello', 'greeting' ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| function save() { | ||
| const count = 5; | ||
| return _n( 'One item', 'Multiple items', count ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| function save() { | ||
| const count = 5; | ||
| return _nx( 'One item', 'Multiple items', count, 'context' ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| function save() { | ||
| return ( | ||
| <button> | ||
| <span>{ __( 'Click me' ) }</span> | ||
| </button> | ||
| ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| // Multiple translation calls in save | ||
| function save() { | ||
| const label = __( 'Label' ); | ||
| return <div title={ _x( 'Title', 'context' ) }>{ label }</div>; | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: ` | ||
| // Translation after a nested inner function named save must still be caught | ||
| function save() { | ||
| function save() {} | ||
| return __( 'Hello World' ); | ||
| } | ||
| `, | ||
| errors: [ | ||
| { | ||
| messageId: 'noI18nInSave', | ||
| type: 'CallExpression', | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| } ); |
| const DS_TOKEN_PREFIX = 'wpds-'; | ||
| const wpdsTokensRegex = new RegExp( `(?:^|[^\\w])--${ DS_TOKEN_PREFIX }`, 'i' ); | ||
| module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( { | ||
| meta: { | ||
| type: 'problem', | ||
| docs: { | ||
| description: 'Disallow any usage of --wpds-* CSS custom properties', | ||
| }, | ||
| schema: [], | ||
| messages: { | ||
| disallowed: | ||
| 'Design System tokens (--wpds-*) should not be used in this context.', | ||
| }, | ||
| }, | ||
| create( context ) { | ||
| const selector = `:matches(Literal[value=${ wpdsTokensRegex }], TemplateLiteral TemplateElement[value.raw=${ wpdsTokensRegex }])`; | ||
| return { | ||
| /** @param {import('estree').Literal | import('estree').TemplateElement} node */ | ||
| [ selector ]( node ) { | ||
| context.report( { | ||
| node, | ||
| messageId: 'disallowed', | ||
| } ); | ||
| }, | ||
| }; | ||
| }, | ||
| } ); |
| /** | ||
| * Internal dependencies | ||
| */ | ||
| const { | ||
| TRANSLATION_FUNCTIONS, | ||
| getTranslateFunctionName, | ||
| } = require( '../utils' ); | ||
| module.exports = { | ||
| meta: { | ||
| type: 'problem', | ||
| schema: [], | ||
| messages: { | ||
| noI18nInSave: | ||
| 'Translation functions should not be used in block save functions. Translated content is saved to the database and will not update if the language changes.', | ||
| }, | ||
| docs: { | ||
| description: 'Disallow translation functions in block save methods', | ||
| category: 'Best Practices', | ||
| recommended: false, | ||
| }, | ||
| }, | ||
| create( context ) { | ||
| let saveFunctionDepth = 0; | ||
| const filename = context.getFilename(); | ||
| // Skip deprecated files as they preserve old behavior including translation functions | ||
| const normalizedFilename = filename.replace( /\\/g, '/' ); | ||
| const isDeprecatedFile = | ||
| normalizedFilename.includes( '/deprecated.js' ) || | ||
| normalizedFilename.includes( '/deprecated.ts' ) || | ||
| normalizedFilename.includes( '/deprecated.jsx' ) || | ||
| normalizedFilename.includes( '/deprecated.tsx' ); | ||
| if ( isDeprecatedFile ) { | ||
| return {}; | ||
| } | ||
| const isSaveFile = | ||
| normalizedFilename.endsWith( '/save.js' ) || | ||
| normalizedFilename.endsWith( '/save.ts' ) || | ||
| normalizedFilename.endsWith( '/save.jsx' ) || | ||
| normalizedFilename.endsWith( '/save.tsx' ); | ||
| return { | ||
| // Track when we enter a function named 'save' | ||
| FunctionDeclaration( node ) { | ||
| if ( node.id && node.id.name === 'save' ) { | ||
| saveFunctionDepth++; | ||
| } | ||
| }, | ||
| 'FunctionDeclaration:exit'( node ) { | ||
| if ( node.id && node.id.name === 'save' ) { | ||
| saveFunctionDepth--; | ||
| } | ||
| }, | ||
| // Track arrow functions assigned to 'save' | ||
| VariableDeclarator( node ) { | ||
| if ( | ||
| node.id && | ||
| node.id.name === 'save' && | ||
| node.init && | ||
| ( node.init.type === 'ArrowFunctionExpression' || | ||
| node.init.type === 'FunctionExpression' ) | ||
| ) { | ||
| saveFunctionDepth++; | ||
| } | ||
| }, | ||
| 'VariableDeclarator:exit'( node ) { | ||
| if ( | ||
| node.id && | ||
| node.id.name === 'save' && | ||
| node.init && | ||
| ( node.init.type === 'ArrowFunctionExpression' || | ||
| node.init.type === 'FunctionExpression' ) | ||
| ) { | ||
| saveFunctionDepth--; | ||
| } | ||
| }, | ||
| // Track object properties named 'save' | ||
| 'Property[key.name="save"]'( node ) { | ||
| if ( | ||
| node.value && | ||
| ( node.value.type === 'FunctionExpression' || | ||
| node.value.type === 'ArrowFunctionExpression' ) | ||
| ) { | ||
| saveFunctionDepth++; | ||
| } | ||
| }, | ||
| 'Property[key.name="save"]:exit'( node ) { | ||
| if ( | ||
| node.value && | ||
| ( node.value.type === 'FunctionExpression' || | ||
| node.value.type === 'ArrowFunctionExpression' ) | ||
| ) { | ||
| saveFunctionDepth--; | ||
| } | ||
| }, | ||
| // Check for translation function calls | ||
| CallExpression( node ) { | ||
| const { callee } = node; | ||
| const functionName = getTranslateFunctionName( callee ); | ||
| if ( ! TRANSLATION_FUNCTIONS.has( functionName ) ) { | ||
| return; | ||
| } | ||
| // Report if we're in a save file or inside a save function | ||
| if ( isSaveFile || saveFunctionDepth > 0 ) { | ||
| context.report( { | ||
| node, | ||
| messageId: 'noI18nInSave', | ||
| } ); | ||
| } | ||
| }, | ||
| }; | ||
| }, | ||
| }; |
+5
-5
| { | ||
| "name": "@wordpress/eslint-plugin", | ||
| "version": "24.2.1-next.v.202602241322.0+bce7cff88", | ||
| "version": "24.3.0", | ||
| "description": "ESLint plugin for WordPress development.", | ||
@@ -43,5 +43,5 @@ "author": "The WordPress Contributors", | ||
| "@typescript-eslint/parser": "^6.4.1", | ||
| "@wordpress/babel-preset-default": "^8.40.1-next.v.202602241322.0+bce7cff88", | ||
| "@wordpress/prettier-config": "^4.40.1-next.v.202602241322.0+bce7cff88", | ||
| "@wordpress/theme": "^0.8.1-next.v.202602241322.0+bce7cff88", | ||
| "@wordpress/babel-preset-default": "^8.41.0", | ||
| "@wordpress/prettier-config": "^4.41.0", | ||
| "@wordpress/theme": "^0.8.0", | ||
| "cosmiconfig": "^7.0.0", | ||
@@ -82,3 +82,3 @@ "eslint-config-prettier": "^8.3.0", | ||
| }, | ||
| "gitHead": "943dde7f0b600ce238726c36284bc9f70ce0ffa4" | ||
| "gitHead": "8bfc179b9aed74c0a6dd6e8edf7a49e40e4f87cc" | ||
| } |
+1
-0
@@ -87,2 +87,3 @@ # ESLint Plugin | ||
| | [components-no-unsafe-button-disabled](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/components-no-unsafe-button-disabled.md) | Disallow using `disabled` on Button without `accessibleWhenDisabled`. | ✓ | | ||
| | [no-i18n-in-save](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-i18n-in-save.md) | Disallow translation functions in block save methods. | | | ||
| | [no-unguarded-get-range-at](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unguarded-get-range-at.md) | Disallow the usage of unguarded `getRangeAt` calls. | ✓ | | ||
@@ -89,0 +90,0 @@ | [no-unsafe-wp-apis](https://github.com/WordPress/gutenberg/tree/HEAD/packages/eslint-plugin/docs/rules/no-unsafe-wp-apis.md) | Disallow the usage of unsafe APIs from `@wordpress/*` packages | ✓ | |
@@ -30,2 +30,14 @@ import { RuleTester } from 'eslint'; | ||
| }, | ||
| { | ||
| code: `const token = 'var(--wpds-color-fg-content-neutral)';`, | ||
| }, | ||
| { | ||
| code: `const name = 'something--wpds-color';`, | ||
| }, | ||
| { | ||
| code: '`${ prefix }: var(--wpds-color-fg-content-neutral)`', | ||
| }, | ||
| { | ||
| code: '`var(--wpds-color-fg-content-neutral) ${ suffix }`', | ||
| }, | ||
| ], | ||
@@ -38,2 +50,5 @@ invalid: [ | ||
| messageId: 'onlyKnownTokens', | ||
| data: { | ||
| tokenNames: "'--wpds-nonexistent-token'", | ||
| }, | ||
| }, | ||
@@ -58,6 +73,77 @@ ], | ||
| messageId: 'onlyKnownTokens', | ||
| data: { | ||
| tokenNames: "'--wpds-nonexistent'", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: `const token = 'var(--wpds-nonexistent-token)';`, | ||
| errors: [ | ||
| { | ||
| messageId: 'onlyKnownTokens', | ||
| data: { | ||
| tokenNames: "'--wpds-nonexistent-token'", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: 'const token = `var(--wpds-nonexistent-token)`;', | ||
| errors: [ | ||
| { | ||
| messageId: 'onlyKnownTokens', | ||
| data: { | ||
| tokenNames: "'--wpds-nonexistent-token'", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: 'const token = `var(--wpds-dimension-gap-${ size })`;', | ||
| errors: [ | ||
| { | ||
| messageId: 'dynamicToken', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: '<div style={ { gap: `var(--wpds-dimension-gap-${ size })` } } />', | ||
| errors: [ | ||
| { | ||
| messageId: 'dynamicToken', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: `const token = '--wpds-nonexistent-token';`, | ||
| errors: [ | ||
| { | ||
| messageId: 'onlyKnownTokens', | ||
| data: { | ||
| tokenNames: "'--wpds-nonexistent-token'", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: 'const style = `--wpds-dimension-gap-${ size }`;', | ||
| errors: [ | ||
| { | ||
| messageId: 'dynamicToken', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| code: '`${ prefix }: var(--wpds-nonexistent-token)`', | ||
| errors: [ | ||
| { | ||
| messageId: 'onlyKnownTokens', | ||
| data: { | ||
| tokenNames: "'--wpds-nonexistent-token'", | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| } ); |
@@ -39,3 +39,3 @@ const tokenListModule = require( '@wordpress/theme/design-tokens.js' ); | ||
| const knownTokens = new Set( tokenList ); | ||
| const wpdsTokensRegex = new RegExp( `[^\\w]--${ DS_TOKEN_PREFIX }`, 'i' ); | ||
| const wpdsTokensRegex = new RegExp( `(?:^|[^\\w])--${ DS_TOKEN_PREFIX }`, 'i' ); | ||
@@ -52,9 +52,80 @@ module.exports = /** @type {import('eslint').Rule.RuleModule} */ ( { | ||
| 'The following CSS variables are not valid Design System tokens: {{ tokenNames }}', | ||
| dynamicToken: | ||
| 'Design System tokens must not be dynamically constructed, as they cannot be statically verified for correctness or processed automatically to inject fallbacks.', | ||
| }, | ||
| }, | ||
| create( context ) { | ||
| const disallowedTokensAST = `JSXAttribute[name.name="style"] :matches(Literal[value=${ wpdsTokensRegex }], TemplateLiteral TemplateElement[value.raw=${ wpdsTokensRegex }])`; | ||
| const dynamicTemplateLiteralAST = `TemplateLiteral[expressions.length>0]:has(TemplateElement[value.raw=${ wpdsTokensRegex }])`; | ||
| const staticTokensAST = `:matches(Literal[value=${ wpdsTokensRegex }], TemplateLiteral[expressions.length=0] TemplateElement[value.raw=${ wpdsTokensRegex }])`; | ||
| const dynamicTokenEndRegex = new RegExp( | ||
| `--${ DS_TOKEN_PREFIX }[\\w-]*$` | ||
| ); | ||
| return { | ||
| /** | ||
| * For template literals with expressions, check each quasi | ||
| * individually: flag as dynamic only when a `--wpds-*` token | ||
| * name is split across a quasi/expression boundary, and | ||
| * validate any complete static tokens normally. | ||
| * | ||
| * @param {import('estree').TemplateLiteral} node | ||
| */ | ||
| [ dynamicTemplateLiteralAST ]( node ) { | ||
| let hasDynamic = false; | ||
| const unknownTokens = []; | ||
| for ( const quasi of node.quasis ) { | ||
| const raw = quasi.value.raw; | ||
| const value = quasi.value.cooked ?? raw; | ||
| const isFollowedByExpression = ! quasi.tail; | ||
| if ( | ||
| isFollowedByExpression && | ||
| dynamicTokenEndRegex.test( raw ) | ||
| ) { | ||
| hasDynamic = true; | ||
| } | ||
| const tokens = extractCSSVariables( | ||
| value, | ||
| DS_TOKEN_PREFIX | ||
| ); | ||
| // Remove the trailing incomplete token — it's the one | ||
| // being dynamically constructed by the next expression. | ||
| if ( isFollowedByExpression ) { | ||
| const endMatch = value.match( /(--([\w-]+))$/ ); | ||
| if ( endMatch ) { | ||
| tokens.delete( endMatch[ 1 ] ); | ||
| } | ||
| } | ||
| for ( const token of tokens ) { | ||
| if ( ! knownTokens.has( token ) ) { | ||
| unknownTokens.push( token ); | ||
| } | ||
| } | ||
| } | ||
| if ( hasDynamic ) { | ||
| context.report( { | ||
| node, | ||
| messageId: 'dynamicToken', | ||
| } ); | ||
| } | ||
| if ( unknownTokens.length > 0 ) { | ||
| context.report( { | ||
| node, | ||
| messageId: 'onlyKnownTokens', | ||
| data: { | ||
| tokenNames: unknownTokens | ||
| .map( ( token ) => `'${ token }'` ) | ||
| .join( ', ' ), | ||
| }, | ||
| } ); | ||
| } | ||
| }, | ||
| /** @param {import('estree').Literal | import('estree').TemplateElement} node */ | ||
| [ disallowedTokensAST ]( node ) { | ||
| [ staticTokensAST ]( node ) { | ||
| let computedValue; | ||
@@ -67,3 +138,2 @@ | ||
| if ( typeof node.value === 'string' ) { | ||
| // Get the node's value when it's a "string" | ||
| computedValue = node.value; | ||
@@ -74,3 +144,2 @@ } else if ( | ||
| ) { | ||
| // Get the node's value when it's a `template literal` | ||
| computedValue = node.value.cooked ?? node.value.raw; | ||
@@ -77,0 +146,0 @@ } |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 8 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 8 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
221483
6.23%79
5.33%6589
10.59%0
-100%113
0.89%0
-100%+ Added
- Removed
Updated