Skip to content

[Security] Fix hardcoded JWT secrets, database credentials, and deprecated JWT library (CWE-798 + CWE-1104)#66

Open
saaa99999999 wants to merge 1 commit into
sujit-baniya:masterfrom
saaa99999999:fix/hardcoded-secrets
Open

[Security] Fix hardcoded JWT secrets, database credentials, and deprecated JWT library (CWE-798 + CWE-1104)#66
saaa99999999 wants to merge 1 commit into
sujit-baniya:masterfrom
saaa99999999:fix/hardcoded-secrets

Conversation

@saaa99999999
Copy link
Copy Markdown

@saaa99999999 saaa99999999 commented May 17, 2026

Security Audit Report ??fiber-boilerplate (sujit-baniya)

Manual code audit discovered 3 security vulnerabilities (1 Critical, 1 High, 1 Medium).


CRITICAL-1: CWE-798 Hardcoded JWT Signing Keys (CVSS 9.1)

Location: config.sample.yml:55-56, config.yml:55-56

Data Flow:

config.yml:55 token.app_jwt_secret: SECRET_APP
config.yml:56 token.api_jwt_secret: SECRET_API
  ??cleanenv.ReadConfig() [config/config.go:42]
  ??app.Http.Token.AppJwtSecret / app.Http.Token.ApiJwtSecret
  ??config/token.go:32 token.SignedString([]byte(secret)) ??JWT signing
  ??config/token.go:58 return []byte(secret), nil ??JWT verification
  ??rest/middlewares/auth.go:229 SigningKey: []byte(app.Http.Token.AppJwtSecret) ??Web auth
  ??rest/middlewares/auth.go:248 SigningKey: []byte(app.Http.Token.ApiJwtSecret) ??API auth

Description:
Both config.sample.yml and config.yml ship with hardcoded JWT signing keys SECRET_APP and SECRET_API. These are plain-English strings published in the public repository. The same keys are used for both signing and verification (HS256 symmetric algorithm), meaning anyone who reads the repository can forge valid JWT tokens for any user ID. The Token struct also lacked env tags, so environment variables could not override the hardcoded YAML values via cleanenv.

PoC:

package main

import (
    "fmt"
    "time"
    "github.com/form3tech-oss/jwt-go"
)

func main() {
    // Forge a web token using the hardcoded secret
    token := jwt.New(jwt.SigningMethodHS256)
    claims := token.Claims.(jwt.MapClaims)
    claims["id"] = 1  // impersonate any user ID
    claims["exp"] = time.Now().Add(3600 * time.Second).Unix()
    tokenString, _ := token.SignedString([]byte("SECRET_APP"))
    fmt.Println("Forged web token:", tokenString)
    // Set cookie: Verify-Rest-Token=<tokenString>
    // ??Authenticated as user ID 1

    // Forge an API token
    token2 := jwt.New(jwt.SigningMethodHS256)
    claims2 := token2.Claims.(jwt.MapClaims)
    claims2["id"] = 1
    claims2["exp"] = time.Now().Add(3600 * time.Second).Unix()
    apiToken, _ := token2.SignedString([]byte("SECRET_API"))
    fmt.Println("Forged API token:", apiToken)
    // Set header: Verify-Rest-Token: <apiToken>
    // ??Authenticated to API as user ID 1
}

Fix:

  • config.sample.yml + config.yml: Replaced hardcoded secrets with empty strings, forcing user configuration
  • config/token.go:12-13: Added env:"APP_JWT_SECRET" and env:"API_JWT_SECRET" tags for cleanenv environment variable override
  • config/token.go:18-41: Added ValidateSecrets() with minimum 32-character length check and rejection of known default values ("SECRET_APP", "SECRET_API", "secret", "changeme", empty)
  • config/config.go:47-50: Startup validation calls ValidateSecrets() after config loading, exits with error if secrets are weak

Before ??After:

config.sample.yml:55-56:

# Before:
token:
  app_jwt_secret: SECRET_APP
  api_jwt_secret: SECRET_API

# After:
token:
  app_jwt_secret: ""
  api_jwt_secret: ""

config/token.go:12-13:

// Before:
AppJwtSecret string `mapstructure:"APP_JWT_SECRET" yaml:"app_jwt_secret"`
ApiJwtSecret string `mapstructure:"API_JWT_SECRET" yaml:"api_jwt_secret"`

// After:
AppJwtSecret string `mapstructure:"APP_JWT_SECRET" yaml:"app_jwt_secret" env:"APP_JWT_SECRET"`
ApiJwtSecret string `mapstructure:"API_JWT_SECRET" yaml:"api_jwt_secret" env:"API_JWT_SECRET"`

HIGH-2: CWE-798 Hardcoded Database Credentials (CVSS 7.5)

Location: config.sample.yml:6-8, config.sample.yml:13-15, config.yml:6-8, config.yml:13-15

Data Flow:

config.yml:6 username: root / config.yml:7 password: root
  ??cleanenv.ReadConfig() [config/config.go:42]
  ??DatabaseConfig.Drivers["mysql"].Username / Password
  ??config/database.go:53 fmt.Sprintf("%s:%s@tcp(...)...", d.Default.Username, d.Default.Password)
  ??gorm.Open(mysql.Open(connectionString)) [config/database.go:54]

Description:
Default database credentials (root/root for MySQL, postgres/postgres for PostgreSQL) are hardcoded in both the sample and active config files. While DatabaseDriver has env tags (DB_USER, DB_PASS) for environment variable override, the YAML defaults mean anyone deploying with the default config has guessable database credentials.

Fix:

  • config.sample.yml + config.yml: Replaced hardcoded username/password with empty strings for both MySQL and PostgreSQL driver configurations

Before ??After:

# Before:
mysql: &mysql
  username: root
  password: root

# After:
mysql: &mysql
  username: ""
  password: ""

MEDIUM-3: CWE-1104 Use of Unmaintained Third-Party Components ??Deprecated JWT Library (CVSS 5.9)

Location: go.mod:11, config/token.go:7, rest/middlewares/auth.go:15

Description:
The project uses github.com/form3tech-oss/jwt-go v3.2.5, which is an archived, unmaintained fork of the original github.com/dgrijalva/jwt-go (also deprecated). The maintained community successor is github.com/golang-jwt/jwt. The archived library receives no security patches and has known issues:

  • No support for key rotation (kid header handling is buggy in the archived fork)
  • Missing security hardening present in golang-jwt/jwt/v5
  • No signed token validation improvements

Recommendation:
Migrate to github.com/golang-jwt/jwt/v5:

go get github.com/golang-jwt/jwt/v5@latest
go mod tidy

Then replace imports:

// Before:
import "github.com/form3tech-oss/jwt-go"

// After:
import "github.com/golang-jwt/jwt/v5"

Changes in this PR (4 files)

File Change
config.sample.yml Removed hardcoded JWT secrets (4 fields) and DB credentials (4 fields)
config.yml Removed hardcoded JWT secrets (4 fields) and DB credentials (4 fields)
config/token.go Added env tags for cleanenv override; Added ValidateSecrets() with minimum length + known-default rejection
config/config.go Call ValidateSecrets() at startup; fatal exit on weak secret

CVSS 3.1 Vectors

  • CRITICAL-1 (CWE-798 JWT): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N ??9.1
  • HIGH-2 (CWE-798 DB): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N ??7.5 (requires network access to DB)
  • MEDIUM-3 (CWE-1104): CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N ??5.9

Manual security audit of JWT signing and database configuration

Replace hardcoded JWT signing keys (SECRET_APP/SECRET_API) and database
passwords (root/root, postgres/postgres) with empty placeholders that
require user configuration. Add startup validation for JWT secrets with
minimum 32-character length requirement and rejection of known default
values. Add environment variable tags (APP_JWT_SECRET, API_JWT_SECRET)
to Token struct for cleanenv override support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant