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.
Common Misconfiguration
Exposed GitLab tokens can lead to unauthorized repository access, CI/CD pipeline manipulation, and container registry breaches. 😱
Vulnerable Example
# 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"
// 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
# 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 .
// 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', '*'
}
}
);
}
}
# 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
# 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
- 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). ⚙️
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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).