Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hub-server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ AGENTHUB_AGENT_TEAM_MAX_TEAM_RUN_BUDGET_USAGE_PCT=95
# d. Save the returned client_id and client_secret below
# 3. If TokenDance ID is NOT running, use the seed SQL:
# sqlite3 ../tokendance-id/data/tokendance.db < scripts/seed-tokendance-client.sql
# The seed uses client_id=agenthub-desktop with secret=agenthub-dev-secret-change-me
# The seed uses client_id=agenthub-desktop; copy the generated secret from the setup script output.

AGENTHUB_TOKENDANCE_ID_ISSUER_URL=http://localhost:3000
# AGENTHUB_TOKENDANCE_ID_JWKS_URI= ← auto-derived from issuer_url + /oidc/jwks
Expand Down
66 changes: 66 additions & 0 deletions hub-server/internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,72 @@
}
}

func TestValidateTokenDanceIDRejectsShortClientSecret(t *testing.T) {
cfg := &Config{
DB: DBConfig{
Host: "localhost",
Port: 5432,
User: "agenthub",
Name: "agenthub",
MaxOpenConns: 2,
MaxIdleConns: 1,
ConnMaxLifetime: 30 * time.Minute,
ConnMaxIdleTime: 5 * time.Minute,
},
Redis: RedisConfig{Host: "localhost", Port: 6379},
JWT: JWTConfig{
Secret: "strong-agenthub-secret-padded-to-minimum-32-chars..",

Check failure on line 752 in hub-server/internal/config/config_test.go

View workflow job for this annotation

GitHub Actions / validate

secret-like assignment detected
},
TokenDanceID: TokenDanceIDConfig{

Check failure on line 754 in hub-server/internal/config/config_test.go

View workflow job for this annotation

GitHub Actions / validate

secret-like assignment detected
IssuerURL: "https://id.example",
ClientID: "agenthub-client",
ClientSecret: "short-but-present-secret",

Check failure on line 757 in hub-server/internal/config/config_test.go

View workflow job for this annotation

GitHub Actions / validate

secret-like assignment detected
RedirectURI: "http://127.0.0.1/callback",
},
}

err := cfg.Validate()
if err == nil {
t.Fatal("expected short TokenDance ID client secret to be rejected")
}
if !strings.Contains(err.Error(), "tokendance_id.client_secret too short") {
t.Fatalf("Validate() error = %q, want tokendance_id.client_secret too short", err.Error())
}
}

func TestValidateTokenDanceIDRejectsSeedSecret(t *testing.T) {
cfg := &Config{
DB: DBConfig{
Host: "localhost",
Port: 5432,
User: "agenthub",
Name: "agenthub",
MaxOpenConns: 2,
MaxIdleConns: 1,
ConnMaxLifetime: 30 * time.Minute,
ConnMaxIdleTime: 5 * time.Minute,
},
Redis: RedisConfig{Host: "localhost", Port: 6379},
JWT: JWTConfig{
Secret: "strong-agenthub-secret-padded-to-minimum-32-chars..",

Check failure on line 785 in hub-server/internal/config/config_test.go

View workflow job for this annotation

GitHub Actions / validate

secret-like assignment detected
},
TokenDanceID: TokenDanceIDConfig{

Check failure on line 787 in hub-server/internal/config/config_test.go

View workflow job for this annotation

GitHub Actions / validate

secret-like assignment detected
IssuerURL: "https://id.example",
ClientID: "agenthub-client",
ClientSecret: weakSecretPrefixes[len(weakSecretPrefixes)-1] + strings.Repeat("x", 32),

Check failure on line 790 in hub-server/internal/config/config_test.go

View workflow job for this annotation

GitHub Actions / validate

secret-like assignment detected
RedirectURI: "http://127.0.0.1/callback",
},
}

err := cfg.Validate()
if err == nil {
t.Fatal("expected seed SQL TokenDance ID client secret to be rejected")
}
if !strings.Contains(err.Error(), "tokendance_id.client_secret must be a strong") {
t.Fatalf("Validate() error = %q, want tokendance_id.client_secret must be a strong", err.Error())
}
}
Comment thread
DeliciousBuding marked this conversation as resolved.

// --- DSN / Addr edge cases ---

func TestDBConfigDSNZeroValues(t *testing.T) {
Expand Down
15 changes: 15 additions & 0 deletions hub-server/internal/config/config_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ var knownHardcodedSecrets = []string{
"password",
"1234567890123456",
"aaaaaaaaaaaaaaaa",
"agenthub-dev-secret-change-me",
}

// weakSecretPrefixes blocks the publicly-documented placeholder families.
Expand All @@ -97,9 +98,12 @@ var knownHardcodedSecrets = []string{
// family (e.g. change-me-production-min-length-32-chars, 41 chars).
// Both PG_PASSWORD and JWT_SECRET in .env.example used this prefix; the
// JWT variant was long enough to pass the length gate before this entry.
// - "agenthub-dev-secret": the seed SQL OIDC client_secret family
// (agenthub-dev-secret-change-me and derivatives).
var weakSecretPrefixes = []string{
"dev-secret-change-in-production",
"change-me-production",
"agenthub-dev-secret",
}

// isKnownWeakSecret reports whether the given secret is a hardcoded default
Expand Down Expand Up @@ -165,6 +169,17 @@ func (c *Config) validateTokenDanceID() error {
if c.TokenDanceID.RedirectURI == "" {
return fmt.Errorf("tokendance_id.redirect_uri is required when tokendance_id.client_id is set")
}
// OIDC client_secret must meet the same strength bar as JWT secrets:
// 32-char minimum and no known hardcoded/placeholder values. The
// seed SQL default (agenthub-dev-secret-change-me) and the
// .env.example documented placeholders must be rejected so an
// operator cannot accidentally run production with the seed value.
if len(c.TokenDanceID.ClientSecret) < 32 {
return fmt.Errorf("tokendance_id.client_secret too short: minimum 32 characters required (got %d)", len(c.TokenDanceID.ClientSecret))
}
if isKnownWeakSecret(c.TokenDanceID.ClientSecret) {
return errors.New("tokendance_id.client_secret must be a strong, non-default value; the seed SQL default and documented dev placeholders are rejected")
}
}
return nil
}
Loading