> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rafter.so/llms.txt
> Use this file to discover all available pages before exploring further.

# CI/CD Integration

> Add Rafter security to your CI/CD pipelines — local secret scanning and remote code analysis with stable exit codes.

## Fastest Path: `rafter ci init`

Auto-generate CI config for GitHub Actions, GitLab CI, or CircleCI:

```bash theme={null}
rafter ci init                    # auto-detect platform
rafter ci init --platform github  # explicit
rafter ci init --with-backend     # include remote code analysis job (requires RAFTER_API_KEY)
```

This writes a ready-to-commit workflow file. Review it, push, done.

***

## Two CI Modes

Rafter supports two approaches in CI. Choose based on whether you have a Rafter API key.

### Mode 1: Local Secret Scanning (no API key required)

Uses `rafter secrets` — the fast, deterministic scanner that runs pattern matching and Betterleaks secret detection (v0.8.0+; gitleaks successor) directly on the runner. Same inputs, same findings, every time.

```yaml theme={null}
- name: Rafter secret scan
  run: |
    npm install -g @rafter-security/cli
    rafter secrets . --json --quiet
```

Exit code `1` fails the pipeline if secrets are detected. No account or API key needed.

**When to use:** Open-source projects, teams without a Rafter subscription, or any repo where fast secret scanning at commit time is enough.

### Mode 2: Backend Code Analysis (API key required)

Uses `rafter run` to trigger the Rafter code analysis engine for full SAST coverage — a wider range of vulnerability classes beyond secrets. Your code is deleted immediately after the analysis engine completes.

**When to use:** Teams with a Rafter subscription who want full SAST coverage on every push.

See [Step 3 below](#step-3-create-github-workflow) for the complete workflow.

***

## Introduction

Automated security scanning transforms security from an afterthought into a seamless part of your development workflow. Instead of remembering to run scans manually (and inevitably forgetting), your CI/CD pipeline automatically checks every commit and pull request for vulnerabilities.

This guide shows you two approaches:

* **Manual setup**: Step-by-step instructions for hands-on configuration
* **AI-assisted setup**: Copy-paste workflows with AI prompts for rapid deployment

I'd recommend skimming the manual setup first so you understand what's happening, then using the AI-assisted setup for a quick and easy setup.

Plus, you'll learn how API keys work, why they're secure, and how to protect them properly in your CI/CD environment.

If you're not familiar with GitHub Actions, you can learn more about them [here](https://docs.github.com/en/actions).

## Why Automated Security Scanning Matters

### The Manual Scanning Problem

Manual security scanning has three critical flaws:

1. **Human Error**: Developers forget to run scans, especially under pressure
2. **Inconsistent Coverage**: Different team members use different tools or settings
3. **Late Detection**: Vulnerabilities are found after code reaches production

### The Automated Solution

Automated scanning eliminates these problems by:

* **Running on every push**: No forgotten scans
* **Consistent configuration**: Same rules applied every time
* **Early detection**: Vulnerabilities caught before deployment
* **Build failure**: Critical issues block deployment automatically

## Understanding API Keys in CI/CD

### What Are API Keys?

API keys are authentication tokens that allow automated tools to access services on your behalf. Think of them as digital keys that unlock specific capabilities—in this case, security scanning services like Rafter.

### Why API Keys Are Secure

API keys are designed for programmatic access and include several security features:

* **Scoped permissions**: Keys can only access specific services
* **Usage tracking**: All API calls are logged and monitored
* **Easy rotation**: Keys can be regenerated instantly if compromised
* **Environment isolation**: Keys are stored separately from your code

### How GitHub Secrets Protect Your Keys

GitHub Secrets provide enterprise-grade security for sensitive data:

* **Encryption at rest**: Keys are encrypted when stored
* **Encryption in transit**: Keys are encrypted when accessed
* **Access control**: Only authorized workflows can use secrets
* **Audit logging**: All secret access is logged

## Manual Setup: Step-by-Step Configuration

### Step 1: Choose Your Mode

Refer to [Two CI Modes](#two-ci-modes) above. If you're using **local scan**, skip to [Step 3](#step-3-create-github-workflow) and use the local scan workflow. If you're using **backend scan**, continue with Step 2 to get your API key.

### Step 2: Get Your Rafter API Key

*(Backend scan only — skip if using local scan.)*

If you have an existing key saved to a safe place, copy it. Otherwise, generate a new key...

Rafter protects your API keys by not storing them. You get access once, when you generate/refresh the key. Then, refreshing the key invalidates the previous key to keep it safe. It's a way to help you rotate keys, which is best practice.

**Navigate to Account Settings**

1. Go to your Rafter account settings
2. Look for the "API Keys" section

**Generate or Retrieve Key**

1. Click "Generate New API Key" or "Refresh API Key"
2. The key is automatically copied to your clipboard (it won't be shown/copiable again)

> **API keys are sensitive credentials. Never commit them to your repository or share them in plain text.**

**Add API Key to GitHub Secrets**

1. Navigate to your GitHub repository
2. Click "Settings" → "Secrets and variables" → "Actions"
3. Click "New repository secret"
4. **Name**: `RAFTER_API_KEY`
5. **Value**: Paste your API key
6. Click "Add secret"

### Step 3: Create GitHub Workflow

**Create Workflow Directory**

```bash theme={null}
mkdir -p .github/workflows
```

**Option A — Local Scan Workflow** (no API key)

Create `.github/workflows/security-scan.yml`:

```yaml theme={null}
# .github/workflows/security-scan.yml
# No API key required. Runs pattern + Betterleaks secret scanning on the runner.

name: Rafter Secret Scan

on:
  push:
    branches: [ main ]
  pull_request:

permissions:
  contents: read

jobs:
  secret-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Rafter secret scan
        run: |
          npm install -g @rafter-security/cli
          rafter secrets . --json --quiet
```

Exit code `1` fails the job automatically if secrets are detected.

**Option B — Backend Code Analysis Workflow** (API key required)

Create `.github/workflows/security-scan.yml`:

```yaml theme={null}
# .github/workflows/security-scan.yml
#
# Prerequisites:
#   Add your Rafter API key as a GitHub repository secret:
#   Settings -> Secrets and variables -> Actions -> New repository secret
#   Name: RAFTER_API_KEY

name: Rafter Code Analysis

on:
  push:
    branches: [ main ]

permissions:
  contents: read

jobs:
  code-analysis:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install Rafter CLI
        run: npm install -g @rafter-security/cli

      - name: Run code analysis
        env:
          RAFTER_API_KEY: ${{ secrets.RAFTER_API_KEY }}
        run: rafter run --format json --quiet > scan-results.json

      - name: Check for critical vulnerabilities
        run: |
          CRITICAL_COUNT=$(jq '.vulnerabilities | map(select(.level=="error")) | length' scan-results.json)
          if [ "$CRITICAL_COUNT" -gt 0 ]; then
            echo "Found $CRITICAL_COUNT critical vulnerabilities!"
            exit 1
          fi
          echo "No critical vulnerabilities found"

      - name: Upload results
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: security-scan-results
          path: scan-results.json
          retention-days: 30
```

The CLI handles repo/branch auto-detection, polling, and retries — no manual curl loops needed.

### Step 4: Test Your Setup

**Make a Test Commit**

```bash theme={null}
git add .github/workflows/security-scan.yml
git commit -m "Add automated security scanning"
git push
```

**Check GitHub Actions**

1. Go to your repository's "Actions" tab
2. Look for the workflow
3. Verify it runs successfully

## AI-Assisted Setup

Tell your AI assistant:

```
Set up Rafter security scanning for this repo. Run `rafter ci init --platform github`
to generate the workflow, then commit it. If we have a RAFTER_API_KEY, use --with-backend
for full SAST code analysis. Otherwise the local secret scan is fine.
```

Or generate it yourself:

```bash theme={null}
rafter ci init --platform github --with-backend
# Review .github/workflows/rafter-security.yml, then commit and push
```

## Understanding the Workflow Components

### Trigger Configuration

```yaml theme={null}
on:
  push:
    branches: [ main ]
```

This configuration ensures the analysis runs:

* On every push to the main branch
* Automatically, without manual intervention

### CLI-Based Code Analysis

The backend workflow uses the Rafter CLI which handles auto-detection, polling, and retries:

```yaml theme={null}
- name: Run code analysis
  env:
    RAFTER_API_KEY: ${{ secrets.RAFTER_API_KEY }}
  run: rafter run --format json --quiet > scan-results.json
```

The CLI:

* **Auto-detects repo and branch** from the git checkout
* **Polls until complete** — no manual loop needed
* **Returns structured JSON** with consistent schema and documented exit codes

### Exit Codes

The CLI uses stable exit codes that CI pipelines can rely on:

| Code | Meaning                                           | CI Action                 |
| ---- | ------------------------------------------------- | ------------------------- |
| 0    | Success — analysis completed                      | Proceed                   |
| 1    | General error                                     | Fail build                |
| 2    | Scan not found (HTTP 404)                         | Check scan ID             |
| 3    | Quota exhausted (HTTP 429 or 403 scan-mode limit) | Alert / back off          |
| 4    | Insufficient scope / forbidden (HTTP 403)         | Check API key permissions |

Results are also available in your Rafter dashboard.

### Vulnerability Checking

```yaml theme={null}
- name: Check for critical vulnerabilities
  run: |
    CRITICAL_COUNT=$(cat scan-results.json | jq '.vulnerabilities | map(select(.level=="error")) | length')
    
    if [ $CRITICAL_COUNT -gt 0 ]; then
      echo "Found $CRITICAL_COUNT critical vulnerabilities!"
      exit 1
    else
      echo "No critical vulnerabilities found"
    fi
```

This critical step:

* Parses scan results using jq
* Counts error-level vulnerabilities
* Fails the build if critical issues are found
* Provides clear feedback to developers

## Advanced Configuration Options

While we've outlined some powerful use cases below, see our documentation for more advanced configuration options: [Rafter CI/CD Documentation](/api-reference/endpoint/usage).

### Customizing Triggers

You can customize when Rafter runs:

```yaml theme={null}
# Scan on all branches
on: [push, pull_request]

# Scan only on specific file changes
on:
  push:
    paths:
      - 'src/**'
      - 'package.json'

# Schedule regular scans
on:
  schedule:
    - cron: '0 2 * * 1'  # Every Monday at 2 AM
```

### Adjusting Failure Thresholds

Modify the vulnerability checking logic:

```bash theme={null}
# Fail on warnings and errors
WARNING_COUNT=$(cat scan-results.json | jq '.vulnerabilities | map(select(.level=="warning" or .level=="error")) | length')

# Fail only on specific vulnerability types (using hashed rule IDs)
CRITICAL_COUNT=$(cat scan-results.json | jq '.vulnerabilities | map(select(.ruleId | startswith("R-"))) | length')

# Fail on specific vulnerability patterns
SQL_INJECTION_COUNT=$(cat scan-results.json | jq '.vulnerabilities | map(select(.message | contains("SQL injection"))) | length')
```

### Adding Notifications

Integrate with Slack, Discord, or email:

```yaml theme={null}
- name: Notify on failure
  if: failure()
  uses: 8398a7/action-slack@v3
  with:
    status: failure
    text: "Security scan failed: ${{ github.event.head_commit.message }}"
```

## Troubleshooting Common Issues

### API Key Not Found

**Error**: `Error: RAFTER_API_KEY environment variable not set`

**Solution**:

1. Verify the secret exists in GitHub repository settings
2. Check the secret name matches exactly: `RAFTER_API_KEY`
3. Ensure the workflow uses `${{ secrets.RAFTER_API_KEY }}`

### Scan Request Fails

**Error**: `curl: (22) The requested URL returned error: 401` or `curl: (22) The requested URL returned error: 400`

**Solution**:

* **401 Unauthorized**: Check your API key is valid and not expired
* **400 Bad Request**: Verify repository name format (should be owner/repo)
* **Network issues**: The `-fsS` flags will cause curl to fail silently on HTTP errors
* **Branch name issues**: Ensure `${{ github.ref_name }}` returns the correct branch name

### Scan Status Issues

**Error**: Scan stuck in "processing" or "pending" status

**Solution**:

* **Timeout handling**: The workflow automatically fails after 10 minutes
* **Status checking**: Verify the API returns valid status values (completed, failed, processing, pending)
* **Polling frequency**: Adjust `sleep 10` if scans typically take longer
* **API rate limits**: Check if you're hitting API rate limits

### Scan Results Not Found

**Error**: `scan-results.json: No such file or directory`

**Solution**:

* **Check scan completion**: Ensure the scan reached "completed" status
* **Verify file output**: The workflow saves results to `scan-results.json` and `scan-results.md`
* **API response format**: Confirm the API returns results in the expected JSON format
* **Error handling**: The `set -euo pipefail` ensures the script fails if curl commands fail

## Security Best Practices

### API Key Management

* **Rotate keys regularly**: Generate new keys every 90 days
* **Monitor usage**: Check API key usage logs for anomalies
* **Use least privilege**: Only grant necessary permissions
* **Never log keys**: Ensure keys don't appear in logs or outputs

### Workflow Security

* **Pin action versions**: Use specific commit SHAs instead of tags
* **Review permissions**: Limit workflow permissions to minimum required
* **Audit regularly**: Review workflow changes and access patterns
* **Use trusted sources**: Only use official GitHub Actions

### Repository Security

* **Enable branch protection**: Require status checks before merging
* **Use required reviewers**: Require security team review for workflow changes
* **Monitor secrets**: Regularly audit repository secrets
* **Enable security alerts**: Use GitHub's security features

## Measuring Success

### Key Metrics to Track

* **Scan Coverage**: Percentage of commits scanned
* **Detection Rate**: Vulnerabilities found per scan
* **Fix Time**: Average time from detection to resolution
* **False Positive Rate**: Incorrect vulnerability reports

### Success Indicators

* Zero critical vulnerabilities in production deployments
* Consistent scan execution across all branches
* Rapid vulnerability resolution (under 24 hours)
* Developer adoption of security-first practices

## Conclusion

Whether you use the local secret scanner or the full code analysis engine, the result is the same: security coverage on every commit with stable exit codes and structured output that CI pipelines can act on.

Start with `rafter ci init`, customize as needed, and let the code analysis engine do the work.
