Skip to content

Commit 65ab758

Browse files
committed
release: v0.4.2
1 parent 696e042 commit 65ab758

5 files changed

Lines changed: 44 additions & 5 deletions

File tree

docs/changelog.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
## [0.4.2] - 2026-03-10
11+
12+
### Fixed
13+
14+
- **Secure Management/Flux signing with query parameters**:
15+
- `SecureKeyAuth` now signs only the URL path (without query string)
16+
- aligns SDK signatures with server-side verification and Management auth docs
17+
- prevents `401 authentication_failed` / `Invalid signature` on requests with query params
18+
819
## [0.4.1] - 2026-03-05
920

1021
### Fixed
@@ -88,6 +99,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8899
- Error handling guide
89100
- Code examples
90101

102+
[Unreleased]: https://github.com/foxnose/python-sdk/compare/v0.4.2...HEAD
103+
[0.4.2]: https://github.com/foxnose/python-sdk/compare/v0.4.1...v0.4.2
91104
[0.4.1]: https://github.com/foxnose/python-sdk/compare/v0.4.0...v0.4.1
92105
[0.4.0]: https://github.com/foxnose/python-sdk/compare/v0.3.0...v0.4.0
93106
[0.3.0]: https://github.com/foxnose/python-sdk/compare/v0.2.0...v0.3.0

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "foxnose-sdk"
7-
version = "0.4.1"
7+
version = "0.4.2"
88
description = "Official Python client for FoxNose Management and Flux APIs"
99
readme = "README.md"
1010
license = {text = "Apache-2.0"}

src/foxnose_sdk/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,4 @@
154154
"APIRef",
155155
]
156156

157-
__version__ = "0.4.1"
157+
__version__ = "0.4.2"

src/foxnose_sdk/auth/secure.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ def build_headers(self, request: RequestData) -> Mapping[str, str]:
5151
timestamp = self._clock().astimezone(dt.timezone.utc).replace(microsecond=0)
5252
timestamp_str = timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
5353
parsed = urlparse(request.url)
54+
# Query parameters are excluded from the signature payload.
55+
# Server-side verification signs request.path without querystring.
5456
path = parsed.path or "/"
55-
if parsed.query:
56-
path = f"{path}?{parsed.query}"
5757
body_hash = hashlib.sha256(body).hexdigest()
5858
data_to_sign = f"{path}|{body_hash}|{timestamp_str}".encode("utf-8")
5959
signature = self._private_key.sign(data_to_sign, ec.ECDSA(hashes.SHA256()))

tests/test_auth_strategies.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,33 @@ def test_secure_auth_produces_verifiable_signature():
5252
signature_b64 = headers["Authorization"].split(":", 1)[1]
5353
signature = base64.b64decode(signature_b64)
5454
body_hash = hashlib.sha256(body).hexdigest()
55-
expected = f"/api/test?foo=bar|{body_hash}|2024-02-20T18:00:00Z".encode("utf-8")
55+
expected = f"/api/test|{body_hash}|2024-02-20T18:00:00Z".encode("utf-8")
56+
public_obj.verify(signature, expected, ec.ECDSA(hashes.SHA256()))
57+
58+
59+
def test_secure_auth_ignores_query_params_in_signature():
60+
public_key, private_key, public_obj = _generate_keys()
61+
fixed_time = dt.datetime(2024, 2, 20, 18, 0, 0, tzinfo=dt.timezone.utc)
62+
auth = SecureKeyAuth(
63+
public_key=public_key, private_key=private_key, clock=lambda: fixed_time
64+
)
65+
body = b""
66+
request = RequestData(
67+
method="GET",
68+
url="https://example.com/v1/env/folders/tree/item/?path=benchmark_jobs",
69+
path="/v1/env/folders/tree/item/?path=benchmark_jobs",
70+
body=body,
71+
)
72+
headers = auth.build_headers(request)
73+
74+
signature_b64 = headers["Authorization"].split(":", 1)[1]
75+
signature = base64.b64decode(signature_b64)
76+
body_hash = hashlib.sha256(body).hexdigest()
77+
expected = (
78+
f"/v1/env/folders/tree/item/|{body_hash}|2024-02-20T18:00:00Z".encode(
79+
"utf-8"
80+
)
81+
)
5682
public_obj.verify(signature, expected, ec.ECDSA(hashes.SHA256()))
5783

5884

0 commit comments

Comments
 (0)