This guide explains multiple methods to secure your API tokens and credentials for the KSeF Monitor v0.4.
Protected Credentials:
- KSeF API token
- API auth token (REST API Bearer auth)
- Pushover User Key & API Token
- Discord Webhook URL
- Slack Webhook URL
- Email SMTP password
- Webhook authentication token
| Method | Security Level | Complexity | Best For |
|---|---|---|---|
| Environment Variables | Medium | Low | Development |
| Docker Secrets | High | Medium | Production |
| Config File Only | Low | Very Low | NOT RECOMMENDED |
| External Vault | Very High | High | Enterprise |
Sensitive credentials are stored in a .env file and loaded as environment variables. The application reads them instead of storing in config.json.
-
Create .env file:
cp .env.example .env
-
Edit .env with your credentials:
# Required KSEF_TOKEN=your-actual-ksef-token-here # Notification channels (add only channels you're using) # Pushover PUSHOVER_USER_KEY=your-pushover-user-key PUSHOVER_API_TOKEN=your-pushover-api-token # Discord DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... # Slack SLACK_WEBHOOK_URL=https://hooks.slack.com/services/... # Email EMAIL_PASSWORD=your-smtp-password-or-app-password # Webhook WEBHOOK_TOKEN=your-auth-token
Note: Only include secrets for notification channels you've enabled in
config.json -
Use secure config file:
cp config.secure.json config.json
-
Use environment-enabled docker-compose:
cp docker-compose.env.yml docker-compose.yml
-
Run:
docker-compose up -d
✅ Credentials separate from code
✅ .env file in .gitignore (won't be committed)
✅ Easy to update credentials
✅ No rebuild required
chmod 600 .env # Only owner can read/write
chmod 644 config.json # Everyone can read, owner can writeDocker Secrets is a secure way to manage sensitive data in Docker Swarm. Secrets are encrypted and only available to authorized services.
-
Initialize Swarm (if not already):
docker swarm init
-
Create secrets (only for channels you're using):
# KSeF token (required) echo "your-ksef-token" | docker secret create ksef_token - # Pushover credentials (optional) echo "your-pushover-user-key" | docker secret create pushover_user_key - echo "your-pushover-api-token" | docker secret create pushover_api_token - # Discord webhook (optional) echo "https://discord.com/api/webhooks/..." | docker secret create discord_webhook_url - # Slack webhook (optional) echo "https://hooks.slack.com/services/..." | docker secret create slack_webhook_url - # Email SMTP password (optional) echo "your-smtp-password" | docker secret create email_password - # Webhook auth token (optional) echo "your-auth-token" | docker secret create webhook_token -
-
Verify secrets created:
docker secret ls
-
Use secure config:
cp config.secure.json config.json
-
Deploy with secrets:
docker stack deploy -c docker-compose.secrets.yml ksef
For local development without Swarm:
-
Create secrets directory:
mkdir -p secrets chmod 700 secrets
-
Create secret files (only for channels you're using):
# Required echo "your-ksef-token" > secrets/ksef_token # Optional notification channels echo "your-pushover-user-key" > secrets/pushover_user_key echo "your-pushover-api-token" > secrets/pushover_api_token echo "https://discord.com/api/webhooks/..." > secrets/discord_webhook_url echo "https://hooks.slack.com/services/..." > secrets/slack_webhook_url echo "your-smtp-password" > secrets/email_password echo "your-auth-token" > secrets/webhook_token chmod 600 secrets/*
-
Update docker-compose.yml:
secrets: ksef_token: file: ./secrets/ksef_token # Add only secrets for channels you're using pushover_user_key: file: ./secrets/pushover_user_key pushover_api_token: file: ./secrets/pushover_api_token discord_webhook_url: file: ./secrets/discord_webhook_url slack_webhook_url: file: ./secrets/slack_webhook_url email_password: file: ./secrets/email_password webhook_token: file: ./secrets/webhook_token
✅ Encrypted at rest and in transit ✅ Centralized secret management ✅ Automatic rotation support ✅ Access control ✅ Production-grade security
Update a secret:
# Example: Rotating KSeF token
docker secret rm ksef_token
echo "new-token" | docker secret create ksef_token -
docker service update --secret-rm ksef_token --secret-add ksef_token ksef_ksef-monitor
# Example: Updating Discord webhook
docker secret rm discord_webhook_url
echo "https://discord.com/api/webhooks/new-url" | docker secret create discord_webhook_url -
docker service update --secret-rm discord_webhook_url --secret-add discord_webhook_url ksef_ksef-monitorList secrets:
docker secret lsInspect secret (won't show value):
docker secret inspect ksef_token❌ Tokens visible in plain text ❌ Easy to accidentally commit to git ❌ Difficult to rotate credentials ❌ No encryption
Only for:
- Quick testing
- Throwaway environments
- Demo purposes
-
Use restrictive file permissions:
chmod 600 config.json chown root:root config.json
-
Verify .gitignore:
cat .gitignore | grep config.json -
Encrypt the file system: Use encrypted storage for the directory containing config.json
For enterprise deployments, integrate with external secret managers:
# Example integration (add to secrets_manager.py)
import hvac
client = hvac.Client(url='https://vault.example.com')
secret = client.secrets.kv.v2.read_secret_version(path='ksef/tokens')
token = secret['data']['data']['ksef_token']import boto3
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='ksef/prod/token')
token = response['SecretString']from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
client = SecretClient(vault_url="https://myvault.vault.azure.net",
credential=DefaultAzureCredential())
token = client.get_secret("ksef-token").valueGitHub Actions workflows use repository secrets for automated notifications (e.g., Pushover alerts on API spec changes).
- Go to Settings → Secrets and variables → Actions in your GitHub repository
- Click New repository secret
- Add required secrets:
| Secret | Used By | Purpose |
|---|---|---|
PUSHOVER_APP_TOKEN |
check_ksef_openapi.yml, check_ksef_fa_schema.yml |
Pushover app token for CI notifications |
PUSHOVER_USER_KEY |
check_ksef_openapi.yml, check_ksef_fa_schema.yml |
Pushover user key for CI notifications |
- Repository secrets are encrypted and only exposed to workflows
- Secrets are not passed to workflows triggered from forks
- Use separate Pushover app tokens for CI vs application (principle of least privilege)
| Secret | Environment Variable | Docker Secret | Required For | Notes |
|---|---|---|---|---|
| KSeF Token | KSEF_TOKEN |
ksef_token |
Always | API authorization |
| API Auth Token | API_AUTH_TOKEN |
api_auth_token |
API (v0.4) | REST API Bearer auth |
| Pushover User Key | PUSHOVER_USER_KEY |
pushover_user_key |
Pushover | Mobile notifications |
| Pushover API Token | PUSHOVER_API_TOKEN |
pushover_api_token |
Pushover | Mobile notifications |
| Discord Webhook | DISCORD_WEBHOOK_URL |
discord_webhook_url |
Discord | Webhook URL |
| Slack Webhook | SLACK_WEBHOOK_URL |
slack_webhook_url |
Slack | Webhook URL |
| Email Password | EMAIL_PASSWORD |
email_password |
SMTP password | |
| Webhook Token | WEBHOOK_TOKEN |
webhook_token |
Webhook | Optional auth token |
Important: Only configure secrets for notification channels you've enabled in config.json → notifications.channels.
The application loads secrets in this order (first found wins):
- Environment Variables (highest priority)
- Docker Secrets
- Config File (lowest priority)
This allows you to:
- Use config file for development
- Override with environment variables for testing
- Use Docker secrets for production
✅ Never commit secrets to version control
✅ Use .gitignore for sensitive files
✅ Rotate credentials regularly (quarterly minimum)
✅ Use different credentials for test/production
✅ Monitor access logs
✅ Use principle of least privilege
Webhook URLs (Discord, Slack, Custom):
Email: ✅ Use App Passwords instead of account passwords (Gmail, Outlook) ✅ Enable 2FA on email accounts ✅ Use dedicated email accounts for automated notifications ✅ Restrict SMTP access to required IP ranges if possible
Pushover: ✅ Use application-specific API tokens ✅ Separate applications for different environments (dev/prod) ✅ Monitor usage in Pushover dashboard for suspicious activity
# Secrets should be readable only by owner
chmod 600 .env
chmod 600 secrets/*
chmod 600 config.json # if it contains secrets
# Directories should be protected
chmod 700 secrets/
chmod 700 data/# Don't run as root (already handled in Dockerfile)
# Scan for vulnerabilities
docker scan ksef-invoice-monitor
# Use read-only mounts where possible (already configured)
# Enable content trust
export DOCKER_CONTENT_TRUST=1- Use HTTPS for all API calls (enforced)
- Enable firewall rules
- Use private networks for production
- Consider VPN for sensitive operations
# View logs (secrets are masked)
docker-compose logs | grep "loaded from"
# Should see (depending on enabled channels):
# KSeF token loaded from environment variable
# Pushover user key loaded from Docker secret
# Discord webhook URL loaded from environment variable
# Email password loaded from Docker secret
# etc.# View enabled notification channels
docker-compose logs | grep "Enabled channels"
# Example output:
# Enabled channels: discord, email, pushoverdocker-compose exec ksef-monitor python3 -c "
from app.config_manager import ConfigManager
config = ConfigManager('/config/config.json')
print('✓ Config loaded successfully')
print('Environment:', config.get('ksef', 'environment'))
print('Secrets loaded:', 'token' in str(config.get('ksef', 'token')))
"-
Immediately revoke the compromised credentials:
- KSeF: Revoke token in KSeF portal
- Pushover: Regenerate API token in Pushover app settings
- Discord: Delete and recreate webhook in Server Settings
- Slack: Regenerate webhook URL in Slack app settings
- Email: Change SMTP password (for Gmail: revoke App Password)
- Webhook: Rotate authentication token on your endpoint
-
Generate new credentials:
- Create new tokens/URLs for compromised channels
- Update secrets using the appropriate method
-
Rotate secrets:
# Docker secrets docker secret rm ksef_token echo "new-token" | docker secret create ksef_token - docker service update --secret-rm ksef_token --secret-add ksef_token ksef_ksef-monitor
-
Review access logs:
- Check for unauthorized access
- Document the incident
-
Update security measures:
- Implement additional controls
- Review and update this guide
- Credentials may be considered personal data
- Implement appropriate security measures
- Document data processing
- Encrypt credentials at rest and in transit
- Implement access controls
- Maintain audit logs
- Document security policies
- Implement change management
- Regular security reviews
Method: Environment Variables (.env file)
Config: config.secure.json
Compose: docker-compose.env.yml
Permissions: 600 on .envMethod: Docker Secrets (file-based)
Config: config.secure.json
Compose: docker-compose.secrets.yml
Permissions: 700 on secrets/Method: Docker Secrets (Swarm) or External Vault
Config: config.secure.json
Deploy: docker stack deploy
Monitoring: Enabled
Audit Logging: Enabled# 1. Setup
cp .env.example .env
cp config.secure.json config.json
cp docker-compose.env.yml docker-compose.yml
# 2. Edit secrets
nano .env
# 3. Set permissions
chmod 600 .env
# 4. Run
docker-compose up -d# 1. Setup
cp config.secure.json config.json
cp docker-compose.secrets.yml docker-compose.yml
# 2. Create secrets (only for channels you're using)
echo "prod-ksef-token" | docker secret create ksef_token -
# Example: Discord + Email channels
echo "https://discord.com/api/webhooks/..." | docker secret create discord_webhook_url -
echo "smtp-app-password" | docker secret create email_password -
# Or all channels:
# echo "..." | docker secret create pushover_user_key -
# echo "..." | docker secret create pushover_api_token -
# echo "..." | docker secret create slack_webhook_url -
# echo "..." | docker secret create webhook_token -
# 3. Update config.json with enabled channels
nano config.json # Set notifications.channels
# 4. Deploy
docker stack deploy -c docker-compose.yml ksefThe following security controls were implemented based on a v0.4 security audit:
| ID | Control | Description |
|---|---|---|
| F-01 | Auth token auto-generation | API auto-generates a random 48-char token if auth_token is empty when API is enabled. Token is logged at startup. |
| F-02 | Docs disable | /docs, /redoc, /openapi.json can be disabled with docs_enabled: false for production. |
| F-03 | Prometheus bind | Default bind changed from 0.0.0.0 to 127.0.0.1 to prevent unintended exposure. |
| F-04 | Email HTML escaping | All user-controlled fields in HTML emails are escaped via html.escape(). |
| F-06 | Email CRLF | CRLF characters stripped from email Subject header to prevent header injection. |
| F-07 | API rate limiting | slowapi middleware with configurable limits (60/minute default). |
| F-09 | Health info leak | auth_enabled field removed from /health response (info disclosure). |
| F-10 | CORS wildcard | CORS wildcard * rejected when auth_token is set. |
| F-11 | Template sandbox | Jinja2 SandboxedEnvironment replaces Environment (SSTI prevention). |
| N-03 | SSRF redirects | allow_redirects=False on all webhook/notifier HTTP calls. |
If you discover a security vulnerability in KSeF Monitor, please report it responsibly:
- Do NOT open a public GitHub issue for security vulnerabilities
- Email the maintainer directly or use GitHub Security Advisories to report privately
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
Response timeline:
- Acknowledgment within 48 hours
- Assessment and fix plan within 7 days
- Patch release within 30 days (critical issues faster)
We will credit reporters in the release notes (unless anonymity is requested).
Scope: This policy covers the KSeF Monitor application code, Docker configuration, and CI/CD pipelines. It does not cover the KSeF API itself (report those to the Ministry of Finance).
For general security questions:
- Open a GitHub issue (do NOT include actual credentials or vulnerability details)
- Contact maintainers directly for sensitive matters
Remember: Security is not a one-time setup, it's an ongoing process.