---
title: "Set up CI | PackageMaze docs"
description: "Install and publish from GitHub Actions through your PackageMaze Feed with short-lived, per-run credentials instead of long-lived secrets."
canonical: https://www.packagemaze.com/docs/set-up-ci/
---

> Documentation index: https://www.packagemaze.com/llms.txt

# Set up CI

Let a CI job prove its identity with OpenID Connect (OIDC) and exchange that proof for a short-lived PackageMaze Token. Choose [GitHub Actions](#github-actions), [CircleCI](#circleci), or [other CI with OIDC](#other-ci-with-oidc). No long-lived PackageMaze secret is needed.

## Before you start

Have an Organization Admin grant access to the Feed using the provider-specific instructions below. Replace `<organization>/<feed>` with its slugs and `<package-name>` with the name you publish. Each Feed and purpose needs its own access rule: install for dependency jobs, publish for uploads, and docker-build for installs inside Docker images.

The examples use npm and a committed `package-lock.json`. Configure your repository's Feed URLs first using [npm and pnpm setup](https://www.packagemaze.com/docs/set-up-npm-and-pnpm/) or [pip, uv, and Poetry setup](https://www.packagemaze.com/docs/set-up-pip-uv-and-poetry/). The Feed Configuration page provides commands for your chosen client. Request credentials in each job that uses them, close to the install or upload step; keep Tokens out of logs, caches, and saved artifacts.

> Tell your connected agent: "Configure our CI to use PackageMaze OIDC. Check which install and publish paths our provider supports."

Requires a connected agent. See [Connect your agent](https://www.packagemaze.com/docs/connect-your-agent/).

## GitHub Actions

In Feed settings, add a GitHub Actions CI access rule for the repository and purpose. Restrict publishing to the intended workflow, branch or tag, and Environment where applicable. The rule must match the job's signed identity; a workflow permission alone does not grant Feed access.

### Install jobs

Add these permissions at the job or workflow level, preserving any others your job needs. Then add the following steps after checkout under `jobs.<job>.steps`. The action installs `maze` and exchanges GitHub's identity token automatically.

**job or workflow permissions**

```
permissions:
  contents: read
  id-token: write
```

**npm install steps — after checkout**

```
- id: packagemaze-token
  uses: packagemaze/setup-maze@v0.0.5
  with:
    feed: <organization>/<feed>
    purpose: install
- uses: actions/setup-node@v6
  with:
    node-version: "24"
    registry-url: "https://pkg.packagemaze.com/<organization>/<feed>/"
- run: npm ci
  env:
    NODE_AUTH_TOKEN: ${{ steps.packagemaze-token.outputs.token }}
```

[GitHub's OIDC permission reference](https://docs.github.com/en/actions/reference/security/oidc) explains `id-token: write`, including caller permissions for reusable workflows.

### Publish jobs

Grant both install and publish access. Use an install Token for dependencies, run your build, then request a fresh publish Token for the upload. The Package name is required and must match what you publish. This example replaces the install steps above and uses the same permissions.

For Python uploads, use the [PyPI publish recipe](https://www.packagemaze.com/docs/set-up-pip-uv-and-poetry/) with the publish Token. Package and Build pages show the recorded publishing identity.

**npm install, build, and publish steps — after checkout**

```
- id: packagemaze-token
  uses: packagemaze/setup-maze@v0.0.5
  with:
    feed: <organization>/<feed>
    purpose: install
- uses: actions/setup-node@v6
  with:
    node-version: "24"
    registry-url: "https://pkg.packagemaze.com/<organization>/<feed>/"
- run: npm ci
  env:
    NODE_AUTH_TOKEN: ${{ steps.packagemaze-token.outputs.token }}
- run: npm run build --if-present
- id: packagemaze-publish-token
  uses: packagemaze/setup-maze@v0.0.5
  with:
    feed: <organization>/<feed>
    purpose: publish
    package: "<package-name>"
- run: npm publish
  env:
    NODE_AUTH_TOKEN: ${{ steps.packagemaze-publish-token.outputs.token }}
```

### pnpm

> **Watch out:** Current pnpm releases ignore `${...}` credential placeholders in the project `.npmrc`. Use the [pnpm CI recipe](https://www.packagemaze.com/docs/set-up-npm-and-pnpm/): write a trusted temporary npm config, select it with `NPM_CONFIG_USERCONFIG`, and inject the Token only into the pnpm step.

### Docker image builds

Docker image builds pass the Token through a BuildKit secret so it never lands in image layers. Add a `purpose: docker-build` access rule, use one `packagemaze/setup-maze` step per Feed, and mount the generated secret only for the package-client `RUN`. The full Docker workflow and Dockerfile lines are rendered on the Feed Configuration page.

## CircleCI

### Prepare the job and access rules

In Feed settings, add a CircleCI CI access rule with the exact CircleCI organization ID, project ID, and VCS origin (repository address) from the signed identity. Add branch or tag restrictions as needed. Publish and docker-build rules also require at least one CircleCI context ID; attach every required context to the job and configure its access restrictions in CircleCI.

Install a pinned [maze release](https://github.com/packagemaze/maze-cli/releases) in the job image or an earlier setup step: choose the archive for the runner's OS and architecture, verify it against the release's `maze_checksums.txt`, and put the extracted binary on `PATH`. Confirm `maze version` succeeds. Customer setup currently uses the CLI; a public PackageMaze CircleCI orb is not released.

Make CircleCI's [environment CLI](https://circleci.com/docs/guides/permissions-authentication/oidc-tokens-with-custom-claims/) available too. When `MAZE_OIDC_TOKEN` is unset, maze runs this command inside the job to request the correct audience:

**identity request performed by maze**

```
circleci run oidc get --claims '{"aud":"https://api.packagemaze.com"}'
```

Use the organization-scoped issuer; omit `--root-issuer`. The default `CIRCLE_OIDC_TOKEN` variables use the CircleCI organization ID as audience, so do not substitute them for this request. If supplying `MAZE_OIDC_TOKEN`yourself, mint it for `https://api.packagemaze.com`.

### Install jobs

After checkout and CLI installation, add this Bash run step to the job's `steps` in `.circleci/config.yml`. The temporary npm config references the Token only for this step and is removed when the step exits.

**CircleCI npm install step**

```
- run:
    name: Install from PackageMaze
    command: |
      set +x
      set -euo pipefail
      MAZE_TOKEN="$(maze auth exchange-oidc \
        --provider circleci --feed "<organization>/<feed>" \
        --purpose install)"
      export MAZE_TOKEN
      auth_dir="$(mktemp -d)"
      trap 'rm -rf "$auth_dir"' EXIT
      umask 077
      export NPM_CONFIG_USERCONFIG="$auth_dir/npmrc"
      cat > "$NPM_CONFIG_USERCONFIG" <<'EOF'
      registry=https://pkg.packagemaze.com/<organization>/<feed>/
      replace-registry-host=npmjs
      //pkg.packagemaze.com/<organization>/<feed>/:_authToken=${MAZE_TOKEN}
      EOF
      npm ci
```

### Publish jobs

Run the install step and your build first, then add this separate upload step. Attach the context required by the publish rule in `workflows.<workflow>.jobs`; the workflow uses its context name, while PackageMaze matches its signed ID.

**CircleCI workflow job entry**

```
- publish:
    context: <publish-context-name>
```

**CircleCI npm publish step**

```
- run:
    name: Publish to PackageMaze
    command: |
      set +x
      set -euo pipefail
      MAZE_TOKEN="$(maze auth exchange-oidc \
        --provider circleci --feed "<organization>/<feed>" \
        --purpose publish --package "<package-name>")"
      export MAZE_TOKEN
      auth_dir="$(mktemp -d)"
      trap 'rm -rf "$auth_dir"' EXIT
      umask 077
      export NPM_CONFIG_USERCONFIG="$auth_dir/npmrc"
      cat > "$NPM_CONFIG_USERCONFIG" <<'EOF'
      registry=https://pkg.packagemaze.com/<organization>/<feed>/
      replace-registry-host=npmjs
      //pkg.packagemaze.com/<organization>/<feed>/:_authToken=${MAZE_TOKEN}
      EOF
      npm publish
```

## Other CI with OIDC

Use this path to install when your CI provider can issue a signed job identity token with a public HTTPS OIDC discovery document and signing keys. PackageMaze accepts RS256 or ES256 signatures. Token acquisition depends on your provider; follow its documentation to request an identity token inside the running job.

### Grant access

As an Organization Admin, open **Organization Settings → External integrations → Grant access**. Configure these values before running the exchange:

- **Issuer URL:** the provider's exact token issuer, with public OIDC discovery and signing keys.
- **Audience:** `https://api.packagemaze.com`. Request that same audience from the provider. If its audience is fixed, use that exact value in both the grant and request below and review the app's replay warning.
- **Claim matchers:** exact signed claim values that identify your workload. Include at least one platform-assigned tenant, project, or workspace ID; a user-chosen name or email is not sufficient. Add repository or ref constraints when available.
- **Purpose and Feeds:** choose install and select only the Feeds this job needs.

### Exchange the identity and install

Supply the freshly issued identity as `PLATFORM_ID_TOKEN` in this Bash step's environment. With Node.js, npm, curl, and jq installed, the following exchanges it for a Feed Token and installs dependencies. The JSON field `provider: "oidc"` explicitly selects configured OIDC; the maze CLI's automatic provider detection is not this path.

**other CI — OIDC exchange and npm install**

```
set +x
set -euo pipefail
: "${PLATFORM_ID_TOKEN:?Request an OIDC identity token from your CI provider first}"
export PLATFORM_ID_TOKEN
MAZE_TOKEN="$(
  jq -n '{
    provider: "oidc",
    feed: "<organization>/<feed>",
    purpose: "install",
    audience: "https://api.packagemaze.com",
    oidc_token: env.PLATFORM_ID_TOKEN
  }' |
    curl --fail --silent --show-error \
      https://api.packagemaze.com/v1/auth/workload-token \
      --header 'Content-Type: application/json' \
      --data-binary @- |
    jq -er '.token'
)"
export MAZE_TOKEN
unset PLATFORM_ID_TOKEN
auth_dir="$(mktemp -d)"
trap 'rm -rf "$auth_dir"' EXIT
umask 077
export NPM_CONFIG_USERCONFIG="$auth_dir/npmrc"
cat > "$NPM_CONFIG_USERCONFIG" <<'EOF'
registry=https://pkg.packagemaze.com/<organization>/<feed>/
replace-registry-host=npmjs
//pkg.packagemaze.com/<organization>/<feed>/:_authToken=${MAZE_TOKEN}
EOF
npm ci
```

> **Note:** Generic OIDC publishing is not supported yet: a publish exchange returns `oidc_publish_unsupported`, even if a publish rule exists. Use the GitHub Actions or CircleCI publish path above for uploads. A provider without verifiable OIDC cannot use this exchange.

## Verify the setup

Run the job from an identity allowed by its rule, check that Token exchange and installation succeed, and inspect the Feed's Activity. For GitHub Actions and CircleCI, open the Build report to confirm the recorded CI identity. A successful install alone does not prove publish access; verify the upload in your release workflow separately.

If exchange fails, check the Feed, purpose, audience, and identity constraints. For CircleCI, also check attached contexts and run normally: SSH reruns cannot mint PackageMaze Tokens. For other CI, configure the issuer and claim rules first. See [Fix a failing install](https://www.packagemaze.com/docs/fix-a-failing-install/) for package-client failures.

> **Note:** Once publishing works, turn on **Only allow publishing from approved CI jobs** in Feed settings. This blocks npm and PyPI publishes using static Tokens, including existing developer and CI secret-based publish workflows. Packages already in the Feed stay available.

## Protect existing dependency hashes

These checks are optional. Keep your local install commands; adopt stricter CI modes after committing their required lockfiles or hashed requirements. A registry can replace both a package and its advertised hash. Previously reviewed artifact hashes let your client detect that later substitution. New dependency resolution still trusts the source used when those hashes are recorded.

### npm, pnpm, Yarn and Bun

Commit a current lockfile containing artifact hashes before using these commands. Keep pnpm current; versions before 11.4 could replace checksum mismatches during non-frozen installs. For Yarn modern with a checked-in cache, consider `--immutable-cache --check-cache`; checking the cache refetches packages and adds network cost.

**optional CI commands — choose your client**

```
npm ci
pnpm install --frozen-lockfile
yarn install --frozen-lockfile  # Yarn Classic
yarn install --immutable        # Yarn modern
test -f bun.lock && bun install --frozen-lockfile
```

Bun needs the explicit file check because its frozen flag can resolve normally when no lockfile exists. Check for `bun.lockb` instead if your project still uses that format.

### Python clients

For uv projects, commit a current `uv.lock` with artifact hashes before using `uv sync --locked`. With Poetry 2 and a committed lockfile containing file hashes, use `test -f poetry.lock && poetry check --lock && poetry sync`. Verify the equivalent for your existing Poetry version before changing its workflow.

For pip or uv pip, prepare requirements that pin every transitive dependency with `==` and record allowed SHA256 hashes for your target platforms first. Ordinary unhashed requirements will fail in this mode.

**optional CI commands — requires fully pinned, hashed requirements**

```
pip install --require-hashes -r requirements.txt
uv pip install --require-hashes -r requirements.txt
```

PDM was not evaluated in the cited study. Review your pinned client's lock and hash behavior before treating its lockfile mode as an equivalent guarantee.

> **Note:** Preserve trusted hashes during Feed migration and review URL changes separately from digest changes. Investigate integrity errors before changing a lockfile or checksum. A project's lockfile does not cover tool bootstraps, one-shot commands, Git or local dependencies, or every dependency used by a source build.

Minimum Age Policy and CI OIDC credentials do not authenticate downloaded bytes. The upstream PyPI attestation policy currently checks advertised provenance presence, without cryptographic verification. See the [Trail of Bits analysis](https://blog.trailofbits.com/2026/10/14/package-managers-have-an-integrity-problem/), [pip hash-checking mode](https://pip.pypa.io/en/stable/topics/secure-installs/), and [Yarn install options](https://yarnpkg.com/cli/install).

Next: [Agent quickstart](https://www.packagemaze.com/docs/agent-quickstart/)

Related: [Set up npm and pnpm](https://www.packagemaze.com/docs/set-up-npm-and-pnpm/), [Set up pip, uv, and Poetry](https://www.packagemaze.com/docs/set-up-pip-uv-and-poetry/), [Fix a failing install](https://www.packagemaze.com/docs/fix-a-failing-install/)
