• 
      

    Allow replacing a GitHub App's private key.

    Review Request #15200 — Created July 28, 2026 and submitted

    Information

    Review Board
    release-9.x

    Reviewers

    GitHub lets an administrator regenerate an app's private key and revoke
    the old one. When that happens the key Review Board has stored can no
    longer sign app JWTs, which breaks every installation of the app. There
    was no way to recover short of recreating the whole app, since the
    app-record account is hidden and has no credential-editing UI.

    This adds a "Rotate private key" action to the GitHub App connection. It
    accepts a freshly-generated PEM key, validates that it is a usable RSA
    private key, and stores it on the app-record account, restoring the
    connection in place.

    • Ran unit tests.
    • Generated a new private key and uploaded it successfully.
    Summary ID
    Allow replacing a GitHub App's private key.
    GitHub lets an administrator regenerate an app's private key and revoke the old one. When that happens the key Review Board has stored can no longer sign app JWTs, which breaks every installation of the app. There was no way to recover short of recreating the whole app, since the app-record account is hidden and has no credential-editing UI. This adds a "Rotate private key" action to the GitHub App connection. It accepts a freshly-generated PEM key, validates that it is a usable RSA private key, and stores it on the app-record account, restoring the connection in place. Testing Done: - Ran unit tests. - Generated a new private key and uploaded it successfully.
    nqztrxowytwkkklmvmyunyuwuxvlsyvp

    Description From Last Updated

    continuation line over-indented for visual indent Column: 46 Error code: E127

    reviewbot reviewbot

    continuation line over-indented for visual indent Column: 46 Error code: E127

    reviewbot reviewbot

    I think we like to put all strings on their own lines now so how about: raise ValueError( 'The private …

    maubin maubin

    Same here, we can move the ) to its own line.

    maubin maubin

    To be on the safe side, we should urlquote these.

    chipx86 chipx86

    The encode/decode is just a bit hard to read. Can we do: return encrypt_password( base64.b64encode(...) .decode('ascii') ) To check, though, …

    chipx86 chipx86

    Should this be Final[int]?

    chipx86 chipx86

    This is missing the full module path.

    chipx86 chipx86

    Can we use a common function for this? That'll also make it easier for us to move away from encrypt_password …

    chipx86 chipx86

    This is missing Version Added.

    chipx86 chipx86

    Private functions go last. This is a major Claude smell. We never do this and mine keeps trying this.

    chipx86 chipx86

    This is missing a translation.

    chipx86 chipx86
    Checks run (1 failed, 1 succeeded)
    flake8 failed.
    JSHint passed.

    flake8

    david
    Review request changed
    Commits:
    Summary ID
    Allow replacing a GitHub App's private key.
    GitHub lets an administrator regenerate an app's private key and revoke the old one. When that happens the key Review Board has stored can no longer sign app JWTs, which breaks every installation of the app. There was no way to recover short of recreating the whole app, since the app-record account is hidden and has no credential-editing UI. This adds a "Rotate private key" action to the GitHub App connection. It accepts a freshly-generated PEM key, validates that it is a usable RSA private key, and stores it on the app-record account, restoring the connection in place. Testing Done: - Ran unit tests. - Generated a new private key and uploaded it successfully.
    nqztrxowytwkkklmvmyunyuwuxvlsyvp
    Allow replacing a GitHub App's private key.
    GitHub lets an administrator regenerate an app's private key and revoke the old one. When that happens the key Review Board has stored can no longer sign app JWTs, which breaks every installation of the app. There was no way to recover short of recreating the whole app, since the app-record account is hidden and has no credential-editing UI. This adds a "Rotate private key" action to the GitHub App connection. It accepts a freshly-generated PEM key, validates that it is a usable RSA private key, and stores it on the app-record account, restoring the connection in place. Testing Done: - Ran unit tests. - Generated a new private key and uploaded it successfully.
    nqztrxowytwkkklmvmyunyuwuxvlsyvp

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    david
    maubin
    1. 
        
    2. reviewboard/hostingsvcs/github/app_auth.py (Diff revision 3)
       
       
       
      Show all issues

      I think we like to put all strings on their own lines now so how about:

      raise ValueError(
          'The private key is not a valid PEM private key.'
      ) from e
      
    3. Show all issues

      Same here, we can move the ) to its own line.

    4. 
        
    david
    maubin
    1. Ship It!
    2. 
        
    chipx86
    1. 
        
    2. reviewboard/hostingsvcs/github/accounts.py (Diff revision 4)
       
       
       
       
       
      Show all issues

      To be on the safe side, we should urlquote these.

    3. reviewboard/hostingsvcs/github/app_auth.py (Diff revision 4)
       
       
       
      Show all issues

      The encode/decode is just a bit hard to read. Can we do:

      return encrypt_password(
          base64.b64encode(...)
          .decode('ascii')
      )
      

      To check, though, should we be base64-encoding here? A PEM is largely base64-encoded content already. If it were a DER, it'd be more reasonable to encode it.

      1. encrypt_password doesn't work with multi-line content.

    4. reviewboard/hostingsvcs/github/forms.py (Diff revision 4)
       
       
      Show all issues

      Should this be Final[int]?

    5. reviewboard/hostingsvcs/github/views.py (Diff revision 4)
       
       
      Show all issues

      This is missing the full module path.

    6. Show all issues

      Can we use a common function for this?

      That'll also make it easier for us to move away from encrypt_password here as we move to cryptozoology.

    7. Show all issues

      This is missing Version Added.

    8. Show all issues

      Private functions go last.

      This is a major Claude smell. We never do this and mine keeps trying this.

      1. When you say "we" I think you mean "I". I've often put private functions adjacent to where they're used and it feels like it's only the last couple years that you've gotten nitpicky about that ordering.

    9. Show all issues

      This is missing a translation.

    10. 
        
    david
    david
    Review request changed
    Status:
    Completed
    Change Summary:
    Pushed to release-9.x (2337f25)