diff --git a/astro.config.mjs b/astro.config.mjs index ce395630..d5e06396 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -428,6 +428,20 @@ export default defineConfig({ }, ], }, + { + label: "Redact", + collapsed: true, + items: [ + { + label: "Quick start", + link: "/redact/quick-start", + }, + { + label: "Reference", + link: "/redact/reference", + }, + ], + }, { label: "Integrations", collapsed: true, diff --git a/package-lock.json b/package-lock.json index 0a6a5ab2..f7bd12ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@arcjet/next": "1.0.0-alpha.24", "@arcjet/node": "1.0.0-alpha.24", "@arcjet/protocol": "1.0.0-alpha.24", + "@arcjet/redact": "1.0.0-alpha.24", "@arcjet/sveltekit": "1.0.0-alpha.24", "@arcjet/tsconfig": "1.0.0-alpha.24", "@astrojs/check": "0.9.3", @@ -25,6 +26,7 @@ "@clerk/nextjs": "5.4.1", "@connectrpc/connect-node": "1.4.0", "@connectrpc/connect-web": "1.4.0", + "@faker-js/faker": "^9.0.0", "@fontsource-variable/figtree": "5.0.22", "@fontsource-variable/jost": "5.0.20", "@fontsource/ibm-plex-mono": "5.0.14", @@ -383,6 +385,27 @@ "node": ">=18" } }, + "node_modules/@arcjet/redact": { + "version": "1.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@arcjet/redact/-/redact-1.0.0-alpha.24.tgz", + "integrity": "sha512-gZXecJtOBmBPxHqYiNDFM1vt+53gZhFwrCgsVDgZACnRU/ouCCz1Tv0chKMvLX+hA9+b1Jw8q+qpMplm5JIAzw==", + "license": "Apache-2.0", + "dependencies": { + "@arcjet/redact-wasm": "1.0.0-alpha.24" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@arcjet/redact-wasm": { + "version": "1.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@arcjet/redact-wasm/-/redact-wasm-1.0.0-alpha.24.tgz", + "integrity": "sha512-tR6UniRd1Ujs496j111Xb36FQEtPSQoPkqPMIHnkY/C87wBY5U+EmUgUH9sXP+0d811dnbdofPlFiK1aH5ssbg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@arcjet/runtime": { "version": "1.0.0-alpha.24", "resolved": "https://registry.npmjs.org/@arcjet/runtime/-/runtime-1.0.0-alpha.24.tgz", @@ -1894,6 +1917,22 @@ "@expressive-code/core": "^0.35.6" } }, + "node_modules/@faker-js/faker": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.0.tgz", + "integrity": "sha512-dTDHJSmz6c1OJ6HO7jiUiIb4sB20Dlkb3pxYsKm0qTXm2Bmj97rlXIhlvaFsW2rvCi+OLlwKLVSS6ZxFUVZvjQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", diff --git a/package.json b/package.json index 39cc7dcb..887e8ee3 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,15 @@ "astro": "astro" }, "dependencies": { - "@arcjet/bun": "1.0.0-alpha.24", "@arcjet/body": "1.0.0-alpha.24", + "@arcjet/bun": "1.0.0-alpha.24", "@arcjet/decorate": "1.0.0-alpha.24", "@arcjet/env": "1.0.0-alpha.24", "@arcjet/eslint-config": "1.0.0-alpha.24", "@arcjet/next": "1.0.0-alpha.24", "@arcjet/node": "1.0.0-alpha.24", "@arcjet/protocol": "1.0.0-alpha.24", + "@arcjet/redact": "1.0.0-alpha.24", "@arcjet/sveltekit": "1.0.0-alpha.24", "@arcjet/tsconfig": "1.0.0-alpha.24", "@astrojs/check": "0.9.3", @@ -27,6 +28,7 @@ "@clerk/nextjs": "5.4.1", "@connectrpc/connect-node": "1.4.0", "@connectrpc/connect-web": "1.4.0", + "@faker-js/faker": "^9.0.0", "@fontsource-variable/figtree": "5.0.22", "@fontsource-variable/jost": "5.0.20", "@fontsource/ibm-plex-mono": "5.0.14", diff --git a/src/content/docs/redact/quick-start.mdx b/src/content/docs/redact/quick-start.mdx new file mode 100644 index 00000000..95376f91 --- /dev/null +++ b/src/content/docs/redact/quick-start.mdx @@ -0,0 +1,60 @@ +--- +title: "Arcjet Redact quick start" +description: "Quick start guide for redacting sensitive information locally." +--- + +import { + Tabs, + TabItem, + LinkCard, + CardGrid, + Code, +} from "@astrojs/starlight/components"; +import WhatIsArcjet from "/src/components/WhatIsArcjet.astro"; +import FAQs from "/src/components/FAQs.astro"; +import QuickStartRedact from "/src/content/docs/redact/snippets/QuickStartRedact.ts?raw"; +import QuickStartUnredact from "/src/content/docs/redact/snippets/QuickStartUnredact.ts?raw"; +import Comments from "/src/components/Comments.astro"; + +The Arcjet Redaction library makes it easy to redact sensitive information +locally. It is a utility library independent of the main Arcjet SDK so can be +used with or without other Arcjet rules. + + + +## SDK installation + +Follow these steps to get started: + +### 1. Install Arcjet Redact + +In your project root, run the following command to install the Arcjet redact +library: + +```bash +npm i @arcjet/redact +``` + +### 2. Redact some text + +Now all you have to do to Redact text is to call the `redact` function. It can +be configured to redact a number of built-in entity types, or else you can +provide a custom detect function to redact your own types. The types that we support +by default are: `email`, `phone-number`, `ip-address`, `credit-card`. + + + +### 3. Unredact a response + +If you want to un-redact some text that contains the replacements from the redact +function just call `unredact`, it is passed as the value in the return array. + + + +## Get help + +Need help with anything? [Email us](mailto:support@arcjet.com) or [join our +Discord](https://discord.gg/TPra6jqZDC) to get support from our +engineering team. + + diff --git a/src/content/docs/redact/reference.mdx b/src/content/docs/redact/reference.mdx new file mode 100644 index 00000000..e99faf99 --- /dev/null +++ b/src/content/docs/redact/reference.mdx @@ -0,0 +1,92 @@ +--- +title: "Arcjet Redact reference" +description: "Reference for the Arcjet redaction library" +--- + +import { + Tabs, + TabItem, + LinkCard, + CardGrid, + Code, +} from "@astrojs/starlight/components"; +import WhatIsArcjet from "/src/components/WhatIsArcjet.astro"; +import FAQs from "/src/components/FAQs.astro"; +import CustomEntities from "/src/content/docs/redact/snippets/CustomEntities.ts?raw"; +import CustomRedact from "/src/content/docs/redact/snippets/CustomRedact.ts?raw"; +import QuickStartUnredact from "/src/content/docs/redact/snippets/QuickStartUnredact.ts?raw"; +import Comments from "/src/components/Comments.astro"; + +The Arcjet Redaction library makes it easy to redact sensitive information +locally. It is a utility library independent of the main Arcjet SDK so can be +used with or without other Arcjet rules. + + + +## Configuration + +The configuration definition is: + +```ts +type RedactOptions = { + entities?: Array; + contextWindowSize?: number; + detect?: (tokens: string[]) -> Array; + replace?: (detectedEntity: SensitiveInfoType) -> string | undefined; +}; +``` + +- `entities`: The list of entities that you wish to redact. If undefined then all + entities are redacted. Valid values are: `email`, `phone-number`, + `ip-address`, '`credit-card`, or any string returned from `detect`. +- `contextWindowSize` - How many tokens to pass to the `detect` function at a + time. Setting this to a higher value allows for more context to be used when + determing if a token is sensitive or not. For best performance this should be + set to as low a number as possible to provide the context that you need. +- `detect` - An optional function that allows you to detect custom entities. It + will be passed a list of tokens as big as `contextWindowSize` and should + return a list of detected entities of the same length. +- `replace` - An optional function that allows you to define your own + replacements for detected entities. It is passed a string with the type of + entity detected and it should either return a replacement for that entity type + or `undefined`. + +## Redacting and Unredacting + +The Arcjet Redaction library can be used to both redact and unredact text. First +you redact using the `redact()` function which returns both the redacted text +and an `unredact()` function as a tuple. You can use the `unredact()` function +later on to replace redacted entities with their originals. + + + +## Custom entity detection + +When configuring a redaction you can provide a custom entity detect function, +this enables you to detect entities that we don't support out of the box using +custom logic. + +The function will take a list of tokens and must return a list of either +`undefined`, if the corresponding token in the input list is not sensitive, or +the name of the entity if it does match. The number of tokens that are provided +to the function is controlled by the `contextWindowSize` option, which defaults +to 1. If you need additional context to perform detections then you can increase +this value. + +In cases of a conflict the first identified entity type is used. + + + +## Custom entity redaction + +You can provide a function to perform custom entity redaction if have different +requirements for the redacted entities. A common example of this is to use a +library such as [faker.js](https://fakerjs.dev/) to generate redacted entities +that look exactly like the real ones. + +:::note +If you are using `unredact()` it is important that each redacted entity is unique. If many +pieces of sensitive info are replaced by the same string then unredaction won't work correctly. +::: + + diff --git a/src/content/docs/redact/snippets/CustomEntities.ts b/src/content/docs/redact/snippets/CustomEntities.ts new file mode 100644 index 00000000..9cdea6e4 --- /dev/null +++ b/src/content/docs/redact/snippets/CustomEntities.ts @@ -0,0 +1,19 @@ +import { redact } from "@arcjet/redact"; + +const text = "my email-address is test@example.com"; + +function detectDash(tokens: string[]): Array<"contains-dash" | undefined> { + return tokens.map((token) => { + if (token.includes("-")) { + return "contains-dash"; + } + }); +} + +const [redacted] = await redact(text, { + entities: ["email", "contains-dash"], + detect: detectDash, +}); + +console.log(redacted); +// my is diff --git a/src/content/docs/redact/snippets/CustomRedact.ts b/src/content/docs/redact/snippets/CustomRedact.ts new file mode 100644 index 00000000..b25c7cb0 --- /dev/null +++ b/src/content/docs/redact/snippets/CustomRedact.ts @@ -0,0 +1,22 @@ +import { redact } from "@arcjet/redact"; +import { faker } from "@faker-js/faker"; + +const text = "my email address is test@example.com"; + +function customRedact(detectedEntity: string) { + // If we detect an email generate a fake one + if (detectedEntity === "email") { + return faker.internet.email(); + } + + // Otherwise we'll return undefined and use the default + return undefined; +} + +const [redacted] = await redact(text, { + entities: ["email"], + replace: customRedact, +}); + +console.log(redacted); +// my email address is john.smith@email-provider.com diff --git a/src/content/docs/redact/snippets/QuickStartRedact.ts b/src/content/docs/redact/snippets/QuickStartRedact.ts new file mode 100644 index 00000000..35f640a2 --- /dev/null +++ b/src/content/docs/redact/snippets/QuickStartRedact.ts @@ -0,0 +1,10 @@ +import { redact } from "@arcjet/redact"; + +const text = "my email address is test@example.com"; + +const [redacted] = await redact(text, { + entities: ["email"], +}); + +console.log(redacted); +// my email address is diff --git a/src/content/docs/redact/snippets/QuickStartUnredact.ts b/src/content/docs/redact/snippets/QuickStartUnredact.ts new file mode 100644 index 00000000..7999983d --- /dev/null +++ b/src/content/docs/redact/snippets/QuickStartUnredact.ts @@ -0,0 +1,15 @@ +import { redact } from "@arcjet/redact"; + +const text = "My email address is test@example.com"; + +const [redacted, unredact] = await redact(text, { + entities: ["email"], +}); + +console.log(redacted); +// My email address is + +const unredacted = unredact("Your email address is "); + +console.log(unredacted); +// Your email address is test@example.com