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
36 changes: 30 additions & 6 deletions client/adapters/bedrock.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (

type BedrockClient struct {
accessKeyID string
secretAccessKey string
secretAccessKey []byte
sessionToken string
region string
httpClient *http.Client
Expand All @@ -37,12 +37,22 @@ type BedrockClient struct {
guardrails *core.Guardrails
}

// String returns a safe string representation of the Bedrock client.
// Sensitive fields (secret access key) are masked to prevent accidental exposure in logs.
func (c *BedrockClient) String() string {
masked := "****"
if len(c.secretAccessKey) > 4 {
masked = string(c.secretAccessKey[:4]) + "****"
}
return fmt.Sprintf("BedrockClient{access_key_id=%s, region=%s, secret_access_key=%s}", c.accessKeyID, c.region, masked)
}

var _ core.Provider = (*BedrockClient)(nil)

func NewBedrockClient(accessKeyID, secretAccessKey, sessionToken, region string) *BedrockClient {
return &BedrockClient{
accessKeyID: accessKeyID,
secretAccessKey: secretAccessKey,
secretAccessKey: []byte(secretAccessKey),
sessionToken: sessionToken,
region: region,
httpClient: core.NewPooledHTTPClient(core.DefaultTimeout),
Expand Down Expand Up @@ -163,7 +173,21 @@ func (c *BedrockClient) StreamChat(ctx context.Context, messages []core.EyrieMes
// Bedrock uses Amazon EventStream format. Parse it.
reader := newEventStreamReader(resp.Body)
for {
event, err := reader.ReadEvent()
event, err := func() (*eventStreamFrame, error) {
var result *eventStreamFrame
var readErr error
done := make(chan struct{})
go func() {
result, readErr = reader.ReadEvent()
close(done)
}()
select {
case <-done:
return result, readErr
case <-streamCtx.Done():
return nil, streamCtx.Err()
}
}()
if err != nil {
if err != io.EOF {
select {
Expand Down Expand Up @@ -235,7 +259,7 @@ func (c *BedrockClient) StreamChat(ctx context.Context, messages []core.EyrieMes
}

func (c *BedrockClient) Ping(ctx context.Context) error {
if c.region == "" || c.accessKeyID == "" || c.secretAccessKey == "" {
if c.region == "" || c.accessKeyID == "" || len(c.secretAccessKey) == 0 {
return fmt.Errorf("eyrie: bedrock credentials are incomplete")
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://bedrock.%s.amazonaws.com/foundation-models", c.region), nil)
Expand Down Expand Up @@ -432,7 +456,7 @@ func (es *eventStreamReader) ReadEvent() (*eventStreamFrame, error) {
}

func (c *BedrockClient) sign(req *http.Request, body []byte, now time.Time) error {
if c.region == "" || c.accessKeyID == "" || c.secretAccessKey == "" {
if c.region == "" || c.accessKeyID == "" || len(c.secretAccessKey) == 0 {
return fmt.Errorf("eyrie: bedrock credentials are incomplete")
}
service := "bedrock"
Expand Down Expand Up @@ -464,7 +488,7 @@ func (c *BedrockClient) sign(req *http.Request, body []byte, now time.Time) erro
scope,
sha256Hex([]byte(canonicalRequest)),
}, "\n")
signingKey := awsSigningKey(c.secretAccessKey, dateStamp, c.region, service)
signingKey := awsSigningKey(string(c.secretAccessKey), dateStamp, c.region, service)
signature := hex.EncodeToString(hmacSHA256(signingKey, stringToSign))
req.Header.Set("Authorization", fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s", c.accessKeyID, scope, signedHeaders, signature))
return nil
Expand Down
3 changes: 2 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ func ParseCustomHeaders() map[string]string {
name := strings.TrimSpace(line[:idx])
value := strings.TrimSpace(line[idx+1:])
// Reject header names/values containing control characters to prevent injection.
if strings.ContainsAny(name, "\r\n") || strings.ContainsAny(value, "\r\n") {
if strings.IndexFunc(name, func(r rune) bool { return r < 0x20 }) >= 0 ||
strings.IndexFunc(value, func(r rune) bool { return r < 0x20 }) >= 0 {
continue
}
result[name] = value
Expand Down
9 changes: 9 additions & 0 deletions storage/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"os"
"strings"
"time"

Expand Down Expand Up @@ -54,6 +55,14 @@ func Open(path string, opts ...Option) (*SQLiteStore, error) {
_ = db.Close()
return nil, err
}
// Restrict SQLite database file permissions to owner-only read/write.
// Skip for in-memory databases (path == ":memory:") which have no real file.
if path != ":memory:" {
if err := os.Chmod(path, 0o600); err != nil {
_ = db.Close()
return nil, fmt.Errorf("storage: chmod %s: %w", path, err)
}
}
return s, nil
}

Expand Down
Loading