The private feed controls who may download which BitzOrcas.* packages and versions. It does not replace Runtime Licensing, and one customer’s download credential must never be reused for another.
Separate the four identities
| Identity | Permission | Credential location |
|---|---|---|
| Feed administrator | Source, immutability, backup, and audit policy | Feed management plane |
| Publish workflow | Write candidate packages only | GitHub commercial-release environment |
| GA workflow | Read-only restore from the formal feed | GitHub commercial-release environment |
| Customer/developer | Read selected product, channel, and package prefixes | Credential Provider or personal secret store |
The publish API key and Consumer token are distinct credentials. A browser, Consumer repository, template, or NuGet.Config must never receive publish access.
1. Create the feed
The feed product is replaceable, but it must provide:
- an HTTPS endpoint without userinfo, query, or fragment;
- immutable published
PackageId + Versionidentities; - preferably staging/promote, otherwise a recovery protocol that resumes an existing identity only when bytes match exactly;
- least-privilege tokens scoped by customer, product,
BitzOrcas.*prefix, and version channel; - separate, revocable, auditable read and write credentials;
- backup and restore exercises for packages, metadata, and policy.
2. Configure the release repository
Configure these GitHub Repository/Environment values:
| Type | Name | Purpose |
|---|---|---|
| Variable | BITZORCAS_COMMERCIAL_FEED_URL | Credential-free HTTPS feed URL |
| Secret | BITZORCAS_COMMERCIAL_PUSH_API_KEY | Write credential used only by publishing |
| Secret | BITZORCAS_COMMERCIAL_FEED_CREDENTIALS | NuGet-standard read credential used by GA |
| Secret | BITZORCAS_NUGET_SIGNING_CERTIFICATE_BASE64 | Base64 PFX used only by the signing environment |
| Secret | BITZORCAS_NUGET_SIGNING_CERTIFICATE_PASSWORD | Separate password for that PFX |
| Variable | BITZORCAS_NUGET_TIMESTAMP_URL | Trusted RFC 3161 timestamp URL (HTTP or HTTPS) |
| Variable | BITZORCAS_TRUSTED_SIGNER_FINGERPRINTS | Approved package-signing certificates |
| Variable | BITZORCAS_TRUSTED_RELEASE_WORKFLOW | Trusted candidate workflow |
| Variable | BITZORCAS_TRUSTED_ATTESTATION_WORKFLOW | Trusted attestation workflow |
| Variable | BITZORCAS_TRUSTED_ATTESTATION_WORKFLOW_DIGEST | Pinned workflow commit |
| Variable | BITZORCAS_VULNERABILITY_THRESHOLD | Vulnerability gate |
| Variable | BITZORCAS_THIRD_PARTY_LICENSE_POLICY | Third-party license policy |
| Variable | BITZORCAS_APPROVED_LICENSE_EXPRESSIONS | Explicitly approved license expressions |
BITZORCAS_COMMERCIAL_FEED_CREDENTIALS must have this shape:
Username=<read-only-account>;Password=<short-lived-token>;ValidAuthenticationTypes=BasicKeep the two certificate secrets only in the protected commercial-release-builder environment. Keep feed write and GA read credentials in commercial-release. Never put these values in workflow YAML; protect both environments with approval, a small administrator set, and secret-access auditing.
3. Initialize a Consumer
From the product repository root:
export BITZORCAS_COMMERCIAL_FEED_URL="https://packages.example.com/nuget/v3/index.json"
scripts/feed/init-consumer.sh /absolute/path/to/ConsumerSolutionThe script creates a credential-free NuGet.Config only when none exists. Existing files are validated and never overwritten. The result contains:
<clear />to remove inherited machine sources;BitzOrcasCommercial, resolved from%BITZORCAS_COMMERCIAL_FEED_URL%;- a mapping that sends only
BitzOrcas.*to the commercial feed; - a public-package mapping to
nuget.org; - no
packageSourceCredentials.
Validate structure first:
scripts/feed/doctor.sh config /absolute/path/to/ConsumerSolution/NuGet.ConfigThe diagnostic prints status only, never URL, account, or token values.
4. Inject read-only credentials and restore
Prefer the organization’s NuGet Credential Provider. Without one, the wrapper can read a short-lived token securely from the terminal:
# The wrapper exposes the short-lived credential only to this restore process.export BITZORCAS_COMMERCIAL_FEED_URL="https://packages.example.com/nuget/v3/index.json"
scripts/feed/with-credentials.sh -- \ dotnet restore /absolute/path/to/ConsumerSolution/Consumer.slnx \ --configfile /absolute/path/to/ConsumerSolution/NuGet.ConfigCI injects this standard variable from its secret store into the individual restore/install process:
NuGetPackageSourceCredentials_BitzOrcasCommercialDo not use dotnet nuget add source --store-password-in-clear-text. Never put a token in command arguments, shell profiles, image layers, or logs.
5. Run an online clean-cache probe
Structure validation does not prove authentication, entitlement, or package signatures. Select one exact package and version the identity may download:
# Use an exact entitled package version to verify auth, scope, and signatures.scripts/feed/doctor.sh consumer \ /absolute/path/to/ConsumerSolution/NuGet.Config \ --online BitzOrcas.Profile.MiniApi 1.2.3The probe isolates NUGET_PACKAGES, HTTP cache, plugin cache, and DOTNET_CLI_HOME, restores from an empty cache, then runs:
dotnet nuget verify <package> --allBoth restore and full signature verification must pass. A hit from an old developer cache is not evidence.
6. Publish a version
Use this fixed sequence:
- Dispatch Build Commercial Release Candidate on protected
mainwith one exactrelease_train. - Record its successful run id, artifact name, and full 40-character commit.
- Dispatch Publish Commercial Candidate with those exact three values.
- Dispatch Commercial GA with the same values.
- Add the version to customer channels and upgrade notes only after GA passes.
Never repack in the publish job or alter an artifact before upload. Changed bytes require a new version and a new candidate chain.
7. Distribute customer access
Issue a separate, read-only, short-lived, revocable identity for each customer or automation principal. Record:
- customer, product, and owner;
- package prefixes and version channels;
- issue, expiry, and last-use time;
- delivery channel and secret-store location;
- rotation owner and revocation ticket.
Distribute through an enterprise secret manager, Credential Provider, or one-time secure channel. Do not send tokens in email, chat, Wiki, or ticket bodies. Customer repositories retain only credential-free NuGet.Config.
8. Rotate and revoke
Normal rotation:
- Issue a new read token.
- Pass the online probe from an empty cache.
- Update CI/developer secret stores.
- Observe that the old token is no longer used.
- Revoke it and retain evidence.
For leakage:
- Revoke the token immediately.
- Audit downloads, source IPs, package ids, and versions.
- Check whether the secret was reused by publishing or another customer.
- Issue a narrower replacement.
- Freeze the channel and invoke the malicious-package procedure if integrity is uncertain.
A feed outage does not invalidate already deployed Runtime Licenses. Existing deployments continue under their signed local license and offline lease; new restores, upgrades, and environments are affected.
Diagnostic table
| Symptom | Check first |
|---|---|
NU1101 | Source Mapping, entitlement, and exact version publication |
401/403 | Expiry, read scope, and source name BitzOrcasCommercial |
| Local succeeds, CI fails | Four isolated caches and the standard credential variable |
NU300x | Signature, certificate chain, timestamp, and trusted fingerprint |
| Same version, different hash | Stop consumption; investigate overwrite and partial-publish recovery |
| Slow restore | Feed network, Credential Provider, first empty cache, dependency count |
Acceptance checklist
- Consumer configuration is credential-free with exact Source Mapping;
- write and read credentials are physically separated;
- a real customer identity passes clean-cache restore and
dotnet nuget verify --all; - published versions are immutable;
- candidate, publish, and GA bind the same artifact identity and commit;
- tokens have owners, expiry, rotation, and revocation records;
- backup, restore, and partial-publication procedures exist.