fix(audit-1.6,1.7): remove hardcoded test secrets, block bypass flags in prod

- 1.6: Replace hardcoded JWT secrets in chat server tests with runtime-generated
  values (env TEST_JWT_SECRET or uuid-based fallback)
- 1.7: Add validateNoBypassFlagsInProduction() in config; fail startup if
  BYPASS_CONTENT_CREATOR_ROLE or CSRF_DISABLED is set in production

Refs: AUDIT_TECHNIQUE_INTEGRAL_2026_02_15.md items 1.6, 1.7
This commit is contained in:
senke 2026-02-15 14:18:23 +01:00
parent a6a9be9ada
commit 2e04d45a14
5 changed files with 108 additions and 4 deletions

View file

@ -653,8 +653,8 @@ veza/
| 1.3 | ~~Ajouter auth sur les endpoints HLS~~ | **M** | `veza-stream-server/src/routes/api.rs` | **✅ Fait** |
| 1.4 | ~~Remplacer `panic()` par erreur gracieuse si Redis down~~ | **S** | `veza-backend-api/internal/api/routes_core.go:242` | **✅ Fait** |
| 1.5 | ~~Corriger les `.unwrap()` critiques en Rust (paths de production)~~ | **M** | `veza-stream-server/src/`, `veza-chat-server/src/` | **✅ Fait** |
| 1.6 | Supprimer les secrets test hardcodés | **S** | `veza-chat-server/src/config.rs:216`, `jwt_manager.rs:575` | P1 |
| 1.7 | Ajouter protection contre bypass flags en production | **S** | `veza-backend-api/internal/middleware/auth.go`, `csrf.go` | P1 |
| 1.6 | ~~Supprimer les secrets test hardcodés~~ | **S** | `veza-chat-server/src/config.rs:216`, `jwt_manager.rs:575` | **✅ Fait** |
| 1.7 | ~~Ajouter protection contre bypass flags en production~~ | **S** | `veza-backend-api/internal/middleware/auth.go`, `csrf.go` | **✅ Fait** |
| 1.8 | Implémenter OAuth user lookup | **M** | `veza-backend-api/internal/database/database.go:559` | P1 |
| 1.9 | Ajouter `cargo audit` au CI | **S** | `.github/workflows/chat-ci.yml`, `stream-ci.yml` | P2 |

View file

@ -1393,6 +1393,31 @@ func (c *Config) Validate() error {
return fmt.Errorf("RATE_LIMIT_WINDOW validation failed: %w", err)
}
// Audit 1.7: Fail startup if bypass flags are set in production
if err := validateNoBypassFlagsInProduction(c.Env); err != nil {
return err
}
return nil
}
// validateNoBypassFlagsInProduction vérifie qu'aucun flag de bypass n'est activé en production (audit 1.7)
func validateNoBypassFlagsInProduction(env string) error {
envNorm := strings.ToLower(strings.TrimSpace(env))
if envNorm != "production" && envNorm != "prod" {
return nil // Pas en production, pas de vérification
}
var violations []string
if os.Getenv("BYPASS_CONTENT_CREATOR_ROLE") == "true" {
violations = append(violations, "BYPASS_CONTENT_CREATOR_ROLE=true")
}
if os.Getenv("CSRF_DISABLED") == "true" {
violations = append(violations, "CSRF_DISABLED=true")
}
if len(violations) > 0 {
return fmt.Errorf("security: bypass flags are not allowed in production: %s. Remove these environment variables before deploying",
strings.Join(violations, ", "))
}
return nil
}

View file

@ -1,6 +1,7 @@
package config
import (
"os"
"strings"
"testing"
@ -291,3 +292,78 @@ func TestConfig_Validate(t *testing.T) {
})
}
}
// TestValidateNoBypassFlagsInProduction vérifie que les bypass flags bloquent le démarrage en production (audit 1.7)
func TestValidateNoBypassFlagsInProduction(t *testing.T) {
validConfig := &Config{
Env: "development",
AppPort: 8080,
JWTSecret: strings.Repeat("a", 32),
DatabaseURL: "postgresql://user:pass@localhost:5432/db",
RedisURL: "redis://localhost:6379",
RateLimitLimit: 100,
RateLimitWindow: 60,
}
t.Run("production with BYPASS_CONTENT_CREATOR_ROLE fails", func(t *testing.T) {
orig := os.Getenv("BYPASS_CONTENT_CREATOR_ROLE")
defer func() {
if orig != "" {
os.Setenv("BYPASS_CONTENT_CREATOR_ROLE", orig)
} else {
os.Unsetenv("BYPASS_CONTENT_CREATOR_ROLE")
}
}()
os.Setenv("BYPASS_CONTENT_CREATOR_ROLE", "true")
cfg := *validConfig
cfg.Env = "production"
err := cfg.Validate()
require.Error(t, err)
assert.Contains(t, err.Error(), "BYPASS_CONTENT_CREATOR_ROLE")
assert.Contains(t, err.Error(), "bypass flags")
})
t.Run("production with CSRF_DISABLED fails", func(t *testing.T) {
orig := os.Getenv("CSRF_DISABLED")
defer func() {
if orig != "" {
os.Setenv("CSRF_DISABLED", orig)
} else {
os.Unsetenv("CSRF_DISABLED")
}
}()
os.Setenv("CSRF_DISABLED", "true")
cfg := *validConfig
cfg.Env = "production"
err := cfg.Validate()
require.Error(t, err)
assert.Contains(t, err.Error(), "CSRF_DISABLED")
assert.Contains(t, err.Error(), "bypass flags")
})
t.Run("development with bypass flags succeeds", func(t *testing.T) {
origBypass := os.Getenv("BYPASS_CONTENT_CREATOR_ROLE")
origCsrf := os.Getenv("CSRF_DISABLED")
defer func() {
if origBypass != "" {
os.Setenv("BYPASS_CONTENT_CREATOR_ROLE", origBypass)
} else {
os.Unsetenv("BYPASS_CONTENT_CREATOR_ROLE")
}
if origCsrf != "" {
os.Setenv("CSRF_DISABLED", origCsrf)
} else {
os.Unsetenv("CSRF_DISABLED")
}
}()
os.Setenv("BYPASS_CONTENT_CREATOR_ROLE", "true")
os.Setenv("CSRF_DISABLED", "true")
cfg := *validConfig
cfg.Env = "development"
err := cfg.Validate()
require.NoError(t, err)
})
}

View file

@ -213,7 +213,8 @@ impl Default for SecurityConfig {
#[cfg(test)]
Self {
jwt_secret: "test_jwt_secret_minimum_32_characters_long".to_string(),
jwt_secret: env::var("TEST_JWT_SECRET")
.unwrap_or_else(|_| format!("test_{}_{}", uuid::Uuid::new_v4(), "x".repeat(20))),
jwt_access_duration: Duration::from_secs(900), // 15 min
jwt_refresh_duration: Duration::from_secs(86400 * 30), // 30 days
jwt_algorithm: "HS256".to_string(),

View file

@ -571,8 +571,10 @@ mod tests {
use std::time::Duration;
fn create_test_config() -> SecurityConfig {
let jwt_secret = std::env::var("TEST_JWT_SECRET")
.unwrap_or_else(|_| format!("test_{}_{}", Uuid::new_v4(), "x".repeat(20)));
SecurityConfig {
jwt_secret: "test_secret_key_32_chars_minimum_required".to_string(),
jwt_secret,
jwt_access_duration: Duration::from_secs(3600), // 1 heure
jwt_refresh_duration: Duration::from_secs(86400), // 24 heures
jwt_algorithm: "HS256".to_string(),