compliancelayer 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- compliancelayer-0.1.0/PKG-INFO +140 -0
- compliancelayer-0.1.0/README.md +118 -0
- compliancelayer-0.1.0/compliancelayer/__init__.py +20 -0
- compliancelayer-0.1.0/compliancelayer/client.py +288 -0
- compliancelayer-0.1.0/compliancelayer/exceptions.py +32 -0
- compliancelayer-0.1.0/compliancelayer.egg-info/PKG-INFO +140 -0
- compliancelayer-0.1.0/compliancelayer.egg-info/SOURCES.txt +10 -0
- compliancelayer-0.1.0/compliancelayer.egg-info/dependency_links.txt +1 -0
- compliancelayer-0.1.0/compliancelayer.egg-info/requires.txt +1 -0
- compliancelayer-0.1.0/compliancelayer.egg-info/top_level.txt +1 -0
- compliancelayer-0.1.0/pyproject.toml +34 -0
- compliancelayer-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: compliancelayer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the ComplianceLayer security scanning API
|
|
5
|
+
Author-email: ComplianceLayer <dev@compliancelayer.net>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://compliancelayer.net
|
|
8
|
+
Project-URL: Documentation, https://compliancelayer.net/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/Compliance-Layer/compliancelayer-python
|
|
10
|
+
Project-URL: Issues, https://github.com/Compliance-Layer/compliancelayer-python/issues
|
|
11
|
+
Keywords: security,compliance,scanning,msp,cyber-insurance
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: System :: Monitoring
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: httpx>=0.25.0
|
|
22
|
+
|
|
23
|
+
# ComplianceLayer Python SDK
|
|
24
|
+
|
|
25
|
+
Scan any domain for security compliance in seconds. Built for MSPs, DevOps engineers, and anyone who needs to assess domain security posture.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install compliancelayer
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from compliancelayer import ComplianceLayerClient
|
|
37
|
+
|
|
38
|
+
client = ComplianceLayerClient(api_key="cl_your_key_here")
|
|
39
|
+
|
|
40
|
+
# Full scan — runs all 16 security modules, waits for results
|
|
41
|
+
result = client.scan("example.com")
|
|
42
|
+
print(f"{result['domain']}: {result['grade']} ({result['score']}/100)")
|
|
43
|
+
|
|
44
|
+
for finding in result.get("findings", []):
|
|
45
|
+
print(f" [{finding['severity']}] {finding['finding']}")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Free Scan (No API Key Required)
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
client = ComplianceLayerClient() # no key needed
|
|
52
|
+
result = client.free_scan("example.com")
|
|
53
|
+
print(f"Grade: {result['grade']} | Score: {result['score']}/100")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Rate limited to 5 scans/hour. Returns grade + top 3 issues only.
|
|
57
|
+
|
|
58
|
+
## Batch Scanning
|
|
59
|
+
|
|
60
|
+
Scan hundreds of domains at once:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
domains = ["client1.com", "client2.com", "client3.com"]
|
|
64
|
+
results = client.batch_scan(domains)
|
|
65
|
+
|
|
66
|
+
for r in results:
|
|
67
|
+
print(f"{r['domain']}: {r['grade']}")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Domain Monitoring
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# Add a domain for recurring scans
|
|
74
|
+
client.add_domain("important-client.com", monitor=True)
|
|
75
|
+
|
|
76
|
+
# List monitored domains
|
|
77
|
+
for domain in client.domains():
|
|
78
|
+
print(f"{domain['domain']}: last scan {domain.get('last_scanned_at', 'never')}")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Compliance Reports
|
|
82
|
+
|
|
83
|
+
Generate reports formatted for insurance broker submission:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
report = client.compliance_report("example.com")
|
|
87
|
+
print(f"Compliance status: {report['status']}")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## What Gets Scanned
|
|
91
|
+
|
|
92
|
+
ComplianceLayer runs 16 security modules in under 60 seconds:
|
|
93
|
+
|
|
94
|
+
| Module | What it checks |
|
|
95
|
+
|--------|---------------|
|
|
96
|
+
| SSL/TLS | Certificate validity, expiration, chain, protocols |
|
|
97
|
+
| DNS | SPF, DMARC, DKIM, DNSSEC, CAA records |
|
|
98
|
+
| Headers | HSTS, CSP, X-Frame-Options, CORS, permissions |
|
|
99
|
+
| Ports | ~100 common ports for exposure |
|
|
100
|
+
| Email Auth | SPF alignment, DMARC enforcement, DKIM signing |
|
|
101
|
+
| Blacklists | 35+ blocklist databases |
|
|
102
|
+
| Breaches | Known data breach exposure |
|
|
103
|
+
| Cookies | Secure/HttpOnly/SameSite flags |
|
|
104
|
+
| Subdomains | Subdomain enumeration and exposure |
|
|
105
|
+
| Tech Stack | Framework and server fingerprinting |
|
|
106
|
+
| WAF | Web Application Firewall detection |
|
|
107
|
+
| Reputation | Domain reputation scoring |
|
|
108
|
+
| JavaScript | External script audit |
|
|
109
|
+
| Trackers | Third-party tracker detection |
|
|
110
|
+
| WHOIS | Registration and ownership data |
|
|
111
|
+
| Compliance | Insurance-relevant compliance mapping |
|
|
112
|
+
|
|
113
|
+
## Pricing
|
|
114
|
+
|
|
115
|
+
| Plan | Price | Scans/month | Domains |
|
|
116
|
+
|------|-------|-------------|---------|
|
|
117
|
+
| Free | $0 | 10 | 1 |
|
|
118
|
+
| Pro | $99/mo | 1,000 | 50 |
|
|
119
|
+
| Enterprise | $499/mo | 5,000 | 200 |
|
|
120
|
+
|
|
121
|
+
Get your API key at [compliancelayer.net](https://compliancelayer.net)
|
|
122
|
+
|
|
123
|
+
## Error Handling
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from compliancelayer import ComplianceLayerClient, RateLimitError, AuthenticationError
|
|
127
|
+
|
|
128
|
+
client = ComplianceLayerClient(api_key="cl_your_key")
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
result = client.scan("example.com")
|
|
132
|
+
except AuthenticationError:
|
|
133
|
+
print("Invalid API key")
|
|
134
|
+
except RateLimitError as e:
|
|
135
|
+
print(f"Rate limited. Retry after {e.retry_after}s")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# ComplianceLayer Python SDK
|
|
2
|
+
|
|
3
|
+
Scan any domain for security compliance in seconds. Built for MSPs, DevOps engineers, and anyone who needs to assess domain security posture.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install compliancelayer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from compliancelayer import ComplianceLayerClient
|
|
15
|
+
|
|
16
|
+
client = ComplianceLayerClient(api_key="cl_your_key_here")
|
|
17
|
+
|
|
18
|
+
# Full scan — runs all 16 security modules, waits for results
|
|
19
|
+
result = client.scan("example.com")
|
|
20
|
+
print(f"{result['domain']}: {result['grade']} ({result['score']}/100)")
|
|
21
|
+
|
|
22
|
+
for finding in result.get("findings", []):
|
|
23
|
+
print(f" [{finding['severity']}] {finding['finding']}")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Free Scan (No API Key Required)
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
client = ComplianceLayerClient() # no key needed
|
|
30
|
+
result = client.free_scan("example.com")
|
|
31
|
+
print(f"Grade: {result['grade']} | Score: {result['score']}/100")
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Rate limited to 5 scans/hour. Returns grade + top 3 issues only.
|
|
35
|
+
|
|
36
|
+
## Batch Scanning
|
|
37
|
+
|
|
38
|
+
Scan hundreds of domains at once:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
domains = ["client1.com", "client2.com", "client3.com"]
|
|
42
|
+
results = client.batch_scan(domains)
|
|
43
|
+
|
|
44
|
+
for r in results:
|
|
45
|
+
print(f"{r['domain']}: {r['grade']}")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Domain Monitoring
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
# Add a domain for recurring scans
|
|
52
|
+
client.add_domain("important-client.com", monitor=True)
|
|
53
|
+
|
|
54
|
+
# List monitored domains
|
|
55
|
+
for domain in client.domains():
|
|
56
|
+
print(f"{domain['domain']}: last scan {domain.get('last_scanned_at', 'never')}")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Compliance Reports
|
|
60
|
+
|
|
61
|
+
Generate reports formatted for insurance broker submission:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
report = client.compliance_report("example.com")
|
|
65
|
+
print(f"Compliance status: {report['status']}")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## What Gets Scanned
|
|
69
|
+
|
|
70
|
+
ComplianceLayer runs 16 security modules in under 60 seconds:
|
|
71
|
+
|
|
72
|
+
| Module | What it checks |
|
|
73
|
+
|--------|---------------|
|
|
74
|
+
| SSL/TLS | Certificate validity, expiration, chain, protocols |
|
|
75
|
+
| DNS | SPF, DMARC, DKIM, DNSSEC, CAA records |
|
|
76
|
+
| Headers | HSTS, CSP, X-Frame-Options, CORS, permissions |
|
|
77
|
+
| Ports | ~100 common ports for exposure |
|
|
78
|
+
| Email Auth | SPF alignment, DMARC enforcement, DKIM signing |
|
|
79
|
+
| Blacklists | 35+ blocklist databases |
|
|
80
|
+
| Breaches | Known data breach exposure |
|
|
81
|
+
| Cookies | Secure/HttpOnly/SameSite flags |
|
|
82
|
+
| Subdomains | Subdomain enumeration and exposure |
|
|
83
|
+
| Tech Stack | Framework and server fingerprinting |
|
|
84
|
+
| WAF | Web Application Firewall detection |
|
|
85
|
+
| Reputation | Domain reputation scoring |
|
|
86
|
+
| JavaScript | External script audit |
|
|
87
|
+
| Trackers | Third-party tracker detection |
|
|
88
|
+
| WHOIS | Registration and ownership data |
|
|
89
|
+
| Compliance | Insurance-relevant compliance mapping |
|
|
90
|
+
|
|
91
|
+
## Pricing
|
|
92
|
+
|
|
93
|
+
| Plan | Price | Scans/month | Domains |
|
|
94
|
+
|------|-------|-------------|---------|
|
|
95
|
+
| Free | $0 | 10 | 1 |
|
|
96
|
+
| Pro | $99/mo | 1,000 | 50 |
|
|
97
|
+
| Enterprise | $499/mo | 5,000 | 200 |
|
|
98
|
+
|
|
99
|
+
Get your API key at [compliancelayer.net](https://compliancelayer.net)
|
|
100
|
+
|
|
101
|
+
## Error Handling
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from compliancelayer import ComplianceLayerClient, RateLimitError, AuthenticationError
|
|
105
|
+
|
|
106
|
+
client = ComplianceLayerClient(api_key="cl_your_key")
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
result = client.scan("example.com")
|
|
110
|
+
except AuthenticationError:
|
|
111
|
+
print("Invalid API key")
|
|
112
|
+
except RateLimitError as e:
|
|
113
|
+
print(f"Rate limited. Retry after {e.retry_after}s")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""ComplianceLayer Python SDK — scan any domain for security compliance in seconds."""
|
|
2
|
+
|
|
3
|
+
from .client import ComplianceLayerClient
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
ComplianceLayerError,
|
|
6
|
+
AuthenticationError,
|
|
7
|
+
RateLimitError,
|
|
8
|
+
ScanError,
|
|
9
|
+
TimeoutError,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ComplianceLayerClient",
|
|
15
|
+
"ComplianceLayerError",
|
|
16
|
+
"AuthenticationError",
|
|
17
|
+
"RateLimitError",
|
|
18
|
+
"ScanError",
|
|
19
|
+
"TimeoutError",
|
|
20
|
+
]
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""ComplianceLayer API client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .exceptions import (
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
ComplianceLayerError,
|
|
13
|
+
RateLimitError,
|
|
14
|
+
ScanError,
|
|
15
|
+
TimeoutError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
DEFAULT_BASE_URL = "https://api.compliancelayer.net"
|
|
19
|
+
DEFAULT_TIMEOUT = 30.0
|
|
20
|
+
DEFAULT_POLL_INTERVAL = 3.0
|
|
21
|
+
DEFAULT_POLL_TIMEOUT = 120.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ComplianceLayerClient:
|
|
25
|
+
"""Client for the ComplianceLayer security scanning API.
|
|
26
|
+
|
|
27
|
+
Usage::
|
|
28
|
+
|
|
29
|
+
from compliancelayer import ComplianceLayerClient
|
|
30
|
+
|
|
31
|
+
client = ComplianceLayerClient(api_key="cl_your_key_here")
|
|
32
|
+
|
|
33
|
+
# Quick free scan (limited results, no API key required)
|
|
34
|
+
result = client.free_scan("example.com")
|
|
35
|
+
print(f"{result['domain']}: {result['grade']} ({result['score']}/100)")
|
|
36
|
+
|
|
37
|
+
# Full scan (requires API key + paid plan)
|
|
38
|
+
result = client.scan("example.com")
|
|
39
|
+
for category, data in result["categories"].items():
|
|
40
|
+
print(f" {category}: {data['grade']} ({data['score']}/100)")
|
|
41
|
+
|
|
42
|
+
# Batch scan
|
|
43
|
+
results = client.batch_scan(["example.com", "github.com", "stripe.com"])
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
api_key: str | None = None,
|
|
49
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
50
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
51
|
+
):
|
|
52
|
+
self.api_key = api_key
|
|
53
|
+
self.base_url = base_url.rstrip("/")
|
|
54
|
+
headers = {"User-Agent": f"compliancelayer-python/0.1.0"}
|
|
55
|
+
if api_key:
|
|
56
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
57
|
+
self._client = httpx.Client(
|
|
58
|
+
base_url=self.base_url,
|
|
59
|
+
headers=headers,
|
|
60
|
+
timeout=timeout,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def close(self) -> None:
|
|
64
|
+
"""Close the underlying HTTP client."""
|
|
65
|
+
self._client.close()
|
|
66
|
+
|
|
67
|
+
def __enter__(self) -> ComplianceLayerClient:
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def __exit__(self, *args) -> None:
|
|
71
|
+
self.close()
|
|
72
|
+
|
|
73
|
+
# ------------------------------------------------------------------ #
|
|
74
|
+
# Internal helpers
|
|
75
|
+
# ------------------------------------------------------------------ #
|
|
76
|
+
|
|
77
|
+
def _request(self, method: str, path: str, **kwargs) -> dict[str, Any]:
|
|
78
|
+
"""Make an API request and handle errors."""
|
|
79
|
+
resp = self._client.request(method, f"/api/v1{path}", **kwargs)
|
|
80
|
+
if resp.status_code == 401:
|
|
81
|
+
raise AuthenticationError(
|
|
82
|
+
"Invalid or missing API key. Get one at https://compliancelayer.net",
|
|
83
|
+
status_code=401,
|
|
84
|
+
)
|
|
85
|
+
if resp.status_code == 429:
|
|
86
|
+
retry_after = resp.headers.get("Retry-After")
|
|
87
|
+
raise RateLimitError(
|
|
88
|
+
"Rate limit exceeded. Upgrade your plan for higher limits.",
|
|
89
|
+
status_code=429,
|
|
90
|
+
retry_after=int(retry_after) if retry_after else None,
|
|
91
|
+
)
|
|
92
|
+
if resp.status_code >= 400:
|
|
93
|
+
try:
|
|
94
|
+
body = resp.json()
|
|
95
|
+
except Exception:
|
|
96
|
+
body = {"detail": resp.text}
|
|
97
|
+
msg = body.get("detail") or body.get("message") or resp.text
|
|
98
|
+
raise ComplianceLayerError(str(msg), status_code=resp.status_code, response=body)
|
|
99
|
+
return resp.json()
|
|
100
|
+
|
|
101
|
+
# ------------------------------------------------------------------ #
|
|
102
|
+
# Public API
|
|
103
|
+
# ------------------------------------------------------------------ #
|
|
104
|
+
|
|
105
|
+
def free_scan(self, domain: str) -> dict[str, Any]:
|
|
106
|
+
"""Run a free scan. Returns grade + top 3 issues. No API key required.
|
|
107
|
+
|
|
108
|
+
Rate limited to 5 scans/hour per IP.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
domain: The domain to scan (e.g., "example.com").
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
dict with keys: domain, grade, score, top_findings, modules, etc.
|
|
115
|
+
"""
|
|
116
|
+
return self._request("POST", "/scan/free", json={"domain": domain})
|
|
117
|
+
|
|
118
|
+
def scan(
|
|
119
|
+
self,
|
|
120
|
+
domain: str,
|
|
121
|
+
*,
|
|
122
|
+
wait: bool = True,
|
|
123
|
+
poll_interval: float = DEFAULT_POLL_INTERVAL,
|
|
124
|
+
poll_timeout: float = DEFAULT_POLL_TIMEOUT,
|
|
125
|
+
) -> dict[str, Any]:
|
|
126
|
+
"""Run a full authenticated scan across all 16 modules.
|
|
127
|
+
|
|
128
|
+
By default, submits the scan job and polls until complete.
|
|
129
|
+
Set ``wait=False`` to get the job_id immediately and poll manually.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
domain: The domain to scan.
|
|
133
|
+
wait: If True, poll until the scan finishes (default).
|
|
134
|
+
poll_interval: Seconds between poll attempts (default 3).
|
|
135
|
+
poll_timeout: Max seconds to wait before raising TimeoutError (default 120).
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
dict with keys: job_id, status, domain, grade, score, categories, findings.
|
|
139
|
+
"""
|
|
140
|
+
if not self.api_key:
|
|
141
|
+
raise AuthenticationError("API key required for full scans. Use free_scan() for unauthenticated access.")
|
|
142
|
+
|
|
143
|
+
resp = self._request("POST", "/scan", json={"domain": domain})
|
|
144
|
+
|
|
145
|
+
if not wait:
|
|
146
|
+
return resp
|
|
147
|
+
|
|
148
|
+
job_id = resp.get("job_id")
|
|
149
|
+
if not job_id:
|
|
150
|
+
return resp
|
|
151
|
+
|
|
152
|
+
return self.poll_scan(job_id, interval=poll_interval, timeout=poll_timeout)
|
|
153
|
+
|
|
154
|
+
def poll_scan(
|
|
155
|
+
self,
|
|
156
|
+
job_id: str,
|
|
157
|
+
*,
|
|
158
|
+
interval: float = DEFAULT_POLL_INTERVAL,
|
|
159
|
+
timeout: float = DEFAULT_POLL_TIMEOUT,
|
|
160
|
+
) -> dict[str, Any]:
|
|
161
|
+
"""Poll a scan job until it completes or times out.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
job_id: The job ID from scan().
|
|
165
|
+
interval: Seconds between poll attempts.
|
|
166
|
+
timeout: Max seconds to wait.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
The completed scan result.
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
TimeoutError: If the scan doesn't complete within the timeout.
|
|
173
|
+
ScanError: If the scan fails.
|
|
174
|
+
"""
|
|
175
|
+
deadline = time.monotonic() + timeout
|
|
176
|
+
while time.monotonic() < deadline:
|
|
177
|
+
result = self._request("GET", f"/scan/{job_id}")
|
|
178
|
+
status = result.get("status", "")
|
|
179
|
+
if status == "complete":
|
|
180
|
+
return result
|
|
181
|
+
if status == "failed":
|
|
182
|
+
raise ScanError(
|
|
183
|
+
f"Scan failed for job {job_id}",
|
|
184
|
+
response=result,
|
|
185
|
+
)
|
|
186
|
+
time.sleep(interval)
|
|
187
|
+
|
|
188
|
+
raise TimeoutError(
|
|
189
|
+
f"Scan {job_id} did not complete within {timeout}s. "
|
|
190
|
+
f"Last status: {result.get('status')}. Try increasing poll_timeout.",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def batch_scan(
|
|
194
|
+
self,
|
|
195
|
+
domains: list[str],
|
|
196
|
+
*,
|
|
197
|
+
wait: bool = True,
|
|
198
|
+
poll_interval: float = DEFAULT_POLL_INTERVAL,
|
|
199
|
+
poll_timeout: float = 300.0,
|
|
200
|
+
) -> list[dict[str, Any]]:
|
|
201
|
+
"""Scan multiple domains. Submits all jobs, then polls until all complete.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
domains: List of domains to scan.
|
|
205
|
+
wait: If True, wait for all scans to complete.
|
|
206
|
+
poll_interval: Seconds between poll cycles.
|
|
207
|
+
poll_timeout: Max seconds to wait for all scans.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
List of scan results (same shape as scan()).
|
|
211
|
+
"""
|
|
212
|
+
if not self.api_key:
|
|
213
|
+
raise AuthenticationError("API key required for batch scans.")
|
|
214
|
+
|
|
215
|
+
resp = self._request("POST", "/batch", json={"domains": domains})
|
|
216
|
+
jobs = resp.get("jobs", [])
|
|
217
|
+
|
|
218
|
+
if not wait or not jobs:
|
|
219
|
+
return jobs
|
|
220
|
+
|
|
221
|
+
deadline = time.monotonic() + poll_timeout
|
|
222
|
+
results = {}
|
|
223
|
+
pending = {j["job_id"] for j in jobs if "job_id" in j}
|
|
224
|
+
|
|
225
|
+
while pending and time.monotonic() < deadline:
|
|
226
|
+
for job_id in list(pending):
|
|
227
|
+
try:
|
|
228
|
+
result = self._request("GET", f"/scan/{job_id}")
|
|
229
|
+
except ComplianceLayerError:
|
|
230
|
+
continue
|
|
231
|
+
status = result.get("status", "")
|
|
232
|
+
if status in ("complete", "failed"):
|
|
233
|
+
results[job_id] = result
|
|
234
|
+
pending.discard(job_id)
|
|
235
|
+
if pending:
|
|
236
|
+
time.sleep(poll_interval)
|
|
237
|
+
|
|
238
|
+
for job_id in pending:
|
|
239
|
+
results[job_id] = {"job_id": job_id, "status": "timeout"}
|
|
240
|
+
|
|
241
|
+
return [results.get(j["job_id"], j) for j in jobs]
|
|
242
|
+
|
|
243
|
+
def domains(self) -> list[dict[str, Any]]:
|
|
244
|
+
"""List all monitored domains.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
List of domain objects with monitoring config.
|
|
248
|
+
"""
|
|
249
|
+
return self._request("GET", "/domains")
|
|
250
|
+
|
|
251
|
+
def add_domain(self, domain: str, *, monitor: bool = True) -> dict[str, Any]:
|
|
252
|
+
"""Add a domain for monitoring.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
domain: The domain to add.
|
|
256
|
+
monitor: Enable recurring scans (default True).
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
The created domain object.
|
|
260
|
+
"""
|
|
261
|
+
return self._request("POST", "/domains", json={"domain": domain, "monitor": monitor})
|
|
262
|
+
|
|
263
|
+
def compliance_report(self, domain: str) -> dict[str, Any]:
|
|
264
|
+
"""Get a compliance report for a domain.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
domain: The domain to get the report for.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Compliance report with insurance-relevant findings.
|
|
271
|
+
"""
|
|
272
|
+
return self._request("GET", f"/compliance/{domain}")
|
|
273
|
+
|
|
274
|
+
def usage(self) -> dict[str, Any]:
|
|
275
|
+
"""Get current plan usage stats.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
dict with scans_used, scans_limit, domains_used, domains_limit, plan.
|
|
279
|
+
"""
|
|
280
|
+
return self._request("GET", "/usage")
|
|
281
|
+
|
|
282
|
+
def status(self) -> dict[str, Any]:
|
|
283
|
+
"""Check API health.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
dict with status, version, uptime.
|
|
287
|
+
"""
|
|
288
|
+
return self._request("GET", "/status")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""ComplianceLayer SDK exceptions."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ComplianceLayerError(Exception):
|
|
7
|
+
"""Base exception for all SDK errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, status_code: Optional[int] = None, response: Optional[dict] = None):
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.status_code = status_code
|
|
12
|
+
self.response = response
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AuthenticationError(ComplianceLayerError):
|
|
16
|
+
"""Raised when the API key is invalid or missing."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RateLimitError(ComplianceLayerError):
|
|
20
|
+
"""Raised when the rate limit is exceeded."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, message: str, retry_after: Optional[int] = None, **kwargs):
|
|
23
|
+
super().__init__(message, **kwargs)
|
|
24
|
+
self.retry_after = retry_after
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ScanError(ComplianceLayerError):
|
|
28
|
+
"""Raised when a scan fails."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TimeoutError(ComplianceLayerError):
|
|
32
|
+
"""Raised when a scan times out waiting for results."""
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: compliancelayer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the ComplianceLayer security scanning API
|
|
5
|
+
Author-email: ComplianceLayer <dev@compliancelayer.net>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://compliancelayer.net
|
|
8
|
+
Project-URL: Documentation, https://compliancelayer.net/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/Compliance-Layer/compliancelayer-python
|
|
10
|
+
Project-URL: Issues, https://github.com/Compliance-Layer/compliancelayer-python/issues
|
|
11
|
+
Keywords: security,compliance,scanning,msp,cyber-insurance
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: System :: Monitoring
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: httpx>=0.25.0
|
|
22
|
+
|
|
23
|
+
# ComplianceLayer Python SDK
|
|
24
|
+
|
|
25
|
+
Scan any domain for security compliance in seconds. Built for MSPs, DevOps engineers, and anyone who needs to assess domain security posture.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install compliancelayer
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from compliancelayer import ComplianceLayerClient
|
|
37
|
+
|
|
38
|
+
client = ComplianceLayerClient(api_key="cl_your_key_here")
|
|
39
|
+
|
|
40
|
+
# Full scan — runs all 16 security modules, waits for results
|
|
41
|
+
result = client.scan("example.com")
|
|
42
|
+
print(f"{result['domain']}: {result['grade']} ({result['score']}/100)")
|
|
43
|
+
|
|
44
|
+
for finding in result.get("findings", []):
|
|
45
|
+
print(f" [{finding['severity']}] {finding['finding']}")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Free Scan (No API Key Required)
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
client = ComplianceLayerClient() # no key needed
|
|
52
|
+
result = client.free_scan("example.com")
|
|
53
|
+
print(f"Grade: {result['grade']} | Score: {result['score']}/100")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Rate limited to 5 scans/hour. Returns grade + top 3 issues only.
|
|
57
|
+
|
|
58
|
+
## Batch Scanning
|
|
59
|
+
|
|
60
|
+
Scan hundreds of domains at once:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
domains = ["client1.com", "client2.com", "client3.com"]
|
|
64
|
+
results = client.batch_scan(domains)
|
|
65
|
+
|
|
66
|
+
for r in results:
|
|
67
|
+
print(f"{r['domain']}: {r['grade']}")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Domain Monitoring
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# Add a domain for recurring scans
|
|
74
|
+
client.add_domain("important-client.com", monitor=True)
|
|
75
|
+
|
|
76
|
+
# List monitored domains
|
|
77
|
+
for domain in client.domains():
|
|
78
|
+
print(f"{domain['domain']}: last scan {domain.get('last_scanned_at', 'never')}")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Compliance Reports
|
|
82
|
+
|
|
83
|
+
Generate reports formatted for insurance broker submission:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
report = client.compliance_report("example.com")
|
|
87
|
+
print(f"Compliance status: {report['status']}")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## What Gets Scanned
|
|
91
|
+
|
|
92
|
+
ComplianceLayer runs 16 security modules in under 60 seconds:
|
|
93
|
+
|
|
94
|
+
| Module | What it checks |
|
|
95
|
+
|--------|---------------|
|
|
96
|
+
| SSL/TLS | Certificate validity, expiration, chain, protocols |
|
|
97
|
+
| DNS | SPF, DMARC, DKIM, DNSSEC, CAA records |
|
|
98
|
+
| Headers | HSTS, CSP, X-Frame-Options, CORS, permissions |
|
|
99
|
+
| Ports | ~100 common ports for exposure |
|
|
100
|
+
| Email Auth | SPF alignment, DMARC enforcement, DKIM signing |
|
|
101
|
+
| Blacklists | 35+ blocklist databases |
|
|
102
|
+
| Breaches | Known data breach exposure |
|
|
103
|
+
| Cookies | Secure/HttpOnly/SameSite flags |
|
|
104
|
+
| Subdomains | Subdomain enumeration and exposure |
|
|
105
|
+
| Tech Stack | Framework and server fingerprinting |
|
|
106
|
+
| WAF | Web Application Firewall detection |
|
|
107
|
+
| Reputation | Domain reputation scoring |
|
|
108
|
+
| JavaScript | External script audit |
|
|
109
|
+
| Trackers | Third-party tracker detection |
|
|
110
|
+
| WHOIS | Registration and ownership data |
|
|
111
|
+
| Compliance | Insurance-relevant compliance mapping |
|
|
112
|
+
|
|
113
|
+
## Pricing
|
|
114
|
+
|
|
115
|
+
| Plan | Price | Scans/month | Domains |
|
|
116
|
+
|------|-------|-------------|---------|
|
|
117
|
+
| Free | $0 | 10 | 1 |
|
|
118
|
+
| Pro | $99/mo | 1,000 | 50 |
|
|
119
|
+
| Enterprise | $499/mo | 5,000 | 200 |
|
|
120
|
+
|
|
121
|
+
Get your API key at [compliancelayer.net](https://compliancelayer.net)
|
|
122
|
+
|
|
123
|
+
## Error Handling
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from compliancelayer import ComplianceLayerClient, RateLimitError, AuthenticationError
|
|
127
|
+
|
|
128
|
+
client = ComplianceLayerClient(api_key="cl_your_key")
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
result = client.scan("example.com")
|
|
132
|
+
except AuthenticationError:
|
|
133
|
+
print("Invalid API key")
|
|
134
|
+
except RateLimitError as e:
|
|
135
|
+
print(f"Rate limited. Retry after {e.retry_after}s")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
compliancelayer/__init__.py
|
|
4
|
+
compliancelayer/client.py
|
|
5
|
+
compliancelayer/exceptions.py
|
|
6
|
+
compliancelayer.egg-info/PKG-INFO
|
|
7
|
+
compliancelayer.egg-info/SOURCES.txt
|
|
8
|
+
compliancelayer.egg-info/dependency_links.txt
|
|
9
|
+
compliancelayer.egg-info/requires.txt
|
|
10
|
+
compliancelayer.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
httpx>=0.25.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
compliancelayer
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "compliancelayer"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for the ComplianceLayer security scanning API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{name = "ComplianceLayer", email = "dev@compliancelayer.net"}]
|
|
13
|
+
keywords = ["security", "compliance", "scanning", "msp", "cyber-insurance"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: System Administrators",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Security",
|
|
21
|
+
"Topic :: System :: Monitoring",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.25.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://compliancelayer.net"
|
|
29
|
+
Documentation = "https://compliancelayer.net/docs"
|
|
30
|
+
Repository = "https://github.com/Compliance-Layer/compliancelayer-python"
|
|
31
|
+
Issues = "https://github.com/Compliance-Layer/compliancelayer-python/issues"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
include = ["compliancelayer*"]
|