Skip to content

Security

Skipper takes security seriously. Every cluster is hardened by default with production-grade security controls, no configuration required.

Shared responsibility

Skipper provides a strong baseline of infrastructure-level security: encrypted traffic, security headers, rate limiting, isolated namespaces, and automatic backups. However, security is ultimately the responsibility of the development and operations teams building on top of Skipper.

No platform can protect against insecure application code, weak passwords, leaked credentials, or misconfigured services. Skipper gives you the foundation, and you are responsible for building securely on top of it. This includes writing secure application code, managing secrets carefully, keeping dependencies updated, and following the principle of least privilege.

We encourage you to treat Skipper's security features as a baseline, not a guarantee. Review your own application security regularly and follow the best practices documented below.

What Skipper provides out of the box

TLS everywhere

All traffic is encrypted with TLS certificates issued automatically by Let's Encrypt via cert-manager. HTTP requests are redirected to HTTPS.

Security headers

Every response from your applications includes security headers enforced at the ingress level:

HeaderValueProtection
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadForces HTTPS for 1 year
X-Frame-OptionsSAMEORIGINPrevents clickjacking
X-Content-Type-OptionsnosniffPrevents MIME type sniffing
X-XSS-Protection1; mode=blockBlocks reflected XSS attacks
Content-Security-PolicyRestricts script, style, image, and connection sources to selfPrevents XSS and data injection
Referrer-Policystrict-origin-when-cross-originControls referrer information leakage
Server / X-Powered-ByRemovedHides server technology from attackers

Each app gets its own Traefik middleware with these headers. You do not need to configure them in your application.

Customising security settings per app

The defaults work for most applications. Settings can be changed per app from the web console (app detail → Settings tab) or the CLI:

bash
# Disable security headers for a specific app
kip app deploy --name my-mvp --image myimg --port 3000 --no-security-headers

# Set a custom rate limit (requests per second)
kip app deploy --name public-api --image myimg --port 8080 --rate-limit 500

Apps without these flags use the cluster defaults (security headers enabled, 100 req/s rate limit).

CSP allowlist

The default Content Security Policy blocks external resources. If your app loads fonts, stylesheets, scripts, or connects to APIs on other domains, add them to the CSP allowlist.

From the web console: Open the app's Settings tab → enter comma-separated domains in the CSP allowlist field → Save.

Example: To load Google Fonts, add fonts.googleapis.com to the allowlist. The domains are added to style-src, font-src, script-src, and connect-src directives.

Functions have the same CSP settings. Open the function detail panel, then the Settings tab.

TIP

Only add domains you trust. Each allowlisted domain can serve scripts, styles, and fonts to your users.

Rate limiting

All endpoints are rate-limited to 100 requests per second per IP address with a burst allowance of 200. This protects against:

  • Brute force attacks on login endpoints
  • Basic DDoS attacks
  • Credential stuffing
  • API abuse

The rate limit applies at the ingress level before traffic reaches your application.

Pod Security Standards

Skipper enforces the Kubernetes Pod Security Standards baseline profile on all user namespaces. This warns when deployments attempt to:

  • Run containers as root
  • Use privileged mode
  • Access the host network or host PID namespace
  • Use host path volumes
  • Escalate privileges

System namespaces (kube-system, monitoring, etc.) are excluded from enforcement to allow infrastructure components to function.

Authentication

The web console is protected by Dex (OAuth2/OIDC identity provider) with bcrypt-hashed passwords. API access requires a valid JWT token.

Secrets management

Application secrets are stored as Kubernetes Secrets, encrypted at rest by k3s. They are:

  • Never returned in API list responses (only revealed on explicit request)
  • Scoped to the project namespace (one project cannot access another's secrets)
  • Support previous version tracking and rollback
  • Injected into containers via envFrom (not visible in deployment YAML)

Namespace isolation

Each project environment runs in its own Kubernetes namespace. This provides logical isolation: each namespace has its own Deployments, Services, Secrets, and ConfigMaps. A project cannot access another project's secrets or environment variables.

Network isolation is not enforced by default

While resources are logically separated, pods in one namespace can make network requests to services in another namespace if they know the DNS name (e.g. service.other-namespace.svc.cluster.local). This is standard Kubernetes behaviour.

For full network isolation (blocking cross-namespace traffic), NetworkPolicies are needed. This is on the roadmap. If you need strict environment separation now (e.g. preventing test from accessing prod databases), deploy them on separate clusters.

Automatic backups

Daily backups at 3:00 AM include all Kubernetes resources and persistent volume data (databases). Backups are retained for 7 days with one-click restore.

Docker image best practices

Skipper works with any Docker image, but for production deployments we recommend following these security practices:

Run as a non-root user

dockerfile
FROM node:20-alpine

# Create a non-root user
RUN addgroup -S app && adduser -S app -G app

WORKDIR /app
COPY --chown=app:app . .
RUN npm ci --production

# Switch to non-root user
USER app

EXPOSE 3000
CMD ["node", "server.js"]

For Java applications:

dockerfile
FROM eclipse-temurin:21-jre-alpine

RUN addgroup -S app && adduser -S app -G app

WORKDIR /app
COPY --chown=app:app build/libs/*.jar app.jar

USER app

EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

Use minimal base images

Prefer Alpine-based images (node:20-alpine, eclipse-temurin:21-jre-alpine) over full Debian images. Smaller images have fewer packages and a smaller attack surface.

Don't store secrets in images

Never bake secrets, API keys, or passwords into Docker images. Use Skipper's secret management instead:

bash
kip app secret set my-app DATABASE_URL
kip app secret set my-app API_KEY

These are injected as environment variables at runtime, not stored in the image.

Pin image versions

Use specific version tags instead of :latest in production:

bash
# Development
kip app deploy --name api --image registry.example.com/api:latest

# Production
kip app update api --image registry.example.com/api:v1.2.3

Scan images for vulnerabilities

Before deploying to production, scan your images for known CVEs:

bash
# Using Trivy (free, open source)
trivy image registry.example.com/api:v1.2.3

Image scanning integration in Skipper is planned for a future release.

On the roadmap

These security features are planned for future releases:

FeatureDescription
Network policiesBlock cross-namespace network traffic (e.g. test cannot reach prod)
Image scanningScan images for known CVEs before deploying (Trivy integration)
WAFWeb Application Firewall rules at the ingress level
RBACRole-based access per project (viewer, deployer, admin)

Regardless of what infrastructure-level protections Skipper provides, your team is responsible for:

  • Secure application code: parameterised queries, input validation, output encoding, CSRF protection
  • Dependency management: keeping libraries and base images up to date, monitoring for CVEs
  • Secret hygiene: never committing credentials to git, rotating secrets regularly, using the hidden prompt (kip app secret set <app> KEY without =VALUE)
  • Access control: limiting who can deploy to production, using strong passwords, enabling 2FA where available
  • Monitoring: reviewing Grafana dashboards and Loki logs for suspicious activity

Verifying your security headers

You can verify that security headers are active on your deployment:

bash
curl -sI https://your-app.kipper.run | grep -i 'strict-transport\|x-frame\|x-content\|x-xss\|content-security\|referrer'

Expected output:

strict-transport-security: max-age=31536000; includeSubDomains; preload
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
content-security-policy: default-src 'self'; ...
referrer-policy: strict-origin-when-cross-origin

Released under the Apache 2.0 License.