Skip to content

Commit c3bbd47

Browse files
committed
chore: close remaining Nowo full-spec compliance gaps
Wire check-open-prs and demo-smoke into release-check, document Twig overrides in USAGE, and record REQ-SEC-004 Pass (conditional) after AI audit.
1 parent b7e0784 commit c3bbd47

10 files changed

Lines changed: 192 additions & 7 deletions

File tree

.scripts/check-open-prs.sh

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env bash
2+
# Fail if the GitHub repo has unresolved open pull requests (REQ-REL-003).
3+
# Allowed temporary exceptions: label hold|do-not-merge AND a future review-by ISO date
4+
# in the PR body (or first few comments are not fetched; body must carry the date).
5+
set -euo pipefail
6+
7+
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
8+
cd "${ROOT}"
9+
10+
if ! command -v gh >/dev/null 2>&1; then
11+
echo "ERROR: gh CLI is required (REQ-REL-003)." >&2
12+
exit 1
13+
fi
14+
15+
if ! gh auth status >/dev/null 2>&1; then
16+
echo "ERROR: gh is not authenticated — log in for nowo-tech (REQ-REL-003)." >&2
17+
exit 1
18+
fi
19+
20+
if ! command -v python3 >/dev/null 2>&1; then
21+
echo "ERROR: python3 is required to evaluate open PR exceptions (REQ-REL-003)." >&2
22+
exit 1
23+
fi
24+
25+
PRS_JSON="$(gh pr list --state open --limit 100 --json number,title,labels,body,url)"
26+
27+
export PRS_JSON
28+
python3 <<'PY'
29+
import json
30+
import os
31+
import re
32+
import sys
33+
from datetime import date
34+
35+
prs = json.loads(os.environ.get("PRS_JSON") or "[]")
36+
if not prs:
37+
print("OK: no open pull requests (REQ-REL-003)")
38+
sys.exit(0)
39+
40+
HOLD_LABELS = {"hold", "do-not-merge"}
41+
# review-by: 2026-09-01 | review_by: 2026-09-01 | review-by 2026-09-01
42+
REVIEW_BY_RE = re.compile(
43+
r"(?i)\breview[-_ ]?by\b\s*[:=]?\s*(\d{4}-\d{2}-\d{2})"
44+
)
45+
46+
today = date.today()
47+
unresolved = []
48+
held_ok = []
49+
50+
for pr in prs:
51+
labels = {str(l.get("name", "")).lower() for l in (pr.get("labels") or [])}
52+
body = pr.get("body") or ""
53+
m = REVIEW_BY_RE.search(body)
54+
review_by = None
55+
if m:
56+
try:
57+
review_by = date.fromisoformat(m.group(1))
58+
except ValueError:
59+
review_by = None
60+
61+
has_hold = bool(labels & HOLD_LABELS)
62+
hold_valid = has_hold and review_by is not None and review_by >= today
63+
64+
entry = f"#{pr['number']} {pr.get('title') or ''} ({pr.get('url') or ''})"
65+
if hold_valid:
66+
held_ok.append(f"{entry} [hold until {review_by.isoformat()}]")
67+
else:
68+
reasons = []
69+
if not has_hold:
70+
reasons.append("missing hold/do-not-merge label")
71+
if review_by is None:
72+
reasons.append("missing review-by YYYY-MM-DD in body")
73+
elif review_by < today:
74+
reasons.append(f"review-by {review_by.isoformat()} expired")
75+
unresolved.append(f"{entry} — {', '.join(reasons)}")
76+
77+
if held_ok:
78+
print("Allowed hold exceptions:")
79+
for line in held_ok:
80+
print(f" {line}")
81+
82+
if unresolved:
83+
print("ERROR: unresolved open pull requests (REQ-REL-003):", file=sys.stderr)
84+
for line in unresolved:
85+
print(f" {line}", file=sys.stderr)
86+
print(
87+
"Merge, close with reason, or label hold/do-not-merge with a future "
88+
"review-by: YYYY-MM-DD in the PR body.",
89+
file=sys.stderr,
90+
)
91+
sys.exit(1)
92+
93+
print(f"OK: {len(held_ok)} open PR(s) covered by valid hold exceptions (REQ-REL-003)")
94+
sys.exit(0)
95+
PY

Makefile

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# CKEditor 5 Editor Bundle — development (Docker + pnpm + PHPUnit).
2-
.PHONY: help up down build shell install test test-coverage coverage-php-percent cs-check cs-fix qa clean assets assets-build assets-watch test-ts ensure-up rector rector-dry phpstan release-check release-check-demos composer-sync update validate validate-translations check-no-cursor-coauthor strip-cursor-coauthor-from-history setup-hooks
2+
.PHONY: help up down build shell install test test-coverage coverage-php-percent cs-check cs-fix qa clean assets assets-build assets-watch test-ts ensure-up rector rector-dry phpstan release-check release-check-demos composer-sync update validate validate-translations check-no-cursor-coauthor check-open-prs demo-smoke strip-cursor-coauthor-from-history setup-hooks
33

44
COMPOSE_FILE ?= docker-compose.yml
55
COMPOSE ?= docker-compose -f $(COMPOSE_FILE)
@@ -10,6 +10,7 @@ help:
1010
@echo " up / down / build / shell / install"
1111
@echo " assets (pnpm install + build) | test-ts | test | test-coverage"
1212
@echo " qa | release-check | make -C demo (symfony8) — /demo/variants for heights & themes"
13+
@echo " check-open-prs (REQ-REL-003) | demo-smoke (REQ-TEST-011)"
1314
@echo " Demos: make -C demo (see demo/README.md)"
1415

1516
build:
@@ -95,6 +96,15 @@ check-no-cursor-coauthor:
9596
@chmod +x .scripts/check-no-cursor-coauthor.sh
9697
@./.scripts/check-no-cursor-coauthor.sh HEAD
9798

99+
# REQ-REL-003 — no unresolved open GitHub PRs before release
100+
check-open-prs:
101+
@chmod +x .scripts/check-open-prs.sh
102+
@bash .scripts/check-open-prs.sh
103+
104+
# REQ-TEST-011 — demo boots and returns HTTP 200 (delegates to demo/release-verify)
105+
demo-smoke:
106+
@$(MAKE) -C demo release-verify
107+
98108
strip-cursor-coauthor-from-history:
99109
@chmod +x .scripts/strip-cursor-coauthor-from-history.sh
100110
@./.scripts/strip-cursor-coauthor-from-history.sh main
@@ -104,7 +114,7 @@ setup-hooks:
104114
@git config core.hooksPath .githooks
105115
@echo "Git hooks installed (.githooks — includes commit-msg for REQ-GIT-001)."
106116

107-
release-check: check-no-cursor-coauthor ensure-up composer-sync cs-fix cs-check rector-dry phpstan test-coverage test-ts release-check-demos
117+
release-check: check-no-cursor-coauthor check-open-prs ensure-up composer-sync cs-fix cs-check rector-dry phpstan test-coverage test-ts release-check-demos
108118

109119
release-check-demos:
110120
@if [ -d demo ]; then $(MAKE) -C demo release-check; fi

demo/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
DEMOS := symfony8
55

6-
.PHONY: help clean test-all test-coverage-all verify-all release-check release-verify
6+
.PHONY: help clean test-all test-coverage-all verify-all release-check release-verify demo-smoke
77
.PHONY: $(foreach demo,$(DEMOS),up-$(demo) down-$(demo) restart-$(demo) build-$(demo) install-$(demo) update-bundle-$(demo) shell-$(demo) logs-$(demo) test-$(demo) test-coverage-$(demo) verify-$(demo))
88
.PHONY: up down restart build install update-bundle test shell logs test-coverage verify
99

@@ -29,6 +29,7 @@ help:
2929
done
3030
@echo " release-check Pre-release: test-coverage-all, release-verify"
3131
@echo " release-verify For each demo: up → healthcheck HTTP 200 → down"
32+
@echo " demo-smoke Alias of release-verify (REQ-TEST-011)"
3233
@echo ""
3334
@echo "Generic (use DEMO=<name>): make up DEMO=symfony8, make update-bundle DEMO=symfony8, etc."
3435
@echo ""
@@ -173,6 +174,9 @@ release-verify:
173174
echo "OK $$demo (HTTP $$code)"; \
174175
done
175176
@echo "All demos passed release-verify."
177+
# REQ-TEST-011 — boot + one HTTP 200
178+
demo-smoke: release-verify
179+
176180
release-check: test-coverage-all release-verify
177181
@echo "All demos passed release-check."
178182

docs/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- `make check-open-prs` (REQ-REL-003) wired into `release-check` via `.scripts/check-open-prs.sh`.
13+
- `make demo-smoke` / `make -C demo demo-smoke` (REQ-TEST-011) — alias of demo `release-verify` (boot + HTTP 200).
14+
- [`USAGE.md`](USAGE.md): Twig template override procedure and link to the overridable templates table (REQ-TWIG-001).
15+
- [`SECURITY.md`](SECURITY.md): AI security audit record (REQ-SEC-004) — **Pass (conditional)**, 2026-07-27.
16+
17+
### Changed
18+
19+
- Dependabot: merged open dependency/CI bumps (php-cs-fixer, rector, phpstan, actions/cache, action-gh-release, ckeditor5, vite).
20+
- Security docs: CSP / MutationObserver notes; explicit HTML sanitization reminder from [`USAGE.md`](USAGE.md).
21+
1022
## [1.2.2] - 2026-07-23
1123

1224
### Added

docs/CONFIGURATION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Standard Symfony options (`label`, `required`, `translation_domain`, `attr`, …
5656

5757
## Twig overrides
5858

59-
Application templates under `templates/bundles/NowoCkeditor5EditorBundle/` **always win** over the copies inside the package. The bundle registers paths via `TwigPathsPass` so Symfony resolves app overrides first.
59+
**REQ-TWIG-001.** Application templates under `templates/bundles/NowoCkeditor5EditorBundle/` **always win** over the copies inside the package. The bundle registers paths via `TwigPathsPass` so Symfony resolves app overrides first.
6060

6161
### Procedure
6262

docs/DEMO-FRANKENPHP.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ This document describes how the **CKEditor 5 Editor Bundle** demos run under **F
55
## Contents
66

77
- [Overview](#overview)
8+
- [Demo smoke (REQ-TEST-011)](#demo-smoke-req-test-011)
89
- [What each demo includes](#what-each-demo-includes)
910
- [Development](#development)
1011
- [Production / worker mode](#production--worker-mode)
@@ -30,6 +31,18 @@ make -C demo up-symfony8
3031
# http://localhost:8021 (see demo README / PORT in .env)
3132
```
3233

34+
### Demo smoke (REQ-TEST-011)
35+
36+
Prove the demo boots and returns **HTTP 200**:
37+
38+
```bash
39+
make demo-smoke
40+
# or: make -C demo demo-smoke
41+
# or: make -C demo release-verify
42+
```
43+
44+
This starts `demo/symfony8`, curls `http://127.0.0.1:$PORT` (default **8021**), expects **200**, then tears the stack down. Included in `make -C demo release-check` / root `make release-check`.
45+
3346
## What each demo includes
3447

3548
In **`APP_ENV=dev`** (default for the demos):

docs/RELEASE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- [`UPGRADING.md`](UPGRADING.md) updated if there are migration notes.
77
- CI green on `main` ([workflow](../.github/workflows/ci.yml)).
88
- [Release security checklist (12.4.1)](SECURITY.md#release-security-checklist-1241) reviewed.
9+
- `make check-open-prs` passes (REQ-REL-003 — no unresolved open GitHub PRs).
10+
- Prefer `make release-check` before tagging (includes `check-open-prs`, QA, coverage, and demo `release-verify` / smoke).
911

1012
## Version bump
1113

docs/SECURITY.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- [Security considerations for integrators](#security-considerations-for-integrators)
66
- [Bundle responsibility](#bundle-responsibility)
7+
- [AI security audit](#ai-security-audit)
78
- [Supported versions](#supported-versions)
89
- [Reporting a vulnerability](#reporting-a-vulnerability)
910
- [Release security checklist (12.4.1)](#release-security-checklist-1241)
@@ -12,12 +13,24 @@
1213

1314
- **HTML and XSS**: This bundle stores **HTML** produced by CKEditor 5 in a form field. The bundle does **not** enforce HTML sanitization. **Your application** must sanitize or allowlist content before persisting or rendering it (e.g. HTML Purifier, DOMPurify on the client, or server-side filtering), especially for user-generated content.
1415
- **Script tags**: The widget loads `ckeditor5-editor.js` from published bundle assets. Use `assets:install` / AssetMapper hygiene and trusted builds only.
15-
- **Upload endpoints**: If you configure `upload_url`, your endpoint must validate MIME types, size limits, and authentication/authorization; the demos are examples only.
16+
- **CSP**: Prefer loading the published IIFE via `asset(...)` (no inline scripts). The widget uses a long-lived `MutationObserver` for Turbo/AJAX remounts; it does not use `eval`, `document.write`, or `innerHTML` with unsanitized HTML.
17+
- **Upload endpoints**: If you configure `upload_url`, your endpoint must validate MIME types (prefer magic-byte checks), size limits, and authentication/authorization; the demos are examples only — do not copy them to production unchanged.
1618
- **CSRF**: Upload flows may use CSRF tokens (`Ckeditor5EditorType::CSRF_UPLOAD_TOKEN_ID`) — ensure your routes validate them consistently.
1719

1820
## Bundle responsibility
1921

20-
The bundle provides a Symfony form type, Twig themes, translations, and a static JS bundle. It does not execute persisted HTML on the server beyond normal form handling.
22+
The bundle provides a Symfony form type, Twig themes, translations, and a static JS bundle. It does not execute persisted HTML on the server beyond normal form handling. Twig form themes escape dataset attributes with `|e('html_attr')`.
23+
24+
## AI security audit
25+
26+
| Field | Value |
27+
| --- | --- |
28+
| Date | 2026-07-27 |
29+
| Method | Cursor security-review (`src/`, Twig, assets, SECURITY docs, demo `.env.example`, Flex recipe) |
30+
| Grade | **Pass (conditional)** — overall **Medium** (residual) |
31+
| Open residuals | Integrator must sanitize persisted/rendered HTML (XSS with UGC); production upload endpoints must enforce auth, CSRF, content validation, and safe storage; demo upload is not production-ready |
32+
33+
See also the monorepo record in [`BUNDLES_SECURITY_ANALYSIS.md`](https://github.com/nowo-tech/bundles/blob/master/BUNDLES_SECURITY_ANALYSIS.md) (Ckeditor5EditorBundle entry).
2134

2235
## Supported versions
2336

@@ -51,5 +64,6 @@ Before tagging a release, confirm:
5164
| **Dependencies** | `composer audit` run; issues triaged. |
5265
| **Logging** | Logs do not print secrets or session identifiers unnecessarily. |
5366
| **Assets** | Built `ckeditor5-editor.js` is reproducible from source (`pnpm run build`). |
67+
| **AI security audit** | REQ-SEC-004 Pass (good/conditional) recorded (this document + monorepo analysis). |
5468

5569
Record confirmation in the release PR or tag notes.

docs/UPGRADING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ Patch release: FrankenPHP **demo** runtime switch via **`FRANKENPHP_MODE`** and
136136

137137
See [`CHANGELOG.md`](CHANGELOG.md) (section **1.2.1**).
138138

139+
## Unreleased (maintainers)
140+
141+
No application-facing API changes. Contributors: `make release-check` now runs `check-open-prs` (REQ-REL-003) and demos expose `demo-smoke` (REQ-TEST-011). Twig override procedure is also documented in [`USAGE.md`](USAGE.md).
142+
139143
## To 1.2.2 from 1.2.1
140144

141145
Patch release: Nowo standards compliance (PHPStan FrankenPHP rules, FrankenPHP Friendly banner, Twig override docs, coverage percentages, GitHub automation, JSDoc). No bundle API, YAML, or runtime behaviour changes for applications.

docs/USAGE.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Usage
22

3+
## Contents
4+
5+
- [Form type](#form-type)
6+
- [Frontend script](#frontend-script)
7+
- [Presets](#presets)
8+
- [Uploads (optional)](#uploads-optional)
9+
- [Overriding Twig templates (REQ-TWIG-001)](#overriding-twig-templates-req-twig-001)
10+
- [Translation overrides](#translation-overrides)
11+
312
## Form type
413

514
```php
@@ -15,7 +24,7 @@ $builder->add('body', Ckeditor5EditorType::class, [
1524
]);
1625
```
1726

18-
Submitted data is an **HTML string** (store in `TEXT` / `LONGTEXT` / similar).
27+
Submitted data is an **HTML string** (store in `TEXT` / `LONGTEXT` / similar). **Sanitize** before persist and before render — see [SECURITY.md](SECURITY.md).
1928

2029
## Frontend script
2130

@@ -34,3 +43,25 @@ YAML **`preset`** selects which OSS CKEditor build variant is used (`standard`,
3443
## Uploads (optional)
3544

3645
If `upload_url` is set in the profile or merged via `editor_config`, the widget may send multipart uploads with CSRF (`Ckeditor5EditorType::CSRF_UPLOAD_TOKEN_ID`). Your application must expose a compatible endpoint (see demo controllers for reference).
46+
47+
## Overriding Twig templates (REQ-TWIG-001)
48+
49+
Application templates under `templates/bundles/NowoCkeditor5EditorBundle/` **always win** over the copies inside the package (`TwigPathsPass` registers the `@NowoCkeditor5EditorBundle` namespace so app overrides are resolved first).
50+
51+
**Procedure**
52+
53+
1. Pick the `<subpath>` from the [overridable templates table](CONFIGURATION.md#overridable-templates) (path relative to `src/Resources/views/`).
54+
2. Create `templates/bundles/NowoCkeditor5EditorBundle/<subpath>` in your application (same relative path and filename).
55+
3. Clear cache if needed: `php bin/console cache:clear`.
56+
57+
Example:
58+
59+
```text
60+
templates/bundles/NowoCkeditor5EditorBundle/Form/ckeditor5_editor_theme.html.twig
61+
```
62+
63+
Full procedure, logical names (`@NowoCkeditor5EditorBundle/...`), and the complete subpath list: [CONFIGURATION.md — Twig overrides](CONFIGURATION.md#twig-overrides).
64+
65+
## Translation overrides
66+
67+
Translations use the domain **`NowoCkeditor5EditorBundle`**. Override from your app with files under `translations/` using the same domain (e.g. `translations/NowoCkeditor5EditorBundle.en.yaml`). See [CONFIGURATION.md — Translation overrides](CONFIGURATION.md#translation-overrides).

0 commit comments

Comments
 (0)