Skip to main content

Safe CI/CD

Resolve Safe references in CI/CD jobs without leaking values into scripts, logs, or process metadata.

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

{
  "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.

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:

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.

# .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.

# .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.