Skip to content

feat(security): seed default SecurityConfig row on application startup#852

Merged
Wikid82 merged 11 commits intodevelopmentfrom
feature/beta-release
Mar 17, 2026
Merged

feat(security): seed default SecurityConfig row on application startup#852
Wikid82 merged 11 commits intodevelopmentfrom
feature/beta-release

Conversation

@Wikid82
Copy link
Copy Markdown
Owner

@Wikid82 Wikid82 commented Mar 17, 2026

Summary

On a fresh install the security_configs table is auto-migrated by GORM 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 before the user had intentionally disabled anything.

Root Cause

GORM AutoMigrate creates the table schema but does not insert default rows. The GetStatus handler queries WHERE name = 'default' — on a fresh database that row does not exist, so the handler falls back to static config defaults and returns a confusing all-empty state.

Changes

  • Adds SeedDefaultSecurityConfig — uses FirstOrCreate to guarantee a default SecurityConfig row (name="default") exists on every startup with safe, disabled-by-default values
  • Wired into the startup sequence immediately after AutoMigrate in RegisterWithDeps
  • Non-fatal: if the seed fails the application logs a warning and continues
  • Idempotent: existing rows are never touched — safe on all upgrades
  • Adds three unit tests: empty-DB creation, idempotency (two calls → one row), and do-not-overwrite-existing

Security

  • Seeds no secrets (no API keys, passwords, or tokens)
  • CrowdSecAPIURL default is http://127.0.0.1:8085 — loopback only, consistent with every other default reference in the codebase
  • Zero-valued rate-limit fields are safe: the Cerberus rate-limit middleware falls back to hardcoded safe thresholds (100 req/60s/burst-20) when stored values are zero

Testing

  • 3 new unit tests — all pass (in-memory SQLite, no external dependencies)
  • Full 30-package backend suite passes with zero regressions
  • GORM security scan: 0 CRITICAL/HIGH/MEDIUM findings

Related

Part of the fresh-install bug investigation series. Addresses Issue #1 from the community bug report: "some database missing values — i think before crowdsec enabling."

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.
@Wikid82 Wikid82 self-assigned this Mar 17, 2026
@Wikid82 Wikid82 added this to Charon Mar 17, 2026
@github-project-automation github-project-automation bot moved this to Backlog in Charon Mar 17, 2026
@Wikid82 Wikid82 marked this pull request as ready for review March 17, 2026 12:37
Copilot AI review requested due to automatic review settings March 17, 2026 12:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Seeds a default SecurityConfig row on application startup to fix a fresh-install bug where the security status endpoint returned confusing all-disabled zero values because no row existed in the database.

Changes:

  • New SeedDefaultSecurityConfig function using GORM FirstOrCreate to idempotently ensure a default row exists
  • Wired into RegisterWithDeps startup sequence after AutoMigrate, with non-fatal error handling
  • Three unit tests covering empty-DB creation, idempotency, and preservation of existing data

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
backend/internal/models/seed.go New function to seed default SecurityConfig row with safe disabled-by-default values
backend/internal/models/seed_test.go Unit tests for empty DB, idempotency, and no-overwrite scenarios
backend/internal/api/routes/routes.go Calls seed function after AutoMigrate in startup sequence

You can also share your feedback on Copilot code review. Take the survey.

@github-advanced-security
Copy link
Copy Markdown
Contributor

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 17, 2026

✅ Supply Chain Verification Results

PASSED

📦 SBOM Summary

  • Components: 1483

🔍 Vulnerability Scan

Severity Count
🔴 Critical 0
🟠 High 0
🟡 Medium 4
🟢 Low 2
Total 8

📎 Artifacts

  • SBOM (CycloneDX JSON) and Grype results available in workflow artifacts

Generated by Supply Chain Verification workflow • View Details

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 90.47619% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
backend/internal/api/routes/routes.go 0.00% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

- 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.
@Wikid82 Wikid82 moved this from Backlog to In Review in Charon Mar 17, 2026
actions-user and others added 7 commits March 17, 2026 14:29
…ty config

- 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
… test payload

- 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
…n-major-updates

chore(deps): update paulhatch/semantic-version action to v6.0.2 (feature/beta-release)
…on test

- 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
…cert cleanup loop

- 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
@Wikid82 Wikid82 merged commit 3318b4a into development Mar 17, 2026
38 checks passed
@github-project-automation github-project-automation bot moved this from In Review to Done in Charon Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants