A Python client and wrapper around the Cyver API.
Project description
cyver-reporting
A Python client library for the Cyver Pentest Management API. Developed and maintained by penetration testers at Thoropass.
Features
- Two authentication modes — username/password login with email-based 2FA and automatic token refresh, or stateless static API key via
X-API-Key. - Portal validation — availability probe before credentials are ever sent; raises a clear error if the portal is inactive or unreachable. Skipped automatically when a local cache confirms the portal was active in a previous run.
- Pentester role (
PentesterSession) — manage clients, assets, users, teams, projects, continuous projects, findings, and reference data. - Client role (
ClientSession) — read projects and continuous projects scoped to the authenticated client user. - Data builders —
CyverClient,CyverAsset,CyverFinding,CyverFindingEvidence,CyverFindingCustomField, and companion classes provide a validated, fluent interface for constructing API request bodies, catching type and format errors before any request is dispatched. - Typed return values —
get_clients(),get_all_clients(),get_client_by_id(),create_client(),update_client_by_id(),get_assets_by_client_id(),get_all_assets_by_client_id(),create_client_asset(), andupdate_client_asset_by_id()all returnCyverClient/CyverAssetinstances.get_users(),get_all_users(), andget_user_by_id()returnCyverUserinstances.create_finding()andupdate_finding_by_id()returnCyverFindinginstances. Theidproperty on each object carries the server-assigned UUID. - Transparent pagination — every list endpoint ships with a companion
get_all_*()generator that pages through all results automatically. - Secure token caching — OAuth tokens are encrypted with a PBKDF2-derived Fernet key and stored in the OS user-cache directory with owner-only file permissions. The cached portal hostname enables probe-skip on warm starts and stale cache cleanup on failures. API key sessions maintain a separate lightweight cache for the same portal-validation purpose.
- Resilient HTTP transport — automatic retries with exponential back-off on transient server errors and rate-limiting responses.
- Thread-safe — all token state is protected by a reentrant lock, safe for use from multiple threads.
- Context manager support — use
withblocks to guarantee the underlying HTTP session is always closed. - Type annotations — fully annotated and ships with a
py.typedmarker (PEP 561).
Requirements
- Python 3.10 or later
- A valid Cyver portal account with Pentester or Client role
Installation
pip install cyver-reporting
Quick Start
Pentester — username/password
from CyverReporting import PentesterSession, _project_status
with PentesterSession() as client:
client.authenticate("app.cyver.io", "user@example.com", "s3cr3t")
# Iterate over every active project without managing pagination manually
for project in client.get_all_projects(filter_status=_project_status.testing):
print(project.name)
# Fetch all critical and high findings for a specific project
for finding in client.get_all_findings(
project_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
severity_list=["Critical", "High"],
):
print(finding.name, finding.severity)
Pentester — static API key
Pass portal and api_key to the constructor. The session is immediately
ready — no call to authenticate() is needed or allowed:
from CyverReporting import PentesterSession, _project_status
with PentesterSession(portal="app.cyver.io", api_key="sk-...") as client:
for project in client.get_all_projects(filter_status=_project_status.testing):
print(project.name)
Client — username/password
from CyverReporting import ClientSession
with ClientSession() as client:
client.authenticate("app.cyver.io", "user@example.com", "s3cr3t")
for project in client.get_all_projects():
print(project.name)
Client — static API key
from CyverReporting import ClientSession
with ClientSession(portal="app.cyver.io", api_key="sk-...") as client:
for project in client.get_all_projects():
print(project.name)
Two-Factor Authentication
When 2FA is enabled on the account, a verification code is sent to the
registered e-mail address and a Cyver2FARequired is raised on the first call:
from CyverReporting import PentesterSession
from CyverReporting.Exceptions import Cyver2FARequired
client = PentesterSession()
try:
client.authenticate("app.cyver.io", "user@example.com", "s3cr3t")
except Cyver2FARequired:
code = input("Enter the 2FA code sent to your email: ")
client.authenticate("app.cyver.io", "user@example.com", "s3cr3t",
verification_code=code)
Creating a Client (data builder)
Use CyverClient to construct and validate the request body before calling
create_client(). All setter methods return self for fluent chaining:
from CyverReporting import PentesterSession, CyverClient, CyverClientInformation, CyverClientAddress
with PentesterSession(portal="app.cyver.io", api_key="sk-...") as pentester:
new_client = (
CyverClient("Acme Corp")
.set_status(1)
.set_client_number("CLI-0042")
.set_client_information(
CyverClientInformation()
.set_company_name("Acme Corporation")
.set_website("https://acme.example.com")
.set_address(
CyverClientAddress()
.set_street("123 Main St")
.set_city("San Francisco")
.set_country("United States")
)
)
)
created = pentester.create_client(new_client)
print(created.id) # server-assigned UUID
Creating a Client Asset (data builder)
Use CyverAsset to construct and validate an asset request body. Each asset
type imposes additional required fields (e.g. url for Web Application, ip
for Network) that are enforced before the request is dispatched:
from CyverReporting import PentesterSession, CyverAsset
with PentesterSession(portal="app.cyver.io", api_key="sk-...") as pentester:
asset = (
CyverAsset("Main Portal")
.set_url("https://portal.example.com")
.set_type(2) # Web Application — also validates url is set
.set_environment(3) # Production
.set_hosting_type(1) # Public Cloud (Azure)
.set_public_facing(2) # External
)
created = pentester.create_client_asset(client_id, asset)
print(created.id) # server-assigned asset UUID
Creating a Finding (data builder)
Use CyverFinding to construct and validate a finding request body. name is
the only required field; all other fields are optional:
from CyverReporting import PentesterSession, CyverFinding, _finding_severity, _finding_status
with PentesterSession(portal="app.cyver.io", api_key="sk-...") as pentester:
finding = (
CyverFinding("SQL Injection in Login Form")
.set_severity(_finding_severity.high)
.set_status(_finding_status.draft)
.set_cvss31_vector("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
.set_cvss31_score(9.8)
.add_cwe("CWE-89")
)
created = pentester.create_finding(finding)
print(created.id) # server-assigned UUID
Documentation
| Document | Description |
|---|---|
| Authentication | Login, API key auth, portal probe, 2FA, token caching, and session lifecycle |
| Pentester API | All PentesterSession methods and parameters |
| Client API | All ClientSession methods and parameters |
| Exceptions | Exception hierarchy and error handling guide |
API Coverage
The library covers 54 of 69 Cyver API V2.2 endpoints (78%). The remaining endpoints are write-heavy operations (resource creation, full updates, file uploads, report downloads) whose exact request body schemas require additional information beyond what the public API specification provides.
| Section | Implemented | Total |
|---|---|---|
| Authentication | 3 | 3 |
| Clients | 8 | 9 |
| Users | 3 | 5 |
| Teams | 3 | 5 |
| Projects (Pentester) | 10 | 16 |
| Continuous Projects (Pentester) | 11 | 14 |
| Findings | 6 | 7 |
| Reference Data | 6 | 6 |
| Projects (Client) | 2 | 2 |
| Continuous Projects (Client) | 2 | 2 |
Project Structure
CyverReporting/
├── __init__.py # Public exports: CyverSession, PentesterSession, ClientSession, exceptions, data builders, constants
├── Session.py # Base session: authentication, portal probe, token cache, HTTP transport
├── PentesterSession.py # PentesterSession — pentester-role API methods
├── ClientSession.py # ClientSession — client-role API methods
├── DataTypes/ # Cyver* data-builder classes (CyverClient, CyverAsset, CyverUser, …)
│ ├── __init__.py
│ ├── CyverClient.py
│ ├── CyverAsset.py
│ ├── CyverUser.py
│ ├── CyverFinding.py
│ ├── CyverFindingEvidence.py
│ ├── CyverFindingCustomField.py
│ └── …
├── Utils/ # Private helpers, compiled regexes, format validators, and CY_* static constant classes
│ ├── __init__.py
│ ├── RegExPatterns.py
│ ├── StaticAliases.py
│ ├── FormatValidators.py
│ └── FieldExtractors.py
├── Exceptions/ # Full exception hierarchy (CyverError, CyverAuthError, CyverDataValidationError, …)
│ ├── __init__.py
│ ├── Base.py
│ ├── Authentication.py
│ ├── Connection.py
│ └── Validation.py
└── py.typed # PEP 561 marker
tests/
├── conftest.py # Shared fixtures and helpers
├── test_session.py # Session, authentication, portal probe tests
├── test_client.py # ClientSession tests
├── test_pentester.py # PentesterSession tests
├── test_validators_client.py # CyverClient / CyverClientInformation / CyverClientAddress tests
├── test_validators_user.py # CyverUser tests
├── test_finding_evidence.py # CyverFindingEvidence tests
├── test_finding_custom_field.py # CyverFindingCustomField tests
├── test_finding.py # CyverFinding tests
├── test_exceptions.py # Exception hierarchy tests
└── test_utils.py # Format validator tests
Error Handling
from CyverReporting import PentesterSession, _project_status
from CyverReporting.Exceptions import (
Cyver2FARequired,
CyverAuthError,
CyverDataValidationError,
CyverHTTPError,
)
client = PentesterSession()
try:
client.authenticate(portal, username, password)
except Cyver2FARequired:
# 2FA code required — re-call authenticate() with verification_code=
code = input("Enter the 2FA code sent to your email: ")
client.authenticate(portal, username, password, verification_code=code)
except CyverAuthError as e:
if e.reason == "portal_inactive":
print(f"Portal '{portal}' does not exist or is not active.")
elif e.reason == "portal_unreachable":
print(f"Portal '{portal}' could not be reached. Check connectivity.")
else:
print(f"Authentication failed: {e}")
except CyverDataValidationError as e:
# Invalid argument passed to a method or data builder (wrong type, bad UUID, etc.)
print(f"Data error on field '{e.field}' ({e.reason}): {e}")
except CyverHTTPError as e:
# Network failure, non-200 status code, or malformed JSON response
print(f"HTTP error: {e}")
License
MIT — see LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cyver_reporting-0.0.3.tar.gz.
File metadata
- Download URL: cyver_reporting-0.0.3.tar.gz
- Upload date:
- Size: 109.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
864bac9bb37cca7a65cc0ae4b949812178ba2fa80ff9a130c03b90bdf05d1118
|
|
| MD5 |
bad4e71b8543b561a0262a5cad92222c
|
|
| BLAKE2b-256 |
711a87c0c7e9f21566c24a99b00c1f2083d80836ba51d59f092dcfcc2a805d04
|
File details
Details for the file cyver_reporting-0.0.3-py3-none-any.whl.
File metadata
- Download URL: cyver_reporting-0.0.3-py3-none-any.whl
- Upload date:
- Size: 96.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
221ebff1bc88aca6334c1940ea5247a32bdcf9058fa75282d6af3dc1938cdc1b
|
|
| MD5 |
7dd98d0c6ea7ab22a7e3b676356872a1
|
|
| BLAKE2b-256 |
07269839f04686d61695b6fa82f244e2e8510ef25a8e9f5e6efc0375be6c0631
|