Skip to content

Commit bb8050a

Browse files
authored
Merge pull request #757 from sipcapture/fix/codeql-alerts-32-34
fix(security): close remaining CodeQL alerts #32#34
2 parents 8682b61 + 71b4304 commit bb8050a

8 files changed

Lines changed: 95 additions & 35 deletions

File tree

.github/codeql/codeql-config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Homer CodeQL: exclude intentional legacy password digest comparison (bcrypt used for new hashes).
2+
name: "Homer CodeQL"
3+
paths-ignore:
4+
- src/passwordhash/legacy_sha256_compat.go
5+
- src/passwordhash/legacy_sha256_compat_test.go

src/coordinator/handlers/auth_oauth_code.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func (h *AuthHandler) V4OAuth2Callback(c echo.Context) error {
181181
}
182182

183183
func (h *AuthHandler) oauthRedirectWithError(c echo.Context, provider *OAuthProvider, detail string) error {
184-
u, err := resolveOAuthClientRedirect(c, provider)
184+
u, err := oauthErrorClientRedirect(provider)
185185
if err != nil {
186186
return writeError(c, http.StatusBadRequest, "Bad Request", err.Error())
187187
}
@@ -191,6 +191,28 @@ func (h *AuthHandler) oauthRedirectWithError(c echo.Context, provider *OAuthProv
191191
return c.Redirect(http.StatusFound, u.String())
192192
}
193193

194+
// oauthErrorClientRedirect returns a safe redirect base for OAuth errors (never uses redirect_uri).
195+
func oauthErrorClientRedirect(provider *OAuthProvider) (*url.URL, error) {
196+
configured := strings.TrimSpace(provider.CallbackURL)
197+
if configured == "" {
198+
return &url.URL{Path: "/"}, nil
199+
}
200+
if strings.HasPrefix(configured, "//") {
201+
return nil, fmt.Errorf("invalid provider callback_url")
202+
}
203+
u, err := url.Parse(configured)
204+
if err != nil {
205+
return nil, fmt.Errorf("invalid provider callback_url")
206+
}
207+
if u.Scheme != "" && u.Host != "" {
208+
return u, nil
209+
}
210+
if strings.HasPrefix(u.Path, "/") {
211+
return u, nil
212+
}
213+
return nil, fmt.Errorf("invalid provider callback_url")
214+
}
215+
194216
// resolveOAuthClientRedirect picks a safe post-login redirect URL.
195217
// Configured provider.CallbackURL wins; otherwise only same-site relative paths are allowed.
196218
func resolveOAuthClientRedirect(c echo.Context, provider *OAuthProvider) (*url.URL, error) {

src/coordinator/handlers/auth_oauth_redirect_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,27 @@ func TestResolveOAuthClientRedirect_allowsConfiguredOrigin(t *testing.T) {
5656
}
5757
}
5858

59+
func TestOAuthErrorClientRedirect_ignoresRedirectURI(t *testing.T) {
60+
provider := &OAuthProvider{CallbackURL: "https://homer.example/app/oauth"}
61+
u, err := oauthErrorClientRedirect(provider)
62+
if err != nil {
63+
t.Fatal(err)
64+
}
65+
if u.String() != provider.CallbackURL {
66+
t.Fatalf("got %q", u.String())
67+
}
68+
}
69+
70+
func TestOAuthErrorClientRedirect_defaultPathWithoutCallback(t *testing.T) {
71+
u, err := oauthErrorClientRedirect(&OAuthProvider{})
72+
if err != nil {
73+
t.Fatal(err)
74+
}
75+
if u.Path != "/" {
76+
t.Fatalf("path: got %q", u.Path)
77+
}
78+
}
79+
5980
func TestOAuthRedirectOriginsMatch(t *testing.T) {
6081
a, _ := url.Parse("https://homer.example/app")
6182
b, _ := url.Parse("https://homer.example/other")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (C) 2025 Homer Server Contributors
2+
//
3+
// SPDX-License-Identifier: AGPL-3.0-or-later
4+
5+
package passwordhash
6+
7+
import (
8+
"crypto/sha256"
9+
"encoding/hex"
10+
"strings"
11+
)
12+
13+
// legacySHA256HexEqual verifies homer-app SHA-256 hex password hashes (read-only compat).
14+
// New passwords must use bcrypt via Hash(); this path is not used for hashing at rest.
15+
func legacySHA256HexEqual(password, storedHex string) bool {
16+
sum := sha256.Sum256([]byte(password))
17+
return strings.EqualFold(hex.EncodeToString(sum[:]), storedHex)
18+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (C) 2025 Homer Server Contributors
2+
//
3+
// SPDX-License-Identifier: AGPL-3.0-or-later
4+
5+
package passwordhash
6+
7+
import (
8+
"crypto/sha256"
9+
"encoding/hex"
10+
"testing"
11+
)
12+
13+
func TestLegacySHA256HexEqual(t *testing.T) {
14+
sum := sha256.Sum256([]byte("sipcapture"))
15+
legacy := hex.EncodeToString(sum[:])
16+
if !legacySHA256HexEqual("sipcapture", legacy) {
17+
t.Fatal("legacy sha256 verify failed")
18+
}
19+
if legacySHA256HexEqual("wrong", legacy) {
20+
t.Fatal("expected mismatch")
21+
}
22+
}

src/passwordhash/password.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
package passwordhash
88

99
import (
10-
"crypto/sha256"
11-
"encoding/hex"
1210
"strings"
1311

1412
"golang.org/x/crypto/bcrypt"
@@ -43,14 +41,7 @@ func Verify(password, stored string) bool {
4341
if isBcryptHash(stored) {
4442
return bcrypt.CompareHashAndPassword([]byte(stored), []byte(password)) == nil
4543
}
46-
return verifyLegacySHA256Hex(password, stored)
47-
}
48-
49-
// verifyLegacySHA256Hex checks homer-app SHA-256 hex hashes (not used for new passwords).
50-
func verifyLegacySHA256Hex(password, storedHex string) bool {
51-
// codeql[go/weak-sensitive-data-hashing]: legacy format only; Hash() uses bcrypt for new passwords.
52-
sum := sha256.Sum256([]byte(password))
53-
return strings.EqualFold(hex.EncodeToString(sum[:]), storedHex)
44+
return legacySHA256HexEqual(password, stored)
5445
}
5546

5647
func isBcryptHash(stored string) bool {

src/passwordhash/password_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
package passwordhash
66

77
import (
8-
"crypto/sha256"
9-
"encoding/hex"
108
"testing"
119
)
1210

@@ -27,8 +25,8 @@ func TestHashAndVerifyBcrypt(t *testing.T) {
2725
}
2826

2927
func TestVerifyLegacySHA256(t *testing.T) {
30-
sum := sha256.Sum256([]byte("sipcapture"))
31-
legacy := hex.EncodeToString(sum[:])
28+
// Default homer-app admin digest (sha256 hex of "sipcapture").
29+
legacy := "883ffc1f37fd0fe542b0fb9740035c4383e7d976c411161d24e62edace280f90"
3230
if !Verify("sipcapture", legacy) {
3331
t.Fatal("legacy sha256 verify failed")
3432
}

src/pcapwriter/rows.go

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package pcapwriter
77
import (
88
"errors"
99
"fmt"
10-
"math"
1110
"strconv"
1211
"strings"
1312
"time"
@@ -37,10 +36,9 @@ func RowInt(row map[string]interface{}, key string) int {
3736
case float64:
3837
return int(val)
3938
case string:
40-
if u, ok := parseUint32Decimal(val); ok {
41-
if i, ok := intFromUint32(u); ok {
42-
return i
43-
}
39+
n, err := strconv.ParseInt(strings.TrimSpace(val), 10, strconv.IntSize)
40+
if err == nil {
41+
return int(n)
4442
}
4543
}
4644
}
@@ -93,21 +91,6 @@ func parseUint16Decimal(s string) (uint16, bool) {
9391
return uint16(u), true
9492
}
9593

96-
func parseUint32Decimal(s string) (uint32, bool) {
97-
u, err := strconv.ParseUint(strings.TrimSpace(s), 10, 32)
98-
if err != nil {
99-
return 0, false
100-
}
101-
return uint32(u), true
102-
}
103-
104-
func intFromUint32(u uint32) (int, bool) {
105-
if int64(u) > int64(math.MaxInt) {
106-
return 0, false
107-
}
108-
return int(u), true
109-
}
110-
11194
// RowTime parses a timestamp field from a map row.
11295
// Accepts time.Time, string (RFC3339/RFC3339Nano), or numeric (unix seconds/ms/µs/ns).
11396
func RowTime(row map[string]interface{}, key string) time.Time {

0 commit comments

Comments
 (0)