Skip to main content

Security Overview

Vega AI is built with security and privacy as core principles. The application implements multiple layers of security including authentication, authorization, data encryption, GDPR-compliant logging, and multi-tenant data isolation.

Authentication & Authorization

JWT-Based Authentication

Vega AI uses JSON Web Tokens (JWT) for stateless authentication:

Access Tokens

Short-lived (60 min) tokens for API authentication

Refresh Tokens

Long-lived (72 hours) tokens for renewing access

Secure Storage

HTTP-only cookies prevent XSS attacks

HMAC-SHA256

Cryptographically signed tokens

Token Structure

type Claims struct {
    UserID    int    `json:"user_id"`
    Username  string `json:"username"`
    Role      string `json:"role"`      // "user" or "admin"
    TokenType string `json:"token_type"` // "access" or "refresh"
    jwt.RegisteredClaims
}

func (s *AuthService) GenerateTokenPair(user *models.User) (*TokenPair, error) {
    accessClaims := &Claims{
        UserID:    user.ID,
        Username:  user.Username,
        Role:      user.Role,
        TokenType: "access",
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.config.AccessTokenExpiry)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "vega-ai",
        },
    }

    accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
    accessTokenString, err := accessToken.SignedString([]byte(s.config.TokenSecret))
    // ...
}

Token Storage

Secure Cookie Configuration:
http.SetCookie(w, &http.Cookie{
    Name:     "access_token",
    Value:    accessToken,
    Path:     "/",
    Domain:   config.CookieDomain,
    MaxAge:   int(config.AccessTokenExpiry.Seconds()),
    Secure:   config.CookieSecure,   // HTTPS only in production
    HttpOnly: true,                   // Prevent JavaScript access
    SameSite: http.SameSiteLaxMode,   // CSRF protection
})

Middleware Authentication

Web Authentication Middleware

func (h *AuthHandler) AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token, err := c.Cookie("access_token")
        if err != nil {
            c.Redirect(http.StatusFound, "/auth/login")
            c.Abort()
            return
        }

        claims, err := h.service.VerifyToken(token)
        if err != nil {
            c.Redirect(http.StatusFound, "/auth/login")
            c.Abort()
            return
        }

        // Inject user context
        c.Set("userID", claims.UserID)
        c.Set("username", claims.Username)
        c.Set("role", claims.Role)
        c.Next()
    }
}

API Authentication Middleware

func (h *AuthHandler) APIAuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
            c.Abort()
            return
        }

        parts := strings.Split(authHeader, " ")
        if len(parts) != 2 || parts[0] != "Bearer" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization header"})
            c.Abort()
            return
        }

        claims, err := h.service.VerifyToken(parts[1])
        if err != nil || claims.TokenType != "access" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
            c.Abort()
            return
        }

        c.Set("userID", claims.UserID)
        c.Next()
    }
}

Google OAuth Integration

Cloud mode requires Google OAuth for authentication:

OAuth Configuration

import "golang.org/x/oauth2"
import "golang.org/x/oauth2/google"

oauthConfig := &oauth2.Config{
    ClientID:     cfg.GoogleClientID,
    ClientSecret: cfg.GoogleClientSecret,
    RedirectURL:  cfg.GoogleClientRedirectURL,
    Scopes: []string{
        "https://www.googleapis.com/auth/userinfo.email",
        "https://www.googleapis.com/auth/userinfo.profile",
    },
    Endpoint: google.Endpoint,
}

OAuth Flow

1

Initiate OAuth

User clicks “Sign in with Google”, redirected to Google consent screen
func (h *GoogleAuthHandler) InitiateLogin(c *gin.Context) {
    state := generateRandomState()
    c.SetCookie("oauth_state", state, 600, "/", "", true, true)
    url := h.oauthConfig.AuthCodeURL(state, oauth2.AccessTypeOffline)
    c.Redirect(http.StatusTemporaryRedirect, url)
}
2

OAuth Callback

Google redirects back with authorization code
func (h *GoogleAuthHandler) HandleCallback(c *gin.Context) {
    state, _ := c.Cookie("oauth_state")
    if c.Query("state") != state {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid state"})
        return
    }
    // ...
}
3

Token Exchange

Exchange authorization code for access token
token, err := h.oauthConfig.Exchange(ctx, code)
if err != nil {
    return nil, err
}
4

Fetch User Info

Retrieve user profile from Google
resp, err := http.Get("https://www.googleapis.com/oauth2/v3/userinfo?access_token=" + token.AccessToken)
// Parse user info and create/update user in database
5

Issue JWT Tokens

Create access and refresh tokens for the user
tokenPair, err := h.authService.GenerateTokenPair(user)
// Set secure cookies

Password Security

Bcrypt Hashing

Passwords are hashed using bcrypt with default cost (10):
import "golang.org/x/crypto/bcrypt"

func HashPassword(password string) (string, error) {
    hash, err := bcrypt.GenerateFromPassword(
        []byte(password), 
        bcrypt.DefaultCost,
    )
    if err != nil {
        return "", fmt.Errorf("failed to hash password: %w", err)
    }
    return string(hash), nil
}

func VerifyPassword(hashedPassword, password string) error {
    return bcrypt.CompareHashAndPassword(
        []byte(hashedPassword), 
        []byte(password),
    )
}
Security Features:
  • Adaptive cost factor (increases over time)
  • Salt automatically generated per password
  • Timing-safe comparison prevents timing attacks
  • Resistant to rainbow table attacks

Rate Limiting

Protects against brute-force attacks:
type AuthRateLimiter struct {
    limiter *rate.Limiter
    mu      sync.Mutex
}

func NewAuthRateLimiter() *AuthRateLimiter {
    // Allow 5 attempts per minute
    return &AuthRateLimiter{
        limiter: rate.NewLimiter(rate.Every(12*time.Second), 5),
    }
}

func (rl *AuthRateLimiter) Allow() bool {
    rl.mu.Lock()
    defer rl.mu.Unlock()
    return rl.limiter.Allow()
}

Multi-Tenant Data Isolation

Row-Level Security

All queries automatically filtered by user_id:
func (r *JobRepository) GetByID(ctx context.Context, userID int, id int) (*models.Job, error) {
    sql, args, _ := sq.Select("*").
        From("jobs").
        Where(sq.Eq{
            "user_id": userID, // Enforced at repository level
            "id":      id,
        }).
        ToSql()

    var job models.Job
    err := r.db.QueryRowContext(ctx, sql, args...).Scan(&job)
    if err == sql.ErrNoRows {
        return nil, ErrJobNotFound // User cannot access other users' data
    }
    return &job, err
}

Repository Pattern Enforcement

Compile-time safety through interfaces:
type JobRepository interface {
    Create(ctx context.Context, userID int, job *models.Job) error
    GetByID(ctx context.Context, userID int, id int) (*models.Job, error)
    Update(ctx context.Context, userID int, job *models.Job) error
    Delete(ctx context.Context, userID int, id int) error
    List(ctx context.Context, userID int, filters JobFilters) ([]*models.Job, error)
}
All methods require userID parameter, preventing accidental cross-tenant data access.

Cache Isolation

Cache keys prefixed with user ID:
func (s *JobService) GetByID(ctx context.Context, userID int, id int) (*models.Job, error) {
    cacheKey := fmt.Sprintf("job:u%d:%d", userID, id)

    var job models.Job
    if err := s.cache.Get(ctx, cacheKey, &job); err == nil {
        return &job, nil
    }

    job, err := s.repo.GetByID(ctx, userID, id)
    if err == nil {
        s.cache.Set(ctx, cacheKey, job, time.Hour)
    }
    return job, err
}
Cache Invalidation:
func (s *JobService) Delete(ctx context.Context, userID int, id int) error {
    if err := s.repo.Delete(ctx, userID, id); err != nil {
        return err
    }

    // Invalidate specific job cache
    cacheKey := fmt.Sprintf("job:u%d:%d", userID, id)
    s.cache.Delete(ctx, cacheKey)

    // Invalidate list caches
    listPattern := fmt.Sprintf("jobs:u%d:*", userID)
    s.cache.DeletePattern(ctx, listPattern)

    return nil
}

GDPR-Compliant Logging

Vega AI implements privacy-first logging that complies with GDPR:

Core Principles

1

No Direct PII

Never log personal data like emails, names, or IP addresses
2

Use References

Log anonymous identifiers (user_id) instead of personal info
3

Hash Identifiers

Use one-way hashes for correlation without exposing data
4

Event-Based

Log events and actions, not sensitive data

What NOT to Log

Never log these:
  • Email addresses
  • Usernames or full names
  • IP addresses
  • OAuth tokens or API keys
  • Password hashes or plaintext passwords
  • Any personally identifiable information (PII)

What TO Log

Safe to log:
  • User references: user_123
  • Event types: login_success, job_created
  • Anonymous metrics: counts, durations
  • Error types (without sensitive data)
  • Request IDs for tracing

Privacy-Aware Logger

type PrivacyLogger struct {
    logger zerolog.Logger
    module string
}

func GetPrivacyLogger(module string) *PrivacyLogger {
    return &PrivacyLogger{
        logger: log.With().Str("module", module).Logger(),
        module: module,
    }
}

func (p *PrivacyLogger) LogAuthEvent(event string, userID int, success bool) {
    p.logger.Info().
        Str("event", event).
        Int("user_ref", userID).  // Reference, not PII
        Bool("success", success).
        Msg("Auth event")
}

Identifier Hashing

import "crypto/sha256"
import "encoding/base64"

func HashIdentifier(identifier string) string {
    h := sha256.Sum256([]byte(identifier))
    return base64.URLEncoding.EncodeToString(h[:])
}

// Usage: Correlate events without exposing email
hashedEmail := HashIdentifier(user.Email)
log.Info().Str("user_hash", hashedEmail).Msg("User action")

Email Redaction

func RedactEmail(email string) string {
    parts := strings.Split(email, "@")
    if len(parts) != 2 {
        return "[invalid]"
    }

    localPart := parts[0]
    domain := parts[1]

    if len(localPart) <= 2 {
        return "*@" + domain
    }

    return string(localPart[0]) + "***" + string(localPart[len(localPart)-1]) + "@" + domain
}

// Example: [email protected] -> j***[email protected]

Example Logging Patterns

Good:
log.Info().
    Int("user_ref", userID).
    Str("event", "job_created").
    Int("job_id", jobID).
    Msg("Job created successfully")

log.Error().
    Err(err).
    Int("user_ref", userID).
    Str("operation", "job_analysis").
    Msg("AI analysis failed")
Bad:
// ❌ Don't do this!
log.Info().
    Str("email", user.Email).              // PII!
    Str("username", user.Username).        // PII!
    Str("ip_address", request.RemoteAddr). // PII!
    Msg("User logged in")

Security Headers

Protects against common web vulnerabilities:
func SecurityHeaders() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("X-Frame-Options", "DENY")
        c.Header("X-Content-Type-Options", "nosniff")
        c.Header("X-XSS-Protection", "1; mode=block")
        c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
        c.Header("Content-Security-Policy", 
            "default-src 'self'; "+
            "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://unpkg.com; "+
            "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; "+
            "img-src 'self' data: https:; "+
            "font-src 'self' data:; "+
            "connect-src 'self';",
        )
        c.Next()
    }
}
Header Explanations:

X-Frame-Options

Prevents clickjacking by disallowing iframe embedding

X-Content-Type-Options

Prevents MIME type sniffing attacks

X-XSS-Protection

Enables browser XSS filtering

Strict-Transport-Security

Forces HTTPS connections for 1 year

Content-Security-Policy

Restricts resource loading to trusted sources

CSRF Protection

Token-based CSRF protection for state-changing operations:
func CSRF(cfg *config.Settings) gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.Request.Method == "GET" || c.Request.Method == "HEAD" || c.Request.Method == "OPTIONS" {
            c.Next()
            return
        }

        // Check CSRF token
        token := c.GetHeader("X-CSRF-Token")
        if token == "" {
            token = c.PostForm("csrf_token")
        }

        sessionToken, _ := c.Cookie("csrf_token")
        if token == "" || token != sessionToken {
            c.JSON(http.StatusForbidden, gin.H{"error": "Invalid CSRF token"})
            c.Abort()
            return
        }

        c.Next()
    }
}

API Security

Input Validation

import "github.com/go-playground/validator/v10"

type CreateJobRequest struct {
    Title          string   `json:"title" binding:"required,min=3,max=200"`
    Description    string   `json:"description" binding:"required,min=10"`
    Location       string   `json:"location" binding:"omitempty,max=100"`
    SourceURL      string   `json:"source_url" binding:"omitempty,url"`
    RequiredSkills []string `json:"required_skills" binding:"omitempty,dive,min=1,max=50"`
}

func (h *JobHandler) CreateJob(c *gin.Context) {
    var req CreateJobRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    // Process validated input
}

SQL Injection Prevention

Using Squirrel query builder prevents SQL injection:
// Safe: Parameterized queries
sql, args, _ := sq.Select("*").
    From("jobs").
    Where(sq.Eq{"user_id": userID, "title": title}).
    ToSql()

db.QueryContext(ctx, sql, args...)

// Generated SQL: SELECT * FROM jobs WHERE user_id = ? AND title = ?
// Args: [123, "Software Engineer"]

Secret Management

Supports Docker secrets via _FILE suffix:
# Environment variable
TOKEN_SECRET=my-secret-key

# Docker secret file
TOKEN_SECRET_FILE=/run/secrets/token_secret

# File contents: my-secret-key
func getEnv(key string, defaultValue string) string {
    if filePath := os.Getenv(key + "_FILE"); filePath != "" {
        // Security checks: absolute path, no symlinks, size limit
        if !filepath.IsAbs(filePath) || strings.Contains(filePath, "..") {
            log.Warn().Msgf("Invalid secret file path: %s", filePath)
            return defaultValue
        }

        content, err := os.ReadFile(filePath)
        if err == nil {
            return strings.TrimSpace(string(content))
        }
    }

    if value := os.Getenv(key); value != "" {
        return value
    }

    return defaultValue
}

Security Best Practices

1

Principle of Least Privilege

Services only access data they need, user isolation enforced
2

Defense in Depth

Multiple security layers: authentication, authorization, encryption
3

Secure Defaults

Security features enabled by default (HTTPS, CSRF, headers)
4

Graceful Degradation

Cache failures don’t expose sensitive data
5

Regular Updates

Dependencies updated regularly for security patches
6

Privacy by Design

GDPR-compliant logging, no PII exposure
Vega AI’s security architecture provides defense in depth with authentication, authorization, data isolation, privacy-first logging, and protection against common web vulnerabilities.

Build docs developers (and LLMs) love