diff --git a/components.d.ts b/components.d.ts index f2c3146f..18d55a57 100644 --- a/components.d.ts +++ b/components.d.ts @@ -130,21 +130,14 @@ declare module '@vue/runtime-core' { NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] - NDivider: typeof import('naive-ui')['NDivider'] NEllipsis: typeof import('naive-ui')['NEllipsis'] - NFormItem: typeof import('naive-ui')['NFormItem'] - NGi: typeof import('naive-ui')['NGi'] - NGrid: typeof import('naive-ui')['NGrid'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] - NInputNumber: typeof import('naive-ui')['NInputNumber'] - NLabel: typeof import('naive-ui')['NLabel'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] NScrollbar: typeof import('naive-ui')['NScrollbar'] - NSpin: typeof import('naive-ui')['NSpin'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] @@ -160,6 +153,7 @@ declare module '@vue/runtime-core' { RouterView: typeof import('vue-router')['RouterView'] RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default'] + SensitiveDataMasker: typeof import('./src/tools/sensitive-data-masker/sensitive-data-masker.vue')['default'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] diff --git a/package.json b/package.json index d1e6e458..fd132bae 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "cron-validator": "^1.3.1", "cronstrue": "^2.26.0", "crypto-js": "^4.1.1", + "data-guardian": "^1.1.3", "date-fns": "^2.29.3", "dompurify": "^3.0.6", "emojilib": "^3.0.10", @@ -64,6 +65,7 @@ "highlight.js": "^11.7.0", "iarna-toml-esm": "^3.0.5", "ibantools": "^4.3.3", + "ip-regex": "^5.0.0", "js-base64": "^3.7.6", "json5": "^2.2.3", "jwt-decode": "^3.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8619d8c0..3bbb4ac2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ dependencies: crypto-js: specifier: ^4.1.1 version: 4.1.1 + data-guardian: + specifier: ^1.1.3 + version: 1.1.3 date-fns: specifier: ^2.29.3 version: 2.29.3 @@ -92,6 +95,9 @@ dependencies: ibantools: specifier: ^4.3.3 version: 4.3.3 + ip-regex: + specifier: ^5.0.0 + version: 5.0.0 js-base64: specifier: ^3.7.6 version: 3.7.7 @@ -3354,7 +3360,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.8.0(vue@3.3.4) + '@vueuse/shared': 10.11.0(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -3987,19 +3993,19 @@ packages: - vue dev: false - /@vueuse/shared@10.3.0(vue@3.3.4): - resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} + /@vueuse/shared@10.11.0(vue@3.3.4): + resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==} dependencies: - vue-demi: 0.14.5(vue@3.3.4) + vue-demi: 0.14.8(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue dev: false - /@vueuse/shared@10.8.0(vue@3.3.4): - resolution: {integrity: sha512-dUdy6zwHhULGxmr9YUg8e+EnB39gcM4Fe2oKBSrh3cOsV30JcMPtsyuspgFCUo5xxFNaeMf/W2yyKfST7Bg8oQ==} + /@vueuse/shared@10.3.0(vue@3.3.4): + resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} dependencies: - vue-demi: 0.14.7(vue@3.3.4) + vue-demi: 0.14.5(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -4719,6 +4725,11 @@ packages: resolution: {integrity: sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==} dev: false + /data-guardian@1.1.3: + resolution: {integrity: sha512-8M9KHTopFp3MXo7NelcmF+8L77S5jeDy3Uv4RWuYIQSaBZTawvY6W3ad/eLe8db2FcLPoC44DHJb4zWNcpoueQ==} + engines: {node: '>=12'} + dev: false + /data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -6188,6 +6199,11 @@ packages: jsbn: 1.1.0 dev: false + /ip-regex@5.0.0: + resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: false + /is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} dev: true @@ -9158,8 +9174,8 @@ packages: vue: 3.3.4 dev: false - /vue-demi@0.14.7(vue@3.3.4): - resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} + /vue-demi@0.14.8(vue@3.3.4): + resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==} engines: {node: '>=12'} hasBin: true requiresBuild: true @@ -9449,6 +9465,7 @@ packages: /workbox-google-analytics@7.0.0: resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} + deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained dependencies: workbox-background-sync: 7.0.0 workbox-core: 7.0.0 diff --git a/src/tools/index.ts b/src/tools/index.ts index aa861c93..2f0aafea 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -6,6 +6,7 @@ import { tool as asciiTextDrawer } from './ascii-text-drawer'; import { tool as textToUnicode } from './text-to-unicode'; import { tool as safelinkDecoder } from './safelink-decoder'; +import { tool as sensitiveDataMasker } from './sensitive-data-masker'; import { tool as pdfSignatureChecker } from './pdf-signature-checker'; import { tool as numeronymGenerator } from './numeronym-generator'; import { tool as macAddressGenerator } from './mac-address-generator'; @@ -172,6 +173,7 @@ export const toolsByCategory: ToolCategory[] = [ textDiff, numeronymGenerator, asciiTextDrawer, + sensitiveDataMasker, ], }, { diff --git a/src/tools/sensitive-data-masker/index.ts b/src/tools/sensitive-data-masker/index.ts new file mode 100644 index 00000000..ccafe88f --- /dev/null +++ b/src/tools/sensitive-data-masker/index.ts @@ -0,0 +1,12 @@ +import { ShieldLock } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Sensitive data masker', + path: '/sensitive-data-masker', + description: 'Clean sensitive data from textual content (ie logs)', + keywords: ['sensitive', 'data', 'masker', 'obfuscator', 'clean', 'log'], + component: () => import('./sensitive-data-masker.vue'), + icon: ShieldLock, + createdAt: new Date('2024-06-16'), +}); diff --git a/src/tools/sensitive-data-masker/sensitive-data-masker.service.test.ts b/src/tools/sensitive-data-masker/sensitive-data-masker.service.test.ts new file mode 100644 index 00000000..49c61137 --- /dev/null +++ b/src/tools/sensitive-data-masker/sensitive-data-masker.service.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it } from 'vitest'; +import { maskSensitiveData } from './sensitive-data-masker.service'; + +describe('sensitive-data-masker', () => { + describe('maskSensitiveData', () => { + const data = `{ + email: 'john.doe@example.com', + creditCard: '1234 5678 9000 9876', + id: '3f8a43fd-6489-4ec7-bd55-7a1ba172d77b', + name: 'John', + surname: 'Doe', + phone: '+358 40 1234567', + url: 'truc.google.com', + ip4: '83.24.45.56', + ip6: '2001:db8:0:85a3:0:0:ac1f:8001', + mac: '3D:F2:C9:A6:B3:4F', + token: 'eyJhbGciOiJIUzI1NiJ9.ew0KICAic3ViIjogIjEyMzQ1Njc4OTAiLA0KICAibmFtZSI6ICJBbGV4IEtvemxvdiIsDQogICJpYXQiOiAxNTE2MjM5MDIyDQp9.PNKysYFTCenU5bekHCmwIxCUXoYG41H_xc3uN3ZF_b8', +}`; + + it('should maks sensitive data', () => { + expect(maskSensitiveData({ + value: data, + })).toBe(`{ + email: 'jo****************om', + creditCard: '12***************76', + id: '3f********************************7b', + name: 'John', + surname: 'Doe', + phone: '+3***********67', + url: 'tr***********om', + ip4: '83*******56', + ip6: '20*************************01', + mac: '3D*************4F', + token: 'ey*****************************************************************************************************************************************************************b8', +}`); + }); + it('should maks sensitive data (with custom regex)', () => { + expect(maskSensitiveData({ + value: data, + customRegex: 'John\nDoe', + })).toBe(`{ + email: 'jo****************om', + creditCard: '12***************76', + id: '3f********************************7b', + name: '****', + surname: '***', + phone: '+3***********67', + url: 'tr***********om', + ip4: '83*******56', + ip6: '20*************************01', + mac: '3D*************4F', + token: 'ey*****************************************************************************************************************************************************************b8', +}`); + }); + + it('should maks sensitive data (with excluded matchers)', () => { + expect(maskSensitiveData({ + value: data, + excludedMatchers: ['mac', 'ipv4'], + })).toBe(`{ + email: 'jo****************om', + creditCard: '12***************76', + id: '3f********************************7b', + name: 'John', + surname: 'Doe', + phone: '+3***********67', + url: 'tr***********om', + ip4: '83.24.45.56', + ip6: '20*************************01', + mac: '3D:F2:C9:A6:B3:4F', + token: 'ey*****************************************************************************************************************************************************************b8', +}`); + }); + }); +}); diff --git a/src/tools/sensitive-data-masker/sensitive-data-masker.service.ts b/src/tools/sensitive-data-masker/sensitive-data-masker.service.ts new file mode 100644 index 00000000..28e6aa71 --- /dev/null +++ b/src/tools/sensitive-data-masker/sensitive-data-masker.service.ts @@ -0,0 +1,34 @@ +import { type SensitiveContentKey, maskString } from 'data-guardian'; +import ipRegex from 'ip-regex'; + +const jwtRegex = /\b([a-zA-Z0-9_=]{5,})\.([a-zA-Z0-9_=]{5,})\.([a-zA-Z0-9_\-\+\/=]{5,})\b/g; +const phoneRegex = /(?:(\+\d{1,4})[-.\s]?)(?:[(](\d{1,3})[)][-.\s]?)?(\d{1,4})[-.\s]?(\d{1,4})[-.\s]?(\d{1,9})\b/g; +const macRegex = /\b([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})\b/g; +const urlWithOrWithoutPrefixRegex = /\b(https?:\/\/)?(www\.)?[a-zA-Z0-9@:%._+~#=-]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&\/=]*)\b/g; + +export type MatcherNames = 'uuid' | 'creditCard' | 'ssn' | 'url' | 'ipv4' | 'email' | 'passwordInUri' | 'mac' | 'ipv6' | 'urlWithOrWithoutPrefix' | 'jwt' | 'phone'; + +export function maskSensitiveData({ + value, + customRegex = '', + excludedMatchers = [], +}: { + value: string + customRegex?: string + excludedMatchers?: Array +}) { + excludedMatchers = excludedMatchers || []; + const emptyRegex = /(?:)/g; + return maskString(value, null as never, { + customRegex: new RegExp((customRegex || '').split('\n').map(line => `(${line})`).join('|'), 'gi'), + macRegex: excludedMatchers.includes('mac') ? emptyRegex : macRegex, + ipv6Regex: excludedMatchers.includes('ipv6') ? emptyRegex : ipRegex.v6({ includeBoundaries: false }), + urlWithOrWithoutPrefixRegex: excludedMatchers.includes('urlWithOrWithoutPrefix') ? emptyRegex : urlWithOrWithoutPrefixRegex, + jwtRegex: excludedMatchers.includes('jwt') ? emptyRegex : jwtRegex, + phoneRegex: excludedMatchers.includes('phone') ? emptyRegex : phoneRegex, + }, { + excludeMatchers: [...excludedMatchers, ...[ + 'passwordMention', 'password', 'passwordSubstring', + ]] as SensitiveContentKey[], + }); +} diff --git a/src/tools/sensitive-data-masker/sensitive-data-masker.vue b/src/tools/sensitive-data-masker/sensitive-data-masker.vue new file mode 100644 index 00000000..7c9d970e --- /dev/null +++ b/src/tools/sensitive-data-masker/sensitive-data-masker.vue @@ -0,0 +1,64 @@ + + +