Skip to content

Commit 9c591dc

Browse files
guardrails changes
1 parent a1a4f6b commit 9c591dc

File tree

35 files changed

+888
-407
lines changed

35 files changed

+888
-407
lines changed

core/go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ go 1.24
55
toolchain go1.24.3
66

77
require (
8-
github.com/aws/aws-sdk-go-v2 v1.38.0
9-
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10
8+
github.com/aws/aws-sdk-go-v2 v1.39.2
9+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1
1010
github.com/aws/aws-sdk-go-v2/config v1.31.0
1111
github.com/bytedance/sonic v1.14.0
1212
github.com/google/uuid v1.6.0
@@ -21,15 +21,15 @@ require (
2121
github.com/andybalholm/brotli v1.2.0 // indirect
2222
github.com/aws/aws-sdk-go-v2/credentials v1.18.4 // indirect
2323
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 // indirect
24-
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 // indirect
25-
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 // indirect
24+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect
25+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect
2626
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
2727
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
2828
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 // indirect
2929
github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 // indirect
3030
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 // indirect
3131
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 // indirect
32-
github.com/aws/smithy-go v1.22.5 // indirect
32+
github.com/aws/smithy-go v1.23.0 // indirect
3333
github.com/bahlo/generic-list-go v0.2.0 // indirect
3434
github.com/buger/jsonparser v1.1.1 // indirect
3535
github.com/bytedance/sonic/loader v0.3.0 // indirect

core/go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcao
22
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
33
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
44
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
5-
github.com/aws/aws-sdk-go-v2 v1.38.0 h1:UCRQ5mlqcFk9HJDIqENSLR3wiG1VTWlyUfLDEvY7RxU=
6-
github.com/aws/aws-sdk-go-v2 v1.38.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
7-
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
8-
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
5+
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
6+
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
7+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
8+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
99
github.com/aws/aws-sdk-go-v2/config v1.31.0 h1:9yH0xiY5fUnVNLRWO0AtayqwU1ndriZdN78LlhruJR4=
1010
github.com/aws/aws-sdk-go-v2/config v1.31.0/go.mod h1:VeV3K72nXnhbe4EuxxhzsDc/ByrCSlZwUnWH52Nde/I=
1111
github.com/aws/aws-sdk-go-v2/credentials v1.18.4 h1:IPd0Algf1b+Qy9BcDp0sCUcIWdCQPSzDoMK3a8pcbUM=
1212
github.com/aws/aws-sdk-go-v2/credentials v1.18.4/go.mod h1:nwg78FjH2qvsRM1EVZlX9WuGUJOL5od+0qvm0adEzHk=
1313
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 h1:GicIdnekoJsjq9wqnvyi2elW6CGMSYKhdozE7/Svh78=
1414
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3/go.mod h1:R7BIi6WNC5mc1kfRM7XM/VHC3uRWkjc396sfabq4iOo=
15-
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 h1:o9RnO+YZ4X+kt5Z7Nvcishlz0nksIt2PIzDglLMP0vA=
16-
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3/go.mod h1:+6aLJzOG1fvMOyzIySYjOFjcguGvVRL68R+uoRencN4=
17-
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 h1:joyyUFhiTQQmVK6ImzNU9TQSNRNeD9kOklqTzyk5v6s=
18-
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3/go.mod h1:+vNIyZQP3b3B1tSLI0lxvrU9cfM7gpdRXMFfm67ZcPc=
15+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970=
16+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA=
17+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU=
18+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI=
1919
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
2020
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
2121
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
@@ -28,8 +28,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 h1:6csaS/aJmqZQbKhi1EyEMM7y
2828
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0/go.mod h1:59qHWaY5B+Rs7HGTuVGaC32m0rdpQ68N8QCN3khYiqs=
2929
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 h1:MG9VFW43M4A8BYeAfaJJZWrroinxeTi2r3+SnmLQfSA=
3030
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0/go.mod h1:JdeBDPgpJfuS6rU/hNglmOigKhyEZtBmbraLE4GK1J8=
31-
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
32-
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
31+
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
32+
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
3333
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
3434
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
3535
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=

framework/encrypt/encrypt.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Package encrypt provides reversible AES-256-GCM encryption and decryption utilities
2+
// for securing sensitive data like API keys and credentials.
3+
package encrypt
4+
5+
import (
6+
"crypto/aes"
7+
"crypto/cipher"
8+
"crypto/rand"
9+
"encoding/base64"
10+
"fmt"
11+
"io"
12+
"strings"
13+
14+
"github.com/maximhq/bifrost/core/schemas"
15+
)
16+
17+
var encryptionKey []byte
18+
var logger schemas.Logger
19+
20+
const DefaultKey = "bifrost-default-encryption-key-32b"
21+
22+
// Init initializes the encryption key from environment variable
23+
func Init(key string, _logger schemas.Logger) {
24+
logger = _logger
25+
if key == "" {
26+
// In this case encryption will be disabled
27+
logger.Warn("encryption key is not set, encryption will be disabled. To set encryption key: use the encryption_key field in the configuration file or set the BIFROST_ENCRYPTION_KEY environment variable. Note that - once encryption key is set, it cannot be changed later unless you clean up the database.")
28+
return
29+
}
30+
// Ensure key is exactly 32 bytes for AES-256
31+
if len(key) < 32 {
32+
// Pad with zeros if too short
33+
encryptionKey = make([]byte, 32)
34+
copy(encryptionKey, []byte(key))
35+
} else {
36+
// Truncate if too long
37+
encryptionKey = []byte(key)[:32]
38+
}
39+
}
40+
41+
// IsRedacted checks if a secret is redacted
42+
func IsRedacted(secret string) bool {
43+
if secret == "" {
44+
return false
45+
}
46+
47+
// Check if completely redacted (all asterisks)
48+
if secret == strings.Repeat("*", len(secret)) {
49+
return true
50+
}
51+
52+
// Check if partially redacted (starts with asterisks, ends with visible chars)
53+
if len(secret) > 4 && strings.HasPrefix(secret, strings.Repeat("*", len(secret)-4)) {
54+
return true
55+
}
56+
57+
return false
58+
}
59+
60+
// RedactSecret redacts a secret string, keeping the last 4 characters visible
61+
func RedactSecret(secret string) string {
62+
if secret == "" {
63+
return ""
64+
}
65+
length := len(secret)
66+
if length <= 4 {
67+
// If secret is 4 chars or less, redact completely
68+
return strings.Repeat("*", length)
69+
}
70+
// Show last 4 characters
71+
visiblePart := secret[length-4:]
72+
redactedPart := strings.Repeat("*", length-4)
73+
return redactedPart + visiblePart
74+
}
75+
76+
// Encrypt encrypts a plaintext string using AES-256-GCM and returns a base64-encoded ciphertext
77+
func Encrypt(plaintext string) string {
78+
if encryptionKey == nil {
79+
return plaintext
80+
}
81+
if plaintext == "" {
82+
return ""
83+
}
84+
85+
block, err := aes.NewCipher(encryptionKey)
86+
if err != nil {
87+
return plaintext // Fallback to plaintext on error
88+
}
89+
90+
aesGCM, err := cipher.NewGCM(block)
91+
if err != nil {
92+
return plaintext
93+
}
94+
95+
// Create a nonce (number used once)
96+
nonce := make([]byte, aesGCM.NonceSize())
97+
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
98+
return plaintext
99+
}
100+
101+
// Encrypt the data
102+
ciphertext := aesGCM.Seal(nonce, nonce, []byte(plaintext), nil)
103+
104+
// Encode to base64 for storage
105+
return base64.StdEncoding.EncodeToString(ciphertext)
106+
}
107+
108+
// Decrypt decrypts a base64-encoded ciphertext using AES-256-GCM and returns the plaintext
109+
func Decrypt(ciphertext string) (string, error) {
110+
if encryptionKey == nil {
111+
return ciphertext, nil
112+
}
113+
if ciphertext == "" {
114+
return "", nil
115+
}
116+
117+
// Decode from base64
118+
data, err := base64.StdEncoding.DecodeString(ciphertext)
119+
if err != nil {
120+
return "", fmt.Errorf("failed to decode base64: %w", err)
121+
}
122+
123+
block, err := aes.NewCipher(encryptionKey)
124+
if err != nil {
125+
return "", fmt.Errorf("failed to create cipher: %w", err)
126+
}
127+
128+
aesGCM, err := cipher.NewGCM(block)
129+
if err != nil {
130+
return "", fmt.Errorf("failed to create GCM: %w", err)
131+
}
132+
133+
// Extract nonce
134+
nonceSize := aesGCM.NonceSize()
135+
if len(data) < nonceSize {
136+
return "", fmt.Errorf("ciphertext too short")
137+
}
138+
139+
nonce, ciphertextBytes := data[:nonceSize], data[nonceSize:]
140+
141+
// Decrypt the data
142+
plaintext, err := aesGCM.Open(nil, nonce, ciphertextBytes, nil)
143+
if err != nil {
144+
return "", fmt.Errorf("failed to decrypt: %w", err)
145+
}
146+
147+
return string(plaintext), nil
148+
}

framework/encrypt/encrypt_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package encrypt
2+
3+
import (
4+
"testing"
5+
6+
bifrost "github.com/maximhq/bifrost/core"
7+
"github.com/maximhq/bifrost/core/schemas"
8+
)
9+
10+
func TestEncryptDecrypt(t *testing.T) {
11+
// Set a test encryption key
12+
testKey := "test-encryption-key-for-testing-32bytes"
13+
Init(testKey, bifrost.NewDefaultLogger(schemas.LogLevelInfo))
14+
15+
testCases := []struct {
16+
name string
17+
plaintext string
18+
}{
19+
{
20+
name: "Simple text",
21+
plaintext: "hello world",
22+
},
23+
{
24+
name: "AWS Access Key",
25+
plaintext: "AKIAIOSFODNN7EXAMPLE",
26+
},
27+
{
28+
name: "AWS Secret Key",
29+
plaintext: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
30+
},
31+
{
32+
name: "Empty string",
33+
plaintext: "",
34+
},
35+
{
36+
name: "Special characters",
37+
plaintext: "!@#$%^&*()_+-=[]{}|;':\",./<>?`~",
38+
},
39+
{
40+
name: "Long text",
41+
plaintext: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
42+
},
43+
}
44+
45+
for _, tc := range testCases {
46+
t.Run(tc.name, func(t *testing.T) {
47+
// Encrypt
48+
encrypted := Encrypt(tc.plaintext)
49+
50+
// For empty strings, encryption should return empty
51+
if tc.plaintext == "" {
52+
if encrypted != "" {
53+
t.Errorf("Expected empty string for empty input, got: %s", encrypted)
54+
}
55+
return
56+
}
57+
58+
// Encrypted text should be different from plaintext
59+
if encrypted == tc.plaintext {
60+
t.Errorf("Encrypted text should be different from plaintext")
61+
}
62+
63+
// Decrypt
64+
decrypted, err := Decrypt(encrypted)
65+
if err != nil {
66+
t.Fatalf("Failed to decrypt: %v", err)
67+
}
68+
69+
// Decrypted text should match original plaintext
70+
if decrypted != tc.plaintext {
71+
t.Errorf("Decrypted text does not match original.\nExpected: %s\nGot: %s", tc.plaintext, decrypted)
72+
}
73+
})
74+
}
75+
}
76+
77+
func TestEncryptDeterminism(t *testing.T) {
78+
// Set a test encryption key
79+
testKey := "test-encryption-key-for-testing-32bytes"
80+
Init(testKey, bifrost.NewDefaultLogger(schemas.LogLevelInfo))
81+
82+
plaintext := "test-plaintext"
83+
84+
// Encrypt the same text twice
85+
encrypted1 := Encrypt(plaintext)
86+
encrypted2 := Encrypt(plaintext)
87+
88+
// They should be different (due to random nonce)
89+
if encrypted1 == encrypted2 {
90+
t.Errorf("Two encryptions of the same plaintext should produce different ciphertexts (due to random nonce)")
91+
}
92+
93+
// But both should decrypt to the same plaintext
94+
decrypted1, err := Decrypt(encrypted1)
95+
if err != nil {
96+
t.Fatalf("Failed to decrypt first: %v", err)
97+
}
98+
decrypted2, err := Decrypt(encrypted2)
99+
if err != nil {
100+
t.Fatalf("Failed to decrypt second: %v", err)
101+
}
102+
103+
if decrypted1 != plaintext || decrypted2 != plaintext {
104+
t.Errorf("Both decryptions should match original plaintext")
105+
}
106+
}
107+
108+
func TestDecryptInvalidData(t *testing.T) {
109+
// Set a test encryption key
110+
testKey := "test-encryption-key-for-testing-32bytes"
111+
Init(testKey, bifrost.NewDefaultLogger(schemas.LogLevelInfo))
112+
113+
testCases := []struct {
114+
name string
115+
ciphertext string
116+
}{
117+
{
118+
name: "Invalid base64",
119+
ciphertext: "not-valid-base64!@#$",
120+
},
121+
{
122+
name: "Valid base64 but invalid ciphertext",
123+
ciphertext: "YWJjZGVmZ2hpamtsbW5vcA==",
124+
},
125+
{
126+
name: "Too short ciphertext",
127+
ciphertext: "YWJj",
128+
},
129+
}
130+
131+
for _, tc := range testCases {
132+
t.Run(tc.name, func(t *testing.T) {
133+
_, err := Decrypt(tc.ciphertext)
134+
if err == nil {
135+
t.Errorf("Expected error when decrypting invalid data, got nil")
136+
}
137+
})
138+
}
139+
}

framework/go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ require (
1616
)
1717

1818
require (
19-
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
19+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect
2020
github.com/jackc/pgpassfile v1.0.0 // indirect
2121
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
2222
github.com/jackc/pgx/v5 v5.6.0 // indirect
@@ -29,19 +29,19 @@ require (
2929
cloud.google.com/go/compute/metadata v0.8.0 // indirect
3030
github.com/andybalholm/brotli v1.2.0 // indirect
3131
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
32-
github.com/aws/aws-sdk-go-v2 v1.38.0 // indirect
32+
github.com/aws/aws-sdk-go-v2 v1.39.2 // indirect
3333
github.com/aws/aws-sdk-go-v2/config v1.31.0 // indirect
3434
github.com/aws/aws-sdk-go-v2/credentials v1.18.4 // indirect
3535
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.3 // indirect
36-
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.3 // indirect
37-
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.3 // indirect
36+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect
37+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect
3838
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
3939
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
4040
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.3 // indirect
4141
github.com/aws/aws-sdk-go-v2/service/sso v1.28.0 // indirect
4242
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.0 // indirect
4343
github.com/aws/aws-sdk-go-v2/service/sts v1.37.0 // indirect
44-
github.com/aws/smithy-go v1.22.5 // indirect
44+
github.com/aws/smithy-go v1.23.0 // indirect
4545
github.com/bahlo/generic-list-go v0.2.0 // indirect
4646
github.com/buger/jsonparser v1.1.1 // indirect
4747
github.com/bytedance/sonic v1.14.0 // indirect

0 commit comments

Comments
 (0)