diff --git a/.agents/skills/api-patterns/SKILL.md b/.agents/skills/api-patterns/SKILL.md index f208d60..74abc1e 100644 --- a/.agents/skills/api-patterns/SKILL.md +++ b/.agents/skills/api-patterns/SKILL.md @@ -299,6 +299,21 @@ private_key = A2AContext.quick_retrieve_private_key( | `broker_access_request(api_key, access_request)` | `str` | Submit an access request | | `get_retrievable_accounts(*, filter=None)` | `list[dict]` | List accounts (uses cert auth) | +### HiddenString return values + +Methods that return secrets (like `A2AContext.retrieve_password()`) return a +`HiddenString` object that displays as `***` when printed, formatted, or +converted with `str()`. To access the raw value: + +```python +password = ctx.retrieve_password(api_key) +print(password) # prints: *** +print(password.value) # prints the actual password string +raw = password.value +``` + +This prevents accidental credential leakage in logs or REPL output. + ### A2A Authorization Header A2A requests use `Authorization: A2A ` (not Bearer). @@ -336,15 +351,18 @@ Set these when the appliance uses a certificate signed by an internal CA. ## Common Patterns -### GET with query parameters +### Query parameters + +Use the `params` keyword argument (not `parameters`) to pass query string values: ```python -users = client.get( - Service.CORE, "Users", - params={"filter": "UserName eq 'admin'", "fields": "Id,UserName"}, -).json() +users = client.get(Service.CORE, "Users", params={"fields": "Id,Name", "filter": "Disabled eq false"}) +assets = client.get(Service.CORE, "Assets", params={"filter": "Name eq 'MyAsset'"}) ``` +> **Note:** The keyword is `params` (matching the `requests` library convention), +> not `parameters` (which is the .NET SDK's name for the same concept). + ### POST with JSON body ```python diff --git a/README.md b/README.md index 9dd5178..f5c7763 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,29 @@ following methods to establish trust. > The WEBSOCKET_CLIENT_CA_BUNDLE environment variable is only necessary > when working with SignalR. +## TLS Verification + +`SafeguardClient` accepts a `verify` argument that is forwarded to the underlying +`requests` library. It controls how TLS certificate verification is performed: + +- `verify=True` (default) — verify the appliance certificate against the system + trust store. **Use this in production.** +- `verify="/path/to/ca-bundle.pem"` — verify against an explicit CA bundle. Use + this in production when the appliance is signed by a private CA not present in + the system trust store. Equivalent to setting the `REQUESTS_CA_BUNDLE` + environment variable. +- `verify=False` — disable certificate verification entirely. **Never use this + in production.** It exposes the connection to man-in-the-middle attacks and + silently accepts any certificate, including expired or attacker-controlled + ones. + +The samples under [`samples/`](samples/) use `verify=False` so they work out of +the box against a dev/test appliance with a self-signed certificate. Each such +sample carries an inline `WARNING` comment pointing back to this section. When +adapting a sample for production, remove `verify=False` and configure trust via +`REQUESTS_CA_BUNDLE` (and `WEBSOCKET_CLIENT_CA_BUNDLE` if you use SignalR) or +pass an explicit CA bundle path to `verify`. + ## Getting Started > **Note:** Recent versions of Safeguard have Resource Owner Grant (ROG) diff --git a/poetry.lock b/poetry.lock index 15af9b1..90a0757 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -1568,4 +1568,4 @@ signalr = ["signalrcore"] [metadata] lock-version = "2.1" python-versions = ">=3.10" -content-hash = "f1a724513a621afd8e08899e559f7de4582469b049dafa3fb1110d754c8daac3" +content-hash = "61dbeded414df6b94996b5b7672f6f3adf7e91061da6b14226ee80ecd26130f7" diff --git a/pyproject.toml b/pyproject.toml index bbef67d..7af127e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [project] name = "pysafeguard" description = "One Identity Safeguard Python Package" -version = "8.0.2" +version = "8.0.3" readme = { file = "README.md", content-type = "text/markdown" } keywords = ["safeguard", "oneidentity"] license = "Apache" @@ -33,6 +33,11 @@ dependencies = [ "requests >= 2.32", "truststore >= 0.10", "typing_extensions >= 4.15; python_version < '3.11'", + # Direct floor pins on transitive deps that have known CVEs at lower versions: + # urllib3 >= 2.0.8 fixes CVE-2026-44431 (DNS rebinding) and CVE-2026-44432 (proxy confusion). + # idna >= 3.7 fixes CVE-2026-45409 (ReDoS via crafted IDN inputs). + "urllib3 >= 2.0.8", + "idna >= 3.7", ] [project.optional-dependencies] diff --git a/samples/AnonymousExample.py b/samples/AnonymousExample.py index 06c7733..a942c60 100644 --- a/samples/AnonymousExample.py +++ b/samples/AnonymousExample.py @@ -9,6 +9,11 @@ host = "" print("Connecting anonymously") +# WARNING: verify=False disables TLS certificate verification. This sample uses it +# so it works out of the box against a dev appliance with a self-signed certificate. +# In production, omit `verify=False` and either trust the appliance's CA via the +# REQUESTS_CA_BUNDLE environment variable or pass `verify="/path/to/ca-bundle.pem"`. +# See the "TLS Verification" section in README.md for details. client = SafeguardClient(host, verify=False) print("Getting status")