Skip to content

feat(agentstack-server): add ECR registry compatibility#2376

Draft
jezekra1 wants to merge 1 commit intomainfrom
ecr-registry-compatibility
Draft

feat(agentstack-server): add ECR registry compatibility#2376
jezekra1 wants to merge 1 commit intomainfrom
ecr-registry-compatibility

Conversation

@jezekra1
Copy link
Collaborator

@jezekra1 jezekra1 commented Mar 11, 2026

TODO: autogenerated

Ref: #2375

Summary

  • ECR (Amazon Elastic Container Registry) and other registries using Basic auth were returning 404/401 errors because the code assumed all registries use the OAuth2 Bearer token exchange flow
  • Added detection of Basic vs Bearer auth scheme from the www-authenticate response header
  • For Basic auth registries (e.g. ECR), credentials are now sent directly on API requests instead of attempting a token exchange
  • Added fallback for missing Docker-Content-Digest header (ECR doesn't always return it) by computing the digest from the response body

Test plan

  • Unit tests pass (mise run agentstack-server:test:unit — 100 passed)
  • Lint/format checks pass
  • Manual test with an ECR registry to verify image resolution works end-to-end
  • No Docs Needed

ECR (and other registries using Basic auth) were incompatible because the
code assumed all registries use the OAuth2 Bearer token exchange flow.

Changes:
- Detect Basic vs Bearer auth scheme from www-authenticate header
- For Basic auth registries (e.g. ECR), send credentials directly instead
  of attempting token exchange
- Handle missing Docker-Content-Digest header by computing digest from
  response body as fallback
- Refactor auth types to use RegistryAuthInfo named tuple tracking both
  scheme and token URL

Signed-off-by: Radek Ježek <radek.jezek@ibm.com>
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 11, 2026
@jezekra1 jezekra1 marked this pull request as draft March 11, 2026 13:09
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the agentstack-server's compatibility with various container registries, particularly Amazon Elastic Container Registry (ECR). It introduces a more robust authentication mechanism that dynamically adapts to different registry authentication schemes, moving beyond a sole reliance on OAuth2 Bearer tokens. This change ensures that images can be successfully resolved and pulled from registries utilizing Basic authentication, thereby broadening the system's interoperability.

Highlights

  • ECR Compatibility: Resolved 404/401 errors with ECR and other Basic auth registries by addressing the previous assumption of OAuth2 Bearer token exchange.
  • Dynamic Authentication Scheme Detection: Implemented detection of Basic vs Bearer authentication schemes from the www-authenticate response header.
  • Direct Basic Auth Credential Sending: Enabled direct sending of credentials for Basic auth registries, bypassing the token exchange flow.
  • Docker-Content-Digest Fallback: Added a fallback mechanism to compute the Docker-Content-Digest from the response body when the header is missing, specifically for registries like ECR.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • apps/agentstack-server/src/agentstack_server/utils/docker.py
    • Imported the hashlib module for digest computation.
    • Defined RegistryAuthScheme (StrEnum) and RegistryAuthInfo (NamedTuple) to encapsulate authentication scheme and token URL.
    • Updated AUTH_URL_PER_REGISTRY to store RegistryAuthInfo objects instead of plain URLs.
    • Renamed get_registry_auth_endpoint to get_registry_auth_info and refactored it to parse the www-authenticate header, determining if the registry uses Basic or Bearer authentication and storing the relevant info.
    • Added a new private method _basic_auth_header to DockerImageID for constructing Basic authentication headers.
    • Modified get_manifest to use the new get_registry_auth_header function and implemented a fallback to compute the Docker-Content-Digest from the response body if the header is absent.
    • Updated get_labels to use the new get_registry_auth_header for authorization.
    • Renamed get_registry_token to get_registry_auth_header and rewrote its logic to return the appropriate Authorization header string based on the detected authentication scheme (Basic or Bearer token exchange).
Activity
  • Unit tests passed with 100 tests.
  • Lint and format checks passed.
  • Manual testing with an ECR registry is pending to verify end-to-end image resolution.
  • No documentation updates were deemed necessary.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds compatibility for ECR and other registries using Basic authentication by detecting the auth scheme from the www-authenticate header. While the changes are well-structured, the implementation introduces several Server-Side Request Forgery (SSRF) vulnerabilities. The application trusts the realm parameter from the www-authenticate header without validation and follows redirects on user-controlled registry URLs, which could allow an attacker to make the server connect to internal services or leak registry credentials. It is recommended to implement strict validation for the realm URL and disable or restrict redirect following for registry requests. Additionally, consider improving the robustness of parsing Bearer authentication parameters to avoid potential KeyError exceptions.

raise Exception(f"Failed to authenticate: {auth_resp.status_code}, {auth_resp.text}")
return auth_resp.json()["token"]
auth_resp = await client.get(
token_url,
Copy link
Contributor

Choose a reason for hiding this comment

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

security-high high

The token_url used here is constructed from the realm parameter of the www-authenticate header received from a registry (see line 194). Since the realm URL is not validated, an attacker can provide a malicious registry that points the realm to an internal service or an arbitrary external URL. This leads to a Server-Side Request Forgery (SSRF) vulnerability. Additionally, any configured credentials for the registry are sent to this unvalidated URL in the Authorization header.

Remediation: Validate that the realm URL's host matches the registry's host or belongs to a trusted list of authentication providers before making the request.

async def get_registry_auth_info(self) -> RegistryAuthInfo | None:
if self.registry not in AUTH_URL_PER_REGISTRY:
async with httpx.AsyncClient() as client:
registry_resp = await client.get(self.get_manifest_url, follow_redirects=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

Making a request to a user-supplied registry URL with follow_redirects=True can lead to Server-Side Request Forgery (SSRF). An attacker can provide a registry URL they control that redirects the request to internal services, potentially bypassing network restrictions.

Remediation: Consider setting follow_redirects=False or implementing a custom redirect handler that validates the redirect target URL to ensure it does not point to internal or sensitive addresses.

headers["Authorization"] = auth_header

async with httpx.AsyncClient() as client:
manifest_resp = await client.get(self.get_manifest_url, headers=headers, follow_redirects=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

Making a request to a user-supplied registry URL with follow_redirects=True can lead to Server-Side Request Forgery (SSRF). An attacker can provide a registry URL they control that redirects the request to internal services, potentially bypassing network restrictions.

Remediation: Consider setting follow_redirects=False or implementing a custom redirect handler that validates the redirect target URL to ensure it does not point to internal or sensitive addresses.

Comment on lines +194 to +195
auth_url = f"{params['realm']}?service={params['service']}&scope=repository:{{repository}}:{{permissions}}"
AUTH_URL_PER_REGISTRY[self.registry] = RegistryAuthInfo(RegistryAuthScheme.BEARER, auth_url)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Directly accessing params['realm'] and params['service'] can cause a KeyError if these are not present in the www-authenticate header. It's safer to use .get() and validate that the parameters exist before constructing the URL.

Suggested change
auth_url = f"{params['realm']}?service={params['service']}&scope=repository:{{repository}}:{{permissions}}"
AUTH_URL_PER_REGISTRY[self.registry] = RegistryAuthInfo(RegistryAuthScheme.BEARER, auth_url)
realm = params.get("realm")
service = params.get("service")
if not realm or not service:
raise ValueError(f"Incomplete Bearer auth params in www-authenticate header: {header}")
auth_url = f"{realm}?service={service}&scope=repository:{{repository}}:{{permissions}}"
AUTH_URL_PER_REGISTRY[self.registry] = RegistryAuthInfo(RegistryAuthScheme.BEARER, auth_url)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant