@@ -803,6 +803,52 @@ describe("device pairing tokens", () => {
803803 expect ( hasEffectivePairedDeviceRole ( device , "operator" ) ) . toBe ( true ) ;
804804 } ) ;
805805
806+ test ( "filters active token roles to the approved pairing role set" , async ( ) => {
807+ const now = Date . now ( ) ;
808+ const device : PairedDevice = {
809+ deviceId : "device-filtered" ,
810+ publicKey : "pk-filtered" ,
811+ role : "operator" ,
812+ roles : [ "operator" ] ,
813+ tokens : {
814+ node : {
815+ token : "forged-node-token" ,
816+ role : "node" ,
817+ scopes : [ ] ,
818+ createdAtMs : now ,
819+ } ,
820+ operator : {
821+ token : "real-operator-token" ,
822+ role : "operator" ,
823+ scopes : [ "operator.read" ] ,
824+ createdAtMs : now ,
825+ } ,
826+ } ,
827+ createdAtMs : now ,
828+ approvedAtMs : now ,
829+ } ;
830+
831+ expect ( listEffectivePairedDeviceRoles ( device ) ) . toEqual ( [ "operator" ] ) ;
832+ expect ( hasEffectivePairedDeviceRole ( device , "node" ) ) . toBe ( false ) ;
833+ } ) ;
834+
835+ test ( "rejects rotating a token for a role that was never approved" , async ( ) => {
836+ const baseDir = await mkdtemp ( join ( tmpdir ( ) , "openclaw-device-pairing-" ) ) ;
837+ await setupPairedOperatorDevice ( baseDir , [ "operator.pairing" ] ) ;
838+
839+ await expect (
840+ rotateDeviceToken ( {
841+ deviceId : "device-1" ,
842+ role : "node" ,
843+ baseDir,
844+ } ) ,
845+ ) . resolves . toEqual ( { ok : false , reason : "unknown-device-or-role" } ) ;
846+
847+ const paired = await getPairedDevice ( "device-1" , baseDir ) ;
848+ expect ( paired ?. tokens ?. node ) . toBeUndefined ( ) ;
849+ expect ( paired && listEffectivePairedDeviceRoles ( paired ) ) . toEqual ( [ "operator" ] ) ;
850+ } ) ;
851+
806852 test ( "removes paired devices by device id" , async ( ) => {
807853 const baseDir = await mkdtemp ( join ( tmpdir ( ) , "openclaw-device-pairing-" ) ) ;
808854 await setupPairedOperatorDevice ( baseDir , [ "operator.read" ] ) ;
0 commit comments