From f989a69fce5e07990c571460843df7d24db1b65e Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 15 Sep 2025 16:50:48 -0700 Subject: [PATCH 1/4] allow bypassing credential in access token check for certain use cases (SIP server creating dispatch), it's desirable to issue tokens containing credentials so features like auto-egress could continue to function --- auth/accesstoken.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/auth/accesstoken.go b/auth/accesstoken.go index b6804fd77..a8aabb699 100644 --- a/auth/accesstoken.go +++ b/auth/accesstoken.go @@ -29,10 +29,11 @@ const ( // AccessToken produces token signed with API key and secret type AccessToken struct { - apiKey string - secret string - grant ClaimGrants - validFor time.Duration + apiKey string + secret string + grant ClaimGrants + validFor time.Duration + allowSensitiveCredentials bool } func NewAccessToken(key string, secret string) *AccessToken { @@ -138,6 +139,16 @@ func (t *AccessToken) SetAgents(agents ...*livekit.RoomAgentDispatch) *AccessTok return t } +// SetAllowSensitiveCredentials enables the token to contain sensitive credentials, by default it is disabled. +// When tokens are issued to end-users, it's not a good idea to issue sensitive data such as API keys/secrets in them +// JWT tokens are not encrypted, so anything that is issued in them can be read by anyone. +// When the tokens are used in a server environment (i.e. connecting from SIP or Agents), you can bypass the +// credentials check by enabling this option. +func (t *AccessToken) SetAllowSensitiveCredentials(allow bool) *AccessToken { + t.allowSensitiveCredentials = allow + return t +} + func (t *AccessToken) GetGrants() *ClaimGrants { return &t.grant } @@ -147,7 +158,7 @@ func (t *AccessToken) ToJWT() (string, error) { return "", ErrKeysMissing } - if t.grant.RoomConfig != nil { + if t.grant.RoomConfig != nil && !t.allowSensitiveCredentials { if err := t.grant.RoomConfig.CheckCredentials(); err != nil { return "", err } From 77bca46b31afdf4ec5bf04096c555e2012880ca0 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 15 Sep 2025 16:54:56 -0700 Subject: [PATCH 2/4] use this for sip tokens --- sip/token.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sip/token.go b/sip/token.go index 0863d0430..4d3f6bc1d 100644 --- a/sip/token.go +++ b/sip/token.go @@ -51,7 +51,8 @@ func BuildSIPToken(params SIPTokenParams) (string, error) { SetRoomPreset(params.RoomPreset). SetRoomConfig(params.RoomConfig). SetKind(livekit.ParticipantInfo_SIP). - SetValidFor(24 * time.Hour) + SetValidFor(24 * time.Hour). + SetAllowSensitiveCredentials(true) return at.ToJWT() } From ec77c6fe7248976310adae40bce62ae747b403b1 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 15 Sep 2025 16:56:00 -0700 Subject: [PATCH 3/4] changeset --- .changeset/kind-flowers-obey.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/kind-flowers-obey.md diff --git a/.changeset/kind-flowers-obey.md b/.changeset/kind-flowers-obey.md new file mode 100644 index 000000000..00ce682ac --- /dev/null +++ b/.changeset/kind-flowers-obey.md @@ -0,0 +1,5 @@ +--- +"github.com/livekit/protocol": patch +--- + +allow credentials to be stored in SIP tokens From b19780c03561ba5541515a3ff54682256856feff Mon Sep 17 00:00:00 2001 From: David Zhao Date: Mon, 15 Sep 2025 16:59:55 -0700 Subject: [PATCH 4/4] test --- auth/accesstoken_test.go | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/auth/accesstoken_test.go b/auth/accesstoken_test.go index 0e0198b8b..d6255d67f 100644 --- a/auth/accesstoken_test.go +++ b/auth/accesstoken_test.go @@ -179,6 +179,57 @@ func TestAccessToken(t *testing.T) { require.True(t, hasAgentName, "agentName should be present in camelCase") _, hasAgentNameSnakeCase := agent["agent_name"] require.False(t, hasAgentNameSnakeCase, "agent_name should not be present in snake_case") + + t.Run("room configuration blocks sensitive credentials by default", func(t *testing.T) { + apiKey, secret := apiKeypair() + roomConfig := &livekit.RoomConfiguration{ + Egress: &livekit.RoomEgress{ + Room: &livekit.RoomCompositeEgressRequest{ + FileOutputs: []*livekit.EncodedFileOutput{{ + Output: &livekit.EncodedFileOutput_S3{S3: &livekit.S3Upload{Secret: "super-secret"}}, + }}, + }, + }, + } + _, err := NewAccessToken(apiKey, secret). + SetVideoGrant(&VideoGrant{RoomJoin: true, Room: "test-room"}). + SetRoomConfig(roomConfig). + ToJWT() + require.ErrorIs(t, err, ErrSensitiveCredentials) + }) + + t.Run("room configuration allows sensitive credentials when enabled", func(t *testing.T) { + apiKey, secret := apiKeypair() + roomConfig := &livekit.RoomConfiguration{ + Egress: &livekit.RoomEgress{ + Room: &livekit.RoomCompositeEgressRequest{ + FileOutputs: []*livekit.EncodedFileOutput{{ + Output: &livekit.EncodedFileOutput_S3{S3: &livekit.S3Upload{Secret: "super-secret"}}, + }}, + }, + }, + } + value, err := NewAccessToken(apiKey, secret). + SetVideoGrant(&VideoGrant{RoomJoin: true, Room: "test-room"}). + SetRoomConfig(roomConfig). + SetAllowSensitiveCredentials(true). + ToJWT() + require.NoError(t, err) + + v, err := ParseAPIToken(value) + require.NoError(t, err) + claims, err := v.Verify(secret) + require.NoError(t, err) + + rc := (*livekit.RoomConfiguration)(claims.RoomConfig) + require.NotNil(t, rc) + require.NotNil(t, rc.Egress) + require.NotNil(t, rc.Egress.Room) + require.NotEmpty(t, rc.Egress.Room.FileOutputs) + s3Out, ok := rc.Egress.Room.FileOutputs[0].Output.(*livekit.EncodedFileOutput_S3) + require.True(t, ok) + require.Equal(t, "super-secret", s3Out.S3.Secret) + }) }) }