zoobzio January 24, 2026 Edit this page

sctx Integration

slush integrates with sctx to provide cryptographically-verified service access control.

What sctx Provides

sctx is a cryptographic context library that:

  • Issues signed tokens proving identity (certificate-based)
  • Creates guards that verify token signatures and permissions
  • Validates tokens haven't expired or been replayed
  • Maps certificates to permissions via configurable policies

The Value Proposition

Traditional guards check context values set by middleware. An attacker who compromises middleware can set arbitrary values.

sctx guards verify cryptographic proofs:

// Traditional guard - trusts middleware
func requireAdmin(ctx context.Context) error {
    if ctx.Value("role") != "admin" {  // Attacker can forge this
        return errors.New("not admin")
    }
    return nil
}

// sctx guard - verifies cryptographic proof
func sctxGuard(ctx context.Context) error {
    token := sctx.TokenFromContext(ctx)
    return dbGuard.Validate(ctx, token)  // Signature verified
}

Setup

1. Initialize sctx admin

import "github.com/zoobzio/sctx"

// Your PKI setup
privateKey, _ := loadPrivateKey("server.key")
trustedCAs, _ := loadCACertPool("ca.crt")

// Create admin service
admin, err := sctx.NewAdminService[AppMeta](privateKey, trustedCAs)
if err != nil {
    log.Fatal(err)
}

// Configure permission policy
admin.SetPolicy(func(cert *x509.Certificate) (*sctx.Context[AppMeta], error) {
    // Map certificate to permissions
    return &sctx.Context[AppMeta]{
        Permissions: permissionsForCert(cert),
        Metadata:    AppMeta{...},
    }, nil
})

2. Create sctx guards

// Admin token with guard-creation permission
adminToken := getAdminToken()

// Create a guard requiring specific permissions
dbGuard, err := admin.CreateGuard(ctx, adminToken, "db:read", "db:write")
if err != nil {
    log.Fatal(err)
}

mailerGuard, err := admin.CreateGuard(ctx, adminToken, "mailer:send")
if err != nil {
    log.Fatal(err)
}

3. Bridge to slush guards

// Wrap sctx guard as slush guard
func sctxGuard(guard sctx.Guard) slush.Guard {
    return func(ctx context.Context) error {
        token := sctx.TokenFromContext(ctx)
        if token == "" {
            return errors.New("no token in context")
        }
        return guard.Validate(ctx, token)
    }
}

// Register services with cryptographic guards
slush.Register[Database](db).
    Guard(sctxGuard(dbGuard))

slush.Register[Mailer](mailer).
    Guard(sctxGuard(mailerGuard))

4. Set token in request context

In your HTTP middleware:

func tokenMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Extract token from header
        token := sctx.SignedToken(r.Header.Get("X-Auth-Token"))

        // Add to context
        ctx := sctx.WithToken(r.Context(), token)

        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Complete Example

package main

import (
    "context"
    "crypto/x509"
    "errors"
    "log"
    "net/http"

    "github.com/zoobzio/sctx"
    "github.com/zoobzio/slush"
)

type AppMeta struct {
    UserID string
}

func main() {
    // === sctx setup ===
    privateKey, _ := loadPrivateKey("server.key")
    trustedCAs, _ := loadCACertPool("ca.crt")

    admin, _ := sctx.NewAdminService[AppMeta](privateKey, trustedCAs)
    admin.SetPolicy(policyFromCert)

    // Create guards for different services
    adminToken := bootstrapAdminToken(admin)
    dbGuard, _ := admin.CreateGuard(context.Background(), adminToken, "db:access")
    mailerGuard, _ := admin.CreateGuard(context.Background(), adminToken, "mailer:send")

    // === slush setup ===
    db := initDatabase()
    mailer := initMailer()

    slush.Register[Database](db).
        Guard(wrapSctxGuard(dbGuard))

    slush.Register[Mailer](mailer).
        Guard(wrapSctxGuard(mailerGuard))

    // === HTTP server ===
    mux := http.NewServeMux()
    mux.HandleFunc("/send-email", handleSendEmail)

    handler := tokenMiddleware(mux)
    log.Fatal(http.ListenAndServe(":8080", handler))
}

func wrapSctxGuard(guard sctx.Guard) slush.Guard {
    return func(ctx context.Context) error {
        token := sctx.TokenFromContext(ctx)
        if token == "" {
            return errors.New("missing authentication token")
        }
        return guard.Validate(ctx, token)
    }
}

func handleSendEmail(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()

    // This will verify the token has "mailer:send" permission
    mailer, err := slush.Use[Mailer](ctx)
    if err != nil {
        http.Error(w, err.Error(), http.StatusForbidden)
        return
    }

    // Use the mailer...
}

What Gets Verified

When guard.Validate(ctx, token) runs:

  1. Signature verification — Token was signed by trusted key
  2. Expiry check — Token hasn't expired
  3. Replay protection — Nonce hasn't been seen before
  4. Permission check — Token's context has required permissions

Error Handling

sctx errors are descriptive:

db, err := slush.Use[Database](ctx)
if errors.Is(err, slush.ErrAccessDenied) {
    // Unwrap to see sctx reason
    // "token expired"
    // "missing permission: db:write"
    // "invalid signature"
    log.Println(err)
}

Combining with Other Guards

sctx guards compose with regular guards:

slush.Register[Database](db).
    Guard(wrapSctxGuard(dbGuard)).  // Cryptographic verification
    Guard(rateLimit(100)).          // Rate limiting
    Guard(circuitBreaker(5))        // Circuit breaker

Testing

In tests, you can bypass sctx or use test tokens:

func TestWithMockGuard(t *testing.T) {
    slushtesting.ResetRegistry(t)

    // Use AllowAllGuard instead of sctx in tests
    slush.Register[Database](mockDB{}).
        Guard(slushtesting.AllowAllGuard())

    // Test proceeds without PKI setup
}

Or create test tokens with a test CA:

func TestWithTestToken(t *testing.T) {
    admin, _ := sctx.NewAdminService[AppMeta](testKey, testCAs)
    testToken := createTestToken(admin)

    ctx := sctx.WithToken(context.Background(), testToken)
    db, err := slush.Use[Database](ctx)
    // ...
}

Next Steps