Skip to content

Instantly share code, notes, and snippets.

@0xdevalias
Last active May 3, 2024 08:17
Show Gist options
  • Save 0xdevalias/ca22836755c2e1024b1f9c36504d56a3 to your computer and use it in GitHub Desktop.
Save 0xdevalias/ca22836755c2e1024b1f9c36504d56a3 to your computer and use it in GitHub Desktop.
Some notes on decrypting GrowthBook feature flags

Decrypting GrowthBook feature flags

Gotta love somewhat pointless features like “encrypt the feature flags response so people can’t see it”:

In something that needs to be able to access it from a frontend web app by definition:

  • In Chrome DevTools, Network tab, look for a request like this:
  • In Chrome DevTools search panel, search across the sites loaded code for the SDK slug (eg. sdk-xABCD1234EFG)
  • Find code like this:
    • let el = {
        apiHost: "https://cdn.growthbook.io",
        clientKey: "sdk-xABCD1234EFG",
        decryptionKey: "/a1bcdEFghIjKlMnopqrSt==",
        enableDevMode: !(0,
        F.Bl)(),
        subscribeToChanges: !0,
        trackingCallback: (e,t)=>{
          (0,
          ea.L9)("View Experiment", {
            experiment: e,
            result: t
          })
        }
      };
    • a.useEffect)(()=>{
        fetch("https://cdn.growthbook.io/api/features/sdk-xABCD1234EFG").then(e=>e.json()).then(e=>{
          ei.setEncryptedFeatures(e.encryptedFeatures)
        }
        )
      }
  • Then you could extract the decryptionKey and manually decrypt things outside of the running site; or just set some more debug breakpoints and let the site do it for you...
  • Searching the code for setEncryptedFeatures:
    • setFeatures(e) {
        this._ctx.features = e,
        this.ready = !0,
        this._render()
      }
      async setEncryptedFeatures(e, t, r) {
        let i = await P(e, t || this._ctx.decryptionKey, r);
        this.setFeatures(JSON.parse(i))
      }
      setExperiments(e) {
        this._ctx.experiments = e,
        this.ready = !0,
        this._updateAllAutoExperiments()
      }
      async setEncryptedExperiments(e, t, r) {
        let i = await P(e, t || this._ctx.decryptionKey, r);
        this.setExperiments(JSON.parse(i))
      }
      async decryptPayload(e, t, r) {
        return e.encryptedFeatures && (e.features = JSON.parse(await P(e.encryptedFeatures, t || this._ctx.decryptionKey, r)),
        delete e.encryptedFeatures),
        e.encryptedExperiments && (e.experiments = JSON.parse(await P(e.encryptedExperiments, t || this._ctx.decryptionKey, r)),
        delete e.encryptedExperiments),
        e
      }
  • If we set a breakpoint within setFeatures and setExperiments we would be able to see the content after it's decrypted.
  • At that point, we could also store a reference to this / this._ctx / similar to access the rest of the stored data/settings/etc, and read/manipulate it as we like.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment