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
.envfile 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
environmentorenv_fileto pass secrets to containers - Rotate tokens and credentials regularly
- Use scoped tokens with minimum required permissions (e.g.,
read_apionly, not full API access)
Don't¶
- Never hardcode secrets in source code, templates, or configuration files
- Never commit
.envfiles 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 |
Recommended Headers¶
| 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
.gitignoreat the repository root - Add
.envbefore 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 (
*-slimvariants) - Pin base image versions — never use
:latest - Pass secrets via environment variables at runtime, not build args
- Include a
.dockerignorefile to exclude.env,.git,venv/, etc. - Use
HEALTHCHECKto enable container monitoring
Don't¶
- Don't
COPY .envinto the image - Don't embed secrets in
ENVinstructions in the Dockerfile - Don't run as root in production
- Don't use
--privilegedunless 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