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(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
|