Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/ADR-004-copilot-auto-review-ruleset.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ADR-003 covers the strict `main`-only ruleset. But there are repo-wide invariant
2. **`copilot_code_review` on `~ALL` branches** β€” every **newly-created** PR (regardless of base branch) gets Copilot AI review automatically **after ruleset enablement**. This is the WeOwn baseline AI safety pattern (one of two enforcement mechanisms; the other is `weown-bot` being a "human-type" account so legacy auto-trigger fires). **Note**: Copilot evaluates auto-review eligibility at PR-creation time, so PRs that already existed before the ruleset was applied (e.g., PR #13) do **not** retroactively gain auto-review β€” they must be triggered manually for the duration of their open lifecycle. See Β§ Empirical Validation Results below for the controlled experiment confirming this PR-creation-time caching behavior.

These can't go in ADR-003 because they target `~ALL`, not `~DEFAULT_BRANCH`. They warrant a separate ADR because:

- Their compliance mappings differ (focus on data integrity + AI safety + history immutability, not change-management gating)
- They have a defense-in-depth pairing with an **enterprise-level** ruleset that mirrors the same rules at a broader scope
- Their pruning criteria (when to retire either layer) are distinct from ADR-003's review cadence
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,8 @@ GitHub's own alert is often missed because it goes to an email box that may not

**Interaction with workflows**:

- `auto-pr-to-main.yml` runs `gh pr edit --add-reviewer` to *request* a specific reviewer β€” this is a suggestion, not enforcement.
- The ruleset's "1 reviewer + Code Owners review" is the *enforcement* layer. Both are needed: request for discoverability, ruleset for gating.
- `auto-pr-to-main.yml` runs `gh pr edit --add-reviewer` to _request_ a specific reviewer β€” this is a suggestion, not enforcement.
- The ruleset's "1 reviewer + Code Owners review" is the _enforcement_ layer. Both are needed: request for discoverability, ruleset for gating.
- `branch-name-check.yml` is the only workflow currently required as a status check. `pat-health-check.yml` runs on `schedule:` so it cannot be a PR-time required status check; it surfaces red-X independently in the Actions tab when the PAT is ≀3 days from expiration.

### 8.2 Branch Naming Enforcement
Expand Down Expand Up @@ -596,7 +596,7 @@ Consolidated reference for the most common failure signatures across all workflo
| "Branch Name Check" shows red βœ— on PR | Branch doesn't match regex or uses `<dev>` <2 chars / `<description>` <3 chars | Rename the branch locally; force-push is BLOCKED by `non_fast_forward` ruleset β€” open a NEW branch with a compliant name instead |
| **Copilot auto-review** | | |
| No Copilot review after push to existing PR | PR was created before Copilot Business entitlement was provisioned (2026-04-27). Auto-trigger is PR-creation-time. | Manual trigger via `gh api --method POST /repos/WeOwnNetwork/ai/pulls/<N>/requested_reviewers -f reviewers[]=copilot-pull-request-reviewer` (canonical GitHub Copilot reviewer login β€” same value referenced by [ADR-004](../ADR-004-copilot-auto-review-ruleset.md)) or the "Request review" button in GitHub UI. For the long-term fix (new PRs auto-trigger correctly) see [ADR-004](../ADR-004-copilot-auto-review-ruleset.md). |
| No Copilot review on first commit of auto-created PR | **Expected behavior specific to `auto-pr-to-main.yml`.** The workflow pushes commits to the branch *before* creating the PR, so there is no new push delta when the PR is opened β€” Copilot's `review_on_push: true` only fires on pushes made *while the PR is open*. For manually-created PRs (PR opened before commits are pushed), Copilot fires at PR-creation time. | Make any follow-up push to the same branch. Copilot will review the new push automatically. All subsequent pushes on an open PR are reviewed. See [ADR-004 Β§ Empirical Validation Results](../ADR-004-copilot-auto-review-ruleset.md#empirical-validation-results). |
| No Copilot review on first commit of auto-created PR | **Expected behavior specific to `auto-pr-to-main.yml`.** The workflow pushes commits to the branch _before_ creating the PR, so there is no new push delta when the PR is opened β€” Copilot's `review_on_push: true` only fires on pushes made _while the PR is open_. For manually-created PRs (PR opened before commits are pushed), Copilot fires at PR-creation time. | Make any follow-up push to the same branch. Copilot will review the new push automatically. All subsequent pushes on an open PR are reviewed. See [ADR-004 Β§ Empirical Validation Results](../ADR-004-copilot-auto-review-ruleset.md#empirical-validation-results). |
| No Copilot review on brand-new PR (post-2026-04-27) | Either (a) `weown-bot` Copilot Business seat revoked, or (b) rulesets misconfigured | Verify via `gh api /repos/WeOwnNetwork/ai/rulesets/12131972` β†’ rules include `copilot_code_review`; verify enterprise-level ruleset still active in Enterprise Settings |
| **Branch protection / rulesets** | | |
| `Push rejected: non-fast-forward` on feature branch | Normal β€” force-push blocked on `~ALL` branches by Layer 1 + Layer 2 rulesets (see [ADR-004](../ADR-004-copilot-auto-review-ruleset.md)) | Don't force-push. Open a new branch or use merge instead of rebase. |
Expand Down
24 changes: 24 additions & 0 deletions wordpress/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ All notable changes to this WordPress deployment will be documented in this file
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.3.0] - 2026-05-13

### πŸ› οΈ **Template Hardening Fixes (PR #15 Review)**

#### **Fixed**

- **`templates/ingress.yaml`**: TLS `secretName` was hardcoded to `wordpress-tls`. Per-site overrides in `values-burnedout.yaml` (`burnedout-tls`) and `values-ptoken.yaml` (`ptoken-tls`) were silently ignored. The template now reads `secretName` from `.Values.ingress.tls[0].secretName` when `.Values.ingress.tls` is a list, falling back to `wordpress-tls` for backward compatibility.
- **`templates/php-config-configmap.yaml`**: `auto_prepend_file = '/var/www/html/wordfence-waf.php'` was emitted unconditionally, which causes PHP warnings on every request when the Wordfence plugin is not installed. Now gated behind the new `wordpress.wordfence.enabled` flag (default `false`).
- **`templates/ingress.yaml`**: `nginx.ingress.kubernetes.io/server-snippet` annotation is now opt-out via `ingress.serverSnippet.enabled` (default `true`). Hardened ingress-nginx controllers reject snippet annotations when `allow-snippet-annotations: false`; setting this to `false` allows deployment on those clusters.

#### **Added**

- `wordpress.wordfence.enabled` (default `false`) in `values.yaml`.
- `ingress.serverSnippet.enabled` (default `true`) in `values.yaml`.

## [3.2.7] - 2026-05-06

### πŸ”§ **Version Bump & Maintenance**

#### **Updated**

- Chart version updated to 3.2.7
- Maintenance and stability improvements

## [3.3.8] - 2025-11-07

### πŸ”§ **Critical Fix: WP_MEMORY_LIMIT Configuration & Database Restoration**
Expand Down
18 changes: 9 additions & 9 deletions wordpress/helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ apiVersion: v2
name: wordpress
description: Enterprise-grade WordPress with enhanced security, zero-trust networking, and automated TLS
type: application
version: 3.2.6
version: 3.3.0
appVersion: "6.8.3"

keywords:
- wordpress
- cms
- blog
- php
- mariadb
- wordpress
- cms
- blog
- php
- mariadb

home: https://github.com/WeOwnNetwork/ai
sources:
- https://github.com/WeOwnNetwork/ai/tree/main/wordpress
- https://github.com/WeOwnNetwork/ai/tree/main/wordpress

maintainers:
- name: Enterprise WordPress
email: admin@example.com
- name: Enterprise WordPress
email: admin@example.com

# No external dependencies - using official MariaDB image via custom StatefulSet

Expand Down
109 changes: 63 additions & 46 deletions wordpress/helm/templates/ingress.yaml
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
{{- if .Values.ingress.enabled -}}
# {{ if .Values.ingress.enabled }}
# {{ $tlsProtocols := "TLSv1.2 TLSv1.3" }}
# {{ $tlsCiphers := "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305" }}
# {{ $tlsSecretName := "wordpress-tls" }}
# {{ if and (hasKey .Values.ingress "tls") (kindIs "map" .Values.ingress.tls) }}
# {{ $tlsProtocols = .Values.ingress.tls.protocols | default $tlsProtocols }}
# {{ $tlsCiphers = .Values.ingress.tls.ciphers | default $tlsCiphers }}
# {{ end }}
# {{ if and (hasKey .Values.ingress "tls") (kindIs "slice" .Values.ingress.tls) (gt (len .Values.ingress.tls) 0) }}
# {{ $tlsSecretName = (index .Values.ingress.tls 0).secretName | default $tlsSecretName }}
# {{ end }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "wordpress.fullname" . }}
namespace: {{ .Release.Namespace }}
name: '{{ include "wordpress.fullname" . }}'
namespace: "{{ .Release.Namespace }}"
labels:
{{- include "wordpress.labels" . | nindent 4 }}
# {{ include "wordpress.labels" . | nindent 4 }}
app.kubernetes.io/component: ingress
annotations:
{{- toYaml .Values.ingress.annotations | nindent 4 }}
# {{ toYaml .Values.ingress.annotations | nindent 4 }}
# {{ if and (hasKey .Values.ingress "serverSnippet") (kindIs "map" .Values.ingress.serverSnippet) .Values.ingress.serverSnippet.enabled }}
# Block sensitive files at the edge (Task #264)
nginx.ingress.kubernetes.io/server-snippet: |
location ~* /\.(git|env|user\.ini|htaccess) {
deny all;
return 403;
}
location = /wp-config.php {
deny all;
return 403;
}
# {{ end }}
# TLS/SSL Security (Enterprise-Grade, Parameterized)
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
{{- if and (hasKey .Values.ingress "tls") (kindIs "map" .Values.ingress.tls) }}
nginx.ingress.kubernetes.io/ssl-protocols: {{ .Values.ingress.tls.protocols | default "TLSv1.2 TLSv1.3" | quote }}
nginx.ingress.kubernetes.io/ssl-ciphers: {{ .Values.ingress.tls.ciphers | default "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305" | quote }}
{{- else }}
nginx.ingress.kubernetes.io/ssl-protocols: "TLSv1.2 TLSv1.3"
nginx.ingress.kubernetes.io/ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305"
{{- end }}
{{- if and .Values.wordpress.includeWWW .Values.wordpress.redirectToWWW }}
nginx.ingress.kubernetes.io/ssl-protocols: "{{ $tlsProtocols }}"
nginx.ingress.kubernetes.io/ssl-ciphers: "{{ $tlsCiphers }}"
# {{ if and .Values.wordpress.includeWWW .Values.wordpress.redirectToWWW }}
# Redirect root domain to www subdomain (using from-to-www annotation)
nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
{{- end }}
# {{ end }}
# WordPress-specific optimizations
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
Expand All @@ -32,37 +49,37 @@ metadata:
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
nginx.ingress.kubernetes.io/rate-limit-connections: "10"
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
# {{ if .Values.ingress.className }}
ingressClassName: "{{ .Values.ingress.className }}"
# {{ end }}
tls:
- hosts:
- {{ .Values.wordpress.domain | quote }}
{{- if .Values.wordpress.includeWWW }}
- {{ printf "www.%s" .Values.wordpress.domain | quote }}
{{- end }}
secretName: wordpress-tls
- hosts:
- "{{ .Values.wordpress.domain }}"
# {{ if .Values.wordpress.includeWWW }}
- '{{ printf "www.%s" .Values.wordpress.domain }}'
# {{ end }}
secretName: "{{ $tlsSecretName }}"
rules:
- host: {{ .Values.wordpress.domain | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "wordpress.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- if .Values.wordpress.includeWWW }}
- host: {{ printf "www.%s" .Values.wordpress.domain | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "wordpress.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- end }}
{{- end }}
- host: "{{ .Values.wordpress.domain }}"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: '{{ include "wordpress.fullname" . }}'
port:
# {{ printf "number: %v" .Values.service.port | nindent 18 }}
# {{ if .Values.wordpress.includeWWW }}
- host: '{{ printf "www.%s" .Values.wordpress.domain }}'
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: '{{ include "wordpress.fullname" . }}'
port:
# {{ printf "number: %v" .Values.service.port | nindent 18 }}
# {{ end }}
# {{ end }}
18 changes: 11 additions & 7 deletions wordpress/helm/templates/php-config-configmap.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "wordpress.fullname" . }}-php-config
namespace: {{ .Release.Namespace }}
name: '{{ include "wordpress.fullname" . }}-php-config'
namespace: "{{ .Release.Namespace }}"
labels:
{{- include "wordpress.labels" . | nindent 4 }}
# {{ include "wordpress.labels" . | nindent 4 }}
data:
uploads.ini: |
; PHP Upload Configuration for WordPress
; Allows plugin/theme uploads up to 64MB
upload_max_filesize = 64M
post_max_size = 64M
; PHP Upload Configuration - Codified (D152 & #264)
upload_max_filesize = 128M
post_max_size = 128M
max_execution_time = 300
max_input_time = 300
memory_limit = 256M
{{- if and (hasKey .Values.wordpress "wordfence") .Values.wordpress.wordfence.enabled }}

; Wordfence WAF Hardening: Forces loading via PHP-FPM
auto_prepend_file = '/var/www/html/wordfence-waf.php'
{{- end }}
13 changes: 13 additions & 0 deletions wordpress/helm/values-burnedout.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
wordpress:
domain: "burnedout.xyz"
wordpressBlogName: "BurnedOut"
includeWWW: true
redirectToWWW: true

ingress:
enabled: true
className: "nginx"
tls:
- secretName: burnedout-tls
hosts:
- burnedout.xyz
13 changes: 13 additions & 0 deletions wordpress/helm/values-ptoken.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
wordpress:
domain: "ptoken.agency"
wordpressBlogName: "PToken Agency"
includeWWW: true
redirectToWWW: true

ingress:
enabled: true
className: "nginx"
tls:
- secretName: ptoken-tls
hosts:
- ptoken.agency
Loading
Loading