Skip to main content

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.
  • Portal-surface classes (PentesterPortal / ClientPortal) — opt-in subclasses that add wrappers for the undocumented frontend API the Cyver web UI exercises (project-file management, finding libraries, finding templates, etc.). Inherit every documented V2.2 method from their parent session class, plus the additional portal methods. Static API keys are rejected at construction — only password-authenticated access tokens are accepted by this surface.
  • Data buildersCyverClient, CyverAsset, CyverUser, CyverProject, CyverFinding, CyverFindingEvidence, CyverFindingCustomField, CyverFindingLibrary, CyverProjectFile, plus read-only template classes (CyverProjectTemplate, CyverReportTemplate, CyverChecklistTemplate, CyverComplianceTemplate, CyverReportTemplateSection) and supporting classes (CyverClientInformation, CyverClientAddress, CyverProjectDates, CyverPlanningDate) — all provide a validated, fluent interface for constructing API request bodies, catching type and format errors before any request is dispatched.
  • Typed return values — every read and write method returns the matching Cyver* data-builder instance instead of a raw dict. get_clients() / get_all_clients() / get_client_by_id() / create_client() / update_client_by_id() return CyverClient. The get_assets_by_client_id() family and create_client_asset() / update_client_asset_by_id() return CyverAsset. get_users() / get_all_users() / get_user_by_id() / create_user() / update_user_by_id() return CyverUser. PentesterSession.get_projects() / get_all_projects() / get_project_by_id() / create_project() and ClientSession.get_projects() / get_all_projects() / get_project_by_id() return CyverProject. get_findings() / get_all_findings() / get_finding_by_id() / create_finding() / update_finding_by_id() return CyverFinding. Project-/report-/checklist-/compliance-norm-template getters return the corresponding CyverProjectTemplate / CyverReportTemplate / CyverChecklistTemplate / CyverComplianceTemplate. The portal-surface methods follow the same convention: get_project_images_files() / get_project_files() return CyverProjectFile; get_finding_libraries() returns CyverFindingLibrary; get_finding_templates() returns CyverFinding instances hydrated in template mode. The id property 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. Opt out at construction with cache_dir=None, redirect to a custom directory with cache_dir=Path(...), or set the CYVER_REPORTING_CACHE_DIR environment variable globally — see Cache Configuration.
  • Robust cache failure handling — corrupted, undecryptable, or unwritable cache files are caught at every entry point that touches the cache (authenticate(), token refresh, the API-key constructor flow), logged as a WARNING, and the session falls back to fresh portal probe + re-authentication. The library never breaks because the cache is damaged. Callers that want explicit handling can catch CyverCachingError.
  • 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 with blocks to guarantee the underlying HTTP session is always closed.
  • Type annotations — fully annotated and ships with a py.typed marker (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(project_id, finding)
    print(created.id)  # server-assigned UUID

Portal surface (undocumented frontend API)

PentesterPortal and ClientPortal add wrappers around the frontend-only endpoints the Cyver web UI exercises — project-file management, finding libraries, finding templates, finding search across the full pentester surface, and other operations the documented V2.2 API does not expose. They subclass PentesterSession / ClientSession, so the full V2.2 method surface is available unchanged; the additional methods are decorated with @portal_unstable and emit a CyverPortalInstabilityWarning on first call.

The portal API rejects static API keys, so portal classes raise CyverAuthMethodError at construction if api_key= is supplied. Use password authentication instead.

from CyverReporting import PentesterPortal, PentesterSession
from CyverReporting.Utils import _finding_library

# Fetch finding templates from a library and create project findings
# from them — the canonical "fetch template → modify → create" workflow.
with PentesterPortal() as portal, PentesterSession() as pentester:
    portal.authenticate("app.cyver.io", "user@example.com", "s3cr3t")
    pentester.authenticate("app.cyver.io", "user@example.com", "s3cr3t")

    for template in portal.get_all_finding_templates(
        finding_library_id="1556a4fe-bee8-440a-9151-c746b27a5c0b",
        filter_status=_finding_library.published,
    ):
        # template.id is None — server assigns a new UUID on create.
        template.set_recommendation("Custom recommendation for this engagement.")
        pentester.create_finding(my_project_id, template)

See docs/portal.md for the full admitted-method list, the endpoint-admission protocol, and the stability disclaimer.

Disabling the credential cache

Pass cache_dir=None (or set CYVER_REPORTING_CACHE_DIR= empty) for ephemeral runtimes — Lambda, ECS, CI runners, read-only filesystems, or privacy-sensitive environments where tokens must never touch disk. The portal probe then runs on every authentication, and warm-start optimisations are skipped:

from CyverReporting import PentesterSession

with PentesterSession(cache_dir=None) as client:  # no on-disk caching
    client.authenticate("app.cyver.io", "user@example.com", "s3cr3t")
    ...
# Or via env var, no code change:
CYVER_REPORTING_CACHE_DIR= python my_script.py

See docs/authentication.md for the full kwarg/env-var matrix and the failure-handling contract.


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
Portal surface (undocumented) PentesterPortal / ClientPortal — opt-in access to the Cyver web frontend API
Exceptions Exception hierarchy and error handling guide

API Coverage

Documented V2.2 surface

The library covers approximately 56 of 76 Cyver API V2.2 endpoints (~74%). The remaining endpoints are tracked under their respective items in ROADMAP1 (write-side resource creates, file imports, report downloads, full client-side CRUD).

Section Implemented Total
Authentication 3 3
Clients 8 8
Users 5 5
Teams 3 5
Projects (Pentester) 12 13
Continuous Projects (Pentester) 10 13
Findings 5 6
Reference Data 6 6
Projects (Client) 2 4
Continuous Projects (Client) 2 4
Findings (Client) 0 1
Assets (Client) 0 4
Users (Client) 0 4

Portal surface (undocumented)

PentesterPortal exposes wrappers around frontend-only endpoints the Cyver web UI exercises but the V2.2 API does not document. Each method is admitted under the protocol described in docs/portal.md. The currently admitted set covers project-file management (get_project_images_files, get_project_files, update_project_file, delete_project_file — plus the corresponding get_all_* iterators), finding-library CRUD (get_finding_libraries, create_finding_library, delete_finding_library), finding-template listing (get_finding_templates), full-surface finding search (search_findings / search_all_findings), and a unique-code generator (create_unique_finding_code) that computes the next free F-YYYY-N (or TP-YYYY-N) identifier with an on-disk cache fast-path.

ClientPortal is currently a skeleton (no portal-specific methods admitted yet); it inherits the full ClientSession surface unchanged.


Project Structure

cyver-reporting/                 # source dir — imported as `CyverReporting`
├── __init__.py                  # Public exports: session classes, portal classes, exceptions, data builders, alias constants
├── Session.py                   # Base session: authentication, portal probe, token cache (with cache_dir + CYVER_REPORTING_CACHE_DIR), HTTP transport
├── PentesterSession.py          # Pentester-role V2.2 API methods
├── ClientSession.py             # Client-role V2.2 API methods
├── PentesterPortal.py           # PentesterSession + admitted frontend-only methods (project files, finding libraries, finding templates, finding search, unique-code helper)
├── ClientPortal.py              # ClientSession + frontend-only methods (skeleton; no methods admitted yet)
├── DataTypes/                   # Data-builder classes for read+write API request/response bodies
│   ├── __init__.py
│   ├── CyverClient.py           # write-side builder (paired with CyverClientInformation, CyverClientAddress)
│   ├── CyverClientInformation.py
│   ├── CyverClientAddress.py
│   ├── CyverAsset.py            # write-side builder for client assets
│   ├── CyverUser.py             # write-side builder for users
│   ├── CyverProject.py          # write-side builder for projects (paired with CyverProjectDates, CyverPlanningDate)
│   ├── CyverProjectDates.py
│   ├── CyverPlanningDate.py
│   ├── CyverFinding.py          # write-side builder for findings (also hydrated in template-mode by get_finding_templates)
│   ├── CyverFindingEvidence.py
│   ├── CyverFindingCustomField.py
│   ├── CyverProjectFile.py      # portal-surface project-file builder (consumed by PentesterPortal)
│   ├── CyverFindingLibrary.py   # portal-surface finding-library builder (consumed by PentesterPortal)
│   ├── CyverProjectTemplate.py     # read-only reference templates
│   ├── CyverChecklistTemplate.py
│   ├── CyverComplianceTemplate.py
│   ├── CyverReportTemplate.py
│   └── CyverReportTemplateSection.py
├── Utils/                       # Private helpers, compiled regexes, format validators, static constant classes
│   ├── __init__.py
│   ├── RegExPatterns.py         # …, _re_cyver_code (matches F-YYYY-N and TP-YYYY-N finding codes)
│   ├── StaticAliases.py         # _asset_type, _client_status, _project_status, _finding_severity, _finding_library, _project_file_type, …
│   ├── FormatValidators.py      # _validate_uuid, _validate_finding_severity, _validate_project_file_type, _validate_finding_library, …
│   ├── FieldExtractors.py       # _fd_optional_str, _fd_optional_uuid, _fd_optional_uuid_alias, _fd_nested_list, _parse_cyver_code, … (used by every from_dict)
│   ├── RequestHelpers.py        # _add_indexed_list (ABP-style indexed-array query expander, used by search_findings)
│   ├── FieldCaching.py          # FindingCodeCache (on-disk cache used by create_unique_finding_code)
│   ├── MimeTypes.py             # _guess_mime_type — used by multipart upload helpers
│   └── WarningHandlers.py       # CyverPortalInstabilityWarning + portal_unstable decorator
├── Exceptions/                  # Full exception hierarchy
│   ├── __init__.py
│   ├── Base.py                  # CyverError, CyverReferenceError, CyverCachingError
│   ├── Authentication.py        # CyverAuthError, CyverAuthMethodError, Cyver2FARequired
│   ├── Connection.py            # CyverHTTPError, CyverNotFoundError, CyverPermissionError, CyverRateLimitError
│   └── Validation.py            # CyverDataValidationError
└── 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_portal.py               # Portal skeletons + portal_unstable decorator + admission allow-list
├── test_portal_*.py             # Per-method portal tests (project files, finding libraries, finding templates)
├── test_validators_client.py    # CyverClient / CyverClientInformation / CyverClientAddress tests
├── test_validators_user.py      # CyverUser tests
├── test_finding.py              # CyverFinding tests (incl. template-mode hydration)
├── test_finding_evidence.py     # CyverFindingEvidence tests
├── test_finding_custom_field.py # CyverFindingCustomField tests
├── test_finding_library.py      # CyverFindingLibrary tests
├── test_project_file.py         # CyverProjectFile tests
├── test_cache_disable.py        # cache_dir + CYVER_REPORTING_CACHE_DIR + CyverCachingError
├── test_exceptions.py           # Exception hierarchy tests
├── test_utils.py                # Format validator tests
└── tools/                       # Maintenance scripts (not part of the SDK package)
    ├── audit_trigger_events.py     # Postman-vs-source audit of `?triggerEvents=` wiring
    └── regenerate_api_coverage.py  # Regenerates the README API Coverage table from Postman

Error Handling

from CyverReporting import PentesterSession, _project_status
from CyverReporting.Exceptions import (
    Cyver2FARequired,
    CyverAuthError,
    CyverAuthMethodError,
    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 CyverAuthMethodError:
    # Raised at construction by PentesterPortal / ClientPortal when an
    # `api_key=` was supplied — the portal API rejects static keys.
    print("Portal classes require password authentication.")
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}")

CyverCachingError (cache I/O / decrypt failures) is caught internally at every entry point that touches the cache and degrades the session to a fresh portal probe + re-authentication, with a WARNING log. Callers don't need to handle it directly unless they invoke the private cache I/O methods themselves. See docs/exceptions.md for the full exception hierarchy and reason-code reference.


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

cyver_reporting-0.0.8.tar.gz (245.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cyver_reporting-0.0.8-py3-none-any.whl (184.1 kB view details)

Uploaded Python 3

File details

Details for the file cyver_reporting-0.0.8.tar.gz.

File metadata

  • Download URL: cyver_reporting-0.0.8.tar.gz
  • Upload date:
  • Size: 245.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for cyver_reporting-0.0.8.tar.gz
Algorithm Hash digest
SHA256 086142ee0a6181073ab92136f4452815d576f6b1fcad9f7e43d6e3eab1f5c527
MD5 5723af72a1c90987d85286df7b1e8f45
BLAKE2b-256 7924379fbf94e3427ad3faee6888e6f6e2fcbf46623ea8e410632e99c019d67f

See more details on using hashes here.

File details

Details for the file cyver_reporting-0.0.8-py3-none-any.whl.

File metadata

File hashes

Hashes for cyver_reporting-0.0.8-py3-none-any.whl
Algorithm Hash digest
SHA256 ac74fd0ba247028bb6264909bba99475c429cc82a2fb9777712d549eb4fb7f0a
MD5 ba468fce076be7977f7f167bd5d51a29
BLAKE2b-256 b28ea338cf4e4dc1833376c23ccf06b52a597f1493a22e839945786e1b13f49c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page