Skip to content
ocx
install

Errors & credentials: PackageRef: carried, never parsed

Part 2 of 2 of Errors & credentials.

ocx owns the package-identifier grammar. PackageRef stores whatever identifier string it was given and hands it back byte for byte — the SDK never re-derives or re-validates the grammar itself:

from ocx_sdk import PackageRef
ref = PackageRef("ocx.sh/astral-sh/uv:0.9.7")
carried_forward = PackageRef("ocx.sh/astral-sh/uv:0.9.7", metadata={"source": "lock"})
assert str(ref) == "ocx.sh/astral-sh/uv:0.9.7"
assert ref == carried_forward # identity is the identifier alone; metadata is decoration

Identity is the identifier string alone — metadata is whatever context came along with the ref from the JSON row that produced it, so two refs to the same package parsed out of two different commands must compare equal even when their metadata differs. Any parameter typed PackageLike accepts either a bare str or a PackageRef and coerces with str().

One vocabulary for both bootstrap and runtime auth: BasicAuth(user, password) and BearerAuth(token) — None elsewhere means anonymous, matching ocx’s own type set. Their secret fields are field(repr=False), with a hand-written __repr__ that masks the value — the dataclass-generated repr would otherwise leak into logs, tracebacks, and pytest diffs:

from ocx_sdk import BasicAuth, BearerAuth
assert repr(BasicAuth("ci", "hunter2")) == "BasicAuth(user='ci', password=***)"
assert repr(BearerAuth("ghp_secret")) == "BearerAuth(token=***)"

Beyond the repr mask, every secret value the SDK composed into a spawn environment is exact-string-redacted from captured stderr, on_log lines, logged argv, and exception text — the choke point is _env.build_spawn_env(), which returns the finished environment paired with a redact callable that _process applies to every outbound surface before it leaves the SDK. A caller who puts a token into invoke’s raw argv gets it scrubbed the same way, even though doing so is documented against.

One caveat the redaction can’t reach: at log_level="trace", ocx itself may print secrets it holds that never passed through the SDK’s own composition — the scrub only catches values the SDK was given.

ocx does not scrub non-forwarded environment variables from a spawned child — so a tool started through Project.exec or package.exec inherits OCX_AUTH_*, whatever set it to (ambient environment or explicit OcxConfig.auth). This is pinned by a contract test against the real binary specifically so an upstream change to that behavior breaks loudly rather than silently. The credential-free pattern: authenticate for the pull(), then run the build step through a config with credentials cleared —

# illustrative: needs a real Project handle.
project.pull() # needs the token
project.with_config(auth={}).exec(["task", "build"]) # the build step doesn't see it

— covered in full, with the rest of the hermetic-CI threat model, in Hermetic CI.

Ocx.login writes credentials through ocx’s own store — the token travels on stdin via --password-stdin, never in argv, and is redacted for the call’s duration like any other secret. OcxConfig.docker_config points ocx at an isolated credential store directory; keep it 0700, since it holds registry credentials on disk.