Skip to content
Merged
28 changes: 23 additions & 5 deletions .agents/skills/api-patterns/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <apiKey>` (not Bearer).
Expand Down Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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]
Expand Down
5 changes: 5 additions & 0 deletions samples/AnonymousExample.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down