TL;DR — Build
safe., a real-time User Safety Gateway that inspects user-generated content against configurable policies, enforces rate limits, and produces immutable audit trails. It demonstrates policy-as-code, stream processing, and fault-tolerant design — the exact systems skills hiring managers look for in senior engineering candidates.
The demand for safety infrastructure engineers has exploded. Platforms from Discord to Stripe need people who can build systems that protect users at scale. Yet most portfolio projects showcase CRUD apps and todo lists — nothing that signals you understand policy engines, backpressure, or auditability. safe. fills that gap. It’s a compact, fully runnable gateway that sits between a user and a platform, evaluating every piece of content against safety policies before it reaches your backend. In this guide, you’ll build it from scratch using Go, Open Policy Agent (OPA), and Redis — a stack that mirrors production safety systems at companies like Discord and Reddit.
Why This Project Stands Out on a CV
safe. demonstrates a cluster of high-value skills that hiring managers actively screen for:
- Policy-as-Code engineering — You’ll use OPA/Rego to decouple safety policy from application logic, the same pattern used at Netflix, Stripe, and Slack. This signals you understand declarative configuration and the separation of concerns that matter in regulated industries.
- Real-time stream processing — The gateway evaluates content inline, requiring you to think about latency budgets, concurrency, and backpressure. This is the core of any production safety pipeline.
- Distributed systems fundamentals — Rate limiting with Redis, structured audit logging, and fault-tolerant decision paths teach you patterns that transfer directly to payment processing, fraud detection, and access control.
- Observability and auditability — Every decision the gateway makes is logged with structured metadata, teaching you the discipline of “if it isn’t logged, it didn’t happen” — a mindset that separates junior engineers from senior ones.
- Security-adjacent architecture — Content moderation sits at the intersection of safety, compliance (GDPR, DSA), and platform trust. Building this shows you can reason about adversarial inputs and regulatory constraints.
The roles this signals include Safety Engineer, Platform Engineer, Trust & Safety Infrastructure, and Backend/Systems Engineer at any company that processes user-generated content.
Architecture Overview
safe. is composed of five tightly integrated components that form a synchronous evaluation pipeline. Here’s how they fit together:
┌──────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ HTTP Ingress │────▶│ Policy Engine │────▶│ Rate Limiter │
│ (Go net/http)│ │ (OPA/Rego) │ │ (Redis + Sliding│
└──────────────┘ └──────────────────┘ │ Window) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Decision Router │
│ (Allow/Block/ │
│ Flag) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Audit Logger │
│ (Structured JSON│
│ → File/Sink) │
└─────────────────┘
- HTTP Ingress — A minimal Go HTTP server that receives user content payloads (text, metadata, user ID) via POST. It normalizes input and passes it downstream.
- Policy Engine — Powered by OPA’s Rego language. Policies are written declaratively (e.g., “block messages containing flagged phrases above a confidence threshold”) and evaluated at request time. This decouples policy updates from code deploys.
- Rate Limiter — Uses Redis with a sliding window algorithm to enforce per-user content submission limits. Prevents abuse amplification even if individual messages pass policy checks.
- Decision Router — Aggregates outputs from the policy engine and rate limiter into a single decision:
ALLOW,BLOCK, orFLAG_FOR_REVIEW. Each decision carries a reason code and confidence score. - Audit Logger — Writes every decision as a structured JSON event to a persistent sink (file or remote log service), including timestamp, user ID, content hash, policy version, and decision metadata.
The critical design insight is that the policy engine and rate limiter operate independently and in parallel, then converge at the decision router. This means a rate-limited user isn’t blocked by policy (and vice versa) — the router combines signals. This pattern mirrors how production systems like Discord’s AutoMod actually work.
Building It Step by Step
We’ll build this in Go (1.22+) with OPA as a sidecar via its REST API, and Redis for rate limiting. Here are the core steps with real, runnable code.
Step 1: Initialize the project and dependencies
mkdir safe-gateway && cd safe-gateway
go mod init github.com/yourorg/safe
go get github.com/redis/go-redis/v9 github.com/gorilla/mux
You’ll also need a running OPA instance (we’ll use Docker later). For now, set up the project skeleton.
Step 2: Define the Rego safety policy
Create policies/safety.rego:
package safety
import data.lib.phrases
default allow = false
# Allow if content passes all checks
allow {
not contains_flagged_phrase(input.content)
not exceeds_toxicity_threshold(input.content)
input.content_count < max_daily_limit
}
contains_flagged_phrase(content) {
phrase := phrases.flagged[_]
contains_lower(content, phrase)
}
contains_lower(content, phrase) {
lower(content) == phrase
}
exceeds_toxicity_threshold(content) {
score := toxicity_score(content)
score > 0.8
}
# Placeholder — in production, call a toxicity model
toxicity_score(content) = score {
score := 0.0
}
max_daily_limit = 100
And policies/lib/phrases.rego:
package lib.phrases
flagged = ["spam_phrase_1", "spam_phrase_2", "harassment_term"]
Step 3: Build the Go policy client
This is the bridge between your Go server and OPA:
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
)
type PolicyRequest struct {
Content string `json:"content"`
UserID string `json:"user_id"`
ContentCount int `json:"content_count"`
}
type PolicyDecision struct {
Allow bool `json:"allow"`
Reasons []string `json:"reasons"`
}
func EvaluatePolicy(ctx context.Context, client *http.Client, req PolicyRequest) (PolicyDecision, error) {
body, _ := json.Marshal(req)
resp, err := client.Post("http://localhost:8181/v1/data/safety/allow", "application/json", bytes.NewReader(body))
if err != nil {
return PolicyDecision{}, fmt.Errorf("opa eval failed: %w", err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
// Parse OPA's boolean result from the response
allow := result["result"].(map[string]interface{})["allow"].(bool)
return PolicyDecision{Allow: allow}, nil
}
Step 4: Implement the Redis sliding window rate limiter
package main
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
type RateLimiter struct {
client *redis.Client
window time.Duration
maxHits int
}
func (rl *RateLimiter) Allow(ctx context.Context, userID string) (bool, error) {
key := fmt.Sprintf("ratelimit:%s", userID)
now := time.Now().UnixMilli()
windowStart := now - int64(rl.window.Milliseconds())
// Sliding window: remove old entries, count current ones
pipe := rl.client.TxPipeline()
pipe.ZRemRangeByScore(ctx, key, "-inf", fmt.Sprintf("%d", windowStart))
pipe.ZCard(ctx, key)
pipe.ZAdd(ctx, key, redis.Z{Score: float64(now), Member: now})
pipe.Expire(ctx, key, rl.window)
_, err := pipe.Exec(ctx)
if err != nil {
return false, err
}
count := pipe.ZCard(ctx, key).Val()
return count <= int64(rl.maxHits), nil
}
Step 5: Wire the decision router
func handleEvaluate(w http.ResponseWriter, r *http.Request) {
var req PolicyRequest
json.NewDecoder(r.Body).Decode(&req)
ctx := r.Context()
decision := Decision{UserID: req.UserID, Timestamp: time.Now()}
// Evaluate policy and rate limit in parallel
policyResult := make(chan PolicyDecision, 1)
rateResult := make(chan bool, 1)
go func() {
d, _ := EvaluatePolicy(ctx, httpClient, req)
policyResult <- d
}()
go func() {
allowed, _ := rateLimiter.Allow(ctx, req.UserID)
rateResult <- allowed
}()
policyDec := <-policyResult
rateAllowed := <-rateResult
// Aggregate decisions
if !policyDec.Allow {
decision.Action = "BLOCK"
decision.Reason = "policy_violation"
} else if !rateAllowed {
decision.Action = "FLAG_FOR_REVIEW"
decision.Reason = "rate_limit_exceeded"
} else {
decision.Action = "ALLOW"
decision.Reason = "passed_all_checks"
}
// Emit audit event (see Step 6)
auditLog.Emit(decision)
json.NewEncoder(w).Encode(decision)
}
Step 6: Structured audit logger
type AuditEvent struct {
Timestamp time.Time `json:"timestamp"`
UserID string `json:"user_id"`
Action string `json:"action"`
Reason string `json:"reason"`
PolicyVer string `json:"policy_version"`
ContentSig string `json:"content_sha256"` // hash, never log raw content
}
func (l *AuditLogger) Emit(event AuditEvent) {
entry, _ := json.Marshal(event)
fmt.Fprintf(l.writer, "%s\n", entry) // or ship to a log sink
}
Hashing the content instead of logging raw text is a deliberate privacy-conscious design choice — it gives you auditability without storing PII, which matters for GDPR compliance.
Running and Testing It
Start the infrastructure:
# Start Redis
docker run -d --name safe-redis -p 6379:6379 redis:7-alpine
# Start OPA with your policies
docker run -d --name safe-opa -p 8181:8181 \
-v $(pwd)/policies:/policies \
openpolicyagent/opa run --server /policies
Run the gateway:
go run main.go --port 8080 --redis-addr localhost:6379 --opa-addr localhost:8181
Send a test request:
curl -X POST http://localhost:8080/evaluate \
-H "Content-Type: application/json" \
-d '{
"content": "This is a normal message",
"user_id": "user-42",
"content_count": 5
}'
Expected response:
{"action":"ALLOW","reason":"passed_all_checks","user_id":"user-42"}
Test a blocked message:
curl -X POST http://localhost:8080/evaluate \
-H "Content-Type: application/json" \
-d '{"content": "spam_phrase_1", "user_id": "user-99", "content_count": 3}'
Expected: {"action":"BLOCK","reason":"policy_violation"}
Verify the audit log — check your output sink for structured JSON events. You can also add a Go test suite:
func TestRateLimiter_AllowsUnderLimit(t *testing.T) {
rl := NewRateLimiter(redisClient, 1*time.Minute, 10)
ctx := context.Background()
for i := 0; i < 10; i++ {
allowed, err := rl.Allow(ctx, "test-user")
require.NoError(t, err)
require.True(t, allowed)
}
}
Run with go test ./... -v. You should see all tests pass, confirming the pipeline works end-to-end.
Extending It: Your Roadmap to Senior-Level
Here are six concrete upgrades that transform safe. from a demo into a production-grade system — each one maps to a skill senior engineers are expected to own:
Add persistent policy versioning with GitOps — Store Rego policies in a Git repository and use OPA’s bundle API to push updates automatically. This teaches you CI/CD for infrastructure, a core senior skill. Why it matters: Policy changes must be auditable, reversible, and reviewed — just like application code.
Implement horizontal scaling with consistent hashing — Replace the single Redis instance with Redis Cluster and shard rate-limit keys by user ID. Why it matters: A single point of failure or throughput bottleneck will cripple a safety system at scale; distributed rate limiting is what platforms like Discord actually run.
Add OpenTelemetry tracing and metrics — Instrument every stage of the pipeline (policy eval latency, rate limiter hit rate, decision distribution) with Prometheus metrics and Jaeger traces. Why it matters: Observability is how you prove the system is working and diagnose failures before users complain — it’s the difference between “it works” and “we know it works.”
Build a circuit breaker for the OPA engine — If OPA becomes unresponsive, fall back to a cached policy decision or a safe-default allow/block. Use the
gobreakerlibrary in Go. Why it matters: Safety systems must degrade gracefully; a cascading failure where the gateway itself blocks all traffic is worse than a lenient fallback.Integrate a machine learning toxicity classifier — Replace the placeholder
toxicity_scorein Rego with a call to a model served via Triton Inference Server or a lightweight ONNX runtime. Why it matters: Real safety systems combine rule-based checks with ML — this teaches you the integration patterns (gRPC, batching, timeout budgets) that production ML pipelines require.Add a replay and backfill engine — Store raw content hashes in a durable queue (Kafka or NATS JetStream) so you can re-evaluate historical content when policies change. Why it matters: Policy updates must be retroactive; if a new harassment term is added, you need to find and re-check previously posted content — this is a non-trivial distributed systems problem that senior engineers solve regularly.
Each of these upgrades is independently impressive on a CV and collectively demonstrates the full spectrum of production systems engineering.
Key Takeaways
safe.demonstrates policy-as-code, real-time stream processing, and distributed systems — the exact skill clusters hiring managers screen for in safety and platform engineering roles.- The architecture separates policy evaluation, rate limiting, and decision routing as independent components that converge — mirroring production systems at scale.
- The implementation uses OPA/Rego for declarative policy, Redis for distributed rate limiting, and Go for the gateway — a stack that transfers directly to real production roles.
- Structured audit logging with hashed content shows you understand privacy-by-design and regulatory compliance (GDPR, DSA) — not just technical implementation.
- The six extension upgrades (GitOps, horizontal scaling, observability, circuit breakers, ML integration, replay) map precisely to senior engineer expectations.
- Every piece of code in this guide is real, runnable Go — not pseudocode. Clone it, extend it, and deploy it.
Further Reading
- Open Policy Agent Documentation — The canonical reference for Rego, OPA bundles, and the REST API you used in this project. Study the bundle API and data layering sections to prepare for the GitOps upgrade.
- Redis Sorted Sets and Sliding Window Rate Limiting — Official Redis documentation on sorted sets, which power the sliding window algorithm implemented in Step 4.
- RFC 8705: OAuth 2.0 Mutual-TLS Client Authentication — If you extend
safe.to require mTLS between services, this RFC defines the certificate-bound token pattern that production safety systems use for service-to-service authentication. - OpenTelemetry Specification — The canonical spec for instrumenting the observability upgrade. Study the trace and metrics data models to understand what to emit from each pipeline stage.
- Netflix’s Approach to Policy-as-Code at Scale — A Netflix engineering blog post describing how they use OPA in production, directly relevant to the GitOps and horizontal scaling upgrades.
- Discord’s Moderation Infrastructure — Discord’s public engineering writeup on their AutoMod system, which uses the same policy-engine-plus-ML architecture pattern that
safe.implements and extends. - gobreaker: Circuit Breaker Pattern in Go — The library to use for the circuit breaker upgrade; study its state machine to understand how production systems handle dependency failures gracefully.
Now go build safe. — your future hiring manager will notice.