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

# GitLab Tokens and CI Variables

> Securing GitLab access tokens and CI/CD variables

## Common Misconfiguration

Exposed GitLab tokens can lead to unauthorized repository access, CI/CD pipeline manipulation, and container registry breaches. 😱

### Vulnerable Example

```yaml theme={null}
# VULNERABLE - .gitlab-ci.yml with hardcoded tokens
variables:
  # Never hardcode tokens!
  GITLAB_TOKEN: "glpat-xxxxxxxxxxxxxxxxxxxx"
  DEPLOY_TOKEN: "gldt-xxxxxxxxxxxxxxxxxxxx"
  REGISTRY_TOKEN: "glrt-xxxxxxxxxxxxxxxxxxxx"
  RUNNER_TOKEN: "GR1348941xxxxxxxxxxxxxxxxxxxx"

stages:
  - build
  - deploy

build:
  script:
    # Hardcoded project token
    - git clone https://gitlab-ci-token:glpat-xxxxxxxxxxxxxxxxxxxx@gitlab.com/org/private-repo.git
    - docker login -u gitlab-ci-token -p glpat-xxxxxxxxxxxxxxxxxxxx registry.gitlab.com
    
deploy:
  script:
    # Hardcoded deploy token
    - curl --header "PRIVATE-TOKEN: glpat-xxxxxxxxxxxxxxxxxxxx" "https://gitlab.com/api/v4/projects"
```

```javascript theme={null}
// VULNERABLE - Hardcoded GitLab API credentials
const axios = require('axios');

class GitLabClient {
    constructor() {
        // Never hardcode these!
        this.personalAccessToken = 'glpat-xxxxxxxxxxxxxxxxxxxx';
        this.projectAccessToken = 'glpat-yyyy-xxxxxxxxxxxx';
        this.deployToken = {
            username: 'gitlab+deploy-token-1234',
            password: 'gldt-xxxxxxxxxxxxxxxxxxxx'
        };
        this.jobToken = process.env.CI_JOB_TOKEN || 'glctt-xxxxxxxxxxxxxxxxxxxx'; // Fallback is bad
        this.feedToken = 'feed_token_xxxxxxxxxxxxxxxxxxxx';
        
        this.apiUrl = 'https://gitlab.com/api/v4';
    }

    async makeRequest(endpoint) {
        return axios.get(`${this.apiUrl}${endpoint}`, {
            headers: {
                'PRIVATE-TOKEN': this.personalAccessToken
            }
        });
    }
}
```

## Secure Example

```yaml theme={null}
# SECURE - .gitlab-ci.yml using CI/CD variables
variables:
  DOCKER_REGISTRY: $CI_REGISTRY       # Predefined variable
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE    # Predefined variable

stages:
  - build
  - test
  - deploy

before_script:
  # Use predefined CI variables for context
  - echo "Running in project ${CI_PROJECT_PATH}"
  - echo "Commit ${CI_COMMIT_SHA} on branch ${CI_COMMIT_REF_NAME}"

build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    # Use CI_REGISTRY variables for secure Docker registry authentication
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHA .
    - docker push $DOCKER_IMAGE:$CI_COMMIT_SHA
  only: # Limit job execution
    - main
    - develop

deploy_production:
  stage: deploy
  image: alpine:latest # Minimal image
  before_script:
    - apk add --no-cache curl git # Install only needed tools
  script:
    # Use masked & protected CI/CD variables (set in GitLab UI: Settings > CI/CD > Variables)
    # DEPLOY_TRIGGER_TOKEN: Masked, Protected
    # TARGET_PROJECT_ID: Not masked, Protected
    - |
      curl --fail --request POST \
        --form token=$DEPLOY_TRIGGER_TOKEN \
        --form ref=main \
        --form "variables[ENVIRONMENT]=production" \
        "https://gitlab.com/api/v4/projects/${TARGET_PROJECT_ID}/trigger/pipeline"
  environment: # Define GitLab Environment
    name: production
    url: https://app.example.com
  only:
    refs:
      - main # Only run on main branch
  when: manual # Require manual trigger for production deploy

# Using GitLab's dependency proxy (securely caches Docker Hub images)
build_with_proxy:
  stage: build
  # Use the proxy-enabled image name format
  image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:latest 
  services:
    - name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/docker:dind
      alias: docker
  before_script:
    # Login to proxy using job token credentials
    - docker login -u $CI_DEPENDENCY_PROXY_USER -p $CI_DEPENDENCY_PROXY_PASSWORD $CI_DEPENDENCY_PROXY_SERVER
  script:
    - docker build -t myapp:latest .
```

```javascript theme={null}
// SECURE - Using environment variables and secure token management
const axios = require('axios');

class SecureGitLabClient {
    constructor() {
        this.apiUrl = process.env.GITLAB_API_URL || 'https://gitlab.com/api/v4';
        this.token = null;
        this.tokenHeader = null; // Store which header to use
    }

    async initialize() {
        // Check for different token types in order of preference
        // CI_JOB_TOKEN is preferred as it's short-lived and scoped
        if (process.env.CI_JOB_TOKEN) {
            this.token = process.env.CI_JOB_TOKEN;
            this.tokenHeader = 'JOB-TOKEN';
        } 
        // Use a PAT (Personal, Project, or Group) if outside CI or needing broader access
        else if (process.env.GITLAB_ACCESS_TOKEN) {
            this.token = process.env.GITLAB_ACCESS_TOKEN;
            this.tokenHeader = 'PRIVATE-TOKEN';
        } 
        // Add other auth methods if needed (e.g., OAuth)
        else {
            throw new Error('No GitLab authentication token found (checked CI_JOB_TOKEN, GITLAB_ACCESS_TOKEN)');
        }
        
        // Validate token by making a simple API call
        await this.validateToken();
    }

    async validateToken() {
        try {
            // Fetching user info requires 'read_user' scope for PATs
            // CI_JOB_TOKEN might only allow access within the project scope
            const endpoint = this.tokenHeader === 'JOB-TOKEN' ? `/projects/${process.env.CI_PROJECT_ID}` : '/user';
            const response = await this.makeRequest(endpoint);
            console.log(`GitLab token validated successfully. Type: ${this.tokenHeader}`);
            return true;
        } catch (error) {
            throw new Error(`GitLab token validation failed: ${error.message}`);
        }
    }

    async makeRequest(endpoint, options = {}) {
        const headers = { ...options.headers }; // Copy existing headers if any
        
        // Set appropriate header based on token type
        if (this.tokenHeader) {
            headers[this.tokenHeader] = this.token;
        }
        
        return axios({
            url: `${this.apiUrl}${endpoint}`,
            method: options.method || 'GET', // Default to GET
            headers,
            data: options.data, // Include data for POST/PUT etc.
            ...options // Pass other axios options
        });
    }

    // Example: Fetch only non-sensitive variables
    async getProjectVariables(projectId) {
        const response = await this.makeRequest(`/projects/${projectId}/variables`);
        // Filter out masked variables if needed, although API should hide values
        return response.data.filter(v => !v.masked); 
    }

    // Example: Create a protected and masked variable using the API
    async createProtectedVariable(projectId, key, value, environmentScope = '*') {
        return this.makeRequest(
            `/projects/${projectId}/variables`,
            {
                method: 'POST',
                data: {
                    key: key,
                    value: value,
                    protected: true,       // Only available in protected branches/tags
                    masked: true,          // Hidden in job logs (requires specific format)
                    environment_scope: environmentScope // e.g., 'production', 'staging', '*'
                }
            }
        );
    }
}
```

```dockerfile theme={null}
# SECURE - Docker example for secure GitLab registry access

# --- Option 1: Using BuildKit Secrets (Token not in final image) ---
# syntax=docker/dockerfile:1.4 
FROM alpine:latest AS builder
ARG GITLAB_USER
# Mount the secret file during this RUN command only
RUN --mount=type=secret,id=gitlab_registry_password \
    apk add --no-cache docker-cli && \
    export GITLAB_PASS=$(cat /run/secrets/gitlab_registry_password) && \
    docker login -u "$GITLAB_USER" -p "$GITLAB_PASS" registry.gitlab.com && \
    # Now pull base images or perform other registry operations
    docker pull registry.gitlab.com/my-group/my-base-image:latest && \
    # Copy needed artifacts
    mkdir /app && cp /path/to/artifact /app/

# Build command: docker build --secret id=gitlab_registry_password,src=./gitlab_pass.txt --build-arg GITLAB_USER=myuser .

# --- Final Stage ---
FROM alpine:latest
COPY --from=builder /app /app
# No credentials remain in this final image

# --- Option 2: Multi-stage build (Simpler but still effective) ---
FROM alpine:latest AS downloader
ARG GITLAB_TOKEN 
RUN apk add --no-cache curl && \
    # Use token to download artifacts securely
    curl -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
         "https://gitlab.com/api/v4/projects/123/jobs/artifacts/main/download?job=build" \
         -o artifacts.zip && \
    unzip artifacts.zip -d /app

# Build command: docker build --build-arg GITLAB_TOKEN=mysecrettoken ...

# --- Final Stage ---
FROM alpine:latest
COPY --from=downloader /app /app
# No credentials remain in this final image
```

```yaml theme={null}
# SECURE - Kubernetes GitLab integration
# 1. Secret for Docker Registry Auth
apiVersion: v1
kind: Secret
metadata:
  name: gitlab-registry-creds
  namespace: my-app
type: kubernetes.io/dockerconfigjson
data:
  # Create using: 
  # kubectl create secret docker-registry gitlab-registry-creds \
  #   --docker-server=registry.gitlab.com \
  #   --docker-username=<your-gitlab-deploy-token-username> \
  #   --docker-password=<your-gitlab-deploy-token-password> \
  #   --namespace=my-app \
  #   --dry-run=client -o json | jq -r '.data.".dockerconfigjson"'
  .dockerconfigjson: <base64-encoded-docker-config>

# 2. Secret for GitLab API Token (e.g., for syncing)
apiVersion: v1
kind: Secret
metadata:
  name: gitlab-api-token-secret
  namespace: tools 
type: Opaque
stringData:
  # Store the actual token value here, injected via secure means (e.g., Vault, SealedSecrets)
  token: ${GITLAB_API_TOKEN} 

# 3. Example Job using the API token
apiVersion: batch/v1
kind: Job
metadata:
  name: gitlab-repo-sync
  namespace: tools
spec:
  template:
    spec:
      containers:
      - name: sync-script
        image: curlimages/curl:latest # Or your custom image
        command: ["/bin/sh", "-c"]
        args:
          - |
            echo "Fetching projects..."
            curl --fail -H "PRIVATE-TOKEN: $GITLAB_TOKEN" \
                 "https://gitlab.com/api/v4/projects?owned=true" 
            # Add actual sync logic here
        env:
        - name: GITLAB_TOKEN
          valueFrom:
            secretKeyRef:
              name: gitlab-api-token-secret
              key: token
      restartPolicy: Never
  backoffLimit: 1
```

***

## Detection Patterns

* GitLab Personal Access Token: `` `glpat-[0-9a-zA-Z\-\_]{20}` ``
* GitLab Project Access Token: `` `glpat-[0-9a-zA-Z\-\_]{20}` ``
* GitLab Group Access Token: `` `glpat-[0-9a-zA-Z\-\_]{20}` ``
* GitLab Deploy Token Password: `` `gldt-[0-9a-zA-Z\-\_]{20}` ``
* GitLab Runner Registration Token: `` `GR1348941[0-9a-zA-Z\-\_]{20}` ``
* GitLab CI/CD Job Token (format): `` `glc[i|j]t-[0-9a-zA-Z\-\_]{20,}` `` (Note: `$CI_JOB_TOKEN` itself is secure in context)
* GitLab Trigger Token: `` `gl[p|t]t-[0-9a-zA-Z]{20,}` ``
* GitLab Feed Token: `` `feed_token_[0-9a-zA-Z\-\_]{20,}` ``

## Prevention Best Practices

1. **Use CI/CD Variables:** **Never** hardcode tokens directly in your `.gitlab-ci.yml` or scripts. Store them in **GitLab CI/CD Variables** (Settings > CI/CD > Variables). ⚙️
2. **Mask & Protect Variables:** For sensitive variables like API keys or deploy tokens, mark them as **Masked** (hides value in job logs, requires specific format) and **Protected** (only available on protected branches/tags). This significantly reduces exposure risk.
3. **Prefer Job Tokens (`$CI_JOB_TOKEN`):** Use the automatically available `$CI_JOB_TOKEN` whenever possible. It's short-lived (only valid for the job's duration) and has limited permissions scoped to the project. It's ideal for accessing the project's own container registry or package registry.
4. **Implement Token Rotation:** Regularly rotate all static tokens (Personal, Project, Group Access Tokens, Deploy Tokens). Define a schedule (e.g., every 90 days) and automate the rotation process if possible using the GitLab API.
5. **Use Project/Group Tokens over Personal:** Avoid using Personal Access Tokens (PATs) for automation. PATs are tied to a user account and often have broad permissions. Use **Project Access Tokens** or **Group Access Tokens** instead, which are designed for automation and have more granular scope control.
6. **Enforce 2FA:** Require Two-Factor Authentication (2FA) for all user accounts, especially those with Maintainer or Owner roles. This prevents account takeover, which could lead to token compromise.
7. **Monitor Audit Events:** Regularly review GitLab's **Audit Events** (Admin Area or Group/Project Settings) for suspicious activity related to token creation, usage, or CI/CD variable changes.
8. **Use Dependency Proxy:** Enable GitLab's **Dependency Proxy** to securely cache Docker Hub images. This reduces reliance on external registries and allows you to authenticate using the GitLab job token (`$CI_DEPENDENCY_PROXY_USER`, `$CI_DEPENDENCY_PROXY_PASSWORD`).
