Offline bundles let you use platform profiles and your own profiles in environments that have no direct internet access, such as air-gap CI systems or internal artifact repositories.
Why offline bundles
In some environments your CI runner is not allowed to reach the internet directly:
- Air-gap CI: Isolated build infrastructure without outbound connectivity to the portal.
- Internal artifact repository: Bundles are downloaded once and mirrored internally so every team accesses the same version.
- Regulatory requirements: Audited environments where external network connections from CI are prohibited or restricted.
Exporting a bundle from the portal
You can download a bundle for a published profile version from the portal:
- Open the profile list or the Platform tab of the profile list.
- On your own profiles, click the kebab menu (⋮) on the desired profile row and select Download bundle; this offers the latest published version. On the Platform tab, click the Download bundle button directly on the profile card — same restriction, latest version only.
- For an older published version, open the profile's detail page instead and use Download bundle there, where every version is available.
The downloaded file is a signed .cgbundle: a ZIP archive containing the rule
files (.cgr) for the pinned version, a manifest with metadata and a content
hash, and an Ed25519 signature over that manifest.
Using a bundle file directly, without portal access at analyze time
For CI that has no route to the portal at all, you can point a profile entry
in .codecharter/config.yml at a local bundle instead of a slug:
# .codecharter/config.yml
version: 1
profiles:
- path: "./bundles/dotnet-standard-2.1.0.cgbundle"
This is the mechanism air-gapped and fully offline setups build on: nothing
about a path: entry ever talks to the portal. To make it work, extract the
downloaded .cgbundle (it is a plain ZIP) into
.codecharter/cache/extracted/<name>/, where <name> is the bundle's file
name without the .cgbundle extension — for the example above, that is
.codecharter/cache/extracted/dotnet-standard-2.1.0/. Do this as a step in
your own CI pipeline or image build, ahead of codecharter analyze; there is
no CodeCharter command that performs the download or extraction for you.
codecharter analyze then reads the rules straight from that directory. If it
is missing, codecharter analyze prints a warning and simply skips that
profile entry rather than failing the run, so a typo in the path is easy to
miss — check the warning output after your first setup.
CodeCharter does not re-verify the bundle's signature or content hash for a
path: entry; the SHA-256 pinning that protects slug-based profiles (see
Using platform profiles) applies
only to bundles resolved through codecharter update. Treat the bundle file
itself as the trust boundary: fetch it over a channel you already trust (an
authenticated download from the portal, checked into your own signed artifact
store, or transferred with your organization's existing integrity controls),
and do not commit it directly into your source repository — pull it from your
internal store as a preceding CI step instead.
Update workflow in air-gap environments
In a fully isolated environment we recommend the following process:
- On a machine with internet access: download the new bundle from the portal.
- Extract it and upload the extracted contents to your internal artifact
repository, keyed by the bundle name so the CI runner can fetch them into
.codecharter/cache/extracted/<name>/. - Update the version in
.codecharter/config.yml(either thepath:value, if it encodes the version, or the referenced file name). - On the internet-connected machine, run
codecharter update --portal-url <url> --api-key <key>to refresh the lockfile if you also use slug-based profiles alongside the offline one. This command requires portal connectivity and cannot run inside the air gap. - Merge
.codecharter/config.ymland.codecharter/codecharter.lock.jsonin a PR. - On the CI runner: have a preceding pipeline step place the extracted bundle
at
.codecharter/cache/extracted/<name>/, then runcodecharter analyzeas usual — it reads thepath:profile straight from that directory.