Skip to content
This repository has been archived by the owner on Apr 2, 2023. It is now read-only.

Commit

Permalink
feat: add detect method to allow discovering PII in normal data
Browse files Browse the repository at this point in the history
  • Loading branch information
tdreyno committed Jun 4, 2021
1 parent e5cf46b commit 170cc65
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 8 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ const name = PII("Thomas")
const lowercaseName = tap(n => console.log(n), name) // Logs "Thomas"
```

#### Detecting PII in Data

Recurses through a data structure and uses a callback to detect values that should become PII.

```typescript
import { PII, detect } from "@tdreyno/pii"

const person = { name: "Thomas" }
const lowercaseName = detect(
data => isObject(person) && Object.keys().some(k => k.includes(name)),
person,
) // Returns PII({ name: "Thomas" })
```

#### Custom PII Redaction

```typescript
Expand Down
28 changes: 28 additions & 0 deletions src/__tests__/detect.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { detect, isPII } from "../index"

const detector = (data: unknown) => Array.isArray(data)

describe("detect", () => {
it("should detect variables", () => {
expect(detect(detector, "test")).toBe("test")
expect(isPII(detect(detector, []))).toBeTruthy()
})

it("should handle Map", () => {
const map = new Map<string, any>([
["a", 1],
["b", []],
])

const detectedArrays = detect(detector, { test: map })
expect(isPII((detectedArrays as any).test.get("b"))).toBeTruthy()
})

it("should handle Set", () => {
const set = new Set([[]])

const detectedArrays = detect(detector, { test: set })
expect(isPII(Array.from((detectedArrays as any).test)[0])).toBeTruthy()
})
})
43 changes: 35 additions & 8 deletions src/pii.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ export interface PII<T> {
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isPIIType = <T>(val: any): val is PII<T> =>
export const isPII = <T>(val: any): val is PII<T> =>
isRecord(val) && val.__brand === "PII"

export const PII = <T>(val: T, msg = "REDACTED"): PII<T> =>
isPIIType<T>(val)
isPII<T>(val)
? val
: ({
__brand: "PII",
Expand All @@ -24,7 +24,7 @@ export function unwrap<T>(item: PII<T>): Exclude<T, PII<any>>
export function unwrap<T>(item: T): Exclude<T, PII<any>>
export function unwrap<T>(item: T | PII<T>): Exclude<T, PII<any>> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return isPIIType(item)
return isPII(item)
? (item as any)[
"__fire_me_if_you_see_me_accessing_this_property_outside_pii_ts"
]
Expand Down Expand Up @@ -135,20 +135,20 @@ export const visitPII = <A, T>(
}

export const containsPII = (input: unknown): boolean =>
isPIIType(input)
isPII(input)
? true
: visitPII(input, {
record: o => Object.values(o).some(containsPII),
map: m =>
Array.from(m).some(([k, v]) => containsPII(k) || containsPII(v)),
array: a => a.some(containsPII),
set: s => Array.from(s).some(containsPII),
primitive: p => isPIIType(p),
object: p => isPIIType(p),
primitive: p => isPII(p),
object: p => isPII(p),
})

export const unwrapObject = (input: unknown): unknown =>
visitPII(isPIIType(input) ? unwrap(input) : input, {
visitPII(isPII(input) ? unwrap(input) : input, {
record: o =>
Object.keys(o).reduce((sum, key) => {
sum[key] = unwrapObject(o[key])
Expand All @@ -165,7 +165,7 @@ export const unwrapObject = (input: unknown): unknown =>
})

export const redact = (redactor: (data: any) => any, input: unknown): unknown =>
visitPII(isPIIType(input) ? redactor(input) : input, {
visitPII(isPII(input) ? redactor(input) : input, {
record: o =>
Object.keys(o).reduce((sum, key) => {
sum[key] = redact(redactor, o[key])
Expand All @@ -183,3 +183,30 @@ export const redact = (redactor: (data: any) => any, input: unknown): unknown =>
primitive: p => p,
object: p => p,
})

export const detect = (
detector: (data: any) => boolean,
input: unknown,
): unknown =>
isPII(input)
? input
: detector(input)
? PII(input)
: visitPII(input, {
record: o =>
Object.keys(o).reduce((sum, key) => {
sum[key] = detect(detector, o[key])
return sum
}, {} as Record<string, unknown>),
map: m =>
new Map(
Array.from(m).map(([k, v]) => [
detect(detector, k),
detect(detector, v),
]),
),
array: a => a.map(x => detect(detector, x)),
set: s => new Set(Array.from(s).map(x => detect(detector, x))),
primitive: p => p,
object: p => p,
})

0 comments on commit 170cc65

Please sign in to comment.