---
title: Safe CI/CD
description: Resolve Safe references in CI/CD jobs without leaking values into scripts, logs, or process metadata.
section: Safe
order: 6
updatedAt: 2026-06-09
slug: safe/ci-cd
---
# Safe CI/CD

CI/CD should use explicit Safe references only. Resolve values at the job boundary, inject them into the child process, and install redaction rules before the process starts.

## Package script pattern

```jsonc
{
  "scripts": {
    "deploy": "cuitty-safe run --scope acme/prod -- bun run deploy:raw",
    "registry:publish": "cuitty-safe run --scope acme/registry -- bun publish"
  },
  "cuittySafe": {
    "env": {
      "DATABASE_URL": "acme/prod/database-url",
      "REGISTRY_TOKEN": "acme/registry/publish-token"
    }
  }
}
```

## Job flow

1. Load job configuration.
2. Build the Safe reference set.
3. Request a Safe run plan.
4. Resolve values from the configured provider.
5. Inject resolved env only into the child process.
6. Install redaction rules for logs.
7. Emit audit metadata without values.

## Service accounts

For 1Password-backed production CI, use a least-privilege service account scoped to the required vault and fields. Cuitty Safe should read the token from the CI provider's secret store, commonly exposed as `OP_SERVICE_ACCOUNT_TOKEN`.

Configure `OP_SERVICE_ACCOUNT_TOKEN` as a masked and protected CI secret. Do not commit it, print it, write it into generated env files, or upload it in artifacts. The examples below use `account/safe/secret` references such as `acme/prod/database-url`; the temporary env files contain references only and are removed before the job exits.

```bash
cuitty-safe connector 1password test --scope acme/prod
cuitty-safe run --scope acme/prod -- bun run deploy:raw
```

Create one harmless smoke-check secret in the production Safe before enabling the jobs:

```text
acme/prod/redaction-smoke = SAFE_REDACTION_SMOKE_VALUE
```

The value is intentionally not sensitive. The job fails if that dummy resolved value would be emitted to CI logs.

## GitLab CI

Store `OP_SERVICE_ACCOUNT_TOKEN` in GitLab Settings > CI/CD > Variables with **Masked** and **Protected** enabled. The job below does not define artifacts, reports, caches, or dotenv outputs for resolved env.

Use a runner image where `cuitty-safe` and the package manager used by the deploy script are already installed.

```yaml
# .gitlab-ci.yml
stages:
  - deploy

deploy:
  stage: deploy
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
  variables:
    CUITTY_SAFE_SCOPE: acme/prod
  before_script:
    - test -n "${OP_SERVICE_ACCOUNT_TOKEN:-}"
    - cuitty-safe connector 1password test --scope "$CUITTY_SAFE_SCOPE"
  script:
    - |
      set -eu
      refs="$(mktemp)"
      trap 'rm -f "$refs"' EXIT
      printf '%s\n' 'SAFE_REDACTION_SMOKE=cuitty-safe:acme/prod/redaction-smoke' > "$refs"

      set +e
      output="$(cuitty-safe run --scope "$CUITTY_SAFE_SCOPE" --env-file "$refs" -- sh -c 'printf "%s\n" "$SAFE_REDACTION_SMOKE"' 2>&1)"
      status=$?
      set -e

      if printf '%s\n' "$output" | grep -F 'SAFE_REDACTION_SMOKE_VALUE' >/dev/null; then
        echo "Cuitty Safe redaction smoke check leaked the dummy value"
        exit 1
      fi

      printf '%s\n' "$output"
      if [ "$status" -ne 0 ]; then
        exit "$status"
      fi
    - cuitty-safe run --scope "$CUITTY_SAFE_SCOPE" -- bun run deploy:raw
```

## GitHub Actions

Store `OP_SERVICE_ACCOUNT_TOKEN` as a repository or environment secret. Use a protected `production` environment for deploy jobs so the secret is available only after the environment's protection rules pass.

Use a runner image or setup step that provides `cuitty-safe` and the package manager used by the deploy script without writing resolved Safe values to the workspace.

```yaml
# .github/workflows/deploy.yml
name: deploy

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    permissions:
      contents: read
    env:
      CUITTY_SAFE_SCOPE: acme/prod
      OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
    steps:
      - uses: actions/checkout@v4

      - name: Verify Safe provider and redaction
        shell: bash
        run: |
          set -euo pipefail
          test -n "${OP_SERVICE_ACCOUNT_TOKEN:-}"
          cuitty-safe connector 1password test --scope "$CUITTY_SAFE_SCOPE"

          refs="$(mktemp)"
          trap 'rm -f "$refs"' EXIT
          printf '%s\n' 'SAFE_REDACTION_SMOKE=cuitty-safe:acme/prod/redaction-smoke' > "$refs"

          set +e
          output="$(cuitty-safe run --scope "$CUITTY_SAFE_SCOPE" --env-file "$refs" -- bash -lc 'printf "%s\n" "$SAFE_REDACTION_SMOKE"' 2>&1)"
          status=$?
          set -e

          if printf '%s\n' "$output" | grep -F 'SAFE_REDACTION_SMOKE_VALUE' >/dev/null; then
            echo "::error::Cuitty Safe redaction smoke check leaked the dummy value"
            exit 1
          fi

          printf '%s\n' "$output"
          if [ "$status" -ne 0 ]; then
            exit "$status"
          fi

      - name: Deploy
        shell: bash
        run: cuitty-safe run --scope "$CUITTY_SAFE_SCOPE" -- bun run deploy:raw
```

Do not add `actions/upload-artifact`, GitLab `artifacts`, cache paths, test snapshots, or dotenv reports that can contain resolved Safe values.

## Cuitty Code and Registry

Cuitty Code can resolve app manifest env, runner env, mirror credentials, webhook signing secrets, OAuth app credentials, deployment keys, Airflow tokens, and Dagger tokens.

Cuitty Registry can resolve npm publish tokens, Cargo registry tokens, PyPI upload tokens, OCI credentials, artifact storage DSNs, signing keys, and provenance keys.

Do not resolve Safe refs inside tight package download paths unless explicitly configured.