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