Skip to content

Added password_strength_checker.py in Python folder#128

Merged
x0lg0n merged 1 commit intox0lg0n:mainfrom
Arni005:add-password-strength-checker
Oct 27, 2025
Merged

Added password_strength_checker.py in Python folder#128
x0lg0n merged 1 commit intox0lg0n:mainfrom
Arni005:add-password-strength-checker

Conversation

@Arni005
Copy link
Contributor

@Arni005 Arni005 commented Oct 27, 2025

Created Password Strength Checker with smarter scoring, clearer feedback, and an interactive user experience.

Summary by Sourcery

Introduce a comprehensive Python password strength checker script featuring advanced scoring based on entropy, character diversity, and common pattern detection, along with interactive and programmatic usage modes.

New Features:

  • Add PasswordStrengthChecker with entropy calculation, common password list checks, and pattern detection for detailed strength scoring
  • Provide interactive CLI mode for real-time password assessment with prioritized feedback
  • Implement quick_check_mode for programmatic strength evaluation
  • Include test_passwords function demonstrating example password analyses

Summary by CodeRabbit

New Features

  • Added an interactive password strength checker for real-time password analysis
  • Delivers strength scoring, entropy calculation, and pattern detection with categorized feedback
  • Detects common passwords and provides actionable guidance for improving password security
  • Offers both interactive mode and programmatic API for flexible usage

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 27, 2025

Reviewer's Guide

Introduces a new standalone module that evaluates password strength by calculating entropy, detecting common patterns, applying granular scoring (length, character diversity, common-password penalties), and presenting feedback via both an interactive CLI and a programmatic quick-check API.

Class diagram for PasswordStrengthChecker and related functions

classDiagram
    class PasswordStrengthChecker {
        - common_passwords: set
        - strength_levels: dict
        + __init__()
        + calculate_entropy(password: str) float
        + check_common_patterns(password: str) List[str]
        + check_password_strength(password: str) Tuple[str, str, List[str], int]
        + get_strength_breakdown(password: str)
    }
    class quick_check_mode {
        + quick_check_mode(password: str) dict
    }
    class interactive_mode {
        + interactive_mode()
    }
    class test_passwords {
        + test_passwords()
    }
    PasswordStrengthChecker <.. quick_check_mode : uses
    PasswordStrengthChecker <.. interactive_mode : uses
    PasswordStrengthChecker <.. test_passwords : uses
Loading

Flow diagram for password strength evaluation process

flowchart TD
    A["User inputs password"] --> B["Check password length"]
    B --> C["Check character diversity"]
    C --> D["Check if password is common"]
    D --> E["Calculate entropy"]
    E --> F["Check for common patterns"]
    F --> G["Aggregate score and feedback"]
    G --> H["Display feedback to user"]
Loading

File-Level Changes

Change Details Files
Built a comprehensive PasswordStrengthChecker class
  • Initialized expanded common password list and strength thresholds
  • Implemented calculate_entropy using character pool size and log₂ formula
  • Added check_common_patterns for repeats, sequences, and substrings
  • Created check_password_strength combining length, diversity, common-password penalty, entropy, and pattern deductions
  • Formatted feedback with emojis, capped score at 5, and mapped to strength levels
  • Provided get_strength_breakdown to categorize and display critical issues, suggestions, and strengths
Python/password_strength_checker.py
Added interactive and programmatic interfaces
  • interactive_mode: console loop with getpass input, exit handling, and retry prompts
  • quick_check_mode: function returning strength data as a dict for API use
  • test_passwords: sample test harness printing strength results for preset cases
Python/password_strength_checker.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link

coderabbitai bot commented Oct 27, 2025

Walkthrough

Introduces a new Python module PasswordStrengthChecker that analyzes password strength through entropy calculation, pattern detection, and common password checking. Provides both interactive and programmatic interfaces with structured feedback mechanisms.

Changes

Cohort / File(s) Summary
New Password Strength Analysis Module
Python/password_strength_checker.py
Adds PasswordStrengthChecker class with entropy calculation, common pattern detection, and comprehensive strength scoring (0-5). Includes check_password_strength() for structured feedback, get_strength_breakdown() for formatted output, calculate_entropy(), and check_common_patterns(). Provides interactive_mode() for real-time password checks and quick_check_mode() for programmatic API. Entry point executes interactive mode by default with test utility support.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PSC as PasswordStrengthChecker
    participant Entropy as Entropy Engine
    participant Patterns as Pattern Detector
    participant Common as Common Passwords DB

    User->>PSC: check_password_strength(password)
    PSC->>PSC: Initialize score & feedback
    PSC->>Common: Check if common password
    Common-->>PSC: Penalty applied if match
    PSC->>Entropy: calculate_entropy(password)
    Entropy-->>PSC: Entropy score
    PSC->>Patterns: check_common_patterns(password)
    Patterns-->>PSC: Pattern issues detected
    PSC->>PSC: Evaluate length & character variety
    PSC->>PSC: Build categorized feedback
    PSC->>PSC: Map score to strength level
    PSC-->>User: (strength, emoji, feedback, score)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas requiring extra attention:
    • Entropy calculation logic and mathematical correctness of the character pool derivation and log₂ formula application
    • Pattern detection algorithms (repeated characters, sequential patterns, substrings) for edge cases and performance with long passwords
    • Common passwords set initialization and matching logic for false positives/negatives
    • Feedback prioritization and categorization logic (critical issues, warnings, strengths)
    • Interactive mode input handling and error cases with getpass integration

Poem

🐰 A checker strong, with entropy's might,
Patterns dance in password light,
Common foes are turned away,
Strength breaks down—hopping feedback's way! 🔐
Interactive magic, feedback so clear,
Security whispers in every ear! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Added password_strength_checker.py in Python folder" is directly related to the changeset and accurately describes what was added. The filename itself clearly conveys that a password strength checker module was introduced, making it specific enough for a teammate scanning the history to understand the primary change. However, the title is somewhat mechanical and includes minor redundancy with "in Python folder," which could be considered a small amount of noise that slightly reduces clarity. Despite this, the core message is sound and the title successfully communicates the essential information about what was added to the repository.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Information disclosure

Description: The detailed feedback printed to stdout may reveal password composition characteristics
(e.g., presence of uppercase/special characters or low entropy), which could leak
sensitive hints if console output is logged or observed; consider minimizing or gating
feedback for production use.
password_strength_checker.py [149-167]

Referred Code
# Prioritize feedback - show most critical first
critical = [f for f in feedback if f.startswith('❌')]
warnings = [f for f in feedback if f.startswith('⚠️')]
positive = [f for f in feedback if f.startswith('✅')]

if critical:
    print("\nCritical Issues:")
    for item in critical[:3]:  # Show top 3 critical issues
        print(f"  {item}")

if warnings:
    print("\nSuggestions:")
    for item in warnings[:3]:  # Show top 3 suggestions
        print(f"  {item}")

if positive and not critical:  # Only show positives if no critical issues
    print("\nStrengths:")
    for item in positive[:3]:
        print(f"  {item}")
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No audit logs: The new interactive flows and strength checks perform user-driven operations without
emitting any audit logs of critical events (e.g., password check attempts or outcomes),
making auditability unclear.

Referred Code
def interactive_mode():
    """Interactive password checking with retries"""
    checker = PasswordStrengthChecker()

    print("🔐 Advanced Password Strength Checker")
    print("=" * 40)

    while True:
        password = getpass.getpass("\nEnter password to check (or 'quit' to exit): ")

        if password.lower() == 'quit':
            break

        if not password:
            print("❌ Please enter a password")
            continue

        checker.get_strength_breakdown(password)

        # Offer to check another password
        continue_check = input("\nCheck another password? (y/n): ").lower()


 ... (clipped 12 lines)
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing try/except: Interactive input and getpass calls lack error handling for I/O and terminal edge cases,
which could cause unhandled exceptions without graceful messages.

Referred Code
def interactive_mode():
    """Interactive password checking with retries"""
    checker = PasswordStrengthChecker()

    print("🔐 Advanced Password Strength Checker")
    print("=" * 40)

    while True:
        password = getpass.getpass("\nEnter password to check (or 'quit' to exit): ")

        if password.lower() == 'quit':
            break

        if not password:
            print("❌ Please enter a password")
            continue

        checker.get_strength_breakdown(password)

        # Offer to check another password
        continue_check = input("\nCheck another password? (y/n): ").lower()


 ... (clipped 2 lines)
Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Sensitive echo risk: The flow uses getpass securely but later asks for a plain input confirmation which may
echo sensitive responses and lacks validation/limits, warranting review for secure
handling policies.

Referred Code
# Offer to check another password
continue_check = input("\nCheck another password? (y/n): ").lower()
if continue_check != 'y':
    break
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Consider decoupling the CLI/print logic from the strength‐checking class so you can reuse the core analysis in different contexts without relying on stdout.
  • The repeated‐substring detection is O(n³) in the worst case; you might limit the substring length or optimize it to avoid performance issues on very long passwords.
  • Hard-coding the common passwords set prevents easy updates—consider loading that list from a configurable external source so it stays maintainable and updatable.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider decoupling the CLI/print logic from the strength‐checking class so you can reuse the core analysis in different contexts without relying on stdout.
- The repeated‐substring detection is O(n³) in the worst case; you might limit the substring length or optimize it to avoid performance issues on very long passwords.
- Hard-coding the common passwords set prevents easy updates—consider loading that list from a configurable external source so it stays maintainable and updatable.

## Individual Comments

### Comment 1
<location> `Python/password_strength_checker.py:164-167` </location>
<code_context>
+            for item in warnings[:3]:  # Show top 3 suggestions
+                print(f"  {item}")
+                
+        if positive and not critical:  # Only show positives if no critical issues
+            print("\nStrengths:")
+            for item in positive[:3]:
</code_context>

<issue_to_address>
**suggestion:** Positive feedback is suppressed when critical issues exist.

Consider displaying strengths alongside critical issues to provide more balanced feedback.

```suggestion
        if positive:
            print("\nStrengths:")
            for item in positive[:3]:
                print(f"  {item}")
```
</issue_to_address>

### Comment 2
<location> `Python/password_strength_checker.py:77` </location>
<code_context>
+                        
+        return patterns
+
+    def check_password_strength(self, password: str) -> Tuple[str, str, List[str], int]:
+        """Comprehensive password strength analysis"""
+        score = 0
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the password strength checker by splitting large methods into smaller private scorers and moving I/O logic out of the class.

Here are a few focused refactorings that will keep all behavior the same but dramatically reduce nesting and size:

1. Split your one big `check_password_strength` into small private scorers:  

```python
class PasswordStrengthChecker:
    def __init__(…): …

    def _score_length(self, pwd: str) -> Tuple[int, List[str]]:
        score, fb = 0, []
        if len(pwd) >= 16:
            score += 2; fb.append("✅ Excellent password length")
        elif len(pwd) >= 12:
            score += 1; fb.append("✅ Good password length")
        elif len(pwd) >= 8:
            score += 1; fb.append("⚠️  Consider using 12+ characters")
        else:
            fb.append("❌ Password should be at least 8 characters long")
        return score, fb

    def _score_diversity(self, pwd: str) -> Tuple[int, List[str]]:
        rules = [
            (r'[A-Z]', "uppercase letters"),
            (r'[a-z]', "lowercase letters"),
            (r'\d',    "numbers"),
            (r'[^A-Za-z0-9]', "special characters"),
        ]
        score, fb = 0, []
        for patt, name in rules:
            if re.search(patt, pwd):
                score += 1; fb.append(f"✅ Contains {name}")
            else:
                fb.append(f"❌ Include {name}")
        return score, fb

    def _apply_common_password_penalty(self, pwd: str) -> Tuple[int, List[str]]:
        norm = re.sub(r'[^a-z0-9]', '', pwd.lower())
        if pwd.lower() in self.common_passwords or norm in self.common_passwords:
            return -2, ["❌ This is a commonly used password"]
        return 0, []

    def _score_entropy(self, pwd: str) -> Tuple[int, List[str]]:
        ent = self.calculate_entropy(pwd)
        if ent >= 60:
            return 1, ["✅ High entropy"]
        if ent >= 40:
            return 0, ["⚠️  Moderate entropy"]
        return 0, ["❌ Low entropy"]
```

Then your `check_password_strength` becomes:

```python
def check_password_strength(self, pwd: str):
    total, feedback = 0, []
    for scorer in (self._score_length,
                   self._score_diversity,
                   self._apply_common_password_penalty,
                   self._score_entropy):
        sc, fb = scorer(pwd)
        total += sc
        feedback.extend(fb)

    patterns = self.check_common_patterns(pwd)
    if patterns:
        total = max(0, total - 1)
        feedback.extend(patterns)

    total = min(max(total, 0), 5)
    strength, emoji = self.strength_levels[total]
    return strength, emoji, feedback, total
```

2. Simplify `check_common_patterns` with any() and comprehensions:

```python
def check_common_patterns(self, pwd: str) -> List[str]:
    lower = pwd.lower()
    out = []
    if re.search(r'(.)\1{2,}', pwd):
        out.append("Avoid repeated characters")
    seqs = ['abc','123','qwe','asd','zxc']
    if any(s in lower or s[::-1] in lower for s in seqs):
        out.append("Avoid sequential patterns")
    # repeated substrings of length ≥3
    if any(pwd.count(sub) > 1
           for length in range(3, len(pwd)//2+1)
           for sub in {pwd[i:i+length] for i in range(len(pwd)-length+1)}):
        out.append("Avoid repeated substrings")
    return out
```

3. Pull all `print` and `getpass` logic out of this class into a separate `password_cli.py` (or similar).  Your checker stays pure-logic and returns data; the CLI module handles I/O.
</issue_to_address>

### Comment 3
<location> `Python/password_strength_checker.py:222-225` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Avoid loops in tests. ([`no-loop-in-tests`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/no-loop-in-tests))

<details><summary>Explanation</summary>Avoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:
* loops
* conditionals

Some ways to fix this:

* Use parametrized tests to get rid of the loop.
* Move the complex logic into helpers.
* Move the complex part into pytest fixtures.

> Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / [Don't Put Logic in Tests](https://abseil.io/resources/swe-book/html/ch12.html#donapostrophet_put_logic_in_tests)
</details>
</issue_to_address>

### Comment 4
<location> `Python/password_strength_checker.py:28` </location>
<code_context>
    def calculate_entropy(self, password: str) -> float:
        """Calculate password entropy to measure randomness"""
        if not password:
            return 0

        # Character pool size estimation
        char_pool = 0
        if re.search(r'[a-z]', password):
            char_pool += 26
        if re.search(r'[A-Z]', password):
            char_pool += 26
        if re.search(r'[0-9]', password):
            char_pool += 10
        if re.search(r'[^A-Za-z0-9]', password):
            char_pool += 32  # Common special characters

        if char_pool == 0:
            return 0

        # Entropy formula: log2(pool_size^length)
        entropy = len(password) * math.log2(char_pool)
        return entropy

</code_context>

<issue_to_address>
**issue (code-quality):** We've found these issues:

- Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-immediately-returned-variable/))
- Lift code into else after jump in control flow ([`reintroduce-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/reintroduce-else/))
- Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))
</issue_to_address>

### Comment 5
<location> `Python/password_strength_checker.py:128` </location>
<code_context>
    def check_password_strength(self, password: str) -> Tuple[str, str, List[str], int]:
        """Comprehensive password strength analysis"""
        score = 0
        feedback = []

        # Length scoring (more granular)
        if len(password) >= 16:
            score += 2
            feedback.append("✅ Excellent password length")
        elif len(password) >= 12:
            score += 1
            feedback.append("✅ Good password length")
        elif len(password) >= 8:
            score += 1
            feedback.append("⚠️  Consider using 12+ characters for better security")
        else:
            feedback.append("❌ Password should be at least 8 characters long")

        # Character diversity scoring
        checks = {
            'uppercase': (r'[A-Z]', "Include uppercase letters"),
            'lowercase': (r'[a-z]', "Include lowercase letters"), 
            'numbers': (r'[0-9]', "Include numbers"),
            'special': (r'[^A-Za-z0-9]', "Include special characters")
        }

        for check_type, (pattern, message) in checks.items():
            if re.search(pattern, password):
                score += 1
                feedback.append(f"✅ Contains {check_type} characters")
            else:
                feedback.append(f"❌ {message}")

        # Common password check (improved)
        normalized_password = re.sub(r'[^a-z0-9]', '', password.lower())
        if (password.lower() in self.common_passwords or 
            normalized_password in self.common_passwords):
            score = max(0, score - 2)  # Heavy penalty for common passwords
            feedback.append("❌ This is a commonly used password - choose something more unique")

        # Entropy check
        entropy = self.calculate_entropy(password)
        if entropy >= 60:
            score += 1
            feedback.append("✅ High entropy - excellent randomness")
        elif entropy >= 40:
            feedback.append("⚠️  Moderate entropy - consider more random characters")
        else:
            feedback.append("❌ Low entropy - password is too predictable")

        # Pattern checks
        pattern_feedback = self.check_common_patterns(password)
        if pattern_feedback:
            score = max(0, score - 1)
            feedback.extend(pattern_feedback)

        # Cap score at maximum
        score = min(score, 5)

        # Get strength rating
        strength, emoji = self.strength_levels.get(score, ("Very Weak", "🔴"))

        return strength, emoji, feedback, score

</code_context>

<issue_to_address>
**issue (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +164 to +167
if positive and not critical: # Only show positives if no critical issues
print("\nStrengths:")
for item in positive[:3]:
print(f" {item}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Positive feedback is suppressed when critical issues exist.

Consider displaying strengths alongside critical issues to provide more balanced feedback.

Suggested change
if positive and not critical: # Only show positives if no critical issues
print("\nStrengths:")
for item in positive[:3]:
print(f" {item}")
if positive:
print("\nStrengths:")
for item in positive[:3]:
print(f" {item}")


return patterns

def check_password_strength(self, password: str) -> Tuple[str, str, List[str], int]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring the password strength checker by splitting large methods into smaller private scorers and moving I/O logic out of the class.

Here are a few focused refactorings that will keep all behavior the same but dramatically reduce nesting and size:

  1. Split your one big check_password_strength into small private scorers:
class PasswordStrengthChecker:
    def __init__(…): …

    def _score_length(self, pwd: str) -> Tuple[int, List[str]]:
        score, fb = 0, []
        if len(pwd) >= 16:
            score += 2; fb.append("✅ Excellent password length")
        elif len(pwd) >= 12:
            score += 1; fb.append("✅ Good password length")
        elif len(pwd) >= 8:
            score += 1; fb.append("⚠️  Consider using 12+ characters")
        else:
            fb.append("❌ Password should be at least 8 characters long")
        return score, fb

    def _score_diversity(self, pwd: str) -> Tuple[int, List[str]]:
        rules = [
            (r'[A-Z]', "uppercase letters"),
            (r'[a-z]', "lowercase letters"),
            (r'\d',    "numbers"),
            (r'[^A-Za-z0-9]', "special characters"),
        ]
        score, fb = 0, []
        for patt, name in rules:
            if re.search(patt, pwd):
                score += 1; fb.append(f"✅ Contains {name}")
            else:
                fb.append(f"❌ Include {name}")
        return score, fb

    def _apply_common_password_penalty(self, pwd: str) -> Tuple[int, List[str]]:
        norm = re.sub(r'[^a-z0-9]', '', pwd.lower())
        if pwd.lower() in self.common_passwords or norm in self.common_passwords:
            return -2, ["❌ This is a commonly used password"]
        return 0, []

    def _score_entropy(self, pwd: str) -> Tuple[int, List[str]]:
        ent = self.calculate_entropy(pwd)
        if ent >= 60:
            return 1, ["✅ High entropy"]
        if ent >= 40:
            return 0, ["⚠️  Moderate entropy"]
        return 0, ["❌ Low entropy"]

Then your check_password_strength becomes:

def check_password_strength(self, pwd: str):
    total, feedback = 0, []
    for scorer in (self._score_length,
                   self._score_diversity,
                   self._apply_common_password_penalty,
                   self._score_entropy):
        sc, fb = scorer(pwd)
        total += sc
        feedback.extend(fb)

    patterns = self.check_common_patterns(pwd)
    if patterns:
        total = max(0, total - 1)
        feedback.extend(patterns)

    total = min(max(total, 0), 5)
    strength, emoji = self.strength_levels[total]
    return strength, emoji, feedback, total
  1. Simplify check_common_patterns with any() and comprehensions:
def check_common_patterns(self, pwd: str) -> List[str]:
    lower = pwd.lower()
    out = []
    if re.search(r'(.)\1{2,}', pwd):
        out.append("Avoid repeated characters")
    seqs = ['abc','123','qwe','asd','zxc']
    if any(s in lower or s[::-1] in lower for s in seqs):
        out.append("Avoid sequential patterns")
    # repeated substrings of length ≥3
    if any(pwd.count(sub) > 1
           for length in range(3, len(pwd)//2+1)
           for sub in {pwd[i:i+length] for i in range(len(pwd)-length+1)}):
        out.append("Avoid repeated substrings")
    return out
  1. Pull all print and getpass logic out of this class into a separate password_cli.py (or similar). Your checker stays pure-logic and returns data; the CLI module handles I/O.

Comment on lines +222 to +225
for pwd in test_cases:
result = quick_check_mode(pwd)
print(f"\nPassword: {pwd}")
print(f"Strength: {result['strength']} {result['emoji']} ({result['score']}/5)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid loops in tests. (no-loop-in-tests)

ExplanationAvoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Use a standard library for security

Replace the custom, simplistic password strength algorithm with a robust,
industry-standard library like zxcvbn-python. This avoids providing a false
sense of security from naive calculations and small, hardcoded password lists.

Examples:

Python/password_strength_checker.py [6-139]
class PasswordStrengthChecker:
    def __init__(self):
        # Expanded common passwords list
        self.common_passwords = {
            'password', '123456', '12345678', '1234', 'qwerty', '12345', 
            'dragon', 'baseball', 'football', 'letmein', 'monkey', 'abc123',
            'shadow', 'master', 'jennifer', '111111','2000', 'superman',
            '1234567', 'freedom','batman', 'trustno1', 'admin', 'welcome', 
            'password1', '123123'
        }

 ... (clipped 124 lines)

Solution Walkthrough:

Before:

class PasswordStrengthChecker:
    def __init__(self):
        # Small, hardcoded list of common passwords
        self.common_passwords = {'password', '123456', ...}

    def calculate_entropy(self, password: str) -> float:
        # Naive entropy calculation based on character sets
        ...

    def check_common_patterns(self, password: str) -> List[str]:
        # Basic checks for sequences and repeats
        ...

    def check_password_strength(self, password: str):
        # Custom scoring logic based on length, diversity, and patterns
        score = 0
        # ... add/subtract points based on custom rules
        return score

After:

from zxcvbn import zxcvbn
from typing import Tuple, List

class PasswordStrengthChecker:
    def check_password_strength(self, password: str) -> Tuple[str, str, List[str], int]:
        results = zxcvbn(password)
        score = results['score'] # Score from 0-4
        
        # Map zxcvbn score and feedback to the desired output format
        strength, emoji = self._map_score_to_strength(score)
        feedback = self._format_feedback(results)
        
        return strength, emoji, feedback, score

    def _map_score_to_strength(self, score: int):
        # ... mapping logic ...

    def _format_feedback(self, results: dict) -> List[str]:
        # ... format zxcvbn's detailed feedback ...
Suggestion importance[1-10]: 10

__

Why: This suggestion correctly identifies a critical design flaw: the custom security algorithm is simplistic and provides a false sense of security, which undermines the entire purpose of the PR.

High
General
Optimize repeated substring detection algorithm

Optimize the repeated substring check by replacing the inefficient nested loop
and count() combination with a more performant approach using a set to track
seen substrings, and improve the feedback message to be more specific.

Python/password_strength_checker.py [66-73]

 # Repeated substrings
 if len(password) >= 6:
-    for i in range(3, len(password)//2 + 1):
-        for j in range(len(password) - i*2 + 1):
-            substring = password[j:j+i]
-            if password.count(substring) >= 2:
-                patterns.append("Avoid repeated character sequences")
-                return patterns
+    # Check for repeated substrings of length 3, 4, and 5
+    for length in range(3, min(6, len(password) // 2 + 1)):
+        substrings_seen = set()
+        for i in range(len(password) - length + 1):
+            substring = password[i:i+length]
+            if substring in substrings_seen:
+                patterns.append(f"Avoid repeated sequences like '{substring}'")
+                return patterns  # Exit after finding the first repeated pattern
+            substrings_seen.add(substring)
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a significant performance issue in the repeated substring check and provides an optimized algorithm that is much more efficient, preventing potential slowdowns on long inputs.

Medium
Improve detection of sequential characters

Enhance the sequential character check by replacing the hardcoded list of
patterns with a dynamic approach that iterates through the password to detect
any sequence of three or more consecutive letters or numbers.

Python/password_strength_checker.py [58-64]

 # Sequential characters
-sequences = ['abc', '123', 'qwe', 'asd', 'zxc']
 password_lower = password.lower()
-for seq in sequences:
-    if seq in password_lower or seq[::-1] in password_lower:
+for i in range(len(password_lower) - 2):
+    # Check for alphabetical sequences like 'abc'
+    if (password_lower[i].isalpha() and password_lower[i+1].isalpha() and password_lower[i+2].isalpha() and
+            ord(password_lower[i+1]) == ord(password_lower[i]) + 1 and
+            ord(password_lower[i+2]) == ord(password_lower[i+1]) + 1):
+        seq = password_lower[i:i+3]
+        patterns.append(f"Avoid sequential patterns like '{seq}'")
+        break
+    # Check for numeric sequences like '123'
+    if (password_lower[i].isdigit() and password_lower[i+1].isdigit() and password_lower[i+2].isdigit() and
+            ord(password_lower[i+1]) == ord(password_lower[i]) + 1 and
+            ord(password_lower[i+2]) == ord(password_lower[i+1]) + 1):
+        seq = password_lower[i:i+3]
         patterns.append(f"Avoid sequential patterns like '{seq}'")
         break
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion improves the logic for detecting weak passwords by replacing a limited, hardcoded list of sequences with a more comprehensive dynamic check for any sequential letters or numbers.

Medium
Possible issue
Improve common password detection logic

Improve the common password check by replacing the simple regex normalization
with a function that handles common "leetspeak" substitutions (e.g., @ for a, 0
for o) to better detect variations of weak passwords.

Python/password_strength_checker.py [110-115]

 # Common password check (improved)
-normalized_password = re.sub(r'[^a-z0-9]', '', password.lower())
-if (password.lower() in self.common_passwords or 
+def normalize_for_common_check(p: str) -> str:
+    p = p.lower()
+    substitutions = {'@': 'a', '4': 'a', '1': 'i', '!': 'i', '3': 'e', '0': 'o', '5': 's', '$': 's'}
+    for char, replacement in substitutions.items():
+        p = p.replace(char, replacement)
+    return p
+
+normalized_password = normalize_for_common_check(password)
+if (password.lower() in self.common_passwords or
     normalized_password in self.common_passwords):
     score = max(0, score - 2)  # Heavy penalty for common passwords
     feedback.append("❌ This is a commonly used password - choose something more unique")
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a weakness in the common password check and proposes a more robust method using "leetspeak" substitutions, which significantly improves the checker's effectiveness at finding weak passwords.

Medium
  • More

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
Python/password_strength_checker.py (3)

9-15: Consider expanding the common passwords list.

The current list contains ~25 common passwords, which provides basic protection. For production use, consider loading from a more comprehensive list (e.g., top 10,000 common passwords) either from a file or a third-party library like zxcvbn.


67-73: Consider optimizing substring detection for very long passwords.

The nested loops combined with password.count() result in O(n³) complexity. For typical passwords (<50 characters), this is fine. However, if the tool might process very long inputs, consider using a more efficient algorithm (e.g., suffix arrays or rolling hash).


1-232: Optional enhancements for future consideration.

The implementation is solid overall. A few optional enhancements to consider:

  1. Unicode support: The entropy calculation doesn't account for Unicode/non-ASCII characters, which might be used in international passwords.
  2. Configurable thresholds: Hardcoded values (entropy levels at lines 119-125, score cap at line 134) could be made configurable via constructor parameters for different security contexts.
  3. Common passwords loading: Consider supporting loading common passwords from an external file to allow users to maintain their own lists without modifying code.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39e1e6d and 171ea69.

📒 Files selected for processing (1)
  • Python/password_strength_checker.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.1)
Python/password_strength_checker.py

217-217: Local variable checker is assigned to but never used

Remove assignment to unused variable checker

(F841)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (7)
Python/password_strength_checker.py (7)

1-4: LGTM!

The imports are appropriate for the functionality. Good choice using getpass for secure password input in interactive mode.


27-48: Entropy calculation is a reasonable approximation.

The implementation correctly calculates theoretical entropy based on character pool size. Note that this assumes uniform random selection, so passwords like "Password123!" will have the same calculated entropy as truly random strings of the same length and character types. This is acceptable for a password strength meter, as true entropy calculation would require analyzing character frequency distributions.


77-139: Well-structured password strength analysis.

The scoring logic is comprehensive and balanced:

  • Granular length scoring encourages longer passwords
  • Character diversity checks cover all major types
  • Smart normalization before common password check (removes special chars)
  • Entropy-based scoring adds depth beyond simple rules
  • Pattern penalties help catch weak passwords that pass basic checks

141-167: Good user experience design.

The feedback prioritization (critical → warnings → strengths) and limiting to top 3 items per category provides clear, actionable guidance without overwhelming the user.


169-191: LGTM!

The interactive mode uses getpass.getpass() for secure password input and handles edge cases appropriately. The flow is user-friendly with clear prompts and retry options.


193-203: LGTM!

Clean programmatic API that returns structured data. Good separation from the interactive mode.


227-232: LGTM!

Standard entry point that runs interactive mode by default, which is appropriate for an end-user tool. The commented test option provides an easy way to view examples.

"MyV3ryS3cur3P@ssw0rd2024!" # Strong
]

checker = PasswordStrengthChecker()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused variable.

The checker variable is assigned but never used. Line 223 calls quick_check_mode(pwd) which creates its own checker instance internally.

Apply this diff to remove the unused variable:

-    checker = PasswordStrengthChecker()
-    
     print("Testing Password Examples:")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checker = PasswordStrengthChecker()
print("Testing Password Examples:")
🧰 Tools
🪛 Ruff (0.14.1)

217-217: Local variable checker is assigned to but never used

Remove assignment to unused variable checker

(F841)

🤖 Prompt for AI Agents
In Python/password_strength_checker.py around line 217, the variable `checker`
is created but never used (quick_check_mode(pwd) makes its own checker); remove
the unused assignment `checker = PasswordStrengthChecker()` from that location
so there is no dead variable left in the code and ensure surrounding code still
calls quick_check_mode(pwd) as before.

@x0lg0n x0lg0n merged commit 7b8464e into x0lg0n:main Oct 27, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants