Authentication#

This page describes the credential model of the sysand client for HTTP(S) indexes and project downloads: where credentials come from, how they are stored and scoped, and how sysand decides which credential to use for a request.

For the conceptual overview, see How authentication works. For step-by-step setup, see Authenticate to an index. For the sysand auth command family, see sysand auth. To create a Sysand Index API token, see Create an API token.

Credential sources and precedence#

sysand takes credentials from two sources:

  • Stored credentials, written by sysand auth login into the operating system’s keyring. Bearer tokens only.

  • SYSAND_CRED_* environment variables, described below. Bearer tokens or HTTP basic (username and password).

When both sources have a credential whose URL pattern matches a request, the environment credential is tried first. This lets a CI job override an interactive credential without removing it. sysand auth status shows both sources and marks stored entries that are shadowed by an environment variable.

Being tried first is not the same as being the only one tried. On an ordinary read, if the environment credential is itself rejected, sysand falls back to a matching stored credential rather than giving up, so setting SYSAND_CRED_* does not guarantee that a stored token is never sent to that host. sysand publish and sysand auth whoami are the exceptions: each selects exactly one credential and stops, so there the environment credential really does displace the stored one.

Precedence decides which credential wins, not when it is sent: for ordinary reads sysand still tries each URL without credentials first, as described under Request behavior.

Stored credentials#

sysand auth login stores one bearer token per index. All stored credentials live in a single entry in the operating system’s keyring: the macOS Keychain, the Windows Credential Manager, or the Secret Service (for example GNOME Keyring or KWallet) on Linux. sysand never writes credentials to a plaintext file.

Along with the token, a stored credential records the index URL it belongs to, the derived URL patterns it covers, the validation result, and, when a validating login ran against an index with an API, the identity behind the token (subject, token display prefix, and expiry). sysand auth status shows all of this; no command ever prints the token itself.

Logging in again to the same index replaces the previous stored token (the old token is discarded locally, not revoked server-side). Removing the last credential deletes the keyring entry entirely.

The index URL doubles as the key of the stored credential, so sysand normalizes it first: the host is lowercased, a default port is dropped, and a missing trailing path slash is added. Different spellings of one index therefore reach the same entry rather than creating duplicates, and sysand auth status prints each key in the form sysand auth logout expects. A URL template is normalized the same way and kept verbatim as the key, placeholder and all, which is separate from the narrower prefix it covers.

The URL must be an absolute http(s) URL, with no # fragment and no username or password in it. A query string is allowed and stays part of the key, which is what makes a forge URL such as ?ref=main usable as an index.

The keyring is only read when a credential might actually be needed: local commands and reads that succeed without authentication never touch it. If the keyring exists but is locked or denies access, sysand reports the error and suggests unlocking it or falling back to SYSAND_CRED_* variables.

On Windows the keyring caps the stored blob at roughly 2.5 KB, measured in UTF-16 units, so about 1280 characters of stored JSON. In practice that is a handful of credentials, three or four with typical tokens, and a single very large token can exceed it on its own. When the limit is hit, sysand auth login fails and points you at sysand auth status and sysand auth logout to remove one, naming any already-expired credentials as the obvious candidates.

Hosts without a keyring#

On a host with no usable keyring backend (many containers, headless servers, and CI runners), sysand auth login refuses to persist the token and instead prints the exact SYSAND_CRED_* variables to set, with the token replaced by a <token> placeholder so the secret never reaches logs. Environment variables are the supported path on such hosts; note that the secret then lives in same-user process environments and typically at rest in CI secret configuration or shell startup files, which is the accepted floor there.

Login validation#

sysand auth login always validates the token against the index before storing it, by probing each surface the index has:

  • the read surface: the index file listing (index.json), and

  • the index API (v1/whoami), only when the index’s discovery configuration advertises an API root.

A surface only counts as tested when the probe actually exercised the credential: a public read surface answers without credentials and proves nothing about the token. Unreachable, redirected, or rate-limited probes are not verdicts either. The token is stored unless every surface that exercised it rejected it; the stored validation claim is scoped to what actually accepted it, for example validated (read), validated (read, api), or stored, not validated when nothing exercised the credential (sysand auth status shows that last record more briefly, as not validated).

Consequences worth knowing:

  • On an index with public reads and an API (such as sysand.com), the API is the only real test, so a mistyped or expired token is refused at login time rather than failing later at publish time.

  • On a fully public index there is nothing to test, and the login stores the token as stored, not validated.

  • On an index that uses username/password (HTTP basic) authentication, the bearer login is refused and the message routes you to the SYSAND_CRED_<X>_BASIC_USER / SYSAND_CRED_<X>_BASIC_PASS variables, the only basic-auth path.

There is no flag to skip validation. An offline or unreachable index cannot exercise the token, so the login degrades gracefully to stored, not validated with warnings, and the token is not transmitted. Responses that could falsely refuse a good token (rate limiting, redirects) are never verdicts. For the residual case of an index that genuinely misbehaves, the SYSAND_CRED_* environment variables remain the way to configure a credential without logging in.

The index’s discovery configuration is fetched before the probes, to learn where the index root and API root live and to scope the credential. The fetch follows these steps:

  • It starts without credentials.

  • If the index answers with any 4xx status except 429, including 404, it is retried once with the just-entered token, because a fully private index may hide its discovery document behind authentication (some hosts answer 404, not 401, when credentials are missing). An authenticated 404 is taken as a definitive “no discovery document”.

  • If discovery is unreachable or gives no clear answer, the credential is scoped to the pattern derived from the URL alone, with a warning.

So each login may send one authenticated request to the discovery URL, and a fully private index still gets its discovery configuration honored and can come out as validated (read, api).

Credential scope#

A stored credential covers URL patterns derived automatically from the index URL: the URL you logged in to (so the discovery fetch itself is authenticated), plus the index root and API root advertised by the index’s discovery configuration when they live elsewhere. Each pattern is the literal URL prefix followed by **, so the credential is only ever sent within those prefixes. sysand auth login prints the covered patterns (the Covers line), and sysand auth status lists them per entry.

For a URL template target (an index URL containing {path} or {path_raw}), the pattern is anchored at the template’s literal prefix before the placeholder, cut back to its last /. A template whose placeholder sits directly after the host is scoped to the host root.

The patterns are a login-time snapshot. Reads and publishes re-fetch the index’s discovery configuration on every run, but a stored credential’s patterns are not updated to follow it: if the index operator later moves the index root or API root outside the credential’s patterns, the credential simply stops matching and requests fail cleanly instead of following the change. Run sysand auth login again to re-derive the patterns and re-validate. See the trust model below.

Trust model#

The discovery configuration at the index URL you supply is the trust anchor: sysand sends the stored credential to the index root and API root that document advertises, including roots on a different host. Trusting the index URL means trusting what it points at.

Over plain http, a network attacker at login time can rewrite the discovery response to point at a hostile API root, which both captures the freshly entered token and persists that scope until you log in again. http remains supported for localhost and trusted networks; on untrusted networks, log in over https only.

Two boundary details: a broad SYSAND_CRED_* pattern takes precedence over stored credentials and is under your own control, and a server may redirect a request within the same host, in which case the credential follows the redirect (cross-host redirects strip it).

Expiry#

When a validating login learns the token’s expiry from the index API, it stores the timestamp. sysand auth status then shows expires in N days or expired per entry, and sysand publish fails fast before uploading when the selected stored token is clearly past its expiry. When a request that used a stored credential fails and the record’s expiry has passed, sysand warns that the credential may be expired or revoked and suggests re-authenticating. The server’s rejection remains the real authority; sysand never refuses a request solely on a locally stored timestamp except in the publish fast-fail above.

Credential environment variables#

HTTP credentials can also be configured through environment variables whose names share one arbitrary label <X>. This is the way to provide credentials in CI, on hosts without a keyring, and for indexes that use HTTP basic authentication.

Basic authentication:

SYSAND_CRED_<X>=<PATTERN>
SYSAND_CRED_<X>_BASIC_USER=<USER>
SYSAND_CRED_<X>_BASIC_PASS=<PASSWORD>

Bearer token authentication:

SYSAND_CRED_<X>=<PATTERN>
SYSAND_CRED_<X>_BEARER_TOKEN=<TOKEN>

<PATTERN> is a URL glob pattern. <X> is only a label that connects the URL pattern to its credential values, but it is required and cannot be one of the role words BASIC_USER, BASIC_PASS, or BEARER_TOKEN.

sysand refuses to run rather than guess when the variables do not form a complete group:

  • a variable with no label, such as SYSAND_CRED_BEARER_TOKEN, because nothing says which credential it belongs to;

  • a pattern with no credential, or a credential with no pattern;

  • a basic-auth user without its password, or the reverse.

sysand auth login, logout, and whoami are exempt and keep working, so a malformed variable never locks you out of fixing your credentials. sysand auth status applies the same validation as every other command and reports the problem.

URL glob matching#

Sysand uses URL glob patterns with literal path separators:

Pattern syntax

Meaning

?

Matches any single character except /.

*

Matches any sequence of characters that does not contain /.

**

Matches any sequence of characters, including /.

For example, the pattern https://*.example.com/** matches URLs such as:

https://www.example.com/projects/project.kpar
https://projects.example.com/index.json
https://projects.example.com/projects/myproject/versions.json

The patterns a stored credential covers use the same syntax and matching; they are always a literal URL prefix followed by **.

Request behavior#

For normal HTTP(S) fetches, Sysand first makes an unauthenticated request. If the response status is in the 4xx range (except 429, which is never treated as an authentication verdict), Sysand retries with credentials whose pattern matches the URL: environment credentials first, and only if no environment credential matches (or the matching ones also fail) the stored credentials. This is also the point where the keyring is read, at most once per command.

If multiple credential patterns within a source match one URL, Sysand logs a warning and tries the matching credentials until one returns a response outside the 4xx range. If none succeeds, the first response is returned.

Redirects are handled differently:

  • If credentials were sent to the original URL and that URL redirects to another URL on the same host, the credentials are also sent to the redirected URL.

  • Sysand does not first try the redirected URL without credentials.

Neither SYSAND_CRED_* variables nor stored credentials configure Git credentials. For Git projects, Sysand runs Git with the user’s existing Git setup, such as SSH keys or credential helpers.

Publish credentials#

In the default auto mode, sysand publish first uses trusted publishing when it detects a supported CI environment. If trusted publishing is unavailable, it falls back to bearer token credentials that match the upload URL. Basic-auth entries are ignored for publishing: the upload is bearer-only and the credential is sent proactively (an upload cannot be retried).

The bearer is selected with source precedence: environment bearer tokens are checked first, and only when none of them matches the upload URL are the stored credentials consulted. Within one source, exactly one pattern must match the upload URL; if more than one does, publishing fails and asks you to refine the patterns of that source.

Outside supported trusted-publishing environments, publishing fails before the upload request is made if no bearer token matches the upload URL, with a hint to run sysand auth login <index-url> to store one.

When an upload is rejected, the error names where the selected bearer came from (a SYSAND_CRED_* variable or a stored credential), because the right fix differs: rotate or unset the environment variable, or re-run sysand auth login. A permission failure (HTTP 403) additionally points at sysand auth status, which shows who the stored token authenticates as.