Gin
Add Nyoxis threat detection to a Gin application using a middleware function and the standard net/http package.
Prerequisites
- Go 1.21 or later
- Gin v1.9 or later
- A Nyoxis workspace API key — get one here
No extra install required
Uses the net/http and encoding/json packages from the Go standard library.
Middleware
Create middleware/nyoxis.go:
go
package middleware
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
const nyoAPI = "https://api.nyoxis.com"
type predictPayload struct {
Method string `json:"method"`
Path string `json:"path"`
Query string `json:"query,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
IPAddr string `json:"ip_addr,omitempty"`
Body string `json:"body,omitempty"`
}
type predictResponse struct {
IsCached bool `json:"is_cached"`
Prediction *struct {
Risk string `json:"risk"`
RiskScore float64 `json:"risk_score"`
Attacks []struct {
Kind string `json:"kind"`
Confidence float64 `json:"confidence"`
} `json:"attacks"`
} `json:"prediction"`
}
// NyoxisWAF returns a Gin middleware that calls /v0/predict for every request.
//
// Parameters:
//
// apiKey - your workspace API token (required)
// blockOnHigh - if true, return 403 Forbidden when risk == "high"
// failOpen - if true (default), pass request through on API failure
func NyoxisWAF(apiKey string, blockOnHigh bool, failOpen bool) gin.HandlerFunc {
if apiKey == "" {
panic("nyoxis: apiKey is required")
}
client := &http.Client{Timeout: 3 * time.Second}
return func(c *gin.Context) {
// Read and restore the body
var rawBody string
if c.Request.Body != nil {
bodyBytes, err := io.ReadAll(io.LimitReader(c.Request.Body, 1<<20 /* 1 MiB */))
if err == nil {
rawBody = string(bodyBytes)
c.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes))
}
}
headers := make(map[string]string, len(c.Request.Header))
for k, vals := range c.Request.Header {
if len(vals) > 0 {
headers[k] = vals[0]
}
}
payload := predictPayload{
Method: c.Request.Method,
Path: c.Request.URL.Path,
Query: c.Request.URL.RawQuery,
Headers: headers,
IPAddr: c.ClientIP(),
Body: rawBody,
}
body, _ := json.Marshal(payload)
url := fmt.Sprintf("%s/v0/predict?api_key=%s", nyoAPI, apiKey)
ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
if err != nil {
log.Printf("[nyoxis] build request error: %v", err)
goto passthrough
}
req.Header.Set("Content-Type", "application/json")
{
resp, err := client.Do(req)
if err != nil {
log.Printf("[nyoxis] prediction error: %v", err)
goto passthrough
}
defer resp.Body.Close()
var verdict predictResponse
if err := json.NewDecoder(resp.Body).Decode(&verdict); err == nil {
// Store verdict in Gin context
c.Set("nyoxis", verdict)
if blockOnHigh && verdict.Prediction != nil && verdict.Prediction.Risk == "high" {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
return
}
}
}
passthrough:
if !failOpen {
c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{"error": "Service unavailable"})
return
}
c.Next()
}
}Register the middleware
go
// main.go
package main
import (
"net/http"
"os"
"github.com/gin-gonic/gin"
"yourmodule/middleware"
)
func main() {
r := gin.Default()
// Apply globally
r.Use(middleware.NyoxisWAF(
os.Getenv("NYOXIS_API_KEY"),
true, // blockOnHigh
true, // failOpen
))
r.GET("/", func(c *gin.Context) {
risk := "unknown"
if v, ok := c.Get("nyoxis"); ok {
if verdict, ok := v.(middleware.PredictResponse); ok && verdict.Prediction != nil {
risk = verdict.Prediction.Risk
}
}
c.JSON(http.StatusOK, gin.H{"ok": true, "risk": risk})
})
r.Run(":8080")
}Acting on the verdict
go
r.GET("/sensitive", func(c *gin.Context) {
if v, exists := c.Get("nyoxis"); exists {
if verdict, ok := v.(middleware.PredictResponse); ok && verdict.Prediction != nil {
p := verdict.Prediction
if p.Risk == "high" || p.Risk == "medium" {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Forbidden"})
return
}
for _, attack := range p.Attacks {
if attack.Kind == "sql_injection" && attack.Confidence > 0.8 {
// Log — could be a false positive; consider alerting first
c.Set("security_alert", attack)
}
}
}
}
c.JSON(http.StatusOK, gin.H{"data": "sensitive content"})
})Next steps
- API Reference — complete field descriptions and status codes.
- Overview — how the classifier and redaction pipeline work.