This commit is contained in:
sharevb 2024-08-09 11:19:46 +01:00 committed by GitHub
commit 82374a0797
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 216 additions and 16 deletions

8
components.d.ts vendored
View file

@ -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']

View file

@ -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",

35
pnpm-lock.yaml generated
View file

@ -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

View file

@ -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,
],
},
{

View file

@ -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'),
});

View file

@ -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',
}`);
});
});
});

View file

@ -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<MatcherNames>
}) {
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[],
});
}

View file

@ -0,0 +1,64 @@
<script setup lang="ts">
import { type MatcherNames, maskSensitiveData } from './sensitive-data-masker.service';
import { withDefaultOnError } from '@/utils/defaults';
const defaultValue = `{
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',
}`;
const customRegex = useStorage('sensitive-data:regex', '');
const excludedMatchers = useStorage('sensitive-data:exclude', [] as string[]);
const allMatchers = [
'uuid', 'creditCard', 'ssn', 'url', 'ipv4', 'email',
'passwordInUri', 'mac', 'ipv6', 'urlWithOrWithoutPrefix',
'jwt', 'phone'];
function transformer(value: string) {
return withDefaultOnError(() => maskSensitiveData({
value,
customRegex: customRegex.value,
excludedMatchers: excludedMatchers.value as MatcherNames[],
}), '');
}
</script>
<template>
<div style="max-width: 600px;">
<c-input-text
v-model:value="customRegex"
label="Your custom cleaning regex(es) (case insensitive):"
placeholder="Your custom cleaning regex(es)"
raw-text
multiline
rows="4"
mb-2
/>
<n-select
v-model:value="excludedMatchers"
placeholder="No Fallback"
multiple
:fallback-option="false"
:options="allMatchers.map(v => ({ label: v, value: v }))"
mb-2
/>
<format-transformer
input-label="Your log/textual data:"
:input-default="defaultValue"
input-placeholder="Paste your log/textual data here..."
output-label="Cleaned version:"
:transformer="transformer"
/>
</div>
</template>