Insufficient Psychological Acceptability

Description

Insufficient Psychological Acceptability occurs when security mechanisms are so difficult, confusing, or frustrating to use that users circumvent, disable, or work around them. Security controls must be usable and understandable to be effective. Overly complex password requirements, intrusive authentication, confusing permission dialogs, and poorly designed security UX lead users to find insecure workarounds.

Risk

Users create weak passwords that meet complex rules superficially. Security warnings become ignored due to warning fatigue. Users share credentials to avoid re-authentication. Security features disabled to improve workflow. Phishing attacks succeed because users are conditioned to click through warnings. Shadow IT emerges to avoid secure but unusable systems.

Solution

Design security that aligns with user workflows. Implement progressive security (stronger for sensitive actions). Use clear, actionable security messaging. Reduce friction for legitimate use while maintaining security. Conduct usability testing of security controls. Provide secure defaults that require minimal user intervention.

Common Consequences

ImpactDetails
SecurityScope: Circumvention

Users disable or bypass security controls.
AuthenticationScope: Weak Credentials

Complex rules lead to predictable passwords.
TrainingScope: Warning Fatigue

Users ignore security warnings.

Example Code + Solution Code

Vulnerable Code

// VULNERABLE: Overly complex password requirements
public class UnusablePasswordValidator {

    public ValidationResult validatePassword(String password) {
        List<String> errors = new ArrayList<>();

        // VULNERABLE: So many rules users write passwords down
        if (password.length() < 16) {
            errors.add("Password must be at least 16 characters");
        }
        if (password.length() > 20) {
            errors.add("Password must be at most 20 characters");
        }
        if (!password.matches(".*[A-Z].*")) {
            errors.add("Must contain uppercase letter");
        }
        if (!password.matches(".*[a-z].*")) {
            errors.add("Must contain lowercase letter");
        }
        if (!password.matches(".*[0-9].*")) {
            errors.add("Must contain number");
        }
        if (!password.matches(".*[!@#$%^&*()].*")) {
            errors.add("Must contain special character from: !@#$%^&*()");
        }
        if (password.matches(".*(.)(\\1{2,}).*")) {
            errors.add("No character can repeat more than twice");
        }
        if (password.matches(".*(012|123|234|345|456|567|678|789).*")) {
            errors.add("No sequential numbers allowed");
        }
        if (password.matches(".*(abc|bcd|cde|def|efg).*")) {
            errors.add("No sequential letters allowed");
        }
        // Must be changed every 30 days
        // Cannot reuse last 24 passwords

        // Result: Users create "Password1!" variations and write them down

        return new ValidationResult(errors.isEmpty(), errors);
    }
}

// VULNERABLE: Constant security interruptions
public class IntrusiveSecurityController {

    @RequestMapping("/**")
    public Object handleAllRequests(HttpServletRequest request) {
        // VULNERABLE: Re-authenticate for every action
        if (!authenticateUser(request)) {
            return redirectToLogin();  // Users share passwords to avoid this
        }

        // VULNERABLE: CAPTCHA on every form
        if (!verifyCaptcha(request)) {
            return showCaptcha();  // Users find workarounds
        }

        // VULNERABLE: Security question for every sensitive page
        if (isSensitivePage(request)) {
            return askSecurityQuestion();  // Users pick easy questions
        }

        return processRequest(request);
    }
}

// VULNERABLE: Confusing security warnings
public class ConfusingSecurityWarnings {

    public void showCertificateWarning() {
        // VULNERABLE: Technical jargon users don't understand
        showDialog(
            "SSL/TLS Certificate Error: " +
            "The certificate chain validation failed. " +
            "ERR_CERT_AUTHORITY_INVALID (0x800B0109). " +
            "The certificate is not signed by a trusted authority. " +
            "SHA-256 fingerprint: a4:b2:c1:d3:e5...",
            "Proceed Anyway", "Cancel"
        );
        // Users learn to click "Proceed Anyway" for everything
    }
}
# VULNERABLE: Password policy that leads to insecure behavior
class UnusablePasswordPolicy:
    def validate(self, password):
        errors = []

        # VULNERABLE: Contradictory/impossible rules
        if len(password) < 14:
            errors.append("Minimum 14 characters")
        if len(password) > 16:
            errors.append("Maximum 16 characters")

        # Requires complexity that forces predictable patterns
        if not re.search(r'[A-Z]', password):
            errors.append("Needs uppercase")
        if not re.search(r'[a-z]', password):
            errors.append("Needs lowercase")
        if not re.search(r'[0-9]', password):
            errors.append("Needs number")
        if not re.search(r'[!@#$%^&*]', password):
            errors.append("Needs special character")

        # No common words - but users just add 1! to common words
        common_words = ['password', 'company', 'admin', 'user']
        for word in common_words:
            if word.lower() in password.lower():
                errors.append(f"Cannot contain '{word}'")

        # VULNERABLE: 24-password history leads to Post-it notes
        if password in self.get_password_history(user_id, count=24):
            errors.append("Cannot reuse recent passwords")

        return len(errors) == 0, errors

# VULNERABLE: Session timeout too aggressive
class IntrusiveSessionManager:
    def __init__(self):
        # VULNERABLE: 5 minute timeout causes constant re-login
        self.session_timeout = 300  # seconds

    def check_session(self, session):
        if time.time() - session.last_activity > self.session_timeout:
            # Users stay logged in on shared computers to avoid this
            session.invalidate()
            return False
        return True

# VULNERABLE: Overly verbose security logging dialogs
def show_security_status():
    # VULNERABLE: Information overload
    message = """
    Security Status Report:
    - TLS 1.3 connection established
    - Certificate: Valid (expires 2024-12-31)
    - OCSP: Verified
    - CT: 3 SCTs present
    - HSTS: Enabled (max-age=31536000)
    - CSP: strict-dynamic enforced
    - X-Frame-Options: DENY
    - Referrer-Policy: strict-origin-when-cross-origin
    ...
    [50 more technical details]

    Do you want to continue?
    """
    # Users learn to click through without reading
    return show_dialog(message, ["Yes", "No"])
// VULNERABLE: Constant interruptions
class IntrusiveSecurityUI {
    constructor() {
        this.lastCaptchaTime = 0;
    }

    // VULNERABLE: CAPTCHA on every action
    async performAction(action) {
        // Users get frustrated and leave
        await this.showCaptcha();

        // Then security question
        await this.askSecurityQuestion();

        // Then MFA
        await this.verifyMFA();

        // Finally perform action
        return action();
    }

    // VULNERABLE: Warning dialogs that train users to click through
    showSecurityWarning() {
        // Technical jargon and scary language
        return showModal({
            title: '⚠️ CRITICAL SECURITY WARNING ⚠️',
            message: `
                Your connection may be compromised!
                Error Code: SEC_ERROR_UNKNOWN_ISSUER
                The certificate is not trusted because no issuer chain was provided.
                Technical Details: Certificate chain incomplete. OCSP response indicates...
                [200 more words of technical content]
            `,
            buttons: ['I understand the risks, proceed anyway', 'Go back to safety']
        });
        // Users learn to always click "proceed"
    }

    // VULNERABLE: Password change forced too often
    enforcePasswordChange(user) {
        const daysSinceChange = (Date.now() - user.passwordChangedAt) / 86400000;
        if (daysSinceChange > 30) {
            // Users increment number: Password1! -> Password2! -> Password3!
            this.forcePasswordChange();
        }
    }
}

// VULNERABLE: Confusing permission requests
function requestPermissions() {
    // Users don't understand what they're agreeing to
    return showPermissionDialog({
        title: 'Permission Required',
        message: 'This app needs the following permissions: ' +
                 'READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, ' +
                 'ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, ' +
                 'READ_CONTACTS, WRITE_CONTACTS, CAMERA, MICROPHONE, ' +
                 'SYSTEM_ALERT_WINDOW, REQUEST_INSTALL_PACKAGES...',
        buttons: ['Allow All', 'Deny']
    });
    // Users click "Allow All" to make it go away
}

Fixed Code

// SAFE: User-friendly password requirements
public class UsablePasswordValidator {

    public ValidationResult validatePassword(String password) {
        // SAFE: Focus on length over complexity
        // Research shows length is more secure than complexity rules

        if (password.length() < 12) {
            return ValidationResult.fail(
                "Password needs at least 12 characters. " +
                "Tip: Try a memorable phrase like 'correct horse battery staple'"
            );
        }

        // Check against breached passwords (not arbitrary rules)
        if (isBreachedPassword(password)) {
            return ValidationResult.fail(
                "This password has appeared in data breaches. " +
                "Please choose a different password for your security."
            );
        }

        // Simple strength meter - not blocking rules
        int strength = calculateStrength(password);
        String strengthMessage = getStrengthFeedback(strength);

        return ValidationResult.success(strengthMessage);
    }

    // SAFE: Clear, actionable feedback
    private String getStrengthFeedback(int strength) {
        if (strength >= 80) {
            return "✓ Strong password";
        } else if (strength >= 60) {
            return "Good password - consider making it longer for extra security";
        } else {
            return "Acceptable - adding more words would make it stronger";
        }
    }
}

// SAFE: Progressive security
public class ProgressiveSecurityController {

    @RequestMapping("/**")
    public Object handleRequest(HttpServletRequest request) {
        // Low-risk actions: minimal friction
        if (isLowRiskAction(request)) {
            return processRequest(request);
        }

        // Medium-risk: verify session is active
        if (isMediumRiskAction(request)) {
            if (!sessionService.isActive(request)) {
                return redirectToLogin();
            }
            return processRequest(request);
        }

        // High-risk: step-up authentication
        if (isHighRiskAction(request)) {
            if (!hasRecentAuthentication(request, Duration.ofMinutes(5))) {
                return requestReauthentication();
            }
            return processRequest(request);
        }

        return processRequest(request);
    }
}

// SAFE: Clear, user-friendly security warnings
public class UserFriendlySecurityWarnings {

    public void showCertificateWarning(CertificateError error) {
        // SAFE: Plain language, clear actions
        if (error.isSelfSigned()) {
            showDialog(
                "This website's security certificate wasn't issued by a " +
                "trusted organization.\n\n" +
                "This might mean:\n" +
                "• You're on a test or internal site (okay)\n" +
                "• Someone might be trying to intercept your data (not okay)\n\n" +
                "If you're not sure, don't proceed.",
                "Go back (recommended)",
                "I understand, proceed anyway"
            );
        }
    }
}
# SAFE: User-friendly password policy
class UsablePasswordPolicy:
    def validate(self, password, username=None):
        # SAFE: Simple length requirement
        if len(password) < 12:
            return False, "Please use at least 12 characters. Passphrases work great!"

        # Check against known breached passwords
        if self.is_breached(password):
            return False, (
                "This password appears in known data breaches. "
                "Please choose a different one to stay secure."
            )

        # Prevent obvious patterns (not arbitrary complexity)
        if username and username.lower() in password.lower():
            return False, "Password shouldn't contain your username"

        # SAFE: Helpful strength feedback instead of blocking
        strength = self.estimate_strength(password)

        if strength >= 3:
            return True, "✓ Strong password"
        elif strength >= 2:
            return True, "Good password. Adding more words would make it even better."
        else:
            return True, "Acceptable password"

    def is_breached(self, password):
        # Check against HaveIBeenPwned or local breach database
        password_hash = hashlib.sha1(password.encode()).hexdigest()
        return self.breach_db.contains(password_hash[:5])

# SAFE: Reasonable session management
class UsableSessionManager:
    def __init__(self):
        # SAFE: Reasonable timeout with activity extension
        self.idle_timeout = 3600  # 1 hour for normal pages
        self.sensitive_timeout = 300  # 5 min for sensitive pages
        self.absolute_timeout = 86400  # 24 hour maximum

    def check_session(self, session, is_sensitive=False):
        timeout = self.sensitive_timeout if is_sensitive else self.idle_timeout

        idle_time = time.time() - session.last_activity
        if idle_time > timeout:
            # SAFE: Explain why and offer easy re-authentication
            return {
                'valid': False,
                'message': f"For your security, please log in again. "
                          f"You've been idle for {int(idle_time/60)} minutes."
            }

        # Extend activity on action
        session.last_activity = time.time()
        return {'valid': True}

# SAFE: Progressive authentication
class ProgressiveAuth:
    def get_required_auth_level(self, action):
        # Different actions need different security levels
        LOW_RISK = ['view_profile', 'browse', 'read']
        MEDIUM_RISK = ['update_profile', 'change_settings']
        HIGH_RISK = ['change_password', 'transfer_funds', 'delete_account']

        if action in HIGH_RISK:
            return 'reauthenticate'  # Password + MFA
        elif action in MEDIUM_RISK:
            return 'session'  # Valid session
        else:
            return 'none'  # Public access

    def verify_auth(self, user, action, credentials=None):
        required = self.get_required_auth_level(action)

        if required == 'none':
            return True

        if required == 'session':
            return user.session.is_valid()

        if required == 'reauthenticate':
            # Only ask for re-auth for truly sensitive actions
            return self.verify_credentials(user, credentials)
// SAFE: User-friendly security in JavaScript
class UserFriendlySecurity {

    // SAFE: Clear, simple password guidance
    validatePassword(password, email) {
        // Length is key - NIST recommends 8+ min, we suggest 12+
        if (password.length < 12) {
            return {
                valid: false,
                message: 'Please use at least 12 characters. Try a phrase you can remember!',
                suggestion: 'Example: "purple elephant jumps high"'
            };
        }

        // Check breached passwords
        if (this.isBreached(password)) {
            return {
                valid: false,
                message: 'This password was found in a data breach. Please use a different one.'
            };
        }

        // Helpful, not blocking
        const strength = this.measureStrength(password);
        return {
            valid: true,
            strength,
            message: this.getStrengthMessage(strength)
        };
    }

    getStrengthMessage(strength) {
        const messages = {
            'strong': '✓ Great password!',
            'good': '✓ Good password',
            'ok': '✓ Acceptable - longer would be better'
        };
        return messages[strength];
    }

    // SAFE: Plain-language security warnings
    showSecurityWarning(type, details) {
        const warnings = {
            'certificate': {
                title: 'Security Warning',
                message: `
                    We can't verify this website's identity.

                    This could mean:
                    • The site is misconfigured
                    • Someone might be intercepting your connection

                    What should I do?
                    • If you expected to see this (like a company internal site), it may be okay
                    • If not, go back for your safety
                `,
                primaryAction: 'Go back',
                secondaryAction: 'I understand, continue'
            },
            'unsaved': {
                title: 'Unsaved Changes',
                message: 'You have unsaved changes. Leave anyway?',
                primaryAction: 'Stay',
                secondaryAction: 'Leave without saving'
            }
        };

        return showDialog(warnings[type]);
    }

    // SAFE: Progressive security based on risk
    async performAction(action, context) {
        const riskLevel = this.assessRisk(action, context);

        if (riskLevel === 'low') {
            return await action();
        }

        if (riskLevel === 'medium') {
            // Just verify session is active
            if (!this.isSessionActive()) {
                await this.showQuickReauth();
            }
            return await action();
        }

        if (riskLevel === 'high') {
            // Full re-authentication with clear explanation
            const reauth = await this.showReauthDialog({
                message: 'This action affects your account security. ' +
                         'Please verify your password.',
                reason: action.description
            });

            if (reauth.success) {
                return await action();
            }
        }
    }
}

Exploited in the Wild

Password Patterns

Complex rules lead to "Password1!" patterns.

Warning Fatigue

Users trained to click through warnings fall for phishing.

Credential Sharing

Intrusive authentication leads to password sharing.


Tools to test/exploit

  • Usability testing frameworks.

  • Password pattern analysis.

  • User behavior analytics.


CVE Examples

  • Security bypasses due to user workarounds.

  • Phishing success from warning fatigue.


References

  1. MITRE. "CWE-655: Insufficient Psychological Acceptability." https://cwe.mitre.org/data/definitions/655.html

  2. NIST SP 800-63B. "Digital Identity Guidelines."