Back to Articles

Credcheck: A Modular Framework for Validating Stolen Credentials in Security Testing

[ View on GitHub ]

Credcheck: A Modular Framework for Validating Stolen Credentials in Security Testing

Hook

Every pentester has encountered the same problem: you've dumped a database with 10,000 credentials, but which ones actually work? Testing them manually would take weeks and likely trigger every fraud detection system between you and the target.

Context

In the post-exploitation phase of security assessments and bug bounty programs, credentials are everywhere. They're in environment variables, configuration files, browser storage, and exposed databases. But raw credentials are just strings until you verify they're valid and determine what they unlock. Traditional approaches force security researchers into a frustrating choice: either manually test credentials one-by-one (slow, error-prone, and attention-grabbing) or write custom scripts for each service API (time-consuming and not reusable across engagements).

Credcheck emerged to solve this credential validation bottleneck by providing a framework that handles both identification and verification. Unlike monolithic credential scanners that focus on discovery in codebases, credcheck assumes you've already found potential credentials and need to answer two questions: "What service is this for?" and "Does it actually work?" This positions it uniquely in the security testing workflow—not as a scanner, but as a post-discovery validation layer that bridges the gap between finding credentials and exploiting them.

Technical Insight

Format Match

Dynamic Import

API Request

Validation Result

Valid/Invalid + Metadata

Identified Format

Verification Status

CLI Interface

DynamicTest Core

Library Import

Passive Check

Active Check

CredUtils

Regex Patterns

Service Loader

Service Modules

Stripe/AWS/GitHub

External Services

Results

System architecture — auto-generated

Credcheck's architecture centers on two complementary validation modes that reflect different phases of credential assessment. The passive mode uses regex patterns to identify credential formats without making network requests, while the active mode actually tests credentials against live service APIs. This dual approach lets you first categorize what you've found, then selectively verify only the promising candidates.

The framework's modular design revolves around a DynamicTest class that dynamically imports service-specific modules. Each service module implements its own validation logic, keeping the core framework clean while making it trivial to add new services. Here's how the structure works:

from credcheck import DynamicTest

# Initialize the framework
tester = DynamicTest()

# Passive check: identify credential format without API calls
stripe_key = "sk_live_4eC39HqLyjWDarjtT1zdp7dc"
if tester.passive_check('stripe', stripe_key):
    print("Matches Stripe API key format")
    
    # Active check: verify if the key actually works
    result = tester.active_check('stripe', stripe_key)
    if result['valid']:
        print(f"Valid Stripe key with scope: {result['scope']}")

Each service module follows a consistent interface pattern. The passive check uses compiled regex patterns for performance, while the active check makes authenticated API requests to verify validity. For Stripe, the implementation might look like:

import re
import requests

class StripeChecker:
    # Stripe keys follow predictable formats
    PATTERNS = {
        'secret': re.compile(r'sk_(live|test)_[A-Za-z0-9]{24,}'),
        'publishable': re.compile(r'pk_(live|test)_[A-Za-z0-9]{24,}')
    }
    
    @staticmethod
    def passive_check(credential):
        """Check if string matches Stripe key format"""
        for key_type, pattern in StripeChecker.PATTERNS.items():
            if pattern.match(credential):
                return {'match': True, 'type': key_type}
        return {'match': False}
    
    @staticmethod
    def active_check(credential):
        """Verify credential against Stripe API"""
        try:
            # Use the /v1/account endpoint for verification
            response = requests.get(
                'https://api.stripe.com/v1/account',
                auth=(credential, ''),
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                return {
                    'valid': True,
                    'account_id': data.get('id'),
                    'email': data.get('email'),
                    'country': data.get('country')
                }
            elif response.status_code == 401:
                return {'valid': False, 'reason': 'Invalid credentials'}
            else:
                return {'valid': False, 'reason': 'Unknown error'}
                
        except requests.exceptions.RequestException as e:
            return {'valid': False, 'error': str(e)}

The framework's library-first design means it integrates cleanly into larger automation pipelines. You're not forced to use a CLI with rigid output formats. Instead, you import the classes and work with Python dictionaries, making it natural to combine credcheck with database queries, log parsers, or custom reporting tools. This architectural choice reflects a mature understanding of how security tools actually get used in practice—rarely standalone, almost always as components in larger workflows.

The CredUtils helper class provides convenience methods for batch processing and format detection across multiple services simultaneously. When you're staring at a config file with dozens of API keys and tokens, you can run all passive checks at once to categorize everything before selectively running expensive active checks:

from credcheck import CredUtils

# Extract all potential credentials from a text dump
config_dump = open('exposed_config.txt').read()
utils = CredUtils()

# Run passive checks against all known patterns
matches = utils.identify_all(config_dump)

# Output: {'stripe': ['sk_live_...'], 'aws': ['AKIA...'], 'github': ['ghp_...']}
for service, credentials in matches.items():
    print(f"Found {len(credentials)} potential {service} credentials")

This passive-first workflow is operationally smart. Active credential checking generates logs on the target service, potentially triggering fraud alerts or account lockouts. By using regex to filter out obvious false positives first, you minimize your detection footprint and only make API calls when there's a high-probability match.

Gotcha

The most significant limitation is incompleteness. The repository's README honestly lists numerous TODOs: missing test cases for most services, incomplete regex patterns, and no PyPI package despite the code being years old. When you examine the codebase, you'll find the Stripe example is well-implemented, but many other services listed in the topics (AWS, GitHub, etc.) have stub implementations or missing modules entirely. This means you're not getting a comprehensive tool—you're getting a framework skeleton that requires substantial work to cover the services you actually need.

Operational security considerations are another critical gotcha that the documentation doesn't adequately address. Active credential checking is inherently noisy. Every API call you make to verify a credential creates a log entry on the target service. For Stripe, that means your verification attempt appears in their dashboard's API logs. For AWS, it's in CloudTrail. For GitHub, it's in the security audit log. If you're testing credentials during an authorized pentest, this might be acceptable, but in bug bounty contexts or red team operations, this detection risk could compromise your assessment. The framework provides no built-in rate limiting, proxy rotation, or delay mechanisms to reduce this footprint—features you'd need to implement yourself if stealth matters.

Verdict

Use if: You're building custom security automation pipelines and need a starting point for credential validation logic that you can extend with service-specific modules. The framework's library-oriented design makes it valuable when you want to embed credential checking into larger Python tools, and you have the development capacity to fill in the missing implementations. It's particularly appropriate for authorized penetration tests where detection isn't a concern and you need to quickly validate hundreds of discovered credentials across a few well-defined services. Skip if: You need production-ready coverage of dozens of services out of the box, require active maintenance and security updates, or operate in environments where detection avoidance is critical. The incomplete state, limited service coverage, and lack of stealth features make it unsuitable for sensitive operations or teams without Python development resources to customize and extend it. Consider more mature alternatives like truffleHog for discovery or keyhacks for validation guidance if you need something ready to use today.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/cybersecurity/secxena-credcheck.svg)](https://starlog.is/api/badge-click/cybersecurity/secxena-credcheck)