| layout | default |
|---|---|
| title | Security |
| nav_order | 5 |
Version: 2.0 Last Updated: 2026-03-26 Security Audit: OWASP Top 10:2021
- Security Overview
- OWASP Security Audit
- Critical Security Issues
- High Priority Issues
- Medium Priority Issues
- Security Best Practices
- Secure Configuration
- Vulnerability Disclosure
- Security Roadmap
Overall Rating: GOOD (8.2/10) -- Updated 2026-03-26 after SSRF fix and connector probe security hardening
kshark demonstrates strong security practices across input validation, credential redaction, SSRF protection, and connector credential handling.
✅ Implemented Controls:
- SSRF protection with two-tier model (DENY loopback/link-local/metadata, WARN RFC1918)
- Redirect-based SSRF bypass prevention (
CheckRedirecthandler) - Command injection prevention via hostname validation
- Credential redaction in all outputs (including
sasl.jaas.config, bearer tokens, connector passwords) - Credential scrubbing in database probe error messages (
ScrubCredentials) - Connect API auth via environment variables (
KSHARK_CONNECT_AUTH,KSHARK_CONNECT_TOKEN) - Path traversal protection
- TLS 1.2+ enforcement
- Certificate expiry monitoring
- Non-root Docker execution
- Response body size limits on HTTP clients (1MB)
- URL scheme validation (http/https only)
- Plain-text credential storage in configuration files (mitigated with file permissions)
- Credentials in memory as
string(consider[]bytefor zeroability in future)
Comprehensive audit performed against OWASP Top 10:2021 standards.
| OWASP Category | Status | Score | Notes |
|---|---|---|---|
| A01 - Broken Access Control | ✅ Secure | 9/10 | Path traversal protection, non-root Docker |
| A02 - Cryptographic Failures | ✅ Good | 7/10 | TLS 1.2+ enforced, SHA256 checksums |
| A03 - Injection | ✅ Secure | 9/10 | Hostname regex, pgQuote DSN quoting |
| A04 - Insecure Design | ✅ Good | 8/10 | Layered architecture, credential separation |
| A05 - Security Misconfiguration | ✅ Good | 7/10 | File permission warnings, env var support |
| A06 - Vulnerable Components | ✅ Good | 8/10 | govulncheck in CI, Dependabot configured |
| A07 - Authentication Failures | ✅ Good | 7/10 | Credential redaction, env var fallback |
| A08 - Data Integrity | ✅ Good | 8/10 | SHA256 report checksums, prompt SHA256 |
| A09 - Logging | ✅ Good | 8/10 | Structured slog logging (JSON/text) |
| A10 - SSRF | ✅ Secure | 9/10 | Two-tier DENY/WARN model, redirect protection |
Overall Security Score: 8.0/10
Original Severity: CRITICAL (CVSS 8.6)
Current Status: RESOLVED (9/10)
Fix Location: cmd/kshark/ssrf.go + cmd/kshark/httpcheck.go
The original code accepted arbitrary URLs from configuration files for Schema Registry, REST Proxy, and the Connect API without validation, enabling internal network scanning, cloud metadata access, and service probing.
A two-tier SSRF protection model is now implemented in cmd/kshark/ssrf.go:
- DENY (hard block): Loopback (
127.0.0.0/8,::1), link-local (169.254.0.0/16,fe80::/10), multicast, broadcast, and other reserved ranges. These are never legitimate targets for Kafka infrastructure. - WARN (allow with warning): RFC1918 private ranges (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16). These are allowed because many Kafka clusters use AWS PrivateLink or VPC peering with private IPs. - Redirect protection:
CheckRedirecthandler validates each redirect destination against the same SSRF rules. - Bounded reads: All HTTP response bodies are limited via
io.LimitReader(1MB) to prevent memory exhaustion. - Scheme validation: Only
http://andhttps://schemes are permitted.
All user-supplied URLs are validated before use:
- Schema Registry URL (
schema.registry.url) - REST Proxy URL (
rest.proxy.url) - AI API endpoint URL
- Kafka Connect REST API URL (
-connect-url)
37 test cases across cmd/kshark/ssrf_test.go, httpcheck_test.go, main_test.go, and ai_test.go cover:
- Loopback, link-local, metadata IPs (denied)
- RFC1918 private ranges (warned)
- Public IPs (allowed)
- Invalid schemes, empty hosts, redirect chains
- Implement
isAllowedURL()function --cmd/kshark/ssrf.go - Implement IP classification (
classifyIP()) --cmd/kshark/ssrf.go - Add redirect validation to
httpClientFromTLS()--checkRedirectSSRF - Update
checkSchemaRegistry()to validate URLs - Update REST Proxy check to validate URLs
- RFC1918 allowed for PrivateLink (WARN, not DENY)
- Test with various malicious URLs -- 37 SSRF test cases across 4 test files + 1 fuzz target
- Document URL validation in configuration guide
Test Cases:
# Should FAIL - Internal IP
schema.registry.url=http://192.168.1.1:8081
# Should FAIL - Localhost
schema.registry.url=http://localhost:8081
# Should FAIL - AWS metadata
schema.registry.url=http://169.254.169.254/latest/meta-data/
# Should PASS - Valid public HTTPS
schema.registry.url=https://schema-registry.example.com
# Should FAIL - Redirect to internal IP
# (Requires redirect test server)Severity: HIGH CVSS Score: 7.5 (High) Location: Multiple files
Credentials are stored in plain text in configuration files:
client.properties- Kafka credentialsai_config.json- AI provider API keys- File permission validation now warns on insecure permissions (
warnInsecurePermissions) - Environment variable expansion (
${VAR}) available to avoid storing secrets in files - No encryption at rest
client.properties:
sasl.username=YOUR_API_KEY
sasl.password=YOUR_API_SECRET # ⚠️ Plain textai_config.json:
{
"provider": "openai",
"api_key": "sk-xxxx..." // ⚠️ Plain text
}- Credentials exposed if file system is compromised
- Credentials visible in process listings
- Credentials may be logged or cached
- Difficult to rotate credentials
- Compliance issues (PCI-DSS, SOC 2, etc.)
Option 1: Environment Variable Support (Quick Win)
Add environment variable substitution:
// Add to loadProperties function (Line 346)
func loadProperties(path string) (map[string]string, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
props := map[string]string{}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
// ✅ ADD THIS: Expand environment variables
value = os.ExpandEnv(value)
props[key] = value
}
}
return props, scanner.Err()
}Usage:
# client.properties
sasl.username=${KAFKA_USERNAME}
sasl.password=${KAFKA_PASSWORD}export KAFKA_USERNAME="my-api-key"
export KAFKA_PASSWORD="my-secret"
./kshark -props client.propertiesOption 2: File Permission Validation
// Add after loadProperties function
func validateFilePermissions(path string) error {
info, err := os.Stat(path)
if err != nil {
return err
}
mode := info.Mode()
// File should not be readable by group or others
if mode.Perm()&0077 != 0 {
return fmt.Errorf("file %s has insecure permissions %o (should be 0600)",
path, mode.Perm())
}
return nil
}
// Call in main() before loading properties
if err := validateFilePermissions(*propsFile); err != nil {
fmt.Fprintf(os.Stderr, "Warning: %v\n", err)
fmt.Fprintln(os.Stderr, "Hint: Run 'chmod 600 <file>' to secure it")
}Option 3: Encrypted Configuration (Future)
// Encrypt configuration files with age or similar
// Decrypt at runtime with passphrase or key file
func loadEncryptedProperties(path, keyFile string) (map[string]string, error) {
// Implementation using age or similar encryption library
}Option 4: Secret Manager Integration (Best Practice)
// AWS Secrets Manager
func getSecretFromAWS(secretName string) (string, error) {
cfg, _ := config.LoadDefaultConfig(context.TODO())
client := secretsmanager.NewFromConfig(cfg)
result, err := client.GetSecretValue(context.TODO(), &secretsmanager.GetSecretValueInput{
SecretId: aws.String(secretName),
})
if err != nil {
return "", err
}
return *result.SecretString, nil
}
// Usage in properties:
// sasl.password=aws-secret://prod/kafka/password- ✅ Done: Environment variable support --
os.ExpandEnv()inloadProperties(),${VAR}syntax - ✅ Done: File permission validation --
warnInsecurePermissions()warns on mode > 0600 - 📅 Next Sprint: Document secret manager integration patterns
- 📅 Future: Implement built-in encryption support
Original Severity: HIGH (CVSS 6.5)
Current Status: RESOLVED (8/10)
Fix Location: cmd/kshark/util.go (logger init), throughout all source files
Structured logging via Go's log/slog is now integrated throughout the application:
--log-format text|jsonflag selects output format--logflag writes scan logs to a file (default:reports/kshark-<timestamp>.log)- All security-relevant events are logged with structured key-value pairs
- Log files include SHA256 checksums in the JSON report for integrity verification
{"time":"2026-03-26T14:30:22Z","level":"DEBUG","msg":"scan start","props":"client.properties","timeout":"1m0s","kafka_timeout":"10s"}
{"time":"2026-03-26T14:30:23Z","level":"DEBUG","msg":"broker check start","host":"broker.example.com","port":"9092"}
{"time":"2026-03-26T14:30:24Z","level":"DEBUG","msg":"broker check ok","addr":"broker.example.com:9092"}
{"time":"2026-03-26T14:30:25Z","level":"DEBUG","msg":"rest proxy URL warning","warning":"RFC1918 address"}Severity: MEDIUM (originally)
Current Status: MITIGATED
Location: cmd/kshark/ai.go
AI API error responses no longer expose raw response bodies to users. Full error details are logged to the structured log file (accessible only to the operator), while user-facing messages show only the HTTP status code. Connector credential values are scrubbed from database probe error messages via ScrubCredentials() in internal/connectapi/redact.go.
Severity: MEDIUM
Location: cmd/kshark/main.go
if strings.HasPrefix(providerConfig.APIKey, "YOUR_") || providerConfig.APIKey == "" {
fmt.Fprintf(os.Stderr, "Error: API key for provider '%s' is a placeholder...\n")
os.Exit(1)
}
// ⚠️ Only checks prefix, doesn't validate key formatfunc validateAPIKey(provider, key string) error {
if key == "" {
return fmt.Errorf("API key is empty")
}
if strings.HasPrefix(key, "YOUR_") {
return fmt.Errorf("API key is a placeholder")
}
// Provider-specific validation
switch provider {
case "openai":
if !strings.HasPrefix(key, "sk-") || len(key) < 20 {
return fmt.Errorf("invalid OpenAI API key format")
}
case "scalytics":
if len(key) < 32 {
return fmt.Errorf("invalid Scalytics API key format")
}
}
return nil
}
// Usage
if err := validateAPIKey(providerConfig.Provider, providerConfig.APIKey); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}Severity: MEDIUM
Location: go.mod, go.sum
github.com/segmentio/kafka-go v0.4.49
github.com/klauspost/compress v1.15.9
golang.org/x/net v0.38.0
1. Run Vulnerability Scan:
# Install govulncheck
go install golang.org/x/vuln/cmd/govulncheck@latest
# Scan for vulnerabilities
govulncheck ./...2. Check for Updates:
go list -m -u all3. Add Dependabot Configuration:
Create .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
reviewers:
- "security-team"
labels:
- "dependencies"
- "security"4. Add to CI/CD:
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main]
pull_request:
schedule:
- cron: '0 0 * * 0' # Weekly
jobs:
govulncheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
- name: Run govulncheck
run: govulncheck ./...# Create secure configuration directory
mkdir -p ~/.kshark
chmod 700 ~/.kshark
# Create configuration with restricted permissions
cat > ~/.kshark/client.properties <<EOF
bootstrap.servers=broker.example.com:9092
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-256
sasl.username=your-username
sasl.password=your-password
EOF
chmod 600 ~/.kshark/client.properties# Set credentials in environment
export KAFKA_USERNAME="my-api-key"
export KAFKA_PASSWORD="my-secret"
# Reference in properties file
cat > client.properties <<EOF
bootstrap.servers=broker.example.com:9092
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-256
sasl.username=${KAFKA_USERNAME}
sasl.password=${KAFKA_PASSWORD}
EOF# Add to .gitignore
cat >> .gitignore <<EOF
*.properties
ai_config.json
license.key
*.key
*.pem
*.crt
*secret*
*credential*
EOF# ✅ GOOD - TLS enabled
security.protocol=SASL_SSL
# ❌ BAD - No encryption
security.protocol=SASL_PLAINTEXT# ✅ BETTER - SCRAM-SHA-256
sasl.mechanism=SCRAM-SHA-256
# ⚠️ OKAY - PLAIN (with TLS only)
sasl.mechanism=PLAINAll user input must be validated:
- ✅ Hostname validation (implemented in
cmd/kshark/diagnostics.go) - ✅ Path traversal prevention (implemented in
cmd/kshark/report.go) - ✅ URL validation / SSRF protection (implemented in
cmd/kshark/ssrf.go)
- ✅ Redact credentials in all outputs
- ✅ Never log passwords
- ✅ Environment variable fallback for connector credentials
- ✅ Database probe error messages scrubbed of credentials
# Regular updates
go get -u ./...
go mod tidy
# Security scanning
govulncheck ./...- No hardcoded credentials
- All user input validated
- Errors don't leak sensitive data
- TLS used for all external connections
- Credentials redacted in logs
- File permissions checked (warning on group/other readable)
- URLs validated before use (SSRF two-tier model)
# Broker connection
bootstrap.servers=${KAFKA_BOOTSTRAP_SERVERS}
# Security - Always use SASL_SSL
security.protocol=SASL_SSL
# Authentication - Prefer SCRAM
sasl.mechanism=SCRAM-SHA-256
sasl.username=${KAFKA_USERNAME}
sasl.password=${KAFKA_PASSWORD}
# TLS - Always verify certificates
ssl.ca.location=/path/to/ca-cert.pem
# Optionally use client certificates
ssl.certificate.location=/path/to/client-cert.pem
ssl.key.location=/path/to/client-key.pem
# Schema Registry (validate URL!)
schema.registry.url=https://schema-registry.example.com
basic.auth.user.info=${SR_KEY}:${SR_SECRET}kubectl create secret generic kshark-credentials \
--from-literal=bootstrap.servers=broker.example.com:9092 \
--from-literal=sasl.username=my-username \
--from-literal=sasl.password=my-password \
--dry-run=client -o yaml | kubectl apply -f -# Create secrets
echo "my-username" | docker secret create kafka_username -
echo "my-password" | docker secret create kafka_password -
# Use in compose
services:
kshark:
image: kshark:latest
secrets:
- kafka_username
- kafka_password
environment:
- KAFKA_USERNAME_FILE=/run/secrets/kafka_username
- KAFKA_PASSWORD_FILE=/run/secrets/kafka_passwordDO NOT open public GitHub issues for security vulnerabilities.
Instead, please:
- Email: security@scalytics.io
- Subject: [SECURITY] kshark vulnerability report
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if available)
- 24 hours: Acknowledgment of report
- 7 days: Initial assessment
- 30 days: Fix developed and tested
- Coordinated disclosure: After fix is released
Security advisories are published at:
- GitHub Security Advisories
- Release notes for patched versions
-
CRITICAL: SSRF protection (two-tier DENY/WARN model in
cmd/kshark/ssrf.go)-
isAllowedURL()with full CIDR classification -
classifyIP()with 14 deny + 4 warn ranges - Schema Registry URL validation
- REST Proxy URL validation
- AI endpoint URL validation
- Redirect-based SSRF prevention (
checkRedirectSSRF)
-
-
HIGH: Environment variable support
-
os.ExpandEnv()inloadProperties()--${VAR}syntax works -
KSHARK_CONNECT_AUTH/KSHARK_CONNECT_TOKENenv var fallback
-
-
HIGH: File permission validation
-
warnInsecurePermissions()warns on group/other readable files - Documented in Security Best Practices
-
-
HIGH: Structured security logging
-
log/slogintegration with JSON/text handlers -
--log-format json|textflag - All security events logged with structured key-value pairs
-
-
MEDIUM: Dependency scanning automation
-
govulncheckin CI pipeline (every push + weekly) - Dependabot for Go modules and GitHub Actions
-
golangci-lintwithgosecin CI
-
-
MEDIUM: Error message sanitization
- AI API errors no longer expose response body
- CLI args redacted in report metadata (
redactArgs()) - Connector credentials scrubbed from error messages
-
LOW: Fuzz testing for security-critical parsers
-
auth_fuzz_test.go-- SASL credential parsing -
properties_fuzz_test.go-- Properties file parsing with env var expansion -
ssrf_fuzz_test.go-- URL validation and IP classification -
jdbc_url_fuzz_test.go-- JDBC URL parsing (DB2, PostgreSQL)
-
-
MEDIUM: CI quality gates
-
.golangci.ymlwithgosecsecurity linter - Coverage gate in CI pipeline
-
govulncheckin weekly security scan workflow (security.yml)
-
-
MEDIUM: Graceful signal handling
- SIGINT/SIGTERM cancel scan context via
signal.Notify - All scan phases guarded by
ctx.Done()inrunScan()
- SIGINT/SIGTERM cancel scan context via
- MEDIUM: Secret manager integration (AWS/Vault/Azure)
- LOW: Configuration file encryption
- LOW: Certificate pinning
- LOW: SBOM generation and release signing
- All credentials use environment variables or secret managers
- Configuration files have 0600 permissions
- SSRF protection implemented
- TLS 1.2+ enforced
- Certificate validation enabled
- Security logging configured
- Dependency vulnerabilities scanned
- Container image scanned (if using Docker)
- Network policies configured (if using Kubernetes)
- Secrets stored in Kubernetes Secrets (not ConfigMaps)
- Non-root user execution
- Read-only filesystem (where possible)
- Minimal container capabilities
- Network policies enforced
- Security monitoring enabled
- Regular security scans scheduled
- Incident response plan documented
These controls should be maintained in all future versions:
-
✅ SSRF Two-Tier Protection (
cmd/kshark/ssrf.go)- DENY loopback/link-local/metadata, WARN RFC1918
- Redirect-based bypass prevention
- Bounded HTTP reads (1MB)
-
✅ Structured slog Logging (
cmd/kshark/util.go)- JSON or text format via
--log-format - All security events logged with structured key-value pairs
- Log file SHA256 checksums in report
- JSON or text format via
-
✅ Command Injection Prevention (
cmd/kshark/diagnostics.go)isValidHostname()regex validation before shell commands
-
✅ Credential Redaction (
cmd/kshark/util.go,internal/connectapi/redact.go)- Redacts: password, secret, token, key, bearer, JAAS config, auth info
ScrubCredentials()for database probe error messagesredactArgs()for CLI arguments in report metadata
-
✅ Path Traversal Prevention (
cmd/kshark/report.go)createSafeReportPath()usesfilepath.Base()to strip directory components
-
✅ TLS Enforcement (
cmd/kshark/tls.go)tls.Config{MinVersion: tls.VersionTLS12}on all TLS connections
-
✅ Certificate Expiry Monitoring (
cmd/kshark/tls.go) -
✅ SHA256 Checksums (
cmd/kshark/util.go)- Report files, log files, and analysis prompts all have SHA256 checksums
-
✅ Timeout Controls - All network operations have configurable timeouts
-
✅ Context Usage - Proper context propagation for cancellation via
runScan(ctx, report, scanConfig{}) -
✅ Signal Handling - SIGINT/SIGTERM gracefully cancel scan context; all phases exit at next
ctx.Done()check -
✅ Non-root Docker User (Dockerfile)
-
✅ HTML Template Auto-escaping (Go html/template)
-
✅ Minimal GitHub Actions Permissions
-
✅ Release Checksum Generation (.goreleaser.yaml)
-
✅ Safe JSON Deserialization (No reflection attacks)
Document Version: 2.0 Security Audit Date: 2026-03-26 Next Security Review: 2026-06-26 Contact: security@scalytics.io