14 KiB
GORM Security Issues Remediation Plan
Status: 🟡 READY TO START Created: 2026-01-28 Scanner Report: 60 issues detected (28 CRITICAL, 2 HIGH, 33 MEDIUM) Estimated Total Time: 8-12 hours Target Completion: 2026-01-29
Executive Summary
The GORM Security Scanner detected 60 pre-existing security issues in the codebase. This plan provides a systematic approach to fix all issues, organized by priority and type.
Issue Breakdown:
- 🔴 28 CRITICAL: 22 ID leaks + 3 exposed secrets + 2 DTO issues + 1 emergency field
- 🔵 33 MEDIUM: Missing primary key tags (informational)
Approach:
- Fix all CRITICAL issues (models, secrets, DTOs)
- Optionally address MEDIUM issues (missing tags)
- Verify with scanner
- Run full test suite
- Update CI to blocking mode
Phase 1: Fix ID Leaks in Models (6-8 hours)
Priority: 🔴 CRITICAL
Estimated Time: 6-8 hours (22 models × 15-20 min each)
Pattern:
// BEFORE (Vulnerable):
ID uint `json:"id" gorm:"primaryKey"`
// AFTER (Secure):
ID uint `json:"-" gorm:"primaryKey"`
Task Checklist
Core Models (6 models)
- User (
internal/models/user.go:23) - ProxyHost (
internal/models/proxy_host.go:9) - Domain (
internal/models/domain.go:11) - DNSProvider (
internal/models/dns_provider.go:11) - SSLCertificate (
internal/models/ssl_certificate.go:10) - AccessList (
internal/models/access_list.go:10)
Security Models (5 models)
- SecurityConfig (
internal/models/security_config.go:10) - SecurityAudit (
internal/models/security_audit.go:9) - SecurityDecision (
internal/models/security_decision.go:10) - SecurityHeaderProfile (
internal/models/security_header_profile.go:10) - SecurityRuleset (
internal/models/security_ruleset.go:9)
Infrastructure Models (5 models)
- Location (
internal/models/location.go:9) - Plugin (
internal/models/plugin.go:8) - RemoteServer (
internal/models/remote_server.go:10) - ImportSession (
internal/models/import_session.go:10) - Setting (
internal/models/setting.go:10)
Integration Models (3 models)
- CrowdsecConsoleEnrollment (
internal/models/crowdsec_console_enrollment.go:7) - CrowdsecPresetEvent (
internal/models/crowdsec_preset_event.go:7) - CaddyConfig (
internal/models/caddy_config.go:9)
Provider & Monitoring Models (3 models)
- DNSProviderCredential (
internal/models/dns_provider_credential.go:11) - EmergencyToken (
internal/models/emergency_token.go:10) - UptimeHeartbeat (
internal/models/uptime.go:39)
Post-Model-Update Tasks
After changing each model:
-
Update API handlers that reference
.ID:# Find usages: grep -r "\.ID" backend/internal/api/handlers/ grep -r "\"id\":" backend/internal/api/handlers/ -
Update service layer queries using
.ID:# Find usages: grep -r "\.ID" backend/internal/services/ -
Verify UUID field exists and is exposed:
UUID string `json:"uuid" gorm:"uniqueIndex"` -
Update tests referencing
.ID:# Find test failures: go test ./... -run TestModel
Phase 2: Fix Exposed Secrets (30 minutes)
Priority: 🔴 CRITICAL
Estimated Time: 30 minutes (3 fields)
Pattern:
// BEFORE (Vulnerable):
APIKey string `json:"api_key"`
// AFTER (Secure):
APIKey string `json:"-"`
Task Checklist
-
User.APIKey - Change to
json:"-"- Location:
internal/models/user.go - Verify: API key is never serialized to JSON
- Location:
-
ManualChallenge.Token - Change to
json:"-"- Location:
internal/services/manual_challenge_service.go:337 - Verify: Challenge token is never exposed
- Location:
-
CaddyConfig.ConfigHash - Change to
json:"-"- Location:
internal/models/caddy_config.go - Verify: Config hash is never exposed
- Location:
Verification
# Ensure no secrets are exposed:
./scripts/scan-gorm-security.sh --report | grep "CRITICAL.*Secret"
# Should return: 0 results
Phase 3: Fix Response DTO Embedding (1-2 hours)
Priority: 🟡 HIGH
Estimated Time: 1-2 hours (2 structs)
Problem: Response structs embed models, inheriting exposed IDs
Pattern:
// BEFORE (Inherits ID exposure):
type ProxyHostResponse struct {
models.ProxyHost // Embeds entire model
Warnings []ProxyHostWarning `json:"warnings,omitempty"`
}
// AFTER (Explicit fields):
type ProxyHostResponse struct {
UUID string `json:"uuid"`
DomainNames []string `json:"domain_names"`
ForwardHost string `json:"forward_host"`
// ... other fields explicitly defined
Warnings []ProxyHostWarning `json:"warnings,omitempty"`
}
Task Checklist
-
ProxyHostResponse (
internal/api/handlers/proxy_host_handler.go:31)- Replace
models.ProxyHostembedding with explicit fields - Include
UUID string json:"uuid"(expose external ID) - Exclude
ID uint(hide internal ID) - Update all handler functions creating ProxyHostResponse
- Update tests
- Replace
-
DNSProviderResponse (
internal/services/dns_provider_service.go:56)- Replace
models.DNSProviderembedding with explicit fields - Include
UUID string json:"uuid"(expose external ID) - Exclude
ID uint(hide internal ID) - Keep
HasCredentials bool json:"has_credentials" - Update all service functions creating DNSProviderResponse
- Update tests
- Replace
Post-DTO-Update Tasks
-
Update handler logic:
// Map model to response: response := ProxyHostResponse{ UUID: model.UUID, DomainNames: model.DomainNames, // ... explicit field mapping } -
Frontend coordination (if needed):
- Frontend likely uses
uuidalready, notid - Verify API client expectations
- Update TypeScript types if needed
- Frontend likely uses
Phase 4: Fix Emergency Break Glass Field (15 minutes)
Priority: 🔴 CRITICAL
Estimated Time: 15 minutes (1 field)
Issue: EmergencyConfig.BreakGlassEnabled exposed
Task Checklist
- EmergencyConfig.BreakGlassEnabled
- Location: Find in security models
- Change: Add
json:"-"or verify it's informational only - Verify: Emergency status not leaking sensitive info
Phase 5: Optional - Fix Missing Primary Key Tags (1 hour)
Priority: 🔵 MEDIUM (Informational)
Estimated Time: 1 hour (33 fields)
Decision: OPTIONAL - Can defer to separate issue
Pattern:
// BEFORE (Missing tag):
ID uint `json:"-"`
// AFTER (Explicit tag):
ID uint `json:"-" gorm:"primaryKey"`
Impact: Missing tags don't cause security issues, but explicit tags improve:
- Query optimizer performance
- Code clarity
- GORM auto-migration accuracy
Recommendation: Create separate issue for this backlog item.
Phase 6: Verification & Testing (1-2 hours)
Task Checklist
1. Scanner Verification (5 minutes)
- Run scanner in report mode:
./scripts/scan-gorm-security.sh --report - Verify: 0 CRITICAL and 0 HIGH issues remain
- Optional: Verify 0 MEDIUM if Phase 5 completed
2. Backend Tests (30 minutes)
- Run full backend test suite:
cd backend && go test ./... -v - Verify: All tests pass
- Fix any test failures related to ID → UUID changes
3. Backend Coverage (15 minutes)
- Run coverage tests:
.github/skills/scripts/skill-runner.sh test-backend-coverage - Verify: Coverage ≥85%
- Address any coverage drops
4. Frontend Tests (if API changes) (30 minutes)
- TypeScript type check:
cd frontend && npm run type-check - Run frontend tests:
.github/skills/scripts/skill-runner.sh test-frontend-coverage - Verify: All tests pass
5. Integration Tests (15 minutes)
- Start Docker environment:
docker-compose up -d - Test affected endpoints:
- GET /api/proxy-hosts (verify UUID, no ID)
- GET /api/dns-providers (verify UUID, no ID)
- GET /api/users/me (verify no APIKey exposed)
6. Final Scanner Check (5 minutes)
- Run scanner in check mode:
./scripts/scan-gorm-security.sh --check echo "Exit code: $?" # Must be 0 - Verify: Exit code 0 (no issues)
Phase 7: Enable Blocking in Pre-commit (5 minutes)
Task Checklist
-
Update
.pre-commit-config.yaml:# CHANGE FROM: stages: [manual] # CHANGE TO: stages: [commit] -
Test pre-commit hook:
pre-commit run --all-files -
Verify: Scanner runs on commit and passes
Phase 8: Update Documentation (15 minutes)
Task Checklist
-
Update
docs/plans/gorm_security_remediation_plan.md:- Mark all tasks as complete
- Add "COMPLETED" status and completion date
-
Update
docs/implementation/gorm_security_scanner_complete.md:- Update "Current Findings" section to show 0 issues
- Update pre-commit status to "Blocking"
-
Update
docs/reports/gorm_scanner_qa_report.md:- Add remediation completion note
-
Update
CHANGELOG.md:- Add entry: "Fixed 60 GORM security issues (ID leaks, exposed secrets)"
Success Criteria
Technical Success ✅
- Scanner reports 0 CRITICAL and 0 HIGH issues
- All backend tests pass
- Coverage maintained (≥85%)
- Pre-commit hook in blocking mode
- CI pipeline passes
Security Success ✅
- No internal database IDs exposed via JSON
- No API keys/tokens/secrets exposed
- Response DTOs use explicit fields
- UUID fields used for all external references
Documentation Success ✅
- Remediation plan marked complete
- Scanner documentation updated
- CHANGELOG updated
- Blocking mode documented
Risk Mitigation
Risk: Breaking API Changes
Mitigation:
- Frontend likely already uses
uuidfield, notid - Test all API endpoints after changes
- Check frontend API client for
idreferences - Coordinate with frontend team if breaking changes needed
Risk: Test Failures
Mitigation:
- Tests may hardcode
.IDreferences - Search and replace
.ID→.UUIDin tests - Verify test fixtures use UUIDs
- Run tests incrementally after each model update
Risk: Handler/Service Breakage
Mitigation:
- Use grep to find all
.IDreferences before changing models - Update handlers/services at the same time as models
- Test each endpoint after updating
- Use compiler errors as a guide (
.IDwill fail to serialize)
Time Estimates Summary
| Phase | Priority | Time | Can Defer? |
|---|---|---|---|
| Phase 1: ID Leaks (22 models) | 🔴 CRITICAL | 6-8 hours | ❌ No |
| Phase 2: Secrets (3 fields) | 🔴 CRITICAL | 30 min | ❌ No |
| Phase 3: DTO Embedding (2 structs) | 🟡 HIGH | 1-2 hours | ❌ No |
| Phase 4: Emergency Field (1 field) | 🔴 CRITICAL | 15 min | ❌ No |
| Phase 5: Missing Tags (33 fields) | 🔵 MEDIUM | 1 hour | ✅ Yes |
| Phase 6: Verification & Testing | Required | 1-2 hours | ❌ No |
| Phase 7: Enable Blocking | Required | 5 min | ❌ No |
| Phase 8: Documentation | Required | 15 min | ❌ No |
| TOTAL (Required) | 9.5-13 hours | ||
| TOTAL (with optional) | 10.5-14 hours |
Quick Start Guide for Tomorrow
Morning Session (4-5 hours)
-
Start Scanner to see baseline:
./scripts/scan-gorm-security.sh --report | tee gorm-before.txt -
Tackle Core Models (User, ProxyHost, Domain, DNSProvider, SSLCertificate, AccessList):
- For each model:
- Change
json:"id"→json:"-" - Verify
UUIDfield exists - Run:
go test ./internal/models/ - Fix compilation errors
- Change
- Batch commit: "fix: hide internal IDs in core GORM models"
- For each model:
-
Coffee Break ☕
Afternoon Session (4-5 hours)
-
Tackle Remaining Models (Security, Infrastructure, Integration models):
- Same pattern as morning
- Batch commit: "fix: hide internal IDs in remaining GORM models"
-
Fix Exposed Secrets (Phase 2):
- Quick 30-minute task
- Commit: "fix: hide API keys and sensitive fields"
-
Fix Response DTOs (Phase 3):
- ProxyHostResponse
- DNSProviderResponse
- Commit: "refactor: use explicit fields in response DTOs"
-
Final Verification (Phase 6):
- Run scanner:
./scripts/scan-gorm-security.sh --check - Run tests:
go test ./... - Run coverage:
.github/skills/scripts/skill-runner.sh test-backend-coverage
- Run scanner:
-
Enable & Document (Phases 7-8):
- Update pre-commit config to blocking mode
- Update all docs
- Final commit: "chore: enable GORM security scanner blocking mode"
Post-Remediation
After all fixes are complete:
- Create PR with all remediation commits
- CI will pass (scanner finds 0 issues)
- Merge to main
- Close chore item in
docs/plans/chores.md - Celebrate 🎉 - Charon is now more secure!
Notes
- Current Branch:
feature/beta-release - Scanner Location:
scripts/scan-gorm-security.sh - Documentation:
docs/implementation/gorm_security_scanner_complete.md - QA Report:
docs/reports/gorm_scanner_qa_report.md
Tips:
- Work in small batches (5-6 models at a time)
- Test incrementally (don't fix everything before testing)
- Use grep to find all
.IDreferences before each change - Commit frequently to make rollback easier if needed
- Take breaks - this is tedious but important work!
Good luck tomorrow! 💪