Application Security

Security is a first-class concern in every KMG project. This page covers the practical dos and don'ts for secrets management, HTTP security headers, environment configuration, and source control hygiene.

Secrets Management

Do

  • Store secrets (API keys, tokens, passwords, database URLs) in environment variables
  • Use a .env file for local development — never commit it to the repository
  • Use CI/CD variables (GitLab CI > Settings > CI/CD > Variables) for pipeline secrets
  • Use Docker Compose environment or env_file to pass secrets to containers
  • Rotate tokens and credentials regularly
  • Use scoped tokens with minimum required permissions (e.g., read_api only, not full API access)

Don't

  • Never hardcode secrets in source code, templates, or configuration files
  • Never commit .env files to the repository
  • Never log secrets (watch out for debug logging that dumps request headers or environment)
  • Never include secrets in Docker images — use runtime environment variables instead
  • Never share secrets via chat, email, or issue trackers — use a password manager or vault

Example: Environment Variable Pattern

# app.py — reading secrets from environment
import os

DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///dev.db")
SECRET_KEY = os.environ.get("SECRET_KEY", "change-me-in-production")
GITLAB_TOKEN = os.environ.get("GITLAB_TOKEN", "")
# docker-compose.yml — passing secrets to the container
services:
  app:
    build: .
    env_file:
      - .env
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - SECRET_KEY=${SECRET_KEY}
# .env — local development only (never committed)
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
SECRET_KEY=a-very-long-random-string
GITLAB_TOKEN=glpat-xxxxxxxxxxxx

HTTP Security Headers

Every KMG application must set the following security headers on all responses.

Required Headers

Header Value Purpose
X-Content-Type-Options nosniff Prevents MIME-type sniffing
X-Frame-Options SAMEORIGIN Prevents clickjacking via iframes
X-XSS-Protection 1; mode=block Enables browser XSS filtering
Referrer-Policy strict-origin-when-cross-origin Controls referrer information
Header Value Purpose
Strict-Transport-Security max-age=31536000; includeSubDomains Enforces HTTPS (HSTS)
Content-Security-Policy See below Controls resource loading
Permissions-Policy camera=(), microphone=(), geolocation=() Restricts browser features

Implementation by Stack

Python / Flask

@app.after_request
def add_security_headers(response):
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "SAMEORIGIN"
    response.headers["X-XSS-Protection"] = "1; mode=block"
    response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
    return response

PHP / Slim

// SecurityHeadersMiddleware.php
$app->add(function ($request, $handler) {
    $response = $handler->handle($request);
    return $response
        ->withHeader('X-Content-Type-Options', 'nosniff')
        ->withHeader('X-Frame-Options', 'SAMEORIGIN')
        ->withHeader('X-XSS-Protection', '1; mode=block')
        ->withHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
});

Ruby on Rails

# config/application.rb
config.action_dispatch.default_headers = {
  'X-Content-Type-Options' => 'nosniff',
  'X-Frame-Options' => 'SAMEORIGIN',
  'X-XSS-Protection' => '1; mode=block',
  'Referrer-Policy' => 'strict-origin-when-cross-origin'
}

Content Security Policy (CSP)

A CSP header restricts where the browser is allowed to load resources from. Tailor this to your application's needs:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; font-src 'self'; img-src 'self' data:; frame-src 'none'

Key principles: - Start with default-src 'self' and open up only what is needed - Avoid 'unsafe-inline' for scripts — use nonces or hashes instead - Whitelist only the CDN domains you actually use - Block frame-src unless your application embeds iframes

Environment Files

The .env File

The .env file holds environment-specific configuration for local development. It must never be committed to version control.

Required .gitignore Entry

.env

What Belongs in .env

Type Example Belongs in .env?
Database credentials DATABASE_URL=postgresql://... Yes
API tokens GITLAB_TOKEN=glpat-xxx Yes
Secret keys SECRET_KEY=random-string Yes
Feature flags DEBUG=true Yes
Port numbers PORT=5055 Optional (can also be in docker-compose.yml)

What Does NOT Belong in .env

Type Where it belongs
Application code configuration config.py or similar
Public URLs (non-secret) docker-compose.yml or config files
Build-time variables Dockerfile ARG/ENV

.env.example

Every project should include a .env.example file that documents the required variables without containing real values:

# .env.example — copy to .env and fill in values
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
SECRET_KEY=change-me
GITLAB_URL=https://gitlab.example.com
GITLAB_TOKEN=
GITLAB_PROJECT_ID=

This file is committed to the repository and serves as documentation for developers setting up the project.

.gitignore

Every KMG project must have a .gitignore file that excludes sensitive and generated files.

Minimum Required Entries

# Secrets
.env

# Python
venv/
__pycache__/
*.pyc
*.egg-info/
dist/
build/

# Testing
.pytest_cache/
htmlcov/
.coverage

# OS files
.DS_Store
Thumbs.db

# IDE
.idea/
.vscode/
*.swp

Stack-Specific Additions

PHP

vendor/
composer.lock

Ruby on Rails

/log/*
/tmp/*
/storage/*
.bundle/

Node.js

node_modules/
.npm

Do

  • Keep .gitignore at the repository root
  • Add .env before your first commit — not after you accidentally commit secrets
  • Include generated files (coverage reports, compiled assets, build artifacts)
  • Include OS and IDE files (.DS_Store, .idea/, .vscode/)

Don't

  • Don't ignore files that other developers need (e.g., requirements.txt, package.json, lock files for reproducible builds)
  • Don't rely on global gitignore — always include a project-level .gitignore
  • Don't ignore test files or configuration templates

Docker Security

Do

  • Run containers as a non-root user (USER appuser)
  • Use minimal base images (*-slim variants)
  • Pin base image versions — never use :latest
  • Pass secrets via environment variables at runtime, not build args
  • Include a .dockerignore file to exclude .env, .git, venv/, etc.
  • Use HEALTHCHECK to enable container monitoring

Don't

  • Don't COPY .env into the image
  • Don't embed secrets in ENV instructions in the Dockerfile
  • Don't run as root in production
  • Don't use --privileged unless absolutely necessary
  • Don't expose unnecessary ports

.dockerignore Example

.env
.git
.gitignore
venv/
__pycache__/
*.pyc
.pytest_cache/
htmlcov/
.coverage
.DS_Store
.idea/
.vscode/
README.md
CLAUDE.md
_plan/

Keycloak OIDC Authentication

Where authentication is required, KMG applications use Keycloak with OpenID Connect (OIDC).

Do

  • Use OIDC authorization code flow (not implicit flow)
  • Validate tokens server-side
  • Store session data server-side (not in cookies or localStorage)
  • Configure proper redirect URIs (exact match, no wildcards in production)
  • Set token expiration to a reasonable duration

Don't

  • Don't store access tokens in localStorage (vulnerable to XSS)
  • Don't use long-lived tokens without refresh
  • Don't skip token validation

Security Checklist

Use this checklist when reviewing or setting up a KMG project:

### Secrets
- [ ] No hardcoded secrets in source code
- [ ] `.env` is in `.gitignore`
- [ ] `.env.example` documents required variables
- [ ] CI/CD secrets are stored in GitLab CI variables

### HTTP Headers
- [ ] X-Content-Type-Options: nosniff
- [ ] X-Frame-Options: SAMEORIGIN
- [ ] X-XSS-Protection: 1; mode=block
- [ ] Referrer-Policy: strict-origin-when-cross-origin

### Docker
- [ ] Non-root user in Dockerfile
- [ ] `.dockerignore` excludes `.env` and `.git`
- [ ] No secrets in Docker image layers
- [ ] Pinned base image version

### Source Control
- [ ] `.gitignore` excludes `.env`, IDE files, build artifacts
- [ ] No secrets in commit history
- [ ] Scoped GitLab tokens with minimum permissions

### Authentication (if applicable)
- [ ] Keycloak OIDC integration
- [ ] Server-side token validation
- [ ] Proper redirect URI configuration