Skip to content

AI data loss prevention for Go

Users paste sensitive data into AI prompts – card numbers, phone numbers, home addresses, and whole résumés – often without realizing the risk. Once that data reaches your AI provider it can end up in logs, training pipelines, or model outputs, well outside your control.

Arcjet sensitive info detection scans prompt content inside your application, before it reaches the AI provider. Detection runs locally in your own environment, so the raw text never leaves your app: only the decision – whether sensitive data was found – is reported to Arcjet. When something is detected you choose what happens next: block the request, strip the data, or warn the user.

Install the SDK and the Rampart backend:

Terminal window
go get github.com/arcjet/arcjet-go github.com/arcjet/arcjet-go/sensitiveinfo/rampart

Rampart runs on-device and detects email addresses, phone numbers, IP addresses, card numbers, names, addresses, and government identifiers. For the full list, see On-device detection with Rampart.

main.go
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"github.com/arcjet/arcjet-go"
"github.com/arcjet/arcjet-go/sensitiveinfo/rampart"
)
var backend = must(rampart.New(rampart.Options{}))
var aj = must(arcjet.NewClient(arcjet.Config{
Key: os.Getenv("ARCJET_KEY"),
Rules: []arcjet.Rule{
arcjet.SensitiveInfo(arcjet.SensitiveInfoOptions{
Mode: arcjet.ModeLive,
Deny: []arcjet.EntityType{
arcjet.SensitiveInfoCreditCardNumber,
arcjet.SensitiveInfoEmail,
},
Backend: backend,
}),
},
}))
type chatRequest struct {
Message string `json:"message"`
}
func chat(w http.ResponseWriter, r *http.Request) {
var body chatRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
decision, err := aj.Protect(
r.Context(),
r,
arcjet.WithSensitiveInfoValue(body.Message),
)
if err != nil {
log.Printf("arcjet: %v", err)
} else if decision.IsDenied() && decision.Reason.IsSensitiveInfo() {
http.Error(w, "Sensitive information detected – remove it from your prompt", http.StatusBadRequest)
return
}
_ = json.NewEncoder(w).Encode(map[string]string{"reply": "..."})
}
func main() {
http.HandleFunc("/chat", chat)
log.Fatal(http.ListenAndServe(":8000", nil))
}
func must[T any](v T, err error) T {
if err != nil {
log.Fatal(err)
}
return v
}

Sensitive info detection runs on-device with Rampart, a named-entity-recognition (NER) model paired with deterministic recognizers:

  • Deterministic recognizers match structured types with validated patterns – card numbers, email addresses, IP addresses, URLs, and Social Security numbers.
  • The NER model detects the free-form PII people actually paste into prompts – names, street addresses, phone numbers, and government or financial identifiers. This is the data a structured-pattern matcher can’t catch.

Detection runs entirely on your own infrastructure. Nothing is sent to a third party for analysis, which is what makes this safe to put in front of an AI provider in the first place.

Use deny to list the entity types to block, or allow to block everything except the types you list (the two are mutually exclusive). Tune the list to your app – for a support bot that legitimately collects phone numbers, leave PHONE_NUMBER out of deny:

arcjet.SensitiveInfo(arcjet.SensitiveInfoOptions{
Mode: arcjet.ModeLive, // Blocks requests. Use ModeDryRun to log only.
Deny: []arcjet.EntityType{
arcjet.SensitiveInfoCreditCardNumber,
arcjet.SensitiveInfoEmail,
},
Backend: backend, // Create once with rampart.New
})

See the entity detection table for every type Rampart supports, and for defining your own custom recognizers.

The Rampart NER model detects names, addresses, and government or financial identifiers. Add them to Deny. Create the backend once at startup with rampart.New:

main.go
package main
import (
"log"
"net/http"
"os"
"github.com/arcjet/arcjet-go"
"github.com/arcjet/arcjet-go/sensitiveinfo/rampart"
)
var backend = must(rampart.New(rampart.Options{}))
var aj = must(arcjet.NewClient(arcjet.Config{
Key: os.Getenv("ARCJET_KEY"),
Rules: []arcjet.Rule{
arcjet.SensitiveInfo(arcjet.SensitiveInfoOptions{
Mode: arcjet.ModeLive,
Deny: []arcjet.EntityType{
arcjet.SensitiveInfoGivenName,
arcjet.SensitiveInfoSurname,
arcjet.SensitiveInfoEmail,
arcjet.SensitiveInfoSSN,
},
Backend: backend,
}),
},
}))
func handler(w http.ResponseWriter, r *http.Request) {
message := r.FormValue("message")
decision, err := aj.Protect(r.Context(), r, arcjet.WithSensitiveInfoValue(message))
if err != nil {
log.Printf("arcjet: %v", err)
}
if decision.Reason.IsSensitiveInfo() {
http.Error(w, "Please remove personal information", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
}
func must[T any](v T, err error) T {
if err != nil {
log.Fatal(err)
}
return v
}

The model weights are bundled (~15 MB) so nothing is fetched at runtime. Inference is pure Go and runs on the request path. The default MaxInputChars is 4096. Phone numbers are left to the model because their digit shape overlaps with financial and government identifiers. For options and the full entity list, see the Rampart reference.

Pass the text to scan as sensitiveInfoValue (JS) / sensitive_info_value (Python) / WithSensitiveInfoValue (Go). For a chat endpoint this is usually the user’s most recent message. Pass the full conversation history instead if you want to scan every message, not only the most recent one – PII can appear earlier in the exchange.

Set mode to "DRY_RUN" (JS) / Mode.DRY_RUN (Python) / ModeDryRun (Go) to log detections without blocking any requests. Run this in production for a while to audit what PII actually shows up in your prompts, then switch to live mode once you’re confident in the entity list.

Sensitive info detection controls what data reaches your AI provider. Pair it with the other AI protection layers for full coverage: