@@ -89,6 +89,7 @@ type ApprovedPairingResult = Extract<
8989> ;
9090type ApprovedPairingDevice = ApprovedPairingResult [ "device" ] ;
9191const INTERNAL_PAIRING_SCOPES = [ "operator.write" , "operator.pairing" ] ;
92+ const INTERNAL_SETUP_SCOPES = [ ...INTERNAL_PAIRING_SCOPES , "operator.talk.secrets" ] ;
9293
9394function createApi ( params ?: {
9495 config ?: OpenClawPluginApi [ "config" ] ;
@@ -286,7 +287,7 @@ describe("device-pair /pair qr", () => {
286287 const result = await command . handler (
287288 createCommandContext ( {
288289 channel : "webchat" ,
289- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
290+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
290291 } ) ,
291292 ) ;
292293 const payload = result as { text ?: string ; mediaUrl ?: string ; sensitiveMedia ?: boolean } ;
@@ -342,6 +343,23 @@ describe("device-pair /pair qr", () => {
342343 } ) ;
343344 } ) ;
344345
346+ it ( "rejects qr setup for internal callers without Talk secret scope" , async ( ) => {
347+ const command = registerPairCommand ( ) ;
348+ const result = await command . handler (
349+ createCommandContext ( {
350+ channel : "webchat" ,
351+ args : "qr" ,
352+ commandBody : "/pair qr" ,
353+ gatewayClientScopes : INTERNAL_PAIRING_SCOPES ,
354+ } ) ,
355+ ) ;
356+
357+ expect ( pluginApiMocks . issueDeviceBootstrapToken ) . not . toHaveBeenCalled ( ) ;
358+ expect ( result ) . toEqual ( {
359+ text : "⚠️ Setup code handoff includes Talk secrets and requires operator.talk.secrets." ,
360+ } ) ;
361+ } ) ;
362+
345363 it ( "reissues the bootstrap token if webchat QR rendering fails before falling back" , async ( ) => {
346364 pluginApiMocks . issueDeviceBootstrapToken
347365 . mockResolvedValueOnce ( {
@@ -358,7 +376,7 @@ describe("device-pair /pair qr", () => {
358376 const result = await command . handler (
359377 createCommandContext ( {
360378 channel : "webchat" ,
361- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
379+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
362380 } ) ,
363381 ) ;
364382 const text = requireText ( result ) ;
@@ -478,7 +496,7 @@ describe("device-pair /pair qr", () => {
478496 const result = await command . handler (
479497 createCommandContext ( {
480498 ...testCase . ctx ,
481- gatewayClientScopes : INTERNAL_PAIRING_SCOPES ,
499+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
482500 } ) ,
483501 ) ;
484502 const text = requireText ( result ) ;
@@ -538,7 +556,7 @@ describe("device-pair /pair qr", () => {
538556 createCommandContext ( {
539557 channel : "discord" ,
540558 senderId : "123" ,
541- gatewayClientScopes : INTERNAL_PAIRING_SCOPES ,
559+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
542560 } ) ,
543561 ) ;
544562 const text = requireText ( result ) ;
@@ -557,7 +575,7 @@ describe("device-pair /pair qr", () => {
557575 createCommandContext ( {
558576 channel : "msteams" ,
559577 senderId : "8:orgid:123" ,
560- gatewayClientScopes : INTERNAL_PAIRING_SCOPES ,
578+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
561579 } ) ,
562580 ) ;
563581 const text = requireText ( result ) ;
@@ -678,6 +696,23 @@ describe("device-pair /pair default setup code", () => {
678696 } ) ;
679697 } ) ;
680698
699+ it ( "rejects setup code issuance for internal callers without Talk secret scope" , async ( ) => {
700+ const command = registerPairCommand ( ) ;
701+ const result = await command . handler (
702+ createCommandContext ( {
703+ channel : "webchat" ,
704+ args : "" ,
705+ commandBody : "/pair" ,
706+ gatewayClientScopes : INTERNAL_PAIRING_SCOPES ,
707+ } ) ,
708+ ) ;
709+
710+ expect ( pluginApiMocks . issueDeviceBootstrapToken ) . not . toHaveBeenCalled ( ) ;
711+ expect ( result ) . toEqual ( {
712+ text : "⚠️ Setup code handoff includes Talk secrets and requires operator.talk.secrets." ,
713+ } ) ;
714+ } ) ;
715+
681716 it ( "fails closed for webchat setup code issuance when scopes are absent" , async ( ) => {
682717 const command = registerPairCommand ( ) ;
683718 const result = await command . handler (
@@ -749,7 +784,7 @@ describe("device-pair /pair default setup code", () => {
749784 channel : "webchat" ,
750785 args : "" ,
751786 commandBody : "/pair" ,
752- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
787+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
753788 } ) ,
754789 ) ;
755790 const text = requireText ( result ) ;
@@ -769,7 +804,7 @@ describe("device-pair /pair default setup code", () => {
769804 channel : "webchat" ,
770805 args : "" ,
771806 commandBody : "/pair" ,
772- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
807+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
773808 } ) ,
774809 ) ;
775810 const text = requireText ( result ) ;
@@ -789,7 +824,7 @@ describe("device-pair /pair default setup code", () => {
789824 channel : "webchat" ,
790825 args : "" ,
791826 commandBody : "/pair" ,
792- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
827+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
793828 } ) ,
794829 ) ;
795830
@@ -808,7 +843,7 @@ describe("device-pair /pair default setup code", () => {
808843 channel : "webchat" ,
809844 args : "" ,
810845 commandBody : "/pair" ,
811- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
846+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
812847 } ) ,
813848 ) ;
814849
@@ -827,7 +862,7 @@ describe("device-pair /pair default setup code", () => {
827862 channel : "webchat" ,
828863 args : "" ,
829864 commandBody : "/pair" ,
830- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
865+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
831866 } ) ,
832867 ) ;
833868
@@ -861,7 +896,7 @@ describe("device-pair /pair default setup code", () => {
861896 channel : "webchat" ,
862897 args : "" ,
863898 commandBody : "/pair" ,
864- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
899+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
865900 } ) ,
866901 ) ;
867902
@@ -890,7 +925,7 @@ describe("device-pair /pair default setup code", () => {
890925 channel : "webchat" ,
891926 args : "" ,
892927 commandBody : "/pair" ,
893- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
928+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
894929 } ) ,
895930 ) ;
896931 const text = requireText ( result ) ;
@@ -910,7 +945,7 @@ describe("device-pair /pair default setup code", () => {
910945 channel : "webchat" ,
911946 args : "" ,
912947 commandBody : "/pair" ,
913- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
948+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
914949 } ) ,
915950 ) ;
916951
@@ -940,7 +975,7 @@ describe("device-pair /pair default setup code", () => {
940975 channel : "webchat" ,
941976 args : "" ,
942977 commandBody : "/pair" ,
943- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
978+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
944979 } ) ,
945980 ) ;
946981
@@ -967,7 +1002,7 @@ describe("device-pair /pair default setup code", () => {
9671002 channel : "webchat" ,
9681003 args : "" ,
9691004 commandBody : "/pair" ,
970- gatewayClientScopes : [ "operator.write" , "operator.pairing" ] ,
1005+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
9711006 } ) ,
9721007 ) ;
9731008
0 commit comments