{
  "slug": "safe/ci-cd",
  "title": "Safe CI/CD",
  "description": "Resolve Safe references in CI/CD jobs without leaking values into scripts, logs, or process metadata.",
  "url": "https://cuitty.com/docs/safe/ci-cd",
  "markdown_url": "https://cuitty.com/docs/safe/ci-cd.md",
  "json_url": "https://cuitty.com/docs/safe/ci-cd.json",
  "frontmatter": {
    "title": "Safe CI/CD",
    "description": "Resolve Safe references in CI/CD jobs without leaking values into scripts, logs, or process metadata.",
    "order": 6,
    "section": "Safe",
    "updatedAt": "2026-06-09"
  },
  "headings": [
    {
      "depth": 1,
      "slug": "safe-cicd",
      "text": "Safe CI/CD"
    },
    {
      "depth": 2,
      "slug": "package-script-pattern",
      "text": "Package script pattern"
    },
    {
      "depth": 2,
      "slug": "job-flow",
      "text": "Job flow"
    },
    {
      "depth": 2,
      "slug": "service-accounts",
      "text": "Service accounts"
    },
    {
      "depth": 2,
      "slug": "gitlab-ci",
      "text": "GitLab CI"
    },
    {
      "depth": 2,
      "slug": "github-actions",
      "text": "GitHub Actions"
    },
    {
      "depth": 2,
      "slug": "cuitty-code-and-registry",
      "text": "Cuitty Code and Registry"
    }
  ],
  "body_markdown": "# Safe CI/CD\n\nCI/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.\n\n## Package script pattern\n\n```jsonc\n{\n  \"scripts\": {\n    \"deploy\": \"cuitty-safe run --scope acme/prod -- bun run deploy:raw\",\n    \"registry:publish\": \"cuitty-safe run --scope acme/registry -- bun publish\"\n  },\n  \"cuittySafe\": {\n    \"env\": {\n      \"DATABASE_URL\": \"acme/prod/database-url\",\n      \"REGISTRY_TOKEN\": \"acme/registry/publish-token\"\n    }\n  }\n}\n```\n\n## Job flow\n\n1. Load job configuration.\n2. Build the Safe reference set.\n3. Request a Safe run plan.\n4. Resolve values from the configured provider.\n5. Inject resolved env only into the child process.\n6. Install redaction rules for logs.\n7. Emit audit metadata without values.\n\n## Service accounts\n\nFor 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`.\n\nConfigure `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.\n\n```bash\ncuitty-safe connector 1password test --scope acme/prod\ncuitty-safe run --scope acme/prod -- bun run deploy:raw\n```\n\nCreate one harmless smoke-check secret in the production Safe before enabling the jobs:\n\n```text\nacme/prod/redaction-smoke = SAFE_REDACTION_SMOKE_VALUE\n```\n\nThe value is intentionally not sensitive. The job fails if that dummy resolved value would be emitted to CI logs.\n\n## GitLab CI\n\nStore `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.\n\nUse a runner image where `cuitty-safe` and the package manager used by the deploy script are already installed.\n\n```yaml\n# .gitlab-ci.yml\nstages:\n  - deploy\n\ndeploy:\n  stage: deploy\n  rules:\n    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'\n  variables:\n    CUITTY_SAFE_SCOPE: acme/prod\n  before_script:\n    - test -n \"${OP_SERVICE_ACCOUNT_TOKEN:-}\"\n    - cuitty-safe connector 1password test --scope \"$CUITTY_SAFE_SCOPE\"\n  script:\n    - |\n      set -eu\n      refs=\"$(mktemp)\"\n      trap 'rm -f \"$refs\"' EXIT\n      printf '%s\\n' 'SAFE_REDACTION_SMOKE=cuitty-safe:acme/prod/redaction-smoke' > \"$refs\"\n\n      set +e\n      output=\"$(cuitty-safe run --scope \"$CUITTY_SAFE_SCOPE\" --env-file \"$refs\" -- sh -c 'printf \"%s\\n\" \"$SAFE_REDACTION_SMOKE\"' 2>&1)\"\n      status=$?\n      set -e\n\n      if printf '%s\\n' \"$output\" | grep -F 'SAFE_REDACTION_SMOKE_VALUE' >/dev/null; then\n        echo \"Cuitty Safe redaction smoke check leaked the dummy value\"\n        exit 1\n      fi\n\n      printf '%s\\n' \"$output\"\n      if [ \"$status\" -ne 0 ]; then\n        exit \"$status\"\n      fi\n    - cuitty-safe run --scope \"$CUITTY_SAFE_SCOPE\" -- bun run deploy:raw\n```\n\n## GitHub Actions\n\nStore `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.\n\nUse 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.\n\n```yaml\n# .github/workflows/deploy.yml\nname: deploy\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    environment: production\n    permissions:\n      contents: read\n    env:\n      CUITTY_SAFE_SCOPE: acme/prod\n      OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Verify Safe provider and redaction\n        shell: bash\n        run: |\n          set -euo pipefail\n          test -n \"${OP_SERVICE_ACCOUNT_TOKEN:-}\"\n          cuitty-safe connector 1password test --scope \"$CUITTY_SAFE_SCOPE\"\n\n          refs=\"$(mktemp)\"\n          trap 'rm -f \"$refs\"' EXIT\n          printf '%s\\n' 'SAFE_REDACTION_SMOKE=cuitty-safe:acme/prod/redaction-smoke' > \"$refs\"\n\n          set +e\n          output=\"$(cuitty-safe run --scope \"$CUITTY_SAFE_SCOPE\" --env-file \"$refs\" -- bash -lc 'printf \"%s\\n\" \"$SAFE_REDACTION_SMOKE\"' 2>&1)\"\n          status=$?\n          set -e\n\n          if printf '%s\\n' \"$output\" | grep -F 'SAFE_REDACTION_SMOKE_VALUE' >/dev/null; then\n            echo \"::error::Cuitty Safe redaction smoke check leaked the dummy value\"\n            exit 1\n          fi\n\n          printf '%s\\n' \"$output\"\n          if [ \"$status\" -ne 0 ]; then\n            exit \"$status\"\n          fi\n\n      - name: Deploy\n        shell: bash\n        run: cuitty-safe run --scope \"$CUITTY_SAFE_SCOPE\" -- bun run deploy:raw\n```\n\nDo not add `actions/upload-artifact`, GitLab `artifacts`, cache paths, test snapshots, or dotenv reports that can contain resolved Safe values.\n\n## Cuitty Code and Registry\n\nCuitty Code can resolve app manifest env, runner env, mirror credentials, webhook signing secrets, OAuth app credentials, deployment keys, Airflow tokens, and Dagger tokens.\n\nCuitty Registry can resolve npm publish tokens, Cargo registry tokens, PyPI upload tokens, OCI credentials, artifact storage DSNs, signing keys, and provenance keys.\n\nDo not resolve Safe refs inside tight package download paths unless explicitly configured.",
  "links_out": []
}