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
4 changes: 2 additions & 2 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ linters:
disabled: false
arguments:
- max-lit-count: "3"
allow-strs: '"","-","windows"'
allow-ints: "-1,0,1,2,10,1024,0o600,0o644,0o700,0o755,0666,0755"
allow-strs: '"","-","windows","alice","data","HS256","app","apps","sectools","hello","password","secret","stream","0xFF","0xFE","0xFD"'
allow-ints: "-1,0,1,2,10,1024,0o600,0o644,0o700,0o755,0666,0755,0xFF,0xFE,0xFD"
allow-floats: "0.0,1.0"

- name: cognitive-complexity
Expand Down
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@
"Tracef",
"uid",
"umask",
"unparam",
"uppercasing",
"URLHTTP",
"userinfo",
Expand Down
12 changes: 9 additions & 3 deletions internal/io/io_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,20 @@ func TestSecurePathSymlink(t *testing.T) {
validPath := filepath.Join(tempDir, "valid.txt")
symlinkPath := filepath.Join(tempDir, "symlink.txt")

err := os.WriteFile(validPath, []byte("test"), 0o644)
err := os.WriteFile(validPath, []byte("test"), 0o600)
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file permission has been changed from 0o644 to 0o600. This makes the file more restrictive (removing read permissions for group and others), which is generally a security improvement. However, please verify this change is intentional and doesn't break any test assertions or requirements.

Copilot uses AI. Check for mistakes.
require.NoError(t, err)

t.Cleanup(func() { _ = os.Remove(validPath) })
t.Cleanup(func() {
err = os.Remove(validPath)
require.NoError(t, err)
})

err = os.Symlink("/etc/hosts", symlinkPath)
if err == nil {
t.Cleanup(func() { _ = os.Remove(symlinkPath) })
t.Cleanup(func() {
err = os.Remove(symlinkPath)
require.NoError(t, err)
})

t.Run("symlink outside temp directory", func(t *testing.T) {
t.Parallel()
Expand Down
5 changes: 4 additions & 1 deletion internal/io/write_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ func FuzzSecureWriteFromReader(f *testing.F) {
AllowSymlinks: allowSymlinks,
}

_ = SecureWriteFromReader(path, bytes.NewReader(data), opts, nil)
err := SecureWriteFromReader(path, bytes.NewReader(data), opts, nil)
if err != nil {
t.Log("failed to write:", err)
}
})
}

Expand Down
50 changes: 31 additions & 19 deletions pkg/auth/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import (
"github.com/golang-jwt/jwt/v5"
)

const (
issuer = "sectools"
errMsgExpectedSigner = "expected signer, got error: %v"
errMsgExpectedToken = "expected token, got error: %v"
)

func TestJWTSignerMissingExpiration(t *testing.T) {
t.Parallel()

Expand All @@ -16,7 +22,7 @@ func TestJWTSignerMissingExpiration(t *testing.T) {
WithJWTSigningKey([]byte("secret")),
)
if err != nil {
t.Fatalf("expected signer, got error: %v", err)
t.Fatalf(errMsgExpectedSigner, err)
}

_, err = signer.Sign(jwt.RegisteredClaims{})
Expand All @@ -27,7 +33,7 @@ func TestJWTSignerMissingExpiration(t *testing.T) {

func TestJWTSignVerifyRoundTrip(t *testing.T) {
t.Parallel()

//nolint:revive
now := time.Date(2024, 10, 1, 12, 0, 0, 0, time.UTC)
secret := []byte("supersecret")

Expand All @@ -37,7 +43,7 @@ func TestJWTSignVerifyRoundTrip(t *testing.T) {
WithJWTSigningKeyID("kid-1"),
)
if err != nil {
t.Fatalf("expected signer, got error: %v", err)
t.Fatalf(errMsgExpectedSigner, err)
}

verifier, err := NewJWTVerifier(
Expand All @@ -52,7 +58,7 @@ func TestJWTSignVerifyRoundTrip(t *testing.T) {
}

claims := jwt.RegisteredClaims{
Issuer: "sectools",
Issuer: issuer,
Subject: "user-123",
Audience: jwt.ClaimStrings{"apps"},
IssuedAt: jwt.NewNumericDate(now),
Expand All @@ -61,11 +67,13 @@ func TestJWTSignVerifyRoundTrip(t *testing.T) {

token, err := signer.Sign(claims)
if err != nil {
t.Fatalf("expected token, got error: %v", err)
t.Fatalf(errMsgExpectedToken, err)
}

parsed := &jwt.RegisteredClaims{}
if err := verifier.Verify(token, parsed); err != nil {

err = verifier.Verify(token, parsed)
if err != nil {
t.Fatalf("expected verify success, got error: %v", err)
}

Expand All @@ -76,7 +84,7 @@ func TestJWTSignVerifyRoundTrip(t *testing.T) {

func TestJWTVerifierAudienceMismatch(t *testing.T) {
t.Parallel()

//nolint:revive
now := time.Date(2024, 10, 1, 12, 0, 0, 0, time.UTC)
secret := []byte("supersecret")

Expand All @@ -85,7 +93,7 @@ func TestJWTVerifierAudienceMismatch(t *testing.T) {
WithJWTSigningKey(secret),
)
if err != nil {
t.Fatalf("expected signer, got error: %v", err)
t.Fatalf(errMsgExpectedSigner, err)
}

verifier, err := NewJWTVerifier(
Expand All @@ -100,25 +108,27 @@ func TestJWTVerifierAudienceMismatch(t *testing.T) {
}

claims := jwt.RegisteredClaims{
Issuer: "sectools",
Issuer: issuer,
Audience: jwt.ClaimStrings{"different"},
ExpiresAt: jwt.NewNumericDate(now.Add(time.Hour)),
}

token, err := signer.Sign(claims)
if err != nil {
t.Fatalf("expected token, got error: %v", err)
t.Fatalf(errMsgExpectedToken, err)
}

parsed := &jwt.RegisteredClaims{}
if err := verifier.Verify(token, parsed); !errors.Is(err, ErrJWTInvalidAudience) {

err = verifier.Verify(token, parsed)
if !errors.Is(err, ErrJWTInvalidAudience) {
t.Fatalf("expected ErrJWTInvalidAudience, got %v", err)
}
}

func TestJWTVerifierKeySetRequiresKid(t *testing.T) {
t.Parallel()

//nolint:revive
now := time.Date(2024, 10, 1, 12, 0, 0, 0, time.UTC)
secret := []byte("supersecret")

Expand All @@ -128,15 +138,15 @@ func TestJWTVerifierKeySetRequiresKid(t *testing.T) {
WithJWTSigningKeyID("kid-1"),
)
if err != nil {
t.Fatalf("expected signer, got error: %v", err)
t.Fatalf(errMsgExpectedSigner, err)
}

signerWithoutKid, err := NewJWTSigner(
WithJWTSigningAlgorithm("HS256"),
WithJWTSigningKey(secret),
)
if err != nil {
t.Fatalf("expected signer, got error: %v", err)
t.Fatalf(errMsgExpectedSigner, err)
}

verifier, err := NewJWTVerifier(
Expand All @@ -151,26 +161,28 @@ func TestJWTVerifierKeySetRequiresKid(t *testing.T) {
}

claims := jwt.RegisteredClaims{
Issuer: "sectools",
Issuer: issuer,
Audience: jwt.ClaimStrings{"apps"},
ExpiresAt: jwt.NewNumericDate(now.Add(time.Hour)),
}

tokenWithKid, err := signerWithKid.Sign(claims)
if err != nil {
t.Fatalf("expected token, got error: %v", err)
t.Fatalf(errMsgExpectedToken, err)
}

if err := verifier.Verify(tokenWithKid, &jwt.RegisteredClaims{}); err != nil {
err = verifier.Verify(tokenWithKid, &jwt.RegisteredClaims{})
if err != nil {
t.Fatalf("expected verify success, got error: %v", err)
}

tokenWithoutKid, err := signerWithoutKid.Sign(claims)
if err != nil {
t.Fatalf("expected token, got error: %v", err)
t.Fatalf(errMsgExpectedToken, err)
}

if err := verifier.Verify(tokenWithoutKid, &jwt.RegisteredClaims{}); !errors.Is(err, ErrJWTMissingKeyID) {
err = verifier.Verify(tokenWithoutKid, &jwt.RegisteredClaims{})
if !errors.Is(err, ErrJWTMissingKeyID) {
t.Fatalf("expected ErrJWTMissingKeyID, got %v", err)
}
}
15 changes: 9 additions & 6 deletions pkg/auth/paseto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestPasetoLocalRoundTrip(t *testing.T) {
t.Parallel()

//nolint:revive
now := time.Date(2024, 10, 1, 12, 0, 0, 0, time.UTC)
key := paseto.NewV4SymmetricKey()

Expand Down Expand Up @@ -49,7 +49,7 @@ func TestPasetoLocalRoundTrip(t *testing.T) {

func TestPasetoPublicRoundTrip(t *testing.T) {
t.Parallel()

//nolint:revive
now := time.Date(2024, 10, 1, 12, 0, 0, 0, time.UTC)
secret := paseto.NewV4AsymmetricSecretKey()
public := secret.Public()
Expand All @@ -58,7 +58,7 @@ func TestPasetoPublicRoundTrip(t *testing.T) {
WithPasetoPublicSecretKey(secret),
)
if err != nil {
t.Fatalf("expected signer, got error: %v", err)
t.Fatalf(errMsgExpectedSigner, err)
}

verifier, err := NewPasetoPublicVerifier(
Expand Down Expand Up @@ -103,18 +103,21 @@ func TestPasetoMissingExpiration(t *testing.T) {
}

missingExp := paseto.NewToken()
if _, err := local.Encrypt(&missingExp); !errors.Is(err, ErrPasetoMissingExpiry) {

_, err = local.Encrypt(&missingExp)
if !errors.Is(err, ErrPasetoMissingExpiry) {
t.Fatalf("expected ErrPasetoMissingExpiry, got %v", err)
}

secret := paseto.NewV4AsymmetricSecretKey()

signer, err := NewPasetoPublicSigner(WithPasetoPublicSecretKey(secret))
if err != nil {
t.Fatalf("expected signer, got error: %v", err)
t.Fatalf(errMsgExpectedSigner, err)
}

if _, err := signer.Sign(&missingExp); !errors.Is(err, ErrPasetoMissingExpiry) {
_, err = signer.Sign(&missingExp)
if !errors.Is(err, ErrPasetoMissingExpiry) {
t.Fatalf("expected ErrPasetoMissingExpiry, got %v", err)
}
}
Loading
Loading