{
  "ruleId": "S052",
  "name": "OTP must have ≥20-bit entropy (≥6 digits) and use CSPRNG",
  "description": "Prevent guessable OTP by enforcing CSPRNG and minimal entropy. Ban non-crypto RNG and too-short codes.",
  "category": "security",
  "severity": "error",
  "options": {
    "otpIdentifiers": [
      "otp","oneTime","one_time","passcode","verificationCode","verifyCode",
      "confirmCode","pin","totp","hotp","resetCode","activationCode"
    ],
    "bannedRngApis": {
      "typescript": ["Math\\.random\\s*\\("],
      "javascript": ["Math\\.random\\s*\\("],
      "node": ["crypto\\.pseudoRandomBytes\\s*\\("],
      "java": ["new\\s+Random\\s*\\(","ThreadLocalRandom\\.current\\s*\\("],
      "kotlin": ["kotlin\\.random\\.Random(\\.Default)?"],
      "dart": ["new\\s+Random\\s*\\(","Random\\s*\\("]
    },
    "allowedRngApis": {
      "node": ["crypto\\.randomInt\\s*\\(","crypto\\.randomBytes\\s*\\("],
      "java": ["new\\s+SecureRandom\\s*\\("],
      "kotlin": ["java\\.security\\.SecureRandom"],
      "dart": ["Random\\.secure\\s*\\("]
    },
    "lengthChecks": {
      "numericMinDigits": 6,
      "alphanumericMinLength": 6,
      "regexBadNumeric4": "\\\\b\\\\d{4}\\\\b"
    },
    "totpHotpHints": {
      "requireStandardLib": true,
      "minSecretBits": 128,
      "maxTimeStepSeconds": 30,
      "maxWindow": 1
    },
    "policy": {
      "requireCsprng": true,
      "forbidNonCryptoRng": true,
      "forbidFourDigitOtp": true,
      "requireNoLoggingOfOtp": true,
      "requireSingleUseAndTtl": true
    },
    "detectionHeuristics": {
      "variableNameMatchBoost": true,
      "builderFunctions": ["generateOtp","issueOtp","createCode","sendOtp","totp","hotp"]
    },
    "allowlist": {
      "paths": ["test/","tests/","__tests__/","fixtures/","mocks/"],
      "notes": "Trong test vẫn cấm 4-digit OTP nếu test chạy e2e với hệ thật."
    },
    "thresholds": {
      "maxBannedRngUsages": 0,
      "maxShortOtpPatterns": 0
    }
  }
}