Skip to content

klyrek-assets

Status: built, not yet on PyPI (blocked on PyPI's new-project rate limit) — install from source

Probes for exposed configuration, backup, and version-control files, and extracts/probes cloud storage references found in page content.

from klyrek_assets.sensitive_files import probe_sensitive_files, SENSITIVE_PATHS
from klyrek_assets.buckets import extract_cloud_storage_refs, probe_bucket_exposure

findings = probe_sensitive_files(client, "https://shop.sandbox.klyrek.com/")

Real findings from the sandbox:

{"title": "Exposed .env file", "severity": "critical", "evidence": ["https://shop.sandbox.klyrek.com/.env", "status=200, bytes=333"]}
{"title": "Exposed database backup", "severity": "critical", "evidence": ["https://shop.sandbox.klyrek.com/backup.sql", "status=200, bytes=787"]}

SENSITIVE_PATHS is a curated set, not a fuzzer: .env, .git/config, .git/HEAD, .aws/credentials, wp-config.php.bak, config.php.bak, backup.zip, backup.sql, docker-compose.yml, .htpasswd, .DS_Store, web.config. Each path is probed against a baseline nonexistent-path signature first (a random UUID-suffixed path), so a soft-404 server — one that returns HTTP 200 for everything, including genuinely missing pages — doesn't produce false positives.

API reference

klyrek_assets.sensitive_files

Probe for exposed configuration, backup, and version-control files.

probe_sensitive_files

probe_sensitive_files(client: KlyrekHTTPClient, base_url: str) -> list[Finding]

Probe a curated set of common sensitive file paths at the target root.

Source code in klyrek_assets\sensitive_files.py
def probe_sensitive_files(client: KlyrekHTTPClient, base_url: str) -> list[Finding]:
    """Probe a curated set of common sensitive file paths at the target root."""
    findings: list[Finding] = []
    baseline = _baseline_signature(client, base_url)

    for path, title, severity in SENSITIVE_PATHS:
        url = urljoin(base_url, path)
        if not client.scope.is_authorized(url):
            continue
        try:
            response = client.get(url)
        except Exception:
            logger.debug("failed to probe %s", url, exc_info=True)
            continue
        if response.status_code != 200 or not response.content:
            continue
        signature = (response.status_code, len(response.content))
        if baseline is not None and signature == baseline:
            continue  # looks like a soft-404 catch-all response, not a real hit

        findings.append(
            Finding(
                title=title,
                severity=severity,
                description=f"A request to {path} returned HTTP 200 with a non-empty body "
                "distinct from this server's default not-found response.",
                evidence=[url, f"status={response.status_code}, bytes={len(response.content)}"],
                module="klyrek-assets",
            )
        )
    return findings

klyrek_assets.buckets

Detect references to cloud object storage and probe for public exposure.

extract_cloud_storage_refs

extract_cloud_storage_refs(text: str) -> list[BucketReference]

Return cloud storage references found in HTML/JS body text, deduplicated.

Source code in klyrek_assets\buckets.py
def extract_cloud_storage_refs(text: str) -> list[BucketReference]:
    """Return cloud storage references found in HTML/JS body text, deduplicated."""
    seen: set[tuple[str, str]] = set()
    refs: list[BucketReference] = []
    for provider, pattern in _BUCKET_PATTERNS:
        for match in pattern.finditer(text):
            bucket = match.group(1).lower()
            key = (provider, bucket)
            if key not in seen:
                seen.add(key)
                refs.append(
                    BucketReference(
                        provider=provider, bucket=bucket, probe_url=_probe_url(provider, bucket)
                    )
                )
    return refs

probe_bucket_exposure

probe_bucket_exposure(client: KlyrekHTTPClient, reference: BucketReference) -> Finding | None

Check whether a discovered bucket is publicly listable (S3-compatible XML listing).

Source code in klyrek_assets\buckets.py
def probe_bucket_exposure(client: KlyrekHTTPClient, reference: BucketReference) -> Finding | None:
    """Check whether a discovered bucket is publicly listable (S3-compatible XML listing)."""
    if reference.probe_url is None or not client.scope.is_authorized(reference.probe_url):
        return None
    try:
        response = client.get(reference.probe_url)
    except Exception:
        return None
    if response.status_code == 200 and "<ListBucketResult" in response.text:
        return Finding(
            title=f"Publicly listable {reference.provider} bucket",
            severity=Severity.HIGH,
            description="This bucket returns a directory listing to unauthenticated requests, "
            "exposing the names (and often contents) of every object inside it.",
            evidence=[reference.probe_url],
            module="klyrek-assets",
        )
    return None