Security
Workload hardening (always on)
Every rendered pod, without opt-in:
- non-root, read-only root filesystem, all capabilities dropped, seccomp
RuntimeDefault, no service-account token; - namespaces labeled for Pod Security Admission
restricted; - a default-deny NetworkPolicy admitting only the ingress-controller namespace and same-namespace peers — selecting the application label so canary and blue/green tracks are covered too;
- builds run rootless (BuildKit without privilege, no Docker socket), with path-traversal- and symlink-rejecting artifact extraction.
Tenant isolation
One namespace per tenant per environment (pgf-<tenant>-<env>). API
tokens map to tenants and every read and write is tenant-scoped; the
tenant label travels on all resources. Every state-changing API call lands
in the audit table.
Security header profiles
security: strict in a project expands to a header bundle from the config
bucket:
# security/strict.yaml
headers:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'; base-uri 'self'
Profiles ship for basic, recommended (the platform default), strict,
internal, and pci. Header values are escaped when rendered into nginx
configuration, so a header value can never inject server directives.
Edge authentication
authentication: oidc | jwt | oauth2-proxy is enforced at the ingress via
platform-owned annotation sets (middleware/authentication-<type>.yaml).
Two properties are guaranteed:
- a missing middleware definition fails the upload with the exact object key to fix — never a silently unprotected app;
- middleware annotations override project annotations, so an app cannot annotate its way around authentication.
authentication: basic is enforced inside nginx with an htpasswd secret
the team controls.
Supply chain
Artifact signing (upload side). A tenant policy at
security/signing/<tenant>.yaml can require uploads to carry a
cosign sign-blob signature over app.zip:
required: true
publicKey: |
-----BEGIN PUBLIC KEY-----
...
Unsigned or wrongly-signed uploads are rejected with 403. The artifact digest is recorded at upload and re-verified inside the build job before anything runs.
Image signing and provenance (push side). With the platform signing
secret configured (signingSecret chart value), every built image is
signed with cosign and carries a SLSA provenance attestation recording the
artifact digest, build commands, base images, and timestamps:
cosign verify --key cosign.pub registry/acme/frontend:b-abc123…
cosign verify-attestation --key cosign.pub --type slsaprovenance registry/acme/frontend:b-abc123…
Both steps are hard failures — a signing tenant can never receive an unsigned image. Admission controllers (Kyverno, sigstore policy-controller) can then require Pagifier's signature cluster-wide.