Skip to content

Fix SigV4 signature for URIs with literal query parameters#674

Merged
Alan4506 merged 3 commits intosmithy-lang:developfrom
Alan4506:fix-sigv4-literal-query-params
Mar 31, 2026
Merged

Fix SigV4 signature for URIs with literal query parameters#674
Alan4506 merged 3 commits intosmithy-lang:developfrom
Alan4506:fix-sigv4-literal-query-params

Conversation

@Alan4506
Copy link
Copy Markdown
Contributor

Problem

When generating a client for Amazon Q Business using smithy-python, the ChatSync operation fails with InvalidSignatureException. The same request succeeds with boto3 using identical credentials and endpoint.

The root cause is that ChatSync has a literal query parameter in its Smithy HTTP trait:

"smithy.api#http": {
    "uri": "/applications/{applicationId}/conversations?sync",
    "method": "POST"
}

Link to the model file: https://github.com/aws/api-models-aws/blob/9eafbc359b1c6a7187ac921009b9eef85de71d91/models/qbusiness/service/2023-11-27/qbusiness-2023-11-27.json#L2244-L2250

The ?sync query parameter (no = sign, no value) is dropped during SigV4 canonical query string computation. parse_qsl("sync") returns [] because it ignores keys without values by default. The server includes sync= in its canonical query string, so the signatures don't match.

Fix

Add keep_blank_values=True to both sync and async _format_canonical_query methods. This makes parse_qsl("sync", keep_blank_values=True) return [('sync', '')], which correctly produces sync= in the canonical query string.

Testing

  1. Generate a Q Business client using smithy-python
  2. Create a Q Business anonymous application and pass the application id to an environment variable:
export QBUSINESS_APPLICATION_ID=<application-id>
  1. Run a test like the following:
async def test_chat_sync():
    client = QBusinessClient(config=Config(
        endpoint_uri="https://qbusiness.us-east-1.api.aws",
        region="us-east-1",
        aws_credentials_identity_resolver=EnvironmentCredentialsResolver(),
    ))
    response = await client.chat_sync(ChatSyncInput(
        application_id=APPLICATION_ID,
        user_message="Hello",
        client_token=str(uuid.uuid4()),
    ))
  1. Without this fix: InvalidSignatureException
  2. With this fix: 200 OK with valid response

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@Alan4506 Alan4506 requested a review from a team as a code owner March 30, 2026 21:25
Copy link
Copy Markdown
Contributor

@jonathan343 jonathan343 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks @Alan4506!

Can you add some unit tests in tests/unit/test_signers.py for this case to prevent any regressions? It would look something like below:

Note: Both TestSigV4Signer and TestAsyncSigV4Signer already exist.

class TestSigV4Signer:
    SIGV4_SYNC_SIGNER = SigV4Signer()

    def test_format_canonical_query_keeps_blank_values(self) -> None:
        canonical_query = self.SIGV4_SYNC_SIGNER._format_canonical_query(
            query="foo=bar&baz="
    )

    assert canonical_query == "foo=bar&baz="

class TestAsyncSigV4Signer:
    SIGV4_ASYNC_SIGNER = AsyncSigV4Signer()

    async def test_format_canonical_query_keeps_blank_values(self) -> None:
        canonical_query = await self.SIGV4_ASYNC_SIGNER._format_canonical_query(
            query="foo=bar&baz="
    )

    assert canonical_query == "foo=bar&baz="

Copy link
Copy Markdown
Contributor

@jonathan343 jonathan343 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks @Alan4506!

@Alan4506 Alan4506 merged commit 68d6412 into smithy-lang:develop Mar 31, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants