Skip to content

[FEATURE] Thread dep_ref.port into credential resolution (git credential fill) #785

Description

@edenfunf

Problem

DependencyReference.port is parsed and threaded through URL construction (#665), but is never propagated into the credential resolution path. git credential fill supports a port= field, but APM always sends bare hostname only:

# token_manager.py:111
input=f"protocol=https\nhost={host}\n\n"   # no port= line

Affected code path:

Layer File Gap
Auth resolver core/auth.py:210 resolve_for_dep() reads dep_ref.host, ignores dep_ref.port
Cache key core/auth.py:183 (host.lower(), org.lower()) — no port dimension
Host model core/auth.py:56 HostInfo has no port field
Credential fill core/token_manager.py:111 git credential fill input omits port=

Impact

When a user has the same hostname serving git on different ports with different credentials (e.g. bitbucket.corp.com:7999 for SSH and bitbucket.corp.com:7990 for HTTPS, each requiring distinct PATs), APM's pre-resolved token path (Method 1: authenticated HTTPS) may resolve the wrong credential.

Mitigating factor: for non-GitHub hosts, APM's token resolution typically returns None, and git's own clone process queries credential helpers with the full URL including port — so the direct clone path handles this correctly. The gap only affects the pre-resolved token path in _resolve_token.

Suggested fix

1. HostInfo — add port

@dataclass(frozen=True)
class HostInfo:
    host: str
    port: Optional[int] = None  # Non-standard git port
    kind: str
    has_public_repos: bool
    api_base: str

2. AuthResolver — port-aware cache key

key = (host.lower(), port, org.lower() if org else "")

3. resolve_for_dep — thread port

def resolve_for_dep(self, dep_ref):
    host = dep_ref.host or default_host()
    port = dep_ref.port
    org = dep_ref.repo_url.split("/")[0] if dep_ref.repo_url else None
    return self.resolve(host, org, port=port)

4. resolve_credential_from_git — pass port to git credential protocol

input_str = f"protocol=https\nhost={host}\n"
if port:
    input_str += f"port={port}\n"
input_str += "\n"

All new parameters default to None — fully backward compatible.

Limitation worth documenting

Not all credential helpers honor the port field. OS keychains and gh auth typically store credentials per-hostname. APM can pass the information correctly, but per-port discrimination ultimately depends on the helper implementation. A one-line note in docs/getting-started/authentication.md under "Git credential helper not found" would set expectations.

Context

Follow-up from PR #665 review (auth-expert panel finding). The port field was introduced on DependencyReference and LockedDependency but was only wired into URL construction, not credential resolution.

Refs: #661, #665

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions