jsmasker
v2.0.0
Published
A flexible module to mask sensitive properties in objects with customizable options, for both Node.js and browser
Downloads
303
Maintainers
Readme
JSMasker

JSMasker is a flexible JavaScript module that masks sensitive properties in objects and arrays by replacing them with customizable masks. It can be used in both Node.js and browser environments.
Table of Contents
- Installation
- Usage
- Configuration Options
- Examples
- Migration
- Compatibility
- Building and Publishing
- Contributing
- License
Installation
Node.js
npm install jsmaskerBrowser
Include JSMasker directly in your HTML file using a CDN:
<!-- Latest -->
<script src="https://cdn.jsdelivr.net/npm/jsmasker@latest/dist/jsmasker.umd.min.js"></script>
<!-- Specific version -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jsmasker.umd.min.js"></script>Usage
ESM Guidance
- For Node ESM projects (
"type": "module"):import maskObject from 'jsmasker'. - For CommonJS:
const maskObject = require('jsmasker'). - For bundlers (Vite/Rollup/Webpack): use the bare import
jsmasker; the package exports map points to ESM. - Avoid importing from internal paths like
jsmasker/src/*; use the package entry to stay compatible. - Types are included. In TypeScript,
import maskObject, { type MaskConfig } from 'jsmasker'.
Node (ESM)
import maskObject from 'jsmasker'
const masked = maskObject({ password: 'secret' })Node (CommonJS)
const maskObject = require('jsmasker')
const masked = maskObject({ password: 'secret' })Browser (UMD)
<script src="/dist/jsmasker.umd.min.js"></script>
<script>
const masked = JSMasker({ token: 'abc123' })
console.log(masked)
</script>Webpack (summary)
Webpack 5 handles ESM by default. No special configuration is required to use JSMasker.
// In your app code
import maskObject from 'jsmasker'
const masked = maskObject({ secret: 'value' })Notes:
- Works with both ESM and CommonJS source files. For CommonJS, use
const maskObject = require('jsmasker'). - Avoid deep imports like
jsmasker/src/*; use the package entryjsmaskerso Webpack resolves the published ESM/CJS correctly. - For legacy browser targets, configure your transpiler (Babel/SWC) as usual; JSMasker ships modern syntax but is small and tree-shakeable.
- If you need a global in a plain HTML page, use the UMD build:
dist/jsmasker.umd.min.js(globalJSMasker).
Masking Objects
const sensitiveObject = {
username: 'john_doe',
password: 'secret123',
data: {
apiKey: 'abcdef123456'
}
}
const maskedObject = maskObject(sensitiveObject)
console.log(maskedObject)
// Output:
// {
// username: 'john_doe',
// password: '********',
// data: {
// apiKey: '********'
// }
// }Case-Insensitive Matching
JSMasker performs case-insensitive property matching and normalizes property names by removing special characters. This means properties will be masked regardless of their case or format. For example:
const sensitiveData = {
// All of these will be masked as they match 'password'
password: 'secret1',
Password: 'secret2',
PASSWORD: 'secret3',
pass_word: 'secret4',
'pass-word': 'secret5',
// All of these will be masked as they match 'apikey'
apiKey: 'key1',
API_KEY: 'key2',
'api-key': 'key3',
ApiKey: 'key4'
}
const maskedData = maskObject(sensitiveData)
// All matching properties will be masked regardless of case/formatDefault properties that are automatically masked (case-insensitive):
- password
- key
- secret
- token
- privatekey
- passphrase
You can specify custom properties to mask, which will also be matched case-insensitively:
const data = {
ACCESS_TOKEN: 'abc123',
accessToken: 'def456',
access_token: 'ghi789'
}
const masked = maskObject(data, {
properties: ['access_token'] // will match all variations
})Masking Arrays
Mask values in arrays by targeting the containing key:
const data = {
action: 'keyboard-interactive',
responses: ['sensitive_password', 'another_sensitive_value']
}
const masked = maskObject(data, { properties: ['responses'] })
// masked.responses -> ['********', '********']Nested Structures
JSMasker handles nested objects and arrays:
const complexData = {
user: {
name: 'John Doe',
credentials: ['user123', 'password123']
},
apiKeys: ['key1', 'key2', 'key3']
}
const maskedData = maskObject(complexData, {
properties: ['credentials', 'apiKeys']
})
console.log(maskedData)
// Output:
// {
// user: {
// name: 'John Doe',
// credentials: ['*******', '***********']
// },
// apiKeys: ['****', '****', '****']
// }Configuration Options
- properties: array of strings. Default:
['password','key','secret','token','privatekey','passphrase'] - propertyMatcher: optional function
(key, normalizedKey, value, path) => boolean - maskLength: number or
'random'(default:8) - minLength: number (default:
5) - maxLength: number (default:
15) - maskChar: string (default:
'*') - fullMask: string or boolean (default:
false)
Examples
- Using random mask length:
const masked = maskObject(sensitiveData, { maskLength: 'random', minLength: 3, maxLength: 8 })- Masking specific array elements:
const data = {
publicInfo: ['John', 'Doe', '1990'],
privateInfo: ['SSN123456', 'CC4321']
}
const masked = maskObject(data, { properties: ['privateInfo'] })
// Result:
// {
// publicInfo: ['John', 'Doe', '1990'],
// privateInfo: ['********', '********']
// }- Using a full mask string:
const masked = maskObject(sensitiveData, { fullMask: 'REDACTED' })Features
Nested Object Handling
JSMasker recursively traverses nested objects and arrays, applying the mask to sensitive properties at any level of nesting. This ensures that all sensitive data is properly masked, regardless of its position in the object structure.
Full Mask Option
The fullMask option can be used in two ways:
- As a string: When set to a string value (e.g., 'REDACTED'), all sensitive properties will be replaced with this exact string.
- As a boolean: When set to
true, sensitive properties will be fully masked using the specifiedmaskCharrepeated to match the original string length. When set tofalse(default), the standard masking behavior is applied.
Non-Object Input Handling
If JSMasker receives a non-object input (such as a string, number, or null), it will return the input unchanged. This ensures that the function can be safely used even when the input type is uncertain.
Custom Property Matching
While property lists work well for simple cases, sometimes you need more complex matching logic. JSMasker supports a custom property matcher function that gives you complete control over which properties should be masked:
const sensitiveData = {
user: {
name: 'John Smith',
ssn: '123-45-6789',
creditCard: '4111111111111111',
address: {
street: '123 Main St',
personal: {
birthdate: '01/01/1980'
}
}
}
}
const masked = maskObject(sensitiveData, {
propertyMatcher: (key, normalizedKey, value, path) => {
// Mask properties with specific names
if (['ssn', 'creditcard'].includes(normalizedKey)) return true
// Mask values that look like credit card numbers
if (typeof value === 'string' && value.match(/^\d{16}$/)) return true
// Mask any property inside a 'personal' object
if (path.some(p => String(p).toLowerCase().replace(/[^a-z0-9]/g, '') === 'personal')) return true
// Don't mask anything else
return false
}
})
console.log(masked)
// Output:
// {
// user: {
// name: 'John Smith',
// ssn: '********',
// creditCard: '********',
// address: {
// street: '123 Main St',
// personal: {
// birthdate: '********'
// }
// }
// }
// }The property matcher function receives four arguments:
key: The original property keynormalizedKey: The normalized property key (lowercase with special characters removed)value: The property valuepath: An array representing the path to the current property
When to use Property Lists vs. Custom Matcher Functions
Use property lists when:
- You have a set of well-defined field names to mask
- Your masking requirements are simple and name-based
- You want the simplest configuration possible
Use custom matcher functions when:
- You need to mask based on property values, not just names
- You want to use regular expressions for property matching
- Your masking logic depends on the property's location (path) in the object
- You need conditional logic that can't be expressed with just property names
- You want to implement dynamic or context-aware masking rules
Examples
- Using random mask length:
const masked = maskObject(sensitiveObject, { maskLength: 'random', minLength: 3, maxLength: 8 })- Using a custom mask character:
const masked = maskObject(sensitiveObject, { maskChar: '#' })- Using a full mask string:
const masked = maskObject(sensitiveObject, { fullMask: 'REDACTED' })- Using boolean full mask:
const masked = maskObject(sensitiveObject, { fullMask: true })Migration
This release updates packaging to work seamlessly with modern ESM bundlers (Vite/Rollup) and includes TypeScript types.
What changed (>= 2.x):
- Source is ESM-only; no UMD wrapper and no runtime
require. - Builds ship three formats:
dist/index.esm.js,dist/index.cjs,dist/jsmasker.umd.min.js. - TypeScript declarations are included at
dist/index.d.ts. - Polyfills were removed; target is modern environments (Node >= 18, modern browsers).
- Source is ESM-only; no UMD wrapper and no runtime
Previous issue fixed (<= 1.7.x):
- A UMD wrapper caused a
require('./polyfills')path to leak into browser bundles, leading to “require is not defined” in Vite/Rollup.
- A UMD wrapper caused a
Actions when upgrading:
- Replace deep imports like
jsmasker/src/index.jswithjsmasker. - If your HTML referenced
dist/jsmasker.min.js, switch todist/jsmasker.umd.min.js. - Remove bundler aliases that pointed to internal source files.
- TypeScript: import types directly from the package; no separate
@typesneeded.
- Replace deep imports like
Verify:
- In Vite: import
jsmasker, build, and confirm norequire(appears in the output.
- In Vite: import
Compatibility
- Node: >= 18 (Node 20 recommended)
- Module systems: ESM and CommonJS
- Browser: Modern evergreen browsers
- Bundlers:
- Vite >= 4 (Rollup under the hood)
- Rollup >= 3
- Webpack 5
- Tree-shaking: Yes (
sideEffects: false) - TypeScript: Declarations bundled (
dist/index.d.ts)
Building and Publishing
Built with Vite (library mode). Outputs:
dist/index.esm.js(ESM)dist/index.cjs(CommonJS)dist/jsmasker.umd.min.js(UMD, globalJSMasker)dist/index.d.ts(TypeScript declarations)
JSMasker uses release-please to automate the release process.
How It Works
Conventional Commits: We continue to use Conventional Commits for all commit messages. These structured commit messages allow release-please to automatically determine version bumps and generate changelogs.
Automated Release PRs: When commits are pushed to the main branch, release-please automatically creates or updates a release PR that:
- Bumps the version in package.json
- Updates the CHANGELOG.md
- Updates any version references in documentation
Publishing Process:
- When the release PR is merged, release-please automatically creates a GitHub release and tags the commit
- Our GitHub Actions workflow then automatically publishes the package to npm
Benefits of the New Process
- Fully automated versioning based on commit history
- Consistent changelog generation
- Reduced manual steps and potential for human error
- Streamlined release workflow
For local development and building, you can still use:
npm install # Install dependencies
npm run ci # Run linting, tests, and buildTypeScript
Type definitions are bundled. Example:
import maskObject, { type MaskConfig } from 'jsmasker'
const config: MaskConfig = { maskChar: '#', maskLength: 10 }
const masked = maskObject({ secret: 'value' }, config)For detailed information on our development workflow, please refer to the BUILD.md file.
Contributing
Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
License
This project is licensed under the MIT License - see the LICENSE file for details.
