From 9a622f60fe7a73cdccaf0cc626aa715109e3ba68 Mon Sep 17 00:00:00 2001
From: Pablo Lara <larabjj@gmail.com>
Date: Wed, 30 Jul 2025 15:55:40 +0200
Subject: [PATCH] feat(providers): add GitHub provider support with credential
 types (#8405)

---
 ui/CHANGELOG.md                               |  2 +-
 .../add-credentials/page.tsx                  | 43 +++++-----
 .../update-credentials/page.tsx               | 42 +++++-----
 .../providers-badge/github-provider-badge.tsx | 29 +++++++
 ui/components/icons/providers-badge/index.ts  |  1 +
 .../providers/credentials-update-info.tsx     |  4 +
 .../providers/radio-group-provider.tsx        |  7 ++
 .../workflow/forms/base-credentials-form.tsx  | 41 +++++----
 .../workflow/forms/connect-account-form.tsx   | 18 +++-
 .../credentials-type/github-app-form.tsx      | 47 +++++++++++
 .../github-oauth-app-form.tsx                 | 34 ++++++++
 .../github-personal-access-token-form.tsx     | 40 +++++++++
 .../github/credentials-type/index.ts          |  3 +
 .../select-credentials-type/github/index.ts   |  3 +
 ...group-github-via-credentials-type-form.tsx | 84 +++++++++++++++++++
 .../github/select-via-github.tsx              | 38 +++++++++
 .../github-credentials-form.tsx               | 30 +++++++
 .../workflow/forms/via-credentials/index.ts   |  1 +
 .../ui/entities/get-provider-logo.tsx         |  5 ++
 ui/hooks/use-credentials-form.ts              | 26 ++++++
 ui/lib/external-urls.ts                       |  5 ++
 .../build-crendentials.ts                     | 54 ++++++++++++
 .../provider-credential-fields.ts             | 11 +++
 ui/lib/provider-helpers.ts                    | 65 ++++++++++++++
 ui/types/formSchemas.ts                       | 67 ++++++++++++++-
 ui/types/providers.ts                         | 11 ++-
 26 files changed, 638 insertions(+), 73 deletions(-)
 create mode 100644 ui/components/icons/providers-badge/github-provider-badge.tsx
 create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-app-form.tsx
 create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-oauth-app-form.tsx
 create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-personal-access-token-form.tsx
 create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/index.ts
 create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/github/index.ts
 create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx
 create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/github/select-via-github.tsx
 create mode 100644 ui/components/providers/workflow/forms/via-credentials/github-credentials-form.tsx

diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md
index bd8bf042e5..96c26cad86 100644
--- a/ui/CHANGELOG.md
+++ b/ui/CHANGELOG.md
@@ -8,7 +8,7 @@ All notable changes to the **Prowler UI** are documented in this file.
 
 - Lighthouse banner [(#8259)](https://github.com/prowler-cloud/prowler/pull/8259)
 - Integration with Amazon S3, enabling storage and retrieval of scan data via S3 buckets [(#8056)](https://github.com/prowler-cloud/prowler/pull/8056)
-  
+- Github provider support [(#8405)](https://github.com/prowler-cloud/prowler/pull/8405)
 ___
 
 ## [v1.9.3] (Prowler v5.9.3)
diff --git a/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx
index b867e919d0..723854ade3 100644
--- a/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx
+++ b/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx
@@ -9,6 +9,8 @@ import {
   AddViaServiceAccountForm,
   SelectViaGCP,
 } from "@/components/providers/workflow/forms/select-credentials-type/gcp";
+import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github";
+import { getProviderFormType } from "@/lib/provider-helpers";
 import { ProviderType } from "@/types/providers";
 
 interface Props {
@@ -16,30 +18,27 @@ interface Props {
 }
 
 export default function AddCredentialsPage({ searchParams }: Props) {
-  return (
-    <>
-      {searchParams.type === "aws" && !searchParams.via && (
-        <SelectViaAWS initialVia={searchParams.via} />
-      )}
+  const { type: providerType, via } = searchParams;
+  const formType = getProviderFormType(providerType, via);
 
-      {searchParams.type === "gcp" && !searchParams.via && (
-        <SelectViaGCP initialVia={searchParams.via} />
-      )}
+  switch (formType) {
+    case "selector":
+      if (providerType === "aws") return <SelectViaAWS initialVia={via} />;
+      if (providerType === "gcp") return <SelectViaGCP initialVia={via} />;
+      if (providerType === "github")
+        return <SelectViaGitHub initialVia={via} />;
+      return null;
 
-      {((searchParams.type === "aws" && searchParams.via === "credentials") ||
-        (searchParams.type === "gcp" && searchParams.via === "credentials") ||
-        (searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
-        <AddViaCredentialsForm searchParams={searchParams} />
-      )}
+    case "credentials":
+      return <AddViaCredentialsForm searchParams={searchParams} />;
 
-      {searchParams.type === "aws" && searchParams.via === "role" && (
-        <AddViaRoleForm searchParams={searchParams} />
-      )}
+    case "role":
+      return <AddViaRoleForm searchParams={searchParams} />;
 
-      {searchParams.type === "gcp" &&
-        searchParams.via === "service-account" && (
-          <AddViaServiceAccountForm searchParams={searchParams} />
-        )}
-    </>
-  );
+    case "service-account":
+      return <AddViaServiceAccountForm searchParams={searchParams} />;
+
+    default:
+      return null;
+  }
 }
diff --git a/ui/app/(prowler)/providers/(set-up-provider)/update-credentials/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/update-credentials/page.tsx
index 185a107244..84e15108a9 100644
--- a/ui/app/(prowler)/providers/(set-up-provider)/update-credentials/page.tsx
+++ b/ui/app/(prowler)/providers/(set-up-provider)/update-credentials/page.tsx
@@ -6,6 +6,7 @@ import {
   UpdateViaRoleForm,
 } from "@/components/providers/workflow/forms";
 import { UpdateViaServiceAccountForm } from "@/components/providers/workflow/forms/update-via-service-account-key-form";
+import { getProviderFormType } from "@/lib/provider-helpers";
 import { ProviderType } from "@/types/providers";
 
 interface Props {
@@ -18,30 +19,25 @@ interface Props {
 }
 
 export default function UpdateCredentialsPage({ searchParams }: Props) {
-  return (
-    <>
-      {(searchParams.type === "aws" || searchParams.type === "gcp") &&
-        !searchParams.via && (
-          <CredentialsUpdateInfo
-            providerType={searchParams.type}
-            initialVia={searchParams.via}
-          />
-        )}
+  const { type: providerType, via } = searchParams;
+  const formType = getProviderFormType(providerType, via);
 
-      {((searchParams.type === "aws" && searchParams.via === "credentials") ||
-        (searchParams.type === "gcp" && searchParams.via === "credentials") ||
-        (searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
-        <UpdateViaCredentialsForm searchParams={searchParams} />
-      )}
+  switch (formType) {
+    case "selector":
+      return (
+        <CredentialsUpdateInfo providerType={providerType} initialVia={via} />
+      );
 
-      {searchParams.type === "aws" && searchParams.via === "role" && (
-        <UpdateViaRoleForm searchParams={searchParams} />
-      )}
+    case "credentials":
+      return <UpdateViaCredentialsForm searchParams={searchParams} />;
 
-      {searchParams.type === "gcp" &&
-        searchParams.via === "service-account" && (
-          <UpdateViaServiceAccountForm searchParams={searchParams} />
-        )}
-    </>
-  );
+    case "role":
+      return <UpdateViaRoleForm searchParams={searchParams} />;
+
+    case "service-account":
+      return <UpdateViaServiceAccountForm searchParams={searchParams} />;
+
+    default:
+      return null;
+  }
 }
diff --git a/ui/components/icons/providers-badge/github-provider-badge.tsx b/ui/components/icons/providers-badge/github-provider-badge.tsx
new file mode 100644
index 0000000000..667785de03
--- /dev/null
+++ b/ui/components/icons/providers-badge/github-provider-badge.tsx
@@ -0,0 +1,29 @@
+import * as React from "react";
+
+import { IconSvgProps } from "@/types";
+
+export const GitHubProviderBadge: React.FC<IconSvgProps> = ({
+  size,
+  width,
+  height,
+  ...props
+}) => (
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    aria-hidden="true"
+    fill="none"
+    focusable="false"
+    height={size || height}
+    role="presentation"
+    viewBox="0 0 98 96"
+    width={size || width}
+    {...props}
+  >
+    <path
+      fillRule="evenodd"
+      clipRule="evenodd"
+      d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
+      fill="currentColor"
+    />
+  </svg>
+);
diff --git a/ui/components/icons/providers-badge/index.ts b/ui/components/icons/providers-badge/index.ts
index 86641d82ad..07b45d3cb0 100644
--- a/ui/components/icons/providers-badge/index.ts
+++ b/ui/components/icons/providers-badge/index.ts
@@ -1,5 +1,6 @@
 export * from "./aws-provider-badge";
 export * from "./azure-provider-badge";
 export * from "./gcp-provider-badge";
+export * from "./github-provider-badge";
 export * from "./ks8-provider-badge";
 export * from "./m365-provider-badge";
diff --git a/ui/components/providers/credentials-update-info.tsx b/ui/components/providers/credentials-update-info.tsx
index 2715a1ea6b..0fe75f0cb2 100644
--- a/ui/components/providers/credentials-update-info.tsx
+++ b/ui/components/providers/credentials-update-info.tsx
@@ -2,6 +2,7 @@
 
 import { SelectViaAWS } from "@/components/providers/workflow/forms/select-credentials-type/aws";
 import { SelectViaGCP } from "@/components/providers/workflow/forms/select-credentials-type/gcp";
+import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github";
 import { ProviderType } from "@/types/providers";
 
 interface UpdateCredentialsInfoProps {
@@ -20,6 +21,9 @@ export const CredentialsUpdateInfo = ({
     if (providerType === "gcp") {
       return <SelectViaGCP initialVia={initialVia} />;
     }
+    if (providerType === "github") {
+      return <SelectViaGitHub initialVia={initialVia} />;
+    }
     return null;
   };
 
diff --git a/ui/components/providers/radio-group-provider.tsx b/ui/components/providers/radio-group-provider.tsx
index ec77a148db..fa5021a8ad 100644
--- a/ui/components/providers/radio-group-provider.tsx
+++ b/ui/components/providers/radio-group-provider.tsx
@@ -11,6 +11,7 @@ import {
   AWSProviderBadge,
   AzureProviderBadge,
   GCPProviderBadge,
+  GitHubProviderBadge,
   KS8ProviderBadge,
   M365ProviderBadge,
 } from "../icons/providers-badge";
@@ -71,6 +72,12 @@ export const RadioGroupProvider: React.FC<RadioGroupProviderProps> = ({
                   <span className="ml-2">Kubernetes</span>
                 </div>
               </CustomRadio>
+              <CustomRadio description="GitHub" value="github">
+                <div className="flex items-center">
+                  <GitHubProviderBadge size={26} />
+                  <span className="ml-2">GitHub</span>
+                </div>
+              </CustomRadio>
             </div>
           </RadioGroup>
           {errorMessage && (
diff --git a/ui/components/providers/workflow/forms/base-credentials-form.tsx b/ui/components/providers/workflow/forms/base-credentials-form.tsx
index 39ab8ff6b3..3762532594 100644
--- a/ui/components/providers/workflow/forms/base-credentials-form.tsx
+++ b/ui/components/providers/workflow/forms/base-credentials-form.tsx
@@ -8,6 +8,7 @@ import { CustomButton } from "@/components/ui/custom";
 import { Form } from "@/components/ui/form";
 import { useCredentialsForm } from "@/hooks/use-credentials-form";
 import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
+import { requiresBackButton } from "@/lib/provider-helpers";
 import {
   AWSCredentials,
   AWSCredentialsRole,
@@ -25,6 +26,7 @@ import { AWSRoleCredentialsForm } from "./select-credentials-type/aws/credential
 import { GCPDefaultCredentialsForm } from "./select-credentials-type/gcp/credentials-type";
 import { GCPServiceAccountKeyForm } from "./select-credentials-type/gcp/credentials-type/gcp-service-account-key-form";
 import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form";
+import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form";
 import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form";
 import { M365CredentialsForm } from "./via-credentials/m365-credentials-form";
 
@@ -121,26 +123,29 @@ export const BaseCredentialsForm = ({
             control={form.control as unknown as Control<KubernetesCredentials>}
           />
         )}
+        {providerType === "github" && (
+          <GitHubCredentialsForm
+            control={form.control}
+            credentialsType={searchParamsObj.get("via") || undefined}
+          />
+        )}
 
         <div className="flex w-full justify-end sm:space-x-6">
-          {showBackButton &&
-            (searchParamsObj.get("via") === "credentials" ||
-              searchParamsObj.get("via") === "role" ||
-              searchParamsObj.get("via") === "service-account") && (
-              <CustomButton
-                type="button"
-                ariaLabel="Back"
-                className="w-1/2 bg-transparent"
-                variant="faded"
-                size="lg"
-                radius="lg"
-                onPress={handleBackStep}
-                startContent={!isLoading && <ChevronLeftIcon size={24} />}
-                isDisabled={isLoading}
-              >
-                <span>Back</span>
-              </CustomButton>
-            )}
+          {showBackButton && requiresBackButton(searchParamsObj.get("via")) && (
+            <CustomButton
+              type="button"
+              ariaLabel="Back"
+              className="w-1/2 bg-transparent"
+              variant="faded"
+              size="lg"
+              radius="lg"
+              onPress={handleBackStep}
+              startContent={!isLoading && <ChevronLeftIcon size={24} />}
+              isDisabled={isLoading}
+            >
+              <span>Back</span>
+            </CustomButton>
+          )}
           <CustomButton
             type="submit"
             ariaLabel="Save"
diff --git a/ui/components/providers/workflow/forms/connect-account-form.tsx b/ui/components/providers/workflow/forms/connect-account-form.tsx
index fa1d48abd4..aa3136a78d 100644
--- a/ui/components/providers/workflow/forms/connect-account-form.tsx
+++ b/ui/components/providers/workflow/forms/connect-account-form.tsx
@@ -7,18 +7,19 @@ import { useEffect, useState } from "react";
 import { useForm } from "react-hook-form";
 import * as z from "zod";
 
+import { addProvider } from "@/actions/providers/providers";
+import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs";
 import { useToast } from "@/components/ui";
 import { CustomButton, CustomInput } from "@/components/ui/custom";
 import { Form } from "@/components/ui/form";
+import { addProviderFormSchema, ApiError, ProviderType } from "@/types";
 
-import { addProvider } from "../../../../actions/providers/providers";
-import { addProviderFormSchema, ApiError } from "../../../../types";
 import { RadioGroupProvider } from "../../radio-group-provider";
-import { ProviderTitleDocs } from "../provider-title-docs";
+
 export type FormValues = z.infer<typeof addProviderFormSchema>;
 
 // Helper function for labels and placeholders
-const getProviderFieldDetails = (providerType?: string) => {
+const getProviderFieldDetails = (providerType?: ProviderType) => {
   switch (providerType) {
     case "aws":
       return {
@@ -45,6 +46,11 @@ const getProviderFieldDetails = (providerType?: string) => {
         label: "Domain ID",
         placeholder: "e.g. your-domain.onmicrosoft.com",
       };
+    case "github":
+      return {
+        label: "Username",
+        placeholder: "e.g. your-github-username",
+      };
     default:
       return {
         label: "Provider UID",
@@ -142,6 +148,10 @@ export const ConnectAccountForm = () => {
 
   const handleBackStep = () => {
     setPrevStep((prev) => prev - 1);
+    //Deselect the providerType if the user is going back to the first step
+    if (prevStep === 2) {
+      form.setValue("providerType", undefined as unknown as ProviderType);
+    }
     // Reset the providerUid and providerAlias fields when going back
     form.setValue("providerUid", "");
     form.setValue("providerAlias", "");
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-app-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-app-form.tsx
new file mode 100644
index 0000000000..1d9f77f1bf
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-app-form.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import { Control } from "react-hook-form";
+
+import { CustomInput, CustomTextarea } from "@/components/ui/custom";
+import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
+
+export const GitHubAppForm = ({ control }: { control: Control<any> }) => {
+  return (
+    <>
+      <div className="flex flex-col">
+        <div className="text-md font-bold leading-9 text-default-foreground">
+          Connect via GitHub App
+        </div>
+        <div className="text-sm text-default-500">
+          Please provide your GitHub App ID and private key.
+        </div>
+      </div>
+      <CustomInput
+        control={control}
+        name={ProviderCredentialFields.GITHUB_APP_ID}
+        type="text"
+        label="GitHub App ID"
+        labelPlacement="inside"
+        placeholder="Enter your GitHub App ID"
+        variant="bordered"
+        isRequired
+        isInvalid={
+          !!control._formState.errors[ProviderCredentialFields.GITHUB_APP_ID]
+        }
+      />
+      <CustomTextarea
+        control={control}
+        name={ProviderCredentialFields.GITHUB_APP_KEY}
+        label="GitHub App Private Key"
+        labelPlacement="inside"
+        placeholder="Paste your GitHub App private key here"
+        variant="bordered"
+        isRequired
+        minRows={4}
+        isInvalid={
+          !!control._formState.errors[ProviderCredentialFields.GITHUB_APP_KEY]
+        }
+      />
+    </>
+  );
+};
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-oauth-app-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-oauth-app-form.tsx
new file mode 100644
index 0000000000..115f46bebf
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-oauth-app-form.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import { Control } from "react-hook-form";
+
+import { CustomInput } from "@/components/ui/custom";
+import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
+
+export const GitHubOAuthAppForm = ({ control }: { control: Control<any> }) => {
+  return (
+    <>
+      <div className="flex flex-col">
+        <div className="text-md font-bold leading-9 text-default-foreground">
+          Connect via OAuth App
+        </div>
+        <div className="text-sm text-default-500">
+          Please provide your GitHub OAuth App token.
+        </div>
+      </div>
+      <CustomInput
+        control={control}
+        name={ProviderCredentialFields.OAUTH_APP_TOKEN}
+        type="password"
+        label="OAuth App Token"
+        labelPlacement="inside"
+        placeholder="Enter your GitHub OAuth App token"
+        variant="bordered"
+        isRequired
+        isInvalid={
+          !!control._formState.errors[ProviderCredentialFields.OAUTH_APP_TOKEN]
+        }
+      />
+    </>
+  );
+};
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-personal-access-token-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-personal-access-token-form.tsx
new file mode 100644
index 0000000000..460ae47d81
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-personal-access-token-form.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import { Control } from "react-hook-form";
+
+import { CustomInput } from "@/components/ui/custom";
+import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
+
+export const GitHubPersonalAccessTokenForm = ({
+  control,
+}: {
+  control: Control<any>;
+}) => {
+  return (
+    <>
+      <div className="flex flex-col">
+        <div className="text-md font-bold leading-9 text-default-foreground">
+          Connect via Personal Access Token
+        </div>
+        <div className="text-sm text-default-500">
+          Please provide your GitHub personal access token.
+        </div>
+      </div>
+      <CustomInput
+        control={control}
+        name={ProviderCredentialFields.PERSONAL_ACCESS_TOKEN}
+        type="password"
+        label="Personal Access Token"
+        labelPlacement="inside"
+        placeholder="Enter your GitHub personal access token"
+        variant="bordered"
+        isRequired
+        isInvalid={
+          !!control._formState.errors[
+            ProviderCredentialFields.PERSONAL_ACCESS_TOKEN
+          ]
+        }
+      />
+    </>
+  );
+};
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/index.ts b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/index.ts
new file mode 100644
index 0000000000..e359315250
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/index.ts
@@ -0,0 +1,3 @@
+export * from "./github-app-form";
+export * from "./github-oauth-app-form";
+export * from "./github-personal-access-token-form";
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/index.ts b/ui/components/providers/workflow/forms/select-credentials-type/github/index.ts
new file mode 100644
index 0000000000..3377bb1fd6
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/index.ts
@@ -0,0 +1,3 @@
+export * from "./credentials-type";
+export * from "./radio-group-github-via-credentials-type-form";
+export * from "./select-via-github";
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx
new file mode 100644
index 0000000000..0b24da9bf0
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+import { RadioGroup } from "@nextui-org/react";
+import React from "react";
+import { Control, Controller } from "react-hook-form";
+
+import { CustomRadio } from "@/components/ui/custom";
+import { FormMessage } from "@/components/ui/form";
+
+type RadioGroupGitHubViaCredentialsFormProps = {
+  control: Control<any>;
+  isInvalid: boolean;
+  errorMessage?: string;
+  onChange?: (value: string) => void;
+};
+
+export const RadioGroupGitHubViaCredentialsTypeForm = ({
+  control,
+  isInvalid,
+  errorMessage,
+  onChange,
+}: RadioGroupGitHubViaCredentialsFormProps) => {
+  return (
+    <Controller
+      name="githubCredentialsType"
+      control={control}
+      render={({ field }) => (
+        <>
+          <RadioGroup
+            className="flex flex-wrap"
+            isInvalid={isInvalid}
+            {...field}
+            value={field.value || ""}
+            onValueChange={(value) => {
+              field.onChange(value);
+              if (onChange) {
+                onChange(value);
+              }
+            }}
+          >
+            <div className="flex flex-col gap-4">
+              <span className="text-sm text-default-500">
+                Personal Access Token
+              </span>
+              <CustomRadio
+                description="Use a personal access token for authentication"
+                value="personal_access_token"
+              >
+                <div className="flex items-center">
+                  <span className="ml-2">Personal Access Token</span>
+                </div>
+              </CustomRadio>
+
+              <span className="text-sm text-default-500">OAuth App</span>
+              <CustomRadio
+                description="Use OAuth App token for authentication"
+                value="oauth_app"
+              >
+                <div className="flex items-center">
+                  <span className="ml-2">OAuth App Token</span>
+                </div>
+              </CustomRadio>
+
+              <span className="text-sm text-default-500">GitHub App</span>
+              <CustomRadio
+                description="Use GitHub App ID and private key for authentication"
+                value="github_app"
+              >
+                <div className="flex items-center">
+                  <span className="ml-2">GitHub App</span>
+                </div>
+              </CustomRadio>
+            </div>
+          </RadioGroup>
+          {errorMessage && (
+            <FormMessage className="text-system-error dark:text-system-error">
+              {errorMessage}
+            </FormMessage>
+          )}
+        </>
+      )}
+    />
+  );
+};
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/select-via-github.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/select-via-github.tsx
new file mode 100644
index 0000000000..9fe491f4ca
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/select-via-github.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { useForm } from "react-hook-form";
+
+import { Form } from "@/components/ui/form";
+
+import { RadioGroupGitHubViaCredentialsTypeForm } from "./radio-group-github-via-credentials-type-form";
+
+interface SelectViaGitHubProps {
+  initialVia?: string;
+}
+
+export const SelectViaGitHub = ({ initialVia }: SelectViaGitHubProps) => {
+  const router = useRouter();
+  const form = useForm({
+    defaultValues: {
+      githubCredentialsType: initialVia || "",
+    },
+  });
+
+  const handleSelectionChange = (value: string) => {
+    const url = new URL(window.location.href);
+    url.searchParams.set("via", value);
+    router.push(url.toString());
+  };
+
+  return (
+    <Form {...form}>
+      <RadioGroupGitHubViaCredentialsTypeForm
+        control={form.control}
+        isInvalid={!!form.formState.errors.githubCredentialsType}
+        errorMessage={form.formState.errors.githubCredentialsType?.message}
+        onChange={handleSelectionChange}
+      />
+    </Form>
+  );
+};
diff --git a/ui/components/providers/workflow/forms/via-credentials/github-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/github-credentials-form.tsx
new file mode 100644
index 0000000000..94d3443cb7
--- /dev/null
+++ b/ui/components/providers/workflow/forms/via-credentials/github-credentials-form.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { Control } from "react-hook-form";
+
+import {
+  GitHubAppForm,
+  GitHubOAuthAppForm,
+  GitHubPersonalAccessTokenForm,
+} from "../select-credentials-type/github";
+
+interface GitHubCredentialsFormProps {
+  control: Control<any>;
+  credentialsType?: string;
+}
+
+export const GitHubCredentialsForm = ({
+  control,
+  credentialsType,
+}: GitHubCredentialsFormProps) => {
+  switch (credentialsType) {
+    case "personal_access_token":
+      return <GitHubPersonalAccessTokenForm control={control} />;
+    case "oauth_app":
+      return <GitHubOAuthAppForm control={control} />;
+    case "github_app":
+      return <GitHubAppForm control={control} />;
+    default:
+      return null;
+  }
+};
diff --git a/ui/components/providers/workflow/forms/via-credentials/index.ts b/ui/components/providers/workflow/forms/via-credentials/index.ts
index 9058fc7362..d020b9715f 100644
--- a/ui/components/providers/workflow/forms/via-credentials/index.ts
+++ b/ui/components/providers/workflow/forms/via-credentials/index.ts
@@ -1,3 +1,4 @@
 export * from "./azure-credentials-form";
+export * from "./github-credentials-form";
 export * from "./k8s-credentials-form";
 export * from "./m365-credentials-form";
diff --git a/ui/components/ui/entities/get-provider-logo.tsx b/ui/components/ui/entities/get-provider-logo.tsx
index 17ab559988..047652ffdc 100644
--- a/ui/components/ui/entities/get-provider-logo.tsx
+++ b/ui/components/ui/entities/get-provider-logo.tsx
@@ -4,6 +4,7 @@ import {
   AWSProviderBadge,
   AzureProviderBadge,
   GCPProviderBadge,
+  GitHubProviderBadge,
   KS8ProviderBadge,
   M365ProviderBadge,
 } from "@/components/icons/providers-badge";
@@ -21,6 +22,8 @@ export const getProviderLogo = (provider: ProviderType) => {
       return <KS8ProviderBadge width={35} height={35} />;
     case "m365":
       return <M365ProviderBadge width={35} height={35} />;
+    case "github":
+      return <GitHubProviderBadge width={35} height={35} />;
     default:
       return null;
   }
@@ -38,6 +41,8 @@ export const getProviderName = (provider: ProviderType): string => {
       return "Kubernetes";
     case "m365":
       return "Microsoft 365";
+    case "github":
+      return "GitHub";
     default:
       return "Unknown Provider";
   }
diff --git a/ui/hooks/use-credentials-form.ts b/ui/hooks/use-credentials-form.ts
index df6ffa9517..317d1749ae 100644
--- a/ui/hooks/use-credentials-form.ts
+++ b/ui/hooks/use-credentials-form.ts
@@ -46,6 +46,10 @@ export const useCredentialsForm = ({
     if (providerType === "gcp" && via === "service-account") {
       return addCredentialsServiceAccountFormSchema(providerType);
     }
+    // For GitHub, we need to pass the via parameter to determine which fields are required
+    if (providerType === "github") {
+      return addCredentialsFormSchema(providerType, via);
+    }
     return addCredentialsFormSchema(providerType);
   };
 
@@ -117,6 +121,28 @@ export const useCredentialsForm = ({
           ...baseDefaults,
           [ProviderCredentialFields.KUBECONFIG_CONTENT]: "",
         };
+      case "github":
+        // GitHub credentials based on via parameter
+        if (via === "personal_access_token") {
+          return {
+            ...baseDefaults,
+            [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: "",
+          };
+        }
+        if (via === "oauth_app") {
+          return {
+            ...baseDefaults,
+            [ProviderCredentialFields.OAUTH_APP_TOKEN]: "",
+          };
+        }
+        if (via === "github_app") {
+          return {
+            ...baseDefaults,
+            [ProviderCredentialFields.GITHUB_APP_ID]: "",
+            [ProviderCredentialFields.GITHUB_APP_KEY]: "",
+          };
+        }
+        return baseDefaults;
       default:
         return baseDefaults;
     }
diff --git a/ui/lib/external-urls.ts b/ui/lib/external-urls.ts
index cea7a694ae..1e8210a116 100644
--- a/ui/lib/external-urls.ts
+++ b/ui/lib/external-urls.ts
@@ -25,6 +25,11 @@ export const getProviderHelpText = (provider: string) => {
         text: "Need help connecting your Kubernetes cluster?",
         link: "https://goto.prowler.com/provider-k8s",
       };
+    case "github":
+      return {
+        text: "Need help connecting your GitHub account?",
+        link: "https://goto.prowler.com/provider-github",
+      };
     default:
       return {
         text: "How to setup a provider?",
diff --git a/ui/lib/provider-credentials/build-crendentials.ts b/ui/lib/provider-credentials/build-crendentials.ts
index 791a209c8c..0e2c96f990 100644
--- a/ui/lib/provider-credentials/build-crendentials.ts
+++ b/ui/lib/provider-credentials/build-crendentials.ts
@@ -147,6 +147,56 @@ export const buildKubernetesSecret = (formData: FormData) => {
   return filterEmptyValues(secret);
 };
 
+export const buildGitHubSecret = (formData: FormData) => {
+  // Check which authentication method is being used
+  const hasPersonalToken =
+    formData.get(ProviderCredentialFields.PERSONAL_ACCESS_TOKEN) !== null &&
+    formData.get(ProviderCredentialFields.PERSONAL_ACCESS_TOKEN) !== "";
+  const hasOAuthToken =
+    formData.get(ProviderCredentialFields.OAUTH_APP_TOKEN) !== null &&
+    formData.get(ProviderCredentialFields.OAUTH_APP_TOKEN) !== "";
+  const hasGitHubApp =
+    formData.get(ProviderCredentialFields.GITHUB_APP_ID) !== null &&
+    formData.get(ProviderCredentialFields.GITHUB_APP_ID) !== "";
+
+  if (hasPersonalToken) {
+    const secret = {
+      [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: getFormValue(
+        formData,
+        ProviderCredentialFields.PERSONAL_ACCESS_TOKEN,
+      ),
+    };
+    return filterEmptyValues(secret);
+  }
+
+  if (hasOAuthToken) {
+    const secret = {
+      [ProviderCredentialFields.OAUTH_APP_TOKEN]: getFormValue(
+        formData,
+        ProviderCredentialFields.OAUTH_APP_TOKEN,
+      ),
+    };
+    return filterEmptyValues(secret);
+  }
+
+  if (hasGitHubApp) {
+    const secret = {
+      [ProviderCredentialFields.GITHUB_APP_ID]: getFormValue(
+        formData,
+        ProviderCredentialFields.GITHUB_APP_ID,
+      ),
+      [ProviderCredentialFields.GITHUB_APP_KEY]: getFormValue(
+        formData,
+        ProviderCredentialFields.GITHUB_APP_KEY,
+      ),
+    };
+    return filterEmptyValues(secret);
+  }
+
+  // If no credentials are provided, return empty object
+  return {};
+};
+
 // Main function to build secret configuration
 export const buildSecretConfig = (
   formData: FormData,
@@ -177,6 +227,10 @@ export const buildSecretConfig = (
       secretType: "static",
       secret: buildKubernetesSecret(formData),
     }),
+    github: () => ({
+      secretType: "static",
+      secret: buildGitHubSecret(formData),
+    }),
   };
 
   const builder = secretBuilders[providerType];
diff --git a/ui/lib/provider-credentials/provider-credential-fields.ts b/ui/lib/provider-credentials/provider-credential-fields.ts
index 5cbd6605b9..32dc184292 100644
--- a/ui/lib/provider-credentials/provider-credential-fields.ts
+++ b/ui/lib/provider-credentials/provider-credential-fields.ts
@@ -8,6 +8,7 @@ export const ProviderCredentialFields = {
   CREDENTIALS_TYPE: "credentials_type",
   CREDENTIALS_TYPE_AWS: "aws-sdk-default",
   CREDENTIALS_TYPE_ACCESS_SECRET_KEY: "access-secret-key",
+
   // Base fields for all providers
   PROVIDER_ID: "providerId",
   PROVIDER_TYPE: "providerType",
@@ -35,6 +36,12 @@ export const ProviderCredentialFields = {
 
   // Kubernetes fields
   KUBECONFIG_CONTENT: "kubeconfig_content",
+
+  // GitHub fields
+  PERSONAL_ACCESS_TOKEN: "personal_access_token",
+  OAUTH_APP_TOKEN: "oauth_app_token",
+  GITHUB_APP_ID: "github_app_id",
+  GITHUB_APP_KEY: "github_app_key_content",
 } as const;
 
 // Type for credential field values
@@ -59,6 +66,10 @@ export const ErrorPointers = {
   SESSION_DURATION: "/data/attributes/secret/session_duration",
   ROLE_SESSION_NAME: "/data/attributes/secret/role_session_name",
   SERVICE_ACCOUNT_KEY: "/data/attributes/secret/service_account_key",
+  PERSONAL_ACCESS_TOKEN: "/data/attributes/secret/personal_access_token",
+  OAUTH_APP_TOKEN: "/data/attributes/secret/oauth_app_token",
+  GITHUB_APP_ID: "/data/attributes/secret/github_app_id",
+  GITHUB_APP_KEY: "/data/attributes/secret/github_app_key_content",
 } as const;
 
 export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers];
diff --git a/ui/lib/provider-helpers.ts b/ui/lib/provider-helpers.ts
index d1247c3eb4..becd9bc4f2 100644
--- a/ui/lib/provider-helpers.ts
+++ b/ui/lib/provider-helpers.ts
@@ -2,6 +2,7 @@ import {
   ProviderEntity,
   ProviderProps,
   ProvidersApiResponse,
+  ProviderType,
 } from "@/types/providers";
 
 export const extractProviderUIDs = (
@@ -38,3 +39,67 @@ export const createProviderDetailsMapping = (
     };
   });
 };
+
+// Helper function to determine which form type to show
+export type ProviderFormType =
+  | "selector"
+  | "credentials"
+  | "role"
+  | "service-account"
+  | null;
+
+export const getProviderFormType = (
+  providerType: ProviderType,
+  via?: string,
+): ProviderFormType => {
+  // Providers that need credential type selection
+  const needsSelector = ["aws", "gcp", "github"].includes(providerType);
+
+  // Show selector if no via parameter and provider needs it
+  if (needsSelector && !via) {
+    return "selector";
+  }
+
+  // AWS specific forms
+  if (providerType === "aws") {
+    if (via === "role") return "role";
+    if (via === "credentials") return "credentials";
+  }
+
+  // GCP specific forms
+  if (providerType === "gcp") {
+    if (via === "service-account") return "service-account";
+    if (via === "credentials") return "credentials";
+  }
+
+  // GitHub credential types
+  if (
+    providerType === "github" &&
+    ["personal_access_token", "oauth_app", "github_app"].includes(via || "")
+  ) {
+    return "credentials";
+  }
+
+  // Other providers go directly to credentials form
+  if (!needsSelector) {
+    return "credentials";
+  }
+
+  return null;
+};
+
+// Helper to check if back button should be shown based on via parameter
+export const requiresBackButton = (via?: string | null): boolean => {
+  if (!via) return false;
+
+  const validViaTypes = [
+    "credentials",
+    "role",
+    "service-account",
+    "personal_access_token",
+    "oauth_app",
+    "github_app",
+  ];
+
+  return validViaTypes.includes(via);
+};
diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts
index 2211a7a2cb..b1deadf489 100644
--- a/ui/types/formSchemas.ts
+++ b/ui/types/formSchemas.ts
@@ -3,7 +3,7 @@ import { z } from "zod";
 import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
 import { validateMutelistYaml, validateYaml } from "@/lib/yaml";
 
-import { ProviderType } from "./providers";
+import { PROVIDER_TYPES, ProviderType } from "./providers";
 
 export const addRoleFormSchema = z.object({
   name: z.string().min(1, "Name is required"),
@@ -71,7 +71,7 @@ export const awsCredentialsTypeSchema = z.object({
 
 export const addProviderFormSchema = z
   .object({
-    providerType: z.enum(["aws", "azure", "gcp", "kubernetes", "m365"], {
+    providerType: z.enum(PROVIDER_TYPES, {
       required_error: "Please select a provider type",
     }),
   })
@@ -105,10 +105,18 @@ export const addProviderFormSchema = z
         providerUid: z.string(),
         awsCredentialsType: z.string().optional(),
       }),
+      z.object({
+        providerType: z.literal("github"),
+        [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
+        providerUid: z.string(),
+      }),
     ]),
   );
 
-export const addCredentialsFormSchema = (providerType: string) =>
+export const addCredentialsFormSchema = (
+  providerType: ProviderType,
+  via?: string | null,
+) =>
   z
     .object({
       [ProviderCredentialFields.PROVIDER_ID]: z.string(),
@@ -167,7 +175,22 @@ export const addCredentialsFormSchema = (providerType: string) =>
                     [ProviderCredentialFields.USER]: z.string().optional(),
                     [ProviderCredentialFields.PASSWORD]: z.string().optional(),
                   }
-                : {}),
+                : providerType === "github"
+                  ? {
+                      [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: z
+                        .string()
+                        .optional(),
+                      [ProviderCredentialFields.OAUTH_APP_TOKEN]: z
+                        .string()
+                        .optional(),
+                      [ProviderCredentialFields.GITHUB_APP_ID]: z
+                        .string()
+                        .optional(),
+                      [ProviderCredentialFields.GITHUB_APP_KEY]: z
+                        .string()
+                        .optional(),
+                    }
+                  : {}),
     })
     .superRefine((data: Record<string, any>, ctx) => {
       if (providerType === "m365") {
@@ -190,6 +213,42 @@ export const addCredentialsFormSchema = (providerType: string) =>
           });
         }
       }
+
+      if (providerType === "github") {
+        // For GitHub, validation depends on the 'via' parameter
+        if (via === "personal_access_token") {
+          if (!data[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]) {
+            ctx.addIssue({
+              code: z.ZodIssueCode.custom,
+              message: "Personal Access Token is required",
+              path: [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN],
+            });
+          }
+        } else if (via === "oauth_app") {
+          if (!data[ProviderCredentialFields.OAUTH_APP_TOKEN]) {
+            ctx.addIssue({
+              code: z.ZodIssueCode.custom,
+              message: "OAuth App Token is required",
+              path: [ProviderCredentialFields.OAUTH_APP_TOKEN],
+            });
+          }
+        } else if (via === "github_app") {
+          if (!data[ProviderCredentialFields.GITHUB_APP_ID]) {
+            ctx.addIssue({
+              code: z.ZodIssueCode.custom,
+              message: "GitHub App ID is required",
+              path: [ProviderCredentialFields.GITHUB_APP_ID],
+            });
+          }
+          if (!data[ProviderCredentialFields.GITHUB_APP_KEY]) {
+            ctx.addIssue({
+              code: z.ZodIssueCode.custom,
+              message: "GitHub App Private Key is required",
+              path: [ProviderCredentialFields.GITHUB_APP_KEY],
+            });
+          }
+        }
+      }
     });
 
 export const addCredentialsRoleFormSchema = (providerType: string) =>
diff --git a/ui/types/providers.ts b/ui/types/providers.ts
index b7aae5b944..0967d3304e 100644
--- a/ui/types/providers.ts
+++ b/ui/types/providers.ts
@@ -1,4 +1,13 @@
-export type ProviderType = "aws" | "azure" | "m365" | "gcp" | "kubernetes";
+export const PROVIDER_TYPES = [
+  "aws",
+  "azure",
+  "gcp",
+  "kubernetes",
+  "m365",
+  "github",
+] as const;
+
+export type ProviderType = (typeof PROVIDER_TYPES)[number];
 
 export interface ProviderProps {
   id: string;
