Skip to content

Commit

Permalink
feat(cdp): add ip anon transformation (#28377)
Browse files Browse the repository at this point in the history
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
  • Loading branch information
meikelmosby and greptile-apps[bot] authored Feb 6, 2025
1 parent de1b631 commit db9397e
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { HogFunctionInvocationGlobals } from '../../../types'
import { TemplateTester } from '../../test/test-helpers'
import { template } from './ip-anonymization.template'

describe('ip-anonymization.template', () => {
const tester = new TemplateTester(template)
let mockGlobals: HogFunctionInvocationGlobals

beforeEach(async () => {
await tester.beforeEach()
})

it('should anonymize IPv4 address by zeroing last octet', async () => {
mockGlobals = tester.createGlobals({
event: {
properties: {
$ip: '89.160.20.129',
},
},
})

const response = await tester.invoke({}, mockGlobals)

expect(response.finished).toBe(true)
expect(response.error).toBeUndefined()
expect(response.execResult).toMatchObject({
properties: {
$ip: '89.160.20.0',
},
})
})

it('should handle event with no IP address', async () => {
mockGlobals = tester.createGlobals({
event: {
properties: {},
},
})

const response = await tester.invoke({}, mockGlobals)

expect(response.finished).toBe(true)
expect(response.error).toBeUndefined()
expect(response.execResult).toMatchObject({
properties: {},
})
})

it('should handle invalid IP address format', async () => {
const invalidIPs = [
'89.160.20', // missing octet
'', // empty string
'abc.def.ghi.jkl', // non-numeric
'256.256.256.256', // values > 255
'1.2.3.4.5', // too many octets
'not an ip',
]

for (const invalidIP of invalidIPs) {
mockGlobals = tester.createGlobals({
event: {
properties: {
$ip: invalidIP,
},
},
})

const response = await tester.invoke({}, mockGlobals)

expect(response.finished).toBe(true)
expect(response.error).toBeUndefined()
expect(response.execResult).toMatchObject({
properties: {
$ip: invalidIP,
},
})
}
})

it('should handle various IPv4 formats', async () => {
const testCases = [
{ input: '192.168.1.1', expected: '192.168.1.0' },
{ input: '10.0.0.255', expected: '10.0.0.0' },
{ input: '172.16.254.1', expected: '172.16.254.0' },
]

for (const testCase of testCases) {
mockGlobals = tester.createGlobals({
event: {
properties: {
$ip: testCase.input,
},
},
})

const response = await tester.invoke({}, mockGlobals)

expect(response.finished).toBe(true)
expect(response.error).toBeUndefined()
expect(response.execResult).toMatchObject({
properties: {
$ip: testCase.expected,
},
})
}
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { HogFunctionTemplate } from '../../types'

export const template: HogFunctionTemplate = {
free: true,
status: 'alpha',
type: 'transformation',
id: 'template-ip-anonymization',
name: 'IP Anonymization',
description:
'This transformation sets the last octet of an IP address to zero (e.g., 12.214.31.144 → 12.214.31.0), protecting user privacy and reducing disclosure risk.',
icon_url: '/static/hedgehog/builder-hog-01.png',
category: ['Custom'],
hog: `
// Check if the event has an IP address
if (empty(event.properties?.$ip)) {
print('No IP address found in event')
return event
}
let ip := event.properties.$ip
let parts := splitByString('.', ip)
// Check if we have exactly 4 parts for IPv4
if (length(parts) != 4) {
print('Invalid IP address format: wrong number of octets')
return event
}
// Validate each octet is a number between 0 and 255
for (let i := 1; i <= 4; i := i + 1) {
let octet := toInt(parts[i])
if (octet = null or octet < 0 or octet > 255) {
print('Invalid IP address: octets must be numbers between 0 and 255')
return event
}
}
// Replace the last octet with '0'
let anonymizedIp := concat(parts[1], '.', parts[2], '.', parts[3], '.0')
let returnEvent := event
returnEvent.properties.$ip := anonymizedIp
return returnEvent
`,
inputs_schema: [],
}
2 changes: 2 additions & 0 deletions plugin-server/src/cdp/templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { DESTINATION_PLUGINS, TRANSFORMATION_PLUGINS } from '../legacy-plugins'
import { template as webhookTemplate } from './_destinations/webhook/webhook.template'
import { template as defaultTransformationTemplate } from './_transformations/default/default.template'
import { template as geoipTemplate } from './_transformations/geoip/geoip.template'
import { template as ipAnonymizationTemplate } from './_transformations/ip-anonymization/ip-anonymization.template'
import { HogFunctionTemplate } from './types'

export const HOG_FUNCTION_TEMPLATES_DESTINATIONS: HogFunctionTemplate[] = [webhookTemplate]

export const HOG_FUNCTION_TEMPLATES_TRANSFORMATIONS: HogFunctionTemplate[] = [
defaultTransformationTemplate,
geoipTemplate,
ipAnonymizationTemplate,
]

export const HOG_FUNCTION_TEMPLATES_DESTINATIONS_DEPRECATED: HogFunctionTemplate[] = DESTINATION_PLUGINS.map(
Expand Down

0 comments on commit db9397e

Please sign in to comment.