- accept wildcard proxy host domains like *.example.com with validation and normalization
- make exact hosts win over overlapping wildcards in generated routes and TLS policies
- add unit coverage for host-pattern priority and wildcard domain handling
- add a single test:all entry point and clean up lint/typecheck issues so the suite runs cleanly
- run mobile layout Playwright checks under both chromium and mobile-iphone
Remove Mobile Responsive and Docker Ready cards to get a clean 4+4
grid layout. Docker is already covered in the Deploy section.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Screenshots now show the current UI (with WAF in sidebar) and realistic
traffic data: 2,299 requests over 7 days, country heatmap, protocol
breakdown, user agent distribution, and blocked request log.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The refreshed analytics screenshots showed empty data which looks bad
for the website showcase. Restored the previous versions with real
traffic data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add WAF, Instance Sync, and Mobile Responsive cards to features grid
- Update Auto HTTPS card to mention built-in CA
- Add WAF spotlight section with screenshot
- Update hero tagline and OG meta description
- Refresh all 9 screenshots from running app (new WAF screenshot added)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create tests/e2e/mobile/mobile-layout.spec.ts with 8 tests covering
AppBar/hamburger visibility, drawer open/close, mobile card rendering,
PageHeader button layout, dialog width, card actions, and analytics overflow.
- Fix AnalyticsClient: make Autocomplete full-width on mobile, add
overflow:hidden to outer Stack to prevent body scrollWidth growth.
- Fix WorldMapInner: remove hard-coded minWidth:400 that caused 73px
horizontal overflow on 393px iPhone 15 viewport.
- Fix DashboardLayoutClient: add overflowX:hidden to main content area
to contain chart library elements that exceed viewport width.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When allowWebsocket=true and WAF is enabled, the WAF handler sits first
in the handler chain and processes the initial HTTP upgrade request
(GET + Upgrade: websocket). If any rule matches, Coraza can block the
handshake before SecAuditEngine captures it — producing no log entry
and an unexplained connection failure from the client's perspective.
Fix: when allowWebsocket=true, prepend a phase:1 SecLang rule that
matches Upgrade: websocket (case-insensitive) and turns the rule engine
off for that transaction via ctl:ruleEngine=off. After the 101
Switching Protocols response the connection becomes a raw WebSocket
tunnel that the WAF cannot inspect anyway, so this bypass has no impact
on normal HTTP traffic through the same host.
The rule is inserted before OWASP CRS includes so it always fires first
regardless of which ruleset is loaded.
Add 9 unit tests in caddy-waf.test.ts covering: bypass present/absent,
phase:1 placement, case-insensitive regex, nolog/noauditlog flags,
ordering before CRS, and compatibility with custom directives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bug: when a proxy host had per-host WAF explicitly disabled (enabled:false)
with waf_mode:"merge" (or no waf_mode set), resolveEffectiveWaf entered the
merge branch and returned enabled:true unconditionally, applying the global
WAF to a host the user had opted out of.
Fix: add `if (host.enabled === false) return null` at the top of the merge
branch. Explicit opt-out now takes precedence over the global setting
regardless of mode. The override mode already handled this correctly.
Also extract resolveEffectiveWaf from caddy.ts into caddy-waf.ts so it
can be unit tested. Add 12 new tests covering no-config fallback,
merge opt-out regression, merge settings combination, and override mode.
What runs without OWASP CRS: only SecRuleEngine + audit directives +
any custom_directives. The @coraza.conf-recommended and CRS includes
are gated behind load_owasp_crs (fixed in previous commit).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add onConsoleLog: () => false to vitest config to silence expected
warn/error calls from production code when tests deliberately feed bad
input (e.g. parseJson with malformed JSON). Tests can still use
vi.spyOn(console, ...) to assert on console calls explicitly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- test.yml: add pull_request trigger (safe — permissions: {}, no secrets
referenced; pull_request event from forks gets no secrets and read-only token)
- stale.yml: add explicit permissions (issues: write, pull-requests: write)
instead of relying on potentially over-broad repo defaults
- docker-build-pr.yml: add actions: write permission required for GHA
cache writes (cache-to: type=gha)
Remaining known limitation: actions are pinned to tags (@v3/@v6) rather
than SHAs — moving a tag upstream would run arbitrary code. Low risk for
official Docker/GitHub actions but worth noting.
ci: remove cache-to from PR build, drop actions: write permission
PR builds read from the GHA cache (warmed by main branch pushes) but
don't write back. This avoids needing actions: write on a job that runs
untrusted PR code.
ci: restrict test job permissions to zero
Zero out GITHUB_TOKEN permissions and remove the non-existent
ACTIONS_RUNNER_NO_SECRETS variable. No repo secrets are referenced
in this workflow, so the test runner has no credentials available.
The workflow only triggers on push to protected branches (not PRs),
so code is reviewed before it runs.
ci: add test workflow to run unit/integration tests on push
Runs `npm test` (Vitest unit + integration tests) on every push to
main and develop. E2E tests are excluded as they require a full
Docker Compose stack.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The WAF handler always prepended 'Include @coraza.conf-recommended' to the
SecLang directives regardless of load_owasp_crs. The @-prefixed paths only
resolve from the embedded coraza-coreruleset filesystem, which the Caddy
WAF plugin mounts only when load_owasp_crs=true. Without it Caddy fails:
"failed to readfile: open @coraza.conf-recommended: no such file or directory"
Fix: gate all @-prefixed Include directives behind load_owasp_crs.
Also extract buildWafHandler from caddy.ts into caddy-waf.ts so it can be
unit tested in isolation, and add tests/unit/caddy-waf.test.ts (19 tests)
covering the regression, CRS include ordering, excluded rule IDs, and
handler structure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract pemToBase64Der and buildClientAuthentication from caddy.ts into
a new caddy-mtls.ts module, adding groupMtlsDomainsByCaSet to group mTLS
domains by their CA fingerprint before building TLS connection policies.
Previously all mTLS domains sharing a cert type (auto-managed, imported,
or managed) were grouped into a single policy, causing CA union: a client
cert from CA_B could authenticate against a host that only trusted CA_A.
The fix creates one policy per unique CA set, ensuring strict per-host
CA isolation across all three TLS policy code paths.
Also adds:
- tests/unit/caddy-mtls.test.ts (26 tests) covering pemToBase64Der,
buildClientAuthentication, groupMtlsDomainsByCaSet, and cross-CA
isolation regression tests
- tests/unit/instance-sync-env.test.ts (33 tests) for the five pure
env-reading functions in instance-sync.ts
- tests/integration/instance-sync.test.ts (16 tests) for
buildSyncPayload and applySyncPayload using an in-memory SQLite db
- Fix tests/helpers/db.ts to use a relative import for db/schema so it
works inside vi.mock factory dynamic imports
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move WAF config (enable, CRS, custom directives, templates) from
Settings page into a new Settings tab on the WAF page
- WAF page now has three tabs: Events | Suppressed Rules | Settings
- Rename nav item from "WAF Events" to "WAF", route /waf-events → /waf
- Fix excluded_rule_ids preservation: no longer wiped when form field
is absent (Settings tab omits the hidden field intentionally)
- Allow pre-adding suppressed rules even when WAF is disabled
- Reorder sidebar: Overview, Proxy Hosts, Access Lists, Certificates,
WAF, Analytics, Audit Log, Settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add lookupWafRuleMessageAction server action — queries WAF event
history for a known message for any rule ID
- Suppressed Rules tab: type a rule ID, look it up to see its
description (or a "not triggered yet" note), confirm to suppress
- Duplicate-guard: looking up an already-suppressed rule shows an error
- Search field filters the suppressed list by rule ID or message text
- Newly added rules show their message immediately without page reload
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- IssuedCertsPanel preview: only show active (non-revoked) certs
- ManageIssuedClientCertsDialog: filter out revoked by default; show
"Show revoked (N)" toggle when revoked certs exist
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Split ACME / Imported / CA-mTLS into tabs with count badges
- Add clickable status summary bar (expired / expiring soon / healthy)
- Per-tab search filter by name and domain
- Replace accordion cards with DataTable for imported certs
- Slide-in Drawers (480 px) for add/edit imported and CA certs
- File upload + show/hide toggle for private key in ImportCertDrawer
- CaCertDrawer: Generate / Import PEM tabs for add, simple form for edit
- CA tab: expandable rows showing issued client certs inline
- RelativeTime component: "in 45 days" / "EXPIRED 3 days ago" with date tooltip
- Remove CreateCaCertDialog and EditCaCertDialog (replaced by CaCertDrawer)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>