- Add domains field to certificate mock to exercise per-domain loop
in Dashboard component, covering the previously untested branch
- Extend CrowdSec whitelist test suite with backdrop-click close test
to cover the dialog dismissal handler
- Remove duplicate describe blocks introduced when whitelist API tests
were appended to crowdsec.test.ts, resolving ESLint vitest/no-identical-title
errors that were blocking pre-commit hooks
The Security component renders the CrowdSec card title using the nested
translation key 'security.crowdsec.title', but the test mock only had the
flat key 'security.crowdsec'. The mock fallback returns the key string
itself when a lookup misses, causing getByText('CrowdSec') to find nothing.
Added 'security.crowdsec.title' to the securityTranslations map so the
mock resolves to the expected 'CrowdSec' string, matching the component's
actual t() call and allowing the title assertion to pass.
- Implement test to deselect a row checkbox in CertificateList by clicking it a second time.
- Add test to close detail dialog via the close button in CertificateList.
- Add test to close export dialog via the cancel button in CertificateList.
- Add test to show KEY format badge when a .key file is uploaded in CertificateUploadDialog.
- Add test to ensure no format badge is shown for unknown file extensions in CertificateUploadDialog.
- Implemented CertificateExportDialog for exporting certificates in various formats (PEM, PFX, DER) with options to include private keys and set passwords.
- Created CertificateUploadDialog for uploading certificates, including validation and support for multiple file types (certificates, private keys, chain files).
- Updated DeleteCertificateDialog to use 'domains' instead of 'domain' for consistency.
- Refactored BulkDeleteCertificateDialog and DeleteCertificateDialog tests to accommodate changes in certificate structure.
- Added FileDropZone component for improved file upload experience.
- Enhanced translation files with new keys for certificate management features.
- Updated Certificates page to utilize the new CertificateUploadDialog and clean up the upload logic.
- Adjusted Dashboard and ProxyHosts pages to reflect changes in certificate data structure.
- Implemented certificate parsing for PEM, DER, and PFX formats.
- Added functions to validate key matches and certificate chains.
- Introduced metadata extraction for certificates including common name, domains, and issuer organization.
- Created unit tests for all new functionalities to ensure reliability and correctness.
- Rewrote commit slicing guidance in Management, Planning, and subagent
instruction files to enforce one-feature-one-PR with ordered logical commits
- Removed multi-PR branching logic from the execution workflow
- Prevents partial feature merges that cause user confusion on self-hosted tools
- All cross-references now use "Commit N" instead of "PR-N"
No upstream fix available for libcrypto3/libssl3 in Alpine 3.23.3.
Accepted risk documented in SECURITY.md. Monitoring Alpine security
advisories for patch availability.
Patch vulnerable transitive dependencies across all three compiled
binaries in the Docker image (backend, Caddy, CrowdSec):
- go-jose/v3 and v4: JOSE/JWT validation bypass (CVE-2026-34986)
- otel/sdk: resource leak in OpenTelemetry SDK (CVE-2026-39883)
- pgproto3/v2: buffer overflow via pgx/v4 bump (CVE-2026-32286)
- AWS SDK v2: event stream injection in CrowdSec deps (GHSA-xmrv-pmrh-hhx2)
- OTel HTTP exporters: request smuggling (CVE-2026-39882)
- gRPC: bumped to v1.80.0 for transitive go-jose/v4 resolution
All Dockerfile patches include Renovate annotations for automated
future tracking. Renovate config extended to cover Go version and
GitHub Action refs in skill example workflows, preventing version
drift in non-CI files. SECURITY.md updated with pre-existing Alpine
base image CVE (no upstream fix available).
Nightly Go stdlib CVEs (1.26.1) self-heal on next development sync;
example workflow pinned to 1.26.2 for correctness.
- Removed redundant `gin.SetMode(gin.TestMode)` calls from individual test files.
- Introduced a centralized `TestMain` function in `testmain_test.go` to set the Gin mode for all tests.
- Ensured consistent test environment setup across various handler test files.
- Added ~40 backend tests covering uncovered branches in CrowdSec
dashboard handlers (error paths, validation, export edge cases)
- Patch coverage improved from 81.5% to 98.3%, exceeding 90% threshold
- Fixed DoD ordering: coverage tests now run before the patch report
(the report requires coverage artifacts as input)
- Rewrote the local patch coverage DoD step in both the Management agent
and testing instructions to clarify purpose, prerequisites, required
action on findings, and blocking gate semantics
- Eliminated ambiguous "advisory" language that allowed agents to skip
acting on uncovered lines
- Implemented TopAttackingIPsChart component for visualizing top attacking IPs.
- Created hooks for fetching CrowdSec dashboard data including summary, timeline, top IPs, scenarios, and alerts.
- Added tests for the new hooks to ensure data fetching works as expected.
- Updated translation files for new dashboard terms in multiple languages.
- Refactored CrowdSecConfig page to include a tabbed interface for configuration and dashboard views.
- Added end-to-end tests for CrowdSec dashboard functionality including tab navigation, data display, and interaction with time range and refresh features.
- Updated the list of supported notification provider types to include 'ntfy'.
- Modified the notification settings UI to accommodate the Ntfy provider, including form fields for topic URL and access token.
- Enhanced localization files to include translations for Ntfy-related fields in German, English, Spanish, French, and Chinese.
- Implemented tests for the Ntfy notification provider, covering form rendering, CRUD operations, payload contracts, and security measures.
- Updated existing tests to account for the new Ntfy provider in various scenarios.
Renovate could not resolve the Go module path
github.com/oschwald/geoip2-golang/v2 because the /v2 suffix is a Go
module convention, not a separate GitHub repository. Added a packageRules
entry with an explicit sourceUrl pointing to the actual upstream repo so
Renovate can correctly look up available versions.
No changes to application code, go.mod, or go.sum — the dependency was
already declared correctly.
- Upgraded @tanstack/query-core and @tanstack/react-query from 5.95.0 to 5.95.2
- Updated @typescript-eslint packages from 8.57.1 to 8.57.2
- Bumped @vitest packages from 4.1.0 to 4.1.1
- Updated knip from 6.0.3 to 6.0.4
- Upgraded picomatch from 4.0.3 to 4.0.4 and from 2.3.1 to 2.3.2
- Updated react-router and react-router-dom from 7.13.1 to 7.13.2
- Bumped typescript from 6.0.1-rc to 6.0.2
Removed local i18n mock to allow global mock to function correctly, updated assertions to use resolved English translations for better consistency in test outcomes.
- Added clarity and structure to README files, including recent updates and getting started sections.
- Improved manual verification documentation for CrowdSec authentication, emphasizing expected outputs and success criteria.
- Updated debugging guide with detailed output examples and automatic trace capture information.
- Refined best practices for E2E tests, focusing on efficient polling, locator strategies, and state management.
- Documented triage report for DNS Provider feature tests, highlighting issues fixed and test results before and after improvements.
- Revised E2E test writing guide to include when to use specific helper functions and patterns for better test reliability.
- Enhanced troubleshooting documentation with clear resolutions for common issues, including timeout and token configuration problems.
- Updated tests README to provide quick links and best practices for writing robust tests.
- The certificate section's noteText had previously been translated into
Chinese, German, Spanish, and French but was inadvertently overwritten
with an English string when the individual certificate delete feature
was introduced.
- All four locales now carry properly translated text that also reflects
the updated policy: expired or expiring production certificates that
are not attached to a proxy host are now eligible for deletion.
- Newly introduced keys (deleteConfirmExpiring and other delete-related
keys) remain as English placeholders pending professional translation,
which is the established pattern for this project.
- Update isInUse function to handle certificates without an ID.
- Modify isDeletable function to include 'expiring' status as deletable.
- Adjust CertificateList component to reflect changes in deletable logic.
- Update BulkDeleteCertificateDialog and DeleteCertificateDialog to handle expiring certificates.
- Add tests for expiring certificates in CertificateList and BulkDeleteCertificateDialog.
- Update translations for expiring certificates in multiple languages.
- Implemented BulkDeleteCertificateDialog with confirmation and listing of certificates to be deleted.
- Added translations for bulk delete functionality in English, German, Spanish, French, and Chinese.
- Created unit tests for BulkDeleteCertificateDialog to ensure proper rendering and functionality.
- Developed end-to-end tests for bulk certificate deletion, covering selection, confirmation, and cancellation scenarios.
- Install gotestsum in CI so the coverage script uses compact
pkgname-formatted output instead of go test -v, which produces
massive verbose logs that exceed GitHub Actions' step log buffer
- Upload the full test output as a downloadable artifact on every
run (including failures) so truncated logs never block debugging
- Aligns upload-artifact pin to v7.0.0 matching the rest of the repo
- Implement DeleteCertificateDialog component to handle certificate deletion confirmation.
- Add tests for DeleteCertificateDialog covering various scenarios including rendering, confirmation, and cancellation.
- Update translation files for multiple languages to include new strings related to certificate deletion.
- Create end-to-end tests for certificate deletion UX, including button visibility, confirmation dialog, and success/failure scenarios.
- Add getStoredAuthHeader helper that reads charon_auth_token from
localStorage and constructs an Authorization: Bearer header
- Apply the header to all page.request.* API calls in readImportStatus
and issuePendingSessionCancel
- The previous code relied on the browser cookie jar for these cleanup
API calls; with Secure=true on auth cookies, browsers refuse to send
cookies over HTTP to 127.0.0.1 (IP address, not localhost hostname)
causing silent 401s that left pending ImportSession rows in the DB
- Unreleased sessions caused all subsequent caddy-import tests to show
the pending-session banner instead of the Caddyfile textarea, failing
every test after the first
- The fix mirrors how the React app authenticates: via Authorization
header, which is transport-independent and works on both HTTP and HTTPS
- Remove the conditional secure=false branch from setSecureCookie that
allowed cookies to be issued without the Secure flag when requests
arrived over HTTP from localhost or RFC 1918 private addresses
- Pass the literal true to c.SetCookie directly, eliminating the
dataflow path that triggered CodeQL go/cookie-secure-not-set (CWE-614)
- Remove the now-dead codeql suppression comment; the root cause is
gone, not merely silenced
- Update setSecureCookie doc comment to reflect that Secure is always
true: all major browsers (Chrome 66+, Firefox 75+, Safari 14+) honour
the Secure attribute on localhost HTTP connections, and direct
HTTP-on-private-IP access without TLS is an unsupported deployment
model for Charon which is designed to sit behind Caddy TLS termination
- Update the five TestSetSecureCookie HTTP/local tests that previously
asserted Secure=false to now assert Secure=true, reflecting the
elimination of the insecure code path
- Add Secure=true assertion to TestClearSecureCookie to provide explicit
coverage of the clear-cookie path
The TCP monitor creation form showed a placeholder that instructed users to enter a URL with the tcp:// scheme prefix (e.g., tcp://192.168.1.1:8080). Following this guidance caused a silent HTTP 500 error because Go's net.SplitHostPort rejects any input containing a scheme prefix, expecting bare host:port format only.
- Corrected the urlPlaceholder translation key to remove the tcp:// prefix
- Added per-type dynamic placeholder (urlPlaceholderHttp / urlPlaceholderTcp) so the URL input shows the correct example format as soon as the user selects a monitor type
- Added per-type helper text below the URL input explaining the required format, updated in real time when the type selector changes
- Added client-side validation: typing a scheme prefix (://) in TCP mode shows an inline error and blocks form submission before the request reaches the backend
- Reordered the Create Monitor form so the type selector appears before the URL input, giving users the correct format context before they type
- Type selector onChange now clears any stale urlError to prevent incorrect error messages persisting after switching from TCP back to HTTP
- Added 5 new i18n keys across all 5 supported locales (en, de, fr, es, zh)
- Added 10 RTL unit tests covering all new validation paths including the type-change error-clear scenario
- Added 9 Playwright E2E tests covering placeholder variants, helper text, inline error lifecycle, submission blocking, and successful TCP creation
Closes #issue-5 (TCP monitor UI cannot add monitor when following placeholder)
When CrowdSec is first enabled, the 10-60 second startup window caused
the toggle to immediately flicker back to unchecked, the card badge to
show 'Disabled' throughout startup, CrowdSecKeyWarning to flash before
bouncer registration completed, and CrowdSecConfig to show alarming
LAPI-not-ready banners to the user.
Root cause: the toggle, badge, and warning conditions all read from
stale sources (crowdsecStatus local state and status.crowdsec.enabled
server data) which neither reflects user intent during a pending mutation.
- Derive crowdsecChecked from crowdsecPowerMutation.variables during
the pending window so the UI reflects intent immediately on click,
not the lagging server state
- Show a 'Starting...' badge in warning variant throughout the startup
window so the user knows the operation is in progress
- Suppress CrowdSecKeyWarning unconditionally while the mutation is
pending, preventing the bouncer key alert from flashing before
registration completes on the backend
- Broadcast the mutation's running state to the QueryClient cache via
a synthetic crowdsec-starting key so CrowdSecConfig.tsx can read it
without prop drilling
- In CrowdSecConfig, suppress the LAPI 'not running' (red) and
'initializing' (yellow) banners while the startup broadcast is active,
with a 90-second safety cap to prevent stale state from persisting
if the tab is closed mid-mutation
- Add security.crowdsec.starting translation key to all five locales
- Add two backend regression tests confirming that empty-string setting
values are accepted (not rejected by binding validation), preventing
silent re-introduction of the Issue 4 bug
- Add nine RTL tests covering toggle stabilization, badge text, warning
suppression, and LAPI banner suppression/expiry
- Add four Playwright E2E tests using route interception to simulate
the startup delay in a real browser context
Fixes Issues 3 and 4 from the fresh-install bug report.
The settings handler SSRF test table expected the generic "private ip"
error string for the cloud-metadata case (169.254.169.254). After the
url_validator was updated to return a distinct "cloud metadata" error for
that address, the handler test's errorContains check failed on every CI run.
Updated the test case expectation from "private" to "cloud metadata" to
match the more precise error message now produced by the validator.
- IPv4-mapped cloud metadata (::ffff:169.254.169.254) previously fell through
the IPv4-mapped IPv6 detection block and returned the generic private-IP error
instead of the cloud-metadata error, making the two cases inconsistent
- The IPv4-mapped error path used ip.String() (the raw ::ffff:… form) directly
rather than sanitizeIPForError, potentially leaking the unsanitized IPv6
address in error messages visible to callers
- Now extracts the IPv4 from the mapped address before both the cloud-metadata
comparison and the sanitization call, so ::ffff:169.254.169.254 produces the
same "access to cloud metadata endpoints is blocked" error as 169.254.169.254
and the error message is always sanitized through the shared helper
- Updated the corresponding test to assert the cloud-metadata message and the
absence of the raw IPv6 representation in the error text
HTTP/HTTPS uptime monitors targeting LAN addresses (192.168.x.x,
10.x.x.x, 172.16.x.x) permanently reported 'down' on fresh installs
because SSRF protection rejects RFC 1918 ranges at two independent
checkpoints: the URL validator (DNS-resolution layer) and the safe
dialer (TCP-connect layer). Fixing only one layer leaves the monitor
broken in practice.
- Add IsRFC1918() predicate to the network package covering only the
three RFC 1918 CIDRs; 169.254.x.x (link-local / cloud metadata)
and loopback are intentionally excluded
- Add WithAllowRFC1918() functional option to both SafeHTTPClient and
ValidationConfig; option defaults to false so existing behaviour is
unchanged for every call site except uptime monitors
- In uptime_service.go, pass WithAllowRFC1918() to both
ValidateExternalURL and NewSafeHTTPClient together; a coordinating
comment documents that both layers must be relaxed as a unit
- 169.254.169.254 and the full 169.254.0.0/16 link-local range remain
unconditionally blocked; the cloud-metadata error path is preserved
- 21 new tests across three packages, including an explicit regression
guard that confirms RFC 1918 blocks are still applied without the
option set (TestValidateExternalURL_RFC1918BlockedByDefault)
Fixes issues 6 and 7 from the fresh-install bug report.
- The DB error return branch in SeedDefaultSecurityConfig was never
exercised because all seed tests only ran against a healthy in-memory
database; added a test that closes the underlying connection before
calling the function so the FirstOrCreate error path is reached
- The letsencrypt certificate cleanup loop in Register was unreachable
in all existing tests because no test pre-seeded a ProxyHost with
an letsencrypt cert association; added a test that creates that
precondition so the log and Update lines inside the loop execute
- These were the last two files blocking patch coverage on PR #852
- All unquoted $i loop counter comparisons and ${TMP_COOKIE} curl
option arguments in the rate limit integration script were flagged
by shellcheck SC2086
- Unquoted variables in [ ] test expressions and curl -b/-c options
can cause subtle failures if the value ever contains whitespace or
glob characters, and are a shellcheck hard warning that blocks CI
linting gates
- Quoted all affected variables in place with no logic changes
- The security config Upsert update path copied all rate limit fields
from the incoming request onto the existing database record except
RateLimitMode, so the seeded default value of "disabled" always
survived a POST regardless of what the caller sent
- This silently prevented the Caddy rate_limit handler from being
injected on any container with a pre-existing config record (i.e.,
every real deployment and every CI run after migration)
- Added the missing field assignment so RateLimitMode is correctly
persisted on update alongside all other rate limit settings
- Integration test payload now also sends rate_limit_enable alongside
rate_limit_mode so the handler sync logic fires via its explicit
first branch, providing belt-and-suspenders correctness independent
of which path the caller uses to express intent
- The rate-limit integration test was sending rate_limit_enable:true in the
security config POST, but the backend injects the Caddy rate_limit handler
only when rate_limit_mode is the string "enabled"
- Because rate_limit_mode was absent from the payload, the database default
of "disabled" persisted and the guard condition always evaluated false,
leaving the handler uninjected across all 10 verify attempts
- Replaced the boolean rate_limit_enable with the string field
rate_limit_mode:"enabled" to match the exact contract the backend enforces
- Added HTTP status checks for login and security config POST requests to ensure proper error handling.
- Implemented a readiness gate for the Caddy admin API before applying security configurations.
- Increased sleep duration before verifying rate limit handler to accommodate Caddy's configuration propagation.
- Changed verification failure from a warning to a hard exit to prevent misleading test results.
- Updated Caddy admin API URL to use the canonical trailing slash in multiple locations.
- Adjusted retry parameters for rate limit verification to reduce polling noise.
- Removed stale GeoIP checksum validation from the Dockerfile's non-CI path to simplify the build process.
On a fresh install the security_configs table is auto-migrated but
contains no rows. Any code path reading SecurityConfig by name received
an empty Go struct with zero values, producing an all-disabled UI state
that offered no guidance to the user and made the security status
endpoint appear broken.
Adds a SeedDefaultSecurityConfig function that uses FirstOrCreate to
guarantee a default row exists with safe, disabled-by-default values on
every startup. The call is idempotent — existing rows are never modified,
so upgrades are unaffected. If the seed fails the application logs a
warning and continues rather than crashing.
Zero-valued rate-limit fields are intentional and safe: the Cerberus
rate-limit middleware applies hardcoded fallback thresholds when the
stored values are zero, so enabling rate limiting without configuring
thresholds results in sensible defaults rather than a divide-by-zero or
traffic block.
Adds three unit tests covering the empty-database, idempotent, and
do-not-overwrite-existing paths.
- Updated the list of supported notification provider types to include 'pushover'.
- Enhanced the notifications API tests to validate Pushover integration.
- Modified the notifications form to include fields specific to Pushover, such as API Token and User Key.
- Implemented CRUD operations for Pushover providers in the settings.
- Added end-to-end tests for Pushover provider functionality, including form rendering, payload validation, and security checks.
- Updated translations to include Pushover-specific labels and placeholders.
The slack sub-tests in TestDiscordOnly_CreateRejectsNonDiscord and
TestBlocker3_CreateProviderRejectsNonDiscordWithSecurityEvents were
omitting the required token field from their request payloads.
CreateProvider enforces that Slack providers must have a non-empty
token (the webhook URL) at creation time. Without it the service
returns "slack webhook URL is required", which the handler does not
classify as a 400 validation error, so it falls through to 500.
Add a token field to each test struct, populate it for the slack
case with a valid-format Slack webhook URL, and use
WithSlackURLValidator to bypass the real format check in unit tests —
matching the pattern used in all existing service-level Slack tests.
- Expanded fetchSessionUser to include Bearer token from localStorage as a fallback for authentication when Secure cookies fail.
- Updated headers to conditionally include Authorization if a token is present.
- Ensured compatibility with the recent fix for the Secure cookie flag on private network connections.
- Updated the notification provider types to include 'slack'.
- Modified API tests to handle 'slack' as a valid provider type.
- Enhanced frontend forms to display Slack-specific fields (webhook URL and channel name).
- Implemented CRUD operations for Slack providers, ensuring proper payload structure.
- Added E2E tests for Slack notification provider, covering form rendering, validation, and security checks.
- Updated translations to include Slack-related text.
- Ensured that sensitive information (like tokens) is not exposed in API responses.
- Bump versions of @vitejs/plugin-react, @vitest/coverage-istanbul, @vitest/coverage-v8, and @vitest/ui to their beta releases.
- Upgrade Vite and Vitest to their respective beta versions.
- Adjust Vite configuration to disable code splitting for improved React initialization stability.
- Updated @eslint/js and eslint to version 10.0.0 in package.json.
- Adjusted overrides for eslint-plugin-react-hooks, eslint-plugin-jsx-a11y, and eslint-plugin-promise to ensure compatibility with ESLint v10.
- Modified lefthook.yml to reflect the upgrade and noted the need for plugin support for ESLint v10.
- Removed duplicate @typescript-eslint/utils dependency in frontend/package.json
- Updated TypeScript version from 5.9.3 to 6.0.1-rc in frontend/package.json and package.json
- Adjusted ResizeObserver mock to use globalThis in tests
- Modified tsconfig.json and tsconfig.node.json to include empty types array
- Cleaned up package-lock.json to reflect TypeScript version change and updated dev dependencies
- Added aria-label attributes to buttons in Notifications component for better accessibility.
- Updated Notifications tests to use new button interactions and ensure proper functionality.
- Refactored notifications payload tests to mock API responses and validate payload transformations.
- Improved error handling and feedback in notification provider tests.
- Adjusted Telegram notification provider tests to streamline edit interactions.
- Deleted sa-generate.md, sa-implement.md, and sa-plan.md as they are no longer needed.
- Removed security scan commands for CodeQL, Docker image, Go vulnerabilities, GORM, and Trivy due to redundancy.
- Eliminated SQL code review and optimization commands to streamline processes.
- Removed supply chain remediation command as it is now integrated elsewhere.
- Deleted test commands for backend and frontend coverage and unit tests to simplify testing workflow.
- Updated settings.json and CLAUDE.md to reflect the removal of commands and ensure consistency in documentation.
- Updated API to support Telegram as a notification provider type.
- Enhanced tests to cover Telegram provider creation, updates, and token handling.
- Modified frontend forms to include Telegram-specific fields and validation.
- Added localization strings for Telegram provider.
- Implemented security measures to ensure bot tokens are not exposed in API responses.
- Scope base JS/TS configs to only JS/TS file extensions, preventing
TypeError when ESLint applies core rules to markdown/CSS/JSON files
- Remove silent data loss from duplicate JSON keys in five translation
files where the second dashboard block was overriding the first
- Fix unsafe optional chaining in CredentialManager that would throw
TypeError when providerTypeInfo is undefined
- Remove stale eslint-disable directive for a rule now handled globally
by the unused-imports plugin
- Downgrade high-volume lint rules (testing-library, jsx-a11y, import-x,
vitest) from error to warn to unblock development while preserving
visibility for incremental cleanup
- Create sa-generate.md for generating implementation documentation from plans
- Create sa-implement.md for executing implementation plans step-by-step
- Create sa-plan.md for collaborating with users to design development plans
- Add security scan commands for CodeQL, Docker images, Go vulnerabilities, and GORM
- Implement SQL code review and optimization commands
- Add supply chain vulnerability remediation process
- Introduce backend and frontend test commands with coverage checks
- Update settings.json for command permissions
- Document governance, project overview, code quality rules, and critical architecture rules in CLAUDE.md
- Establish root cause analysis protocol and definition of done for development
- Implemented email notification functionality in the NotificationService.
- Added support for rendering email templates based on event types.
- Created HTML templates for various notification types (security alerts, SSL events, uptime events, and system events).
- Updated the dispatchEmail method to utilize the new email templates.
- Added tests for email template rendering and fallback mechanisms.
- Enhanced documentation to include email notification setup and usage instructions.
- Introduced end-to-end tests for the email notification provider in the settings.
The Dockerfile already centralizes all version pins into top-level ARGs
(GO_VERSION, ALPINE_IMAGE, CROWDSEC_VERSION, EXPR_LANG_VERSION, XNET_VERSION).
This change closes the remaining gaps so those ARGs are the single source of
truth end-to-end:
- nightly-build.yml now resolves the Alpine image digest at build time and
passes ALPINE_IMAGE as a build-arg, matching the docker-build.yml pattern.
Previously, nightly images were built with the Dockerfile ARG default and
without a pinned digest, making runtime Alpine differ from docker-build.yml.
- six CI workflows (quality-checks, codecov-upload, benchmark, e2e-tests-split,
release-goreleaser, codeql) declared a GO_VERSION env var but their setup-go
steps ignored it and hardcoded the version string directly. They now reference
${{ env.GO_VERSION }}, so Renovate only needs to update one value per file
and the env var actually serves its purpose.
- codeql.yml had no GO_VERSION env var at all; one is now added alongside the
existing GOTOOLCHAIN: auto entry.
When Renovate bumps Go, it updates the env var at the top of each workflow and
the Dockerfile ARG — zero manual hunting required.
Unifies the two previously independent email subsystems — MailService
(net/smtp transport) and NotificationService (HTTP-based providers) —
so email can participate in the notification dispatch pipeline.
Key changes:
- SendEmail signature updated to accept context.Context and []string
recipients to enable timeout propagation and multi-recipient dispatch
- NotificationService.dispatchEmail() wires MailService as a first-class
provider type with IsConfigured() guard and 30s context timeout
- 'email' added to isSupportedNotificationProviderType() and
supportsJSONTemplates() returns false for email (plain/HTML only)
- settings_handler.go test-email endpoint updated to new SendEmail API
- Frontend: 'email' added to provider type union in notifications.ts,
Notifications.tsx shows recipient field and hides URL/token fields for
email providers
- All existing tests updated to match new SendEmail signature
- New tests added covering dispatchEmail paths, IsConfigured guards,
recipient validation, and context timeout behaviour
Also fixes confirmed false-positive CodeQL go/email-injection alerts:
- smtp.SendMail, sendSSL w.Write, and sendSTARTTLS w.Write sites now
carry inline codeql[go/email-injection] annotations as required by the
CodeQL same-line suppression spec; preceding-line annotations silently
no-op in current CodeQL versions
- auth_handler.go c.SetCookie annotated for intentional Secure=false on
local non-HTTPS loopback (go/cookie-secure-not-set warning only)
Closes part of #800
The test used a 5ms TTL with a 10ms wall-clock sleep to simulate cache
expiry. On loaded CI runners (Azure eastus), the repull HTTP round-trip
plus disk I/O for Store easily exceeded 5ms, causing the freshly written
cache entry to also appear expired when Load was called immediately after,
producing a spurious 'cache expired' error.
HubCache already exposes a nowFn field for deterministic time injection.
Replace the sleep-based approach with a nowFn that advances the clock 2
hours, making the initial entry appear expired to Apply while keeping the
freshly re-stored entry (retrieved_at ≈ now+2h, TTL=1h) valid for the
final assertion.
Two unit tests cover the code paths introduced when email was registered
as a recognised notification provider type in Stage 2.
- TestSendExternal_EmailProviderSkipsJSONTemplate exercises the goroutine
warn path where an enabled email provider passes isDispatchEnabled but
fails supportsJSONTemplates, producing a warning log without panicking
- TestTestProvider_EmailRejectsJSONTemplateStep asserts TestProvider
returns a clear error for email providers because the JSON template
dispatch path does not apply to email delivery
Patch coverage: 6/6 changed lines covered (100%)
After email was recognised as a supported provider type, the existing
rejection assertion for unsupported types incorrectly included email
in its denial list, causing a nil-dereference panic.
- Remove email from the unsupported-type rejection list and cover it
in the accepted-types path instead
- Correct allFeaturesEnabled fixture to set email flag to true, keeping
the fixture semantically consistent with all other service flags
Add email as a recognized, feature-flagged notification service type.
The flag defaults to false and acts as a dispatch gate alongside the
existing discord, gotify, and webhook notification service flags.
- Add FlagEmailServiceEnabled constant to the notifications feature flag
registry with the canonical key convention
- Register the flag in the handler defaults so it appears in the feature
flags API response with a false default
- Recognise 'email' as a supported notification provider type so that
providers of this type pass the type validation gate
- Gate email dispatch on the new flag in isDispatchEnabled() following
the same pattern as gotify and webhook service flags
- Expand the E2E test fixtures FeatureFlags interface to include the new
flag key so typed fixture objects remain accurate
No email message dispatch is wired in this commit; the flag registration
alone makes the email provider type valid and toggleable.
Remove all deprecated Shoutrrr integration artifacts and dead legacy fallback
code from the notification subsystem.
- Remove legacySendFunc field, ErrLegacyFallbackDisabled error, and
legacyFallbackInvocationError() from notification service
- Delete ShouldUseLegacyFallback() from notification router; simplify
ShouldUseNotify() by removing now-dead providerEngine parameter
- Remove EngineLegacy engine constant; EngineNotifyV1 is the sole engine
- Remove legacy.fallback_enabled feature flag, retiredLegacyFallbackEnvAliases,
and parseFlagBool/resolveRetiredLegacyFallback helpers from flags handler
- Remove orphaned EmailRecipients field from NotificationConfig model
- Delete feature_flags_coverage_v2_test.go (tested only the retired flag path)
- Delete security_notifications_test.go.archived (stale archived file)
- Move FIREFOX_E2E_FIXES_SUMMARY.md to docs/implementation/
- Remove root-level scan artifacts tracked in error; add gitignore patterns to
prevent future tracking of trivy-report.json and related outputs
- Update ARCHITECTURE.instructions.md: Notifications row Shoutrrr → Notify
No functional changes to active notification dispatch or mail delivery.
Three tests broke when the Admin/User/Passthrough privilege model replaced
the old admin/user/guest hierarchy in PR-3.
- user-management: tighten heading locator to name='User Management' to avoid
strict mode violation; the settings layout now renders a second h1
('Settings') alongside the page content heading
- user-lifecycle: update audit trail assertion from 2 to 1; users are now
created with a role in a single API call so the backend does not emit a
user_update audit entry when STEP 2 sends the same role value as creation
- auth-fixtures: replace invalid role='guest' with role='passthrough' in the
guestUser fixture; the 'guest' role was removed in PR-3 and 'passthrough' is
the equivalent lowest-privilege role in the new model
Verified: all three previously-failing tests now pass locally.
The Account.tsx page was removed in PR-2b and replaced by UsersPage.tsx with
a UserDetailModal. Several E2E test sections still referenced UI elements that
only existed in the deleted page, causing CI failures across shards.
- admin-onboarding: update header profile link locator from /settings/account
to /settings/users to match the new navigation target in Layout.tsx
- account-settings: skip five legacy test sections (Profile Management,
Certificate Email, Password Change, API Key Management, Accessibility) that
reference deleted Account.tsx elements (#profile-name, #profile-email,
#useUserEmail, #cert-email) or assume these fields are directly on the page
rather than inside the UserDetailModal
- Each skipped section includes an explanatory comment pointing to the PR-3
'Self-Service Profile via Users Page (F10)' suite as the equivalent coverage
Verified: admin-onboarding 8/8 pass; account-settings 8 pass / 20 skipped
- Implemented middleware to restrict access for passthrough users in management routes.
- Added unit tests for management access requirements based on user roles.
- Updated user model tests to include passthrough role validation.
- Enhanced frontend user management to support passthrough role in invite modal.
- Created end-to-end tests for passthrough user access restrictions and navigation visibility.
- Verified self-service profile management for admins and regular users.
The scheduled CodeQL analysis explicitly passed ref: github.sha, which
is frozen when a cron job is queued, not when it runs. Under load or
during a long queue, the analysis could scan code that is days old,
missing vulnerabilities introduced since the last scheduling window.
Replace with ref: github.ref_name so all trigger types — scheduled,
push, and pull_request — consistently scan the current HEAD of the
branch being processed.
The scheduled weekly rebuild was failing because GitHub Actions froze
github.sha at job-queue time. When the Sunday cron queued a job on
March 1 with Feb 23 code (CADDY_VERSION=2.11.0-beta.2), that job ran
two days later on March 3 still using the old code, missing the caddy
version fix that had since landed on main.
Additionally, caddy-security was unpinned, so xcaddy auto-resolved it
to v1.1.36 which requires caddy/v2@v2.11.1 — conflicting with xcaddy's
internally bundled v2.11.0-beta.2 reference.
- Add ref: github.ref_name to checkout step so the rebuild always
fetches current branch HEAD at run time, not the SHA frozen at queue
time
- Add CADDY_SECURITY_VERSION=1.1.36 ARG to pin the caddy-security
plugin to a known-compatible version; pass it via --with so xcaddy
picks up the pinned release
- Add --with github.com/caddyserver/caddy/v2@v${CADDY_TARGET_VERSION}
to force xcaddy to use the declared Caddy version, overriding its own
internal go.sum pin for caddy
- Add Renovate custom manager for CADDY_SECURITY_VERSION so future
caddy-security releases trigger an automated PR instead of silently
breaking the build
Fixes weekly security rebuild CI failures introduced ~Feb 22 when
caddy-security v1.1.36 was published.
- Wrapped the Settings component in RequireRole to enforce access control for admin and user roles.
- Introduced a new custom hook `useFocusTrap` to manage focus within modal dialogs, enhancing accessibility.
- Applied the focus trap in InviteModal, PermissionsModal, and UserDetailModal to prevent focus from leaving the dialog.
- Updated PassthroughLanding to focus on the heading when the component mounts.
- Deleted the Account page and its associated logic.
- Introduced a new PassthroughLanding page for users without management access.
- Updated Settings page to conditionally display the Users link for admin users.
- Enhanced UsersPage to support passthrough user role, including invite functionality and user detail modal.
- Updated tests to reflect changes in user roles and navigation.
- Changed user role representation from string to UserRole type in User model.
- Updated role assignments in various services and handlers to use the new UserRole constants.
- Modified middleware to handle UserRole type for role checks.
- Refactored tests to align with the new UserRole type.
- Added migration function to convert legacy "viewer" roles to "passthrough".
- Ensured all role checks and assignments are consistent across the application.
The pre-commit version check hook was incorrectly using `git describe`
to find the latest tag, which only traverses the current branch's
ancestry. On feature branches that predate release tags applied to
main/nightly, this caused false failures — reporting v0.19.1 as latest
even though v0.20.0 and v0.21.0 existed globally.
Replaced with `git tag --sort=-v:refname | grep semver | head -1` so
the check always compares .version against the true latest release tag
in the repository, independent of which branch is checked out.
- Consolidated tools for Management, Planning, Playwright Dev, QA Security, and Supervisor agents to streamline functionality and reduce redundancy.
- Updated terminology from "Proper" fix to "Long Term" fix in Management agent for clarity on implementation choices.
- Added mandatory lintr and type checks before declaring slices "DONE" in Management agent to enhance code quality.
- Enhanced argument hints and descriptions across agents for better guidance on usage.
`SECURITY.md` is the project's living security record. It serves two audiences simultaneously: users who need to know what risks exist right now, and the broader community who need confidence that vulnerabilities are being tracked and remediated with discipline. Treat it like a changelog, but for security events — every known issue gets an entry, every resolved issue keeps its entry.
---
## File Structure
`SECURITY.md` must always contain the following top-level sections, in this order:
No other top-level sections are required. Do not collapse or remove sections even when they are empty — use the explicit empty-state placeholder defined below.
---
## Section 1: Known Vulnerabilities
This section lists every vulnerability that is currently unpatched or only partially mitigated. Entries must be sorted with the highest severity first, then by discovery date descending within the same severity tier.
### Entry Format
Each entry is an H3 heading followed by a structured block:
```markdown
### [SEVERITY] CVE-XXXX-XXXXX · Short Title
| Field | Value |
|--------------|-------|
| **ID** | CVE-XXXX-XXXXX (or `CHARON-YYYY-NNN` if no CVE assigned yet) |
| **Severity** | Critical / High / Medium / Low · CVSS v3.1 score if known (e.g. `8.1 · High`) |
A concise technical description of the attack vector, prerequisites, and exploitation
method. Omit proof-of-concept code. Reference CVE advisories or upstream issue
trackers where appropriate.
**Planned Remediation**
Describe the fix strategy: library upgrade, logic refactor, config change, etc.
If a workaround is available in the meantime, document it here.
Link to the tracking issue: [#NNN](https://github.com/owner/repo/issues/NNN)
```
### Empty State
When there are no known vulnerabilities:
```markdown
## Known Vulnerabilities
No known unpatched vulnerabilities at this time.
Last reviewed: YYYY-MM-DD
```
---
## Section 2: Patched Vulnerabilities
This section is a permanent, append-only ledger. Entries are never deleted. Sort newest-patched first. This section builds community trust by demonstrating that issues are resolved promptly and transparently.
### Entry Format
```markdown
### ✅ [SEVERITY] CVE-XXXX-XXXXX · Short Title
| Field | Value |
|--------------|-------|
| **ID** | CVE-XXXX-XXXXX (or internal ID) |
| **Severity** | Critical / High / Medium / Low · CVSS v3.1 score |
| **Patched** | YYYY-MM-DD in `vX.Y.Z` |
**What**
Same description carried over from the Known Vulnerabilities entry.
**Who**
- Discovered by: [Reporter or method]
- Reported: YYYY-MM-DD
**Where**
- Component: [Module or service name]
- File(s): `path/to/affected/file.go`
- Versions affected: `< X.Y.Z`
**When**
- Discovered: YYYY-MM-DD
- Patched: YYYY-MM-DD
- Time to patch: N days
**How**
Same technical description as the original entry.
**Resolution**
Describe exactly what was changed to fix the issue.
When a CVE CVSS score is not yet available, assign a preliminary severity based on these definitions and note it as `(preliminary)` until confirmed.
---
## Internal IDs
If a vulnerability has no CVE assigned, use the format `CHARON-YYYY-NNN` where `YYYY` is the year and `NNN` is a zero-padded sequence number starting at `001` for each year. Example: `CHARON-2025-003`. Assign a CVE ID in the entry retroactively if one is issued later, and add the internal ID as an alias in parentheses.
---
## Responsible Disclosure Preamble
The preamble at the top of `SECURITY.md` (before the vulnerability sections) must include:
- The preferred contact method for reporting vulnerabilities (e.g. a GitHub private advisory link, a security email address, or both)
- An acknowledgment-first response commitment: confirm receipt within 48 hours, even if the full investigation takes longer
- A statement that reporters will not be penalized or publicly named without consent
- A link to the full disclosure policy if one exists
We will acknowledge your report within **48 hours** and provide a remediation
timeline within **7 days**. Reporters are credited with their consent.
We do not pursue legal action against good-faith security researchers.
```
---
## Maintenance Rules
- **Review cadence**: Update the `Last reviewed` date in the Known Vulnerabilities section at least once per release cycle, even if no entries changed.
- **No silent patches**: Every security fix — no matter how minor — must produce an entry in `## Patched Vulnerabilities` before or alongside the release.
- **No redaction**: Do not redact or soften historical entries. Accuracy builds trust; minimizing past issues destroys it.
- **Dependency vulnerabilities**: Transitive dependency CVEs that affect Charon's exposed attack surface must be tracked here the same as first-party vulnerabilities. Pure dev-dependency CVEs with no runtime impact may be omitted at maintainer discretion, but must still be noted in the relevant dependency update PR.
- **Partial mitigations**: If a workaround is deployed but the root cause is not fixed, the entry stays in `## Known Vulnerabilities` with `Status: Mitigated (partial)` and the workaround documented in `**Planned Remediation**`.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.