Security

Security features and best practices for safe-env.

Secret Protection

All values marked with the secret modifier are fully masked in error messages.

How It Works

Instead of showing partial values (like abcd****xyz), safe-env shows only the length:

env({
  JWT_SECRET: "string:min=32:secret",
});

// Error message:
// JWT_SECRET: String must contain at least 32 character(s) 
// (received: [REDACTED (length: 12)])

// ✅ No partial values exposed
// ✅ No information leakage
// ✅ Only length shown for debugging

Best Practice

Always mark sensitive values as secret:

env({
  // ✅ Good: Marked as secret
  JWT_SECRET: "string:min=32:secret",
  API_KEY: "string:min=20:secret",
  DATABASE_URL: "string:url:secret", // URLs can be sensitive too
  
  // ⚠️ Warning: Not marked as secret
  // Full value will be shown in errors if validation fails
  LOG_LEVEL: "string:default=info",
});

DoS Protection

Built-in limits prevent resource exhaustion attacks:

Security Limits

  • DSL strings: Max 1000 characters
  • Environment variables: Max 1000 per schema
  • Variable keys: Max 200 characters
  • Enum values: Max 100 values, 200 chars each
  • Modifiers: Max 20 per DSL string
  • Default values: Max 500 characters
// These will throw errors:
env({
  // ❌ Too many variables
  ...Object.fromEntries(
    Array.from({ length: 1001 }, (_, i) => [`VAR_${i}`, "string"])
  ),
});

env({
  // ❌ DSL string too long
  VAR: "string:" + "x".repeat(1001),
});

env({
  // ❌ Too many enum values
  MODE: "enum:" + Array.from({ length: 101 }, (_, i) => `val${i}`).join(","),
});

Security Best Practices

1. Mark All Secrets

// ✅ Good
env({
  JWT_SECRET: "string:min=32:secret",
  API_KEY: "string:min=20:secret",
  DATABASE_URL: "string:url:secret",
  PRIVATE_KEY: "string:secret",
});

// ❌ Bad: Secrets not marked
env({
  JWT_SECRET: "string:min=32", // Will expose in errors!
  API_KEY: "string:min=20",     // Will expose in errors!
});

2. Don't Log Environment Variables

// ❌ Bad: Logging secrets
console.log("API Key:", ENV.API_KEY);
console.log("JWT Secret:", ENV.JWT_SECRET);
logger.info({ apiKey: ENV.API_KEY }); // Goes to logs!

// ✅ Good: Only log non-sensitive info
console.log("API Key configured:", !!ENV.API_KEY);
console.log("API Key length:", ENV.API_KEY.length);
console.log("Environment:", ENV.NODE_ENV);
logger.info({ hasApiKey: !!ENV.API_KEY });

3. Validate Early

// ✅ Good: Validate at startup
// config/env.ts
import { env } from "@liahus/safe-env";

export const ENV = env({
  DATABASE_URL: "string:url",
  PORT: "number:default=3000",
});

// Application fails fast if validation fails
// server.ts
import { ENV } from "./config/env";
// ... rest of app

4. Use Strong Defaults

// ✅ Good: Sensible defaults
env({
  PORT: "number:default=3000",
  LOG_LEVEL: "string:default=info",
  TIMEOUT: "number:min=1000:default=5000",
});

// ⚠️ Avoid defaults for secrets
// Force explicit configuration
env({
  JWT_SECRET: "string:min=32:secret", // No default - must be provided
  API_KEY: "string:min=20:secret",    // No default - must be provided
});

Error Message Security

What's Exposed

  • Non-secret values: Full value shown (for debugging)
  • Secret values: Only length shown [REDACTED (length: X)]
  • Error messages: Clean, no stack traces
import { env, EnvValidationError } from "@liahus/safe-env";

try {
  const ENV = env({
    DATABASE_URL: "string:url",
    PORT: "number",
    JWT_SECRET: "string:min=32:secret",
  });
} catch (error) {
  if (error instanceof EnvValidationError) {
    console.error(error.message);
    // Output:
    // Environment variable validation failed:
    //   DATABASE_URL: Invalid url (received: undefined)
    //   PORT: Required (received: undefined)
    //   JWT_SECRET: String must contain at least 32 character(s) 
    //               (received: [REDACTED (length: 12)])
    
    // ✅ DATABASE_URL: Full value shown (not sensitive)
    // ✅ PORT: Full value shown (not sensitive)
    // ✅ JWT_SECRET: Only length shown (secret protected)
  }
}

Security Considerations

✅ What safe-env Does

  • • Masks secrets in error messages
  • • Validates input to prevent DoS
  • • Provides clean error messages
  • • No filesystem access
  • • No network access
  • • No external API calls

⚠️ What You Need to Do

  • • Mark sensitive values as secret
  • • Don't log environment variables
  • • Use secure storage for secrets (not in code)
  • • Validate at application startup
  • • Use strong, unique secrets