Skip to content

Commit 9e7be14

Browse files
SuperMarioYLclaude
andcommitted
release: v0.0.22 — refuse insecure auth defaults at startup
When AUTH_ENABLED=true the server refuses to start with an empty/default JWT_SECRET or empty/admin ADMIN_PASSWORD, preventing forgeable tokens and the default password in production. Auth-disabled/local dev unaffected; Helm injects random persisted secrets. Config validation tests added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 341e692 commit 9e7be14

5 files changed

Lines changed: 81 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to the Bison project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.0.22] - 2026-06-19
9+
10+
### Security — Refuse insecure defaults at startup
11+
12+
- When `AUTH_ENABLED=true`, the server now refuses to start if `JWT_SECRET` is empty or still the built-in public default, or if `ADMIN_PASSWORD` is empty or `admin`. This prevents a production deployment from silently running with a forgeable token-signing key or the well-known default password. Auth-disabled and local development are unaffected; the Helm chart already injects randomly generated, persisted secrets. Added table-driven config validation tests.
13+
814
## [0.0.21] - 2026-06-19
915

1016
### Added — Release test/lint gate

api-server/internal/config/config.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,22 @@ type Config struct {
3636
LeaderElectionEnabled bool
3737
}
3838

39+
// Built-in development defaults that MUST NOT be used in production once auth is
40+
// enabled — startup refuses to proceed if they are left unchanged.
41+
const (
42+
defaultAdminPassword = "admin"
43+
defaultJWTSecret = "bison-secret-key-change-in-production"
44+
)
45+
3946
// Load reads configuration from environment variables
4047
func Load() (*Config, error) {
4148
cfg := &Config{
4249
Port: 8080,
4350
Mode: "release",
4451
AuthEnabled: false,
4552
AdminUsername: "admin",
46-
AdminPassword: "admin",
47-
JWTSecret: "bison-secret-key-change-in-production",
53+
AdminPassword: defaultAdminPassword,
54+
JWTSecret: defaultJWTSecret,
4855
OpenCostURL: "",
4956
PrometheusURL: "",
5057
CapsuleEnabled: true,
@@ -102,5 +109,25 @@ func Load() (*Config, error) {
102109
}
103110
}
104111

112+
if err := cfg.validate(); err != nil {
113+
return nil, err
114+
}
115+
105116
return cfg, nil
106117
}
118+
119+
// validate refuses to start with insecure defaults once authentication is enabled,
120+
// so a production deployment cannot accidentally run with the public default JWT
121+
// signing key or the well-known "admin" password.
122+
func (c *Config) validate() error {
123+
if !c.AuthEnabled {
124+
return nil
125+
}
126+
if c.JWTSecret == "" || c.JWTSecret == defaultJWTSecret {
127+
return fmt.Errorf("refusing to start: JWT_SECRET must be set to a non-default value when AUTH_ENABLED=true")
128+
}
129+
if c.AdminPassword == "" || c.AdminPassword == defaultAdminPassword {
130+
return fmt.Errorf("refusing to start: ADMIN_PASSWORD must be set to a non-default value when AUTH_ENABLED=true")
131+
}
132+
return nil
133+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package config
2+
3+
import "testing"
4+
5+
func TestValidate(t *testing.T) {
6+
cases := []struct {
7+
name string
8+
cfg Config
9+
wantErr bool
10+
}{
11+
{
12+
name: "auth disabled allows defaults",
13+
cfg: Config{AuthEnabled: false, JWTSecret: defaultJWTSecret, AdminPassword: defaultAdminPassword},
14+
},
15+
{
16+
name: "auth enabled rejects default jwt secret",
17+
cfg: Config{AuthEnabled: true, JWTSecret: defaultJWTSecret, AdminPassword: "strong-pass"},
18+
wantErr: true,
19+
},
20+
{
21+
name: "auth enabled rejects empty jwt secret",
22+
cfg: Config{AuthEnabled: true, JWTSecret: "", AdminPassword: "strong-pass"},
23+
wantErr: true,
24+
},
25+
{
26+
name: "auth enabled rejects default password",
27+
cfg: Config{AuthEnabled: true, JWTSecret: "a-real-secret", AdminPassword: defaultAdminPassword},
28+
wantErr: true,
29+
},
30+
{
31+
name: "auth enabled accepts strong values",
32+
cfg: Config{AuthEnabled: true, JWTSecret: "a-real-secret", AdminPassword: "strong-pass"},
33+
},
34+
}
35+
for _, tc := range cases {
36+
t.Run(tc.name, func(t *testing.T) {
37+
err := tc.cfg.validate()
38+
if (err != nil) != tc.wantErr {
39+
t.Fatalf("validate() err=%v, wantErr=%v", err, tc.wantErr)
40+
}
41+
})
42+
}
43+
}

deploy/charts/bison/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ apiVersion: v2
22
name: bison
33
description: Bison - GPU 资源计费平台,基于 Capsule 多租户 + OpenCost 成本追踪
44
type: application
5-
version: 0.0.21
6-
appVersion: "0.0.21"
5+
version: 0.0.22
6+
appVersion: "0.0.22"
77
keywords:
88
- gpu
99
- billing

web-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bison-web-ui",
3-
"version": "0.0.21",
3+
"version": "0.0.22",
44
"private": true,
55
"scripts": {
66
"dev": "vite",

0 commit comments

Comments
 (0)