Skip to content

Latest commit

 

History

History
252 lines (197 loc) · 6.6 KB

File metadata and controls

252 lines (197 loc) · 6.6 KB

Security Standards Quick Reference

Part of CLAUDE.md Critical Conventions

When to load: Working on authentication, authorization, RBAC, or handling sensitive data

Critical Security Rules

Token Handling

1. User Token Authentication Required

// ALWAYS for user-initiated operations
reqK8s, reqDyn := GetK8sClientsForRequest(c)
if reqK8s == nil {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing token"})
    c.Abort()
    return
}

2. Token Redaction in Logs

FORBIDDEN:

log.Printf("Authorization: Bearer %s", token)
log.Printf("Request headers: %v", headers)

REQUIRED:

log.Printf("Token length: %d", len(token))
// Redact in URL paths
path = strings.Split(path, "?")[0] + "?token=[REDACTED]"

Token Redaction Pattern: See server/server.go:22-34

// Custom log formatter that redacts tokens
func customRedactingFormatter(param gin.LogFormatterParams) string {
    path := param.Path
    if strings.Contains(path, "token=") {
        path = strings.Split(path, "?")[0] + "?token=[REDACTED]"
    }
    // ... rest of formatting
}

Token Lifetime (K8s TokenRequest)

K8s TokenRequest API does NOT support non-expiring tokens. If ExpirationSeconds is omitted, K8s silently applies a default (~1h). Maximum enforced lifetime: 1 year (31536000 seconds), validated in backend CreateProjectKey. Frontend default: 90 days.

RBAC Enforcement

1. Always Check Permissions Before Operations

ssar := &authv1.SelfSubjectAccessReview{
    Spec: authv1.SelfSubjectAccessReviewSpec{
        ResourceAttributes: &authv1.ResourceAttributes{
            Group:     "vteam.ambient-code",
            Resource:  "agenticsessions",
            Verb:      "list",
            Namespace: project,
        },
    },
}
res, err := reqK8s.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{})
if err != nil || !res.Status.Allowed {
    c.JSON(http.StatusForbidden, gin.H{"error": "Unauthorized"})
    return
}

2. Namespace Isolation

  • Each project maps to a Kubernetes namespace
  • User token must have permissions in that namespace
  • Never bypass namespace checks

Container Security

Always Set SecurityContext for Job Pods

SecurityContext: &corev1.SecurityContext{
    AllowPrivilegeEscalation: boolPtr(false),
    ReadOnlyRootFilesystem:   boolPtr(false),  // Only if temp files needed
    Capabilities: &corev1.Capabilities{
        Drop: []corev1.Capability{"ALL"},
    },
},

Input Validation

1. Validate All User Input

// Validate resource names (K8s DNS label requirements)
if !isValidK8sName(name) {
    c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid name format"})
    return
}

// Validate URLs for repository inputs
if _, err := url.Parse(repoURL); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid repository URL"})
    return
}

2. Sanitize for Log Injection

// Prevent log injection with newlines
name = strings.ReplaceAll(name, "\n", "")
name = strings.ReplaceAll(name, "\r", "")

Common Security Patterns

Pattern 1: Extracting Bearer Token

rawAuth := c.GetHeader("Authorization")
parts := strings.SplitN(rawAuth, " ", 2)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
    c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid Authorization header"})
    return
}
token := strings.TrimSpace(parts[1])
// NEVER log token itself
log.Printf("Processing request with token (len=%d)", len(token))

Pattern 2: Validating Project Access

func ValidateProjectContext() gin.HandlerFunc {
    return func(c *gin.Context) {
        projectName := c.Param("projectName")

        // Get user-scoped K8s client
        reqK8s, _ := GetK8sClientsForRequest(c)
        if reqK8s == nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
            c.Abort()
            return
        }

        // Check if user can access namespace
        ssar := &authv1.SelfSubjectAccessReview{
            Spec: authv1.SelfSubjectAccessReviewSpec{
                ResourceAttributes: &authv1.ResourceAttributes{
                    Resource:  "namespaces",
                    Verb:      "get",
                    Name:      projectName,
                },
            },
        }
        res, err := reqK8s.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, ssar, v1.CreateOptions{})
        if err != nil || !res.Status.Allowed {
            c.JSON(http.StatusForbidden, gin.H{"error": "Access denied to project"})
            c.Abort()
            return
        }

        c.Set("project", projectName)
        c.Next()
    }
}

Pattern 3: Minting Service Account Tokens

// Only backend service account can create tokens for runner pods
tokenRequest := &authv1.TokenRequest{
    Spec: authv1.TokenRequestSpec{
        ExpirationSeconds: int64Ptr(3600),
    },
}

tokenResponse, err := K8sClient.CoreV1().ServiceAccounts(namespace).CreateToken(
    ctx,
    serviceAccountName,
    tokenRequest,
    v1.CreateOptions{},
)
if err != nil {
    return fmt.Errorf("failed to create token: %w", err)
}

// Store token in secret (never log it)
secret := &corev1.Secret{
    ObjectMeta: v1.ObjectMeta{
        Name:      fmt.Sprintf("%s-token", sessionName),
        Namespace: namespace,
    },
    StringData: map[string]string{
        "token": tokenResponse.Status.Token,
    },
}

Security Checklist

Before committing code that handles:

Authentication:

  • Using user token (GetK8sClientsForRequest) for user operations
  • Returning 401 if token is invalid/missing
  • Not falling back to service account on auth failure

Authorization:

  • RBAC check performed before resource access
  • Using correct namespace for permission check
  • Returning 403 if user lacks permissions

Secrets & Tokens:

  • No tokens in logs (use len(token) instead)
  • No tokens in error messages
  • Tokens stored in Kubernetes Secrets
  • Token redaction in request logs

Input Validation:

  • All user input validated
  • Resource names validated (K8s DNS label format)
  • URLs parsed and validated
  • Log injection prevented

Container Security:

  • SecurityContext set on all Job pods
  • AllowPrivilegeEscalation: false
  • Capabilities dropped (ALL)
  • OwnerReferences set for cleanup

Security Review Resources