Skip to content

Align to NPM 2.14.0 (React/TS frontend port)#65

Open
inkly wants to merge 648 commits into
openappsec:masterfrom
inkly:master
Open

Align to NPM 2.14.0 (React/TS frontend port)#65
inkly wants to merge 648 commits into
openappsec:masterfrom
inkly:master

Conversation

@inkly

@inkly inkly commented May 13, 2026

Copy link
Copy Markdown

Summary

Aligns this fork to upstream NginxProxyManager v2.14.0, which is a near-total rewrite of the frontend in React/TypeScript plus an ESM backend migration. All open-appsec WAF customizations have been preserved and re-implemented on top of the new stack.

Last alignment (#37 by @roybarda) brought us to NPM 2.12.3. This PR continues the same effort.

Release tag: `v2.14.0-appsec.1` (pushed to inkly fork).

Why this PR is big

NPM v2.13 → v2.14 deleted the entire EJS/jQuery frontend (`frontend/js/app/**`) and replaced it with React/TS under `frontend/src/`. The same delete-and-rebuild applied to every fork-side open-appsec UI surface. About 600 of the 648 commits in this PR are inherited from NginxProxyManager between v2.12.3 and v2.14.0; the rest is the open-appsec port itself (50 commits).

What changed

Layer Work
Backend Resolved merge conflicts on `proxy-host.js`, schemas, route mounts. Converted 6 fork modules from CJS to ESM (v2.14 set `backend/package.json` to `"type": "module"`). Added Knex migration `20260513120000_open_appsec.js` for the per-host appsec columns. Fixed two latent bugs in fork code in passing (missing `readline` import; `data` referenced before declaration in `nginx-openappsec.js`).
Docker Re-applied attachment install patches (`docker/lib/*.so` copies + `load_module` sed on nginx.conf) against v2.14's restructured Dockerfile. Bumped `Dockerfile.central-mgmt` base to `jc21/nginx-proxy-manager:2.14.0`.
Frontend Re-implemented every open-appsec EJS surface as React/TS: Settings page (YAML editor for local policy), Login branding, sidebar menu entry, ProxyHostModal 5th tab + per-location overrides, and the open-appsec log section as a 4-tab page (All / Detailed / Important / Notifications) with details modal and client-side filtering matching the original fork's semantics (telemetry exclusion on All, severity-based Important, source/level-based Notifications).
i18n 38 new keys across all 20 locale source files. Real English + French; English fallback elsewhere. `prebuild` hook added so `vite build` always recompiles `src/locale/lang/`.
Tooling `.gitattributes` added to enforce LF on docker/scripts/deployment/backend/frontend, preventing Windows-clone CRLF artifacts from breaking shebangs and s6 service scripts.

Notable design decisions

  • Settings page is a YAML editor, not a structured form. Because `backend/internal/setting-openappsec.js getLocalPolicy()` returns the local policy YAML as an opaque string, a structured form would force lossy parse/reserialize roundtrips. Power users edit the policy directly; for the most-common per-host knobs (mode, confidence), structured controls live in the ProxyHost modal (per-host) — which matches where they actually belong.
  • "All" log tab filters telemetry (`waap telemetry`, `waap attack type telemetry`, `ips stats`) to match the original fork's `frontend/js/app/openappsec-log/main.js` semantics. A new "Detailed" tab is a synthesized power-user view that shows everything including telemetry.
  • The migration creates the `use_openappsec`/`minimum_confidence`/`openappsec_mode` columns on `proxy_host` but `proxy-host.js` strips them from SQL writes and overlays them from the YAML on reads — by design (the YAML is the source of truth). The columns are effectively unused; consider removing or documenting in a follow-up.

Pre-existing upstream NPM bug fixed in this PR

`backend/lib/access.js:274` had a missing `new` keyword:

```diff

  •   		throw errs.PermissionError("Permission Denied", err);
    
  •   		throw new errs.PermissionError("Permission Denied", err);
    

```

Without `new`, the strict-mode ESM call returns `undefined` and the line effectively becomes `throw undefined`. Express interprets `next(undefined)` as "no error", so every permission failure surfaces as a 404 (via the catch-all in `routes/main.js`) instead of the intended 403. The same fix has been filed against NginxProxyManager upstream.

End-to-end smoke validation

Validated on a Docker stack against this branch (Docker Desktop on Windows, NPM container + appsec-agent on shared network):

  • `scripts/ci/frontend-build` produces `frontend/dist/` in 1m 1s.
  • `docker build -f docker/Dockerfile -t open-appsec-npm:2.14.0-appsec.1 .` succeeds (1.66 GB image).
  • `docker compose up -d` with NPM + appsec-agent: both containers healthy.
  • Migration `[open_appsec] proxy_host Table altered` runs cleanly on a fresh SQLite DB.
  • Backend boots, all open-appsec ESM modules load.
  • `GET /api/openappsec-settings` returns the local policy YAML.
  • `POST /api/nginx/proxy-hosts` with `use_openappsec: true, openappsec_mode: "prevent-learn", minimum_confidence: "high"` succeeds and writes the correct `specific-rules` and `practices` entries into the shared YAML.
  • `GET /api/nginx/proxy-hosts/{id}` correctly overlays the YAML values onto the response.
  • WAF curl test passes: SQL injection (`?id=1' OR '1'='1`) and XSS payloads against the proxied host are blocked by open-appsec. Agent log shows `securityAction: "Prevent"`, `waapIncidentType: "SQL Injection"`, `eventConfidence: "Very High"`, final score 1000/1000.
  • `GET /api/openappsec-log` (authenticated) returns the WAF event records, ready to be consumed by the new React log section.

Documentation included in this PR

  • `docs/superpowers/plans/2026-05-13-npm-2.14-port.md` — 7-phase implementation plan with file map and per-phase task checklists.
  • `docs/superpowers/plans/2026-05-13-npm-2.14-port-audit.md` — per-phase summary, full 91-file inventory grouped by area, build pipeline, open minor concerns, runtime smoke checklist (all green).

Known minor concerns (non-blocking, candidates for follow-up)

  • `backend/templates/openappsec.conf` is a dead Liquid template (no live reference); safe to remove in a cleanup.
  • The unused `use_openappsec`/`minimum_confidence`/`openappsec_mode` columns on `proxy_host` (see "Notable design decisions" above).
  • Empty-state strings on the four log tabs and modal field labels in `OpenAppsecFields.tsx` remain English-only; minor i18n polish.
  • 2.3 MB main bundle warning persists from upstream's chunking choices (no `manualChunks` config).

Test plan

  • tsc clean across the frontend
  • biome lint clean on all touched files
  • vite build succeeds (one pre-existing chunk-size warning)
  • docker build succeeds
  • docker compose stack runs healthy
  • Migration applies cleanly on fresh DB
  • WAF curl smoke (SQLi + XSS) blocks at the proxy, surfaces in /api/openappsec-log
  • Cypress e2e suite against this branch
  • Manual UI walkthrough in a browser (Settings YAML editor, ProxyHost open-appsec tab, log section tabs)

Bare7a and others added 30 commits November 24, 2025 18:28
Update Locale README.md to include HelpDoc/index.tsx
Fix missing 'ko' in index.ts
Bumps [jws](https://github.com/brianloveswords/node-jws) from 3.2.2 to 3.2.3.
- [Release notes](https://github.com/brianloveswords/node-jws/releases)
- [Changelog](https://github.com/auth0/node-jws/blob/master/CHANGELOG.md)
- [Commits](auth0/node-jws@v3.2.2...v3.2.3)

---
updated-dependencies:
- dependency-name: jws
  dependency-version: 3.2.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Add support for dns verification using Hurricane Electric DDNS credentials as a more secure way over account root credentials.  More information available here: https://github.com/mafredri/certbot-dns-he-ddns
fix for issue NginxProxyManager#2014
when even administrator with all_items visibility got 0 proxy hosts in dashboard.
- Add 2FA setup, enable, disable, and backup code management
- Integrate 2FA challenge flow into login process
- Add frontend modal for 2FA configuration
- Support backup codes for account recovery
jc21 and others added 30 commits February 16, 2026 11:57
…ndabot/npm_and_yarn/test/prod-minor-updates-aef0194d28

Bump eslint-plugin-cypress from 5.2.1 to 5.3.0 in /test in the prod-minor-updates group across 1 directory
…ndabot/npm_and_yarn/test/prod-patch-updates-d4d031af8e

Bump @quobix/vacuum from 0.23.5 to 0.23.8 in /test in the prod-patch-updates group across 1 directory
Bumps the dev-patch-updates group in /frontend with 3 updates: [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react), [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) and [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths).


Updates `@types/react` from 19.2.13 to 19.2.14
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@vitejs/plugin-react` from 5.1.3 to 5.1.4
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@5.1.4/packages/plugin-react)

Updates `vite-tsconfig-paths` from 6.1.0 to 6.1.1
- [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases)
- [Commits](aleclarson/vite-tsconfig-paths@v6.1.0...v6.1.1)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.2.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 5.1.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: vite-tsconfig-paths
  dependency-version: 6.1.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the prod-minor-updates group in /backend with 3 updates: [ajv](https://github.com/ajv-validator/ajv), [mysql2](https://github.com/sidorares/node-mysql2) and [otplib](https://github.com/yeojz/otplib/tree/HEAD/packages/otplib).


Updates `ajv` from 8.17.1 to 8.18.0
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](ajv-validator/ajv@v8.17.1...v8.18.0)

Updates `mysql2` from 3.16.3 to 3.17.1
- [Release notes](https://github.com/sidorares/node-mysql2/releases)
- [Changelog](https://github.com/sidorares/node-mysql2/blob/master/Changelog.md)
- [Commits](sidorares/node-mysql2@v3.16.3...v3.17.1)

Updates `otplib` from 13.2.1 to 13.3.0
- [Release notes](https://github.com/yeojz/otplib/releases)
- [Changelog](https://github.com/yeojz/otplib/blob/main/release.config.json)
- [Commits](https://github.com/yeojz/otplib/commits/v13.3.0/packages/otplib)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 8.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: mysql2
  dependency-version: 3.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: otplib
  dependency-version: 13.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
…/add-czech-translation

Add Czech translation and related locale files
…-internal-error

Fix SQLite Internal Error
…ndabot/npm_and_yarn/frontend/dev-patch-updates-38f6d3601d

Bump the dev-patch-updates group in /frontend with 3 updates
…ndabot/npm_and_yarn/backend/prod-minor-updates-4d12c0f7cc

Bump the prod-minor-updates group in /backend with 3 updates
Bumps the prod-patch-updates group in /frontend with 2 updates: [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) and [country-flag-icons](https://gitlab.com/catamphetamine/country-flag-icons).


Updates `@tanstack/react-query` from 5.90.20 to 5.90.21
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.90.21/packages/react-query)

Updates `country-flag-icons` from 1.6.12 to 1.6.13
- [Changelog](https://gitlab.com/catamphetamine/country-flag-icons/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/catamphetamine/country-flag-icons/compare/v1.6.12...v1.6.13)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.90.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: country-flag-icons
  dependency-version: 1.6.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the dev-minor-updates group in /frontend with 2 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) and [happy-dom](https://github.com/capricorn86/happy-dom).


Updates `@biomejs/biome` from 2.3.14 to 2.4.0
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.4.0/packages/@biomejs/biome)

Updates `happy-dom` from 20.5.3 to 20.6.1
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](capricorn86/happy-dom@v20.5.3...v20.6.1)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: happy-dom
  dependency-version: 20.6.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
…ertificates

Fix uploading of custom certificates
…ndabot/npm_and_yarn/frontend/prod-patch-updates-95db6732c0

Bump the prod-patch-updates group in /frontend with 2 updates
…ndabot/npm_and_yarn/frontend/dev-minor-updates-d71d2fefd7

Bump the dev-minor-updates group in /frontend with 2 updates
…or ESM port

Review of the v2.14.0 merge into align-2.14 surfaced bugs introduced when the
fork's open-appsec patches were re-applied on top of upstream's data→thisData
rename. This commit lands the targeted fixes; broader work (ESM-porting the
openappsec backend modules) is deferred to Phase 2.

- Critical #1 (update()): the three `delete data.X` calls were mutating the
  input parameter, not the SQL payload. `_.assign` had already copied
  use_openappsec / openappsec_mode / minimum_confidence onto thisData, so the
  PATCH was still sending the unknown columns to SQL. Switched the deletes to
  target thisData.

- Critical openappsec#2 (create()): db_data was being built from the un-normalized
  original `data`, so cleanSslHstsData and the advanced_config default never
  reached the INSERT. Restructured: run all normalization on thisData first,
  then clone thisData into db_data and strip the openappsec-only fields. Any
  future upstream normalization is now automatically picked up by the insert.

- Critical openappsec#3 (Option B): backend would not boot because routes/main.js
  ESM-imports the still-CommonJS openappsec-log / openappsec-settings routes.
  Commented out both imports and their router.use mounts with TODO Phase 2
  markers. Full ESM port of the openappsec backend modules is the Phase 2
  task; this keeps Phase 1 bootable for a smoke test of the rest of the merge.

- Important openappsec#4: added `import { nginx as logger } from "../logger.js";`,
  replaced the two console.log calls in the generateConfig .catch blocks with
  logger.error, and dropped the misleading dead `// throw new error.X` comments
  (the import is `errs` post-v2.14, not `error`).

- Important openappsec#5: normalized the appsec field blocks in the three proxy-host
  schema JSONs (post.json, hostID/get.json, hostID/put.json) from spaces to
  tabs to match the surrounding files. Also fixed the stray-space indent on
  the trust_forwarded_proto `$ref` lines in post.json and put.json (pre-
  existing but in the same edit hunks), the trailing space after the locations
  block in post.json, and removed the blank line inside its properties object.
  All three JSONs still parse.

- Important openappsec#6: aligned the .catch in delete()'s openappsec deleteConfig chain
  with the surrounding tab-indent style and removed the trailing-whitespace
  blank line below it.

Verified: node --check on proxy-host.js and routes/main.js, JSON.parse on the
three schema files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, add migration

- Convert 5 fork-only backend modules from CommonJS to ESM to match the
  package-level "type": "module" of the v2.14 backend:
    * backend/internal/nginx-openappsec.js
    * backend/internal/openappsec-log.js  (also adds missing 'node:readline' import)
    * backend/internal/setting-openappsec.js
    * backend/routes/openappsec-log.js
    * backend/routes/openappsec-settings.js
- Convert backend/lib/constants.js (fork-only) from CJS to ESM for the same reason.
- Re-enable the two route mounts in backend/routes/main.js that Phase 1
  had commented out as a TODO (/openappsec-log and /openappsec-settings).
- Add Knex migration 20260513120000_open_appsec.js adding three columns to
  proxy_host: use_openappsec, minimum_confidence, openappsec_mode (matches
  ESM migration style of 20260131163528_trust_forwarded_proto.js).
- Replace two stray console.log() calls with logger.error() in
  backend/internal/proxy-host.js (Phase 1 re-review follow-up).
Adds React/TypeScript UI on top of the v2.14 frontend for the open-appsec
backend integration delivered in Phase 3:

- API client: getOpenAppsecSettings / updateOpenAppsecSettings hitting
  /api/openappsec-settings. The backend returns/accepts the raw YAML
  policy as a string, so the type is modelled as `OpenAppsecLocalPolicy`
  (string) rather than a structured doc.
- TanStack Query hook: useOpenAppsecSettings + useSetOpenAppsecSettings
  mirroring useSetting.
- New Settings sub-tab "open-appsec" with a YAML CodeEditor and a Save
  button. Layout.tsx now switches between tabs via local state (matching
  the existing internal-state pattern; not nested routes).
- Login screen branding: small open-appsec logo + "Protected by
  open-appsec" caption beneath the version string.
- Sidebar menu: new "open-appsec Logs" entry routing to /open-appsec-log.
  Router.tsx gains a small placeholder component for that route which
  Phase 6 will replace with the real log viewer page.
- /ext/appsec/local_policy.yaml logo asset copied to public/images.
- i18n: 8 new keys added to all 20 locale files. English and French
  carry real translations; the other 18 languages fall back to the
  English string (per the porting convention).

Verified:
- npx tsc --noEmit -> clean
- npx vite build  -> succeeds (only the pre-existing 500kB chunk-size
  warning)
- npx biome lint  -> clean (only the pre-existing biome schema-version
  info notice)

Note: the official `node check-locales.cjs` script could not be run on
this Windows host because it spawns `yarn` directly (not via corepack).
A manual verification script confirmed all 8 new keys are present in
every locale file.
Replaces the Phase 4 /open-appsec-log placeholder with a real page that
renders the security log feed from GET /api/openappsec-log. Adds a
typed API client + react-query hook, a shared TanStack Table wrapper
parameterised by a column variant ("summary" or "detailed"), and four
nested tab routes: All, Detailed, Important, Notifications.

Important and Notifications reuse the original fork's filter rules
(eventSeverity in {critical,high} for Important; eventLevel = "action
item" plus a source-name fallback for Notifications). All rows sort by
eventTime descending and open an OpenAppsecLogDetailsModal that lists
the wrapped fields and pretty-prints the raw open-appsec payload as
JSON. A new SeverityFormatter renders the severity badge.

i18n: added 20 keys (tab labels, column headers, details modal title)
to all 20 locale files with English defaults (French translations for
fr.json). Dropped the no-longer-needed openappsec-log.coming-soon key.
…er, use row.original for modal lookup

C1 (Option B): The Critical issue was that `frontend/src/locale/lang/` (the
compiled output read by `IntlProvider`) is gitignored, and `npm run build`
did not chain into `locale-compile`. The CI script `scripts/ci/frontend-build`
runs `yarn locale-compile && yarn build` externally, but `vite.config.ts`
only auto-compiles in `configureServer` (dev mode), so `npm run build` alone
produced a bundle with the 21 new Phase 4-6 keys missing. Fixed by adding a
`prebuild` script to `frontend/package.json` so `npm run build` is now
self-contained; the CI step still works (idempotent re-compile).

I1: The original fork's `frontend/js/app/openappsec-log/main.js` filtered
three telemetry-noise event names ("waap telemetry",
"waap attack type telemetry", "ips stats") from its "All Events" tab.
Phase 6 dropped that filter; restored it in `All/index.tsx` so users
migrating from the fork see the same content under "All". The Detailed
tab is untouched and still shows telemetry for power users.

I2: Replaced fragile `info.row.index` (which is the rendered/sorted index,
not the input array index) with `info.row.original` in `columns.tsx`.
`TableWrapper.tsx` no longer needs to look up `rows[index]`, and `Table.tsx`'s
`onSelectRow` signature now accepts the entry directly. This makes the row
click independent of any future sort/reorder behaviour.

Verification:
- `npx tsc --noEmit` clean
- `biome lint` clean on touched files
- `npm run build` succeeds end-to-end (verified after wiping lang/)
- `grep -c openappsec-log frontend/src/locale/lang/en.json` = 21 post-compile
… templates

The two open-appsec backend templates were committed with CRLF by the
original fork (Windows-authored). On Linux containers this produces
parse errors in YAML and stray \r in NGINX includes. Force LF via
.gitattributes and renormalize.

Also adds rules so future Windows clones (Git autocrlf=true) keep
docker/, scripts/, deployment/, backend/, frontend/ at LF — required
for shebangs, s6 service scripts, and shell helpers to execute
inside the container.

Binary file glob list keeps .so attachment libs, .gz fixtures, etc.
untouched.

Found while running the Phase 7 runtime smoke: docker build failed
at install-s6 with 'bash: -: invalid option' because the shebang
was '#!/bin/bash -e\r' after Windows checkout converted to CRLF.
Upstream v2.14.0 throws 'errs.PermissionError(...)' without 'new' at
lib/access.js:274. Under strict mode (ESM, which v2.14 made default
via package.json 'type: module'), the constructor function executes
with 'this = undefined', returning undefined, so the actual statement
becomes 'throw undefined'.

Express interprets next(undefined) as 'no error', so the request
flows past the .get handler unhandled and lands on main.js's
catch-all '/(.+)/' which throws ItemNotFoundError (404) — masking
every 403 Permission Denied as a 404 Not Found.

Smoke test reproduction: GET /api/users without a token, expect 403
'Permission Denied', got 404 'Not Found - /users' instead.

Fix: prepend 'new' so the PermissionError instance is built
correctly and Express sees the real 403 error.

This is a pre-existing upstream bug we inherited via the v2.14.0
merge in Phase 1. Worth filing back to NginxProxyManager.
Align fork to NPM 2.14.0 (open-appsec port to React/TS frontend)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.