Compare commits

...

38 Commits

Author SHA1 Message Date
alejandrobailo
73b784f72d docs(ui): improve Vercel changelog entry with user-facing details 2026-04-01 18:05:34 +02:00
alejandrobailo
f38942ffde fix(ui): add trim to Vercel API token schema validation 2026-04-01 18:03:10 +02:00
Daniel Barranquero
8f26ecb80e chore: add changelog 2026-04-01 16:12:37 +02:00
Daniel Barranquero
9031ec087f Merge branch 'master' into feat/vercel-ui 2026-04-01 14:03:10 +02:00
Daniel Barranquero
57470c7c98 Merge remote-tracking branch 'origin/master' into feat/vercel-ui
# Conflicts:
#	api/CHANGELOG.md
#	api/src/backend/api/tests/test_views.py
#	docs/user-guide/providers/vercel/authentication.mdx
#	docs/user-guide/providers/vercel/getting-started-vercel.mdx
#	prowler/CHANGELOG.md
#	prowler/config/config.yaml
#	prowler/providers/vercel/services/authentication/authentication_no_stale_tokens/authentication_no_stale_tokens.metadata.json
#	prowler/providers/vercel/services/authentication/authentication_no_stale_tokens/authentication_no_stale_tokens.py
#	prowler/providers/vercel/services/authentication/authentication_token_not_expired/authentication_token_not_expired.metadata.json
#	prowler/providers/vercel/services/authentication/authentication_token_not_expired/authentication_token_not_expired.py
#	prowler/providers/vercel/services/domain/domain_service.py
#	prowler/providers/vercel/services/domain/domain_ssl_certificate_valid/domain_ssl_certificate_valid.metadata.json
#	prowler/providers/vercel/services/domain/domain_ssl_certificate_valid/domain_ssl_certificate_valid.py
#	prowler/providers/vercel/services/project/project_environment_no_secrets_in_plain_type/project_environment_no_secrets_in_plain_type.metadata.json
#	prowler/providers/vercel/services/project/project_environment_no_secrets_in_plain_type/project_environment_no_secrets_in_plain_type.py
#	prowler/providers/vercel/services/project/project_environment_production_vars_not_in_preview/project_environment_production_vars_not_in_preview.metadata.json
#	prowler/providers/vercel/services/project/project_password_protection_enabled/project_password_protection_enabled.py
#	prowler/providers/vercel/services/team/team_member_role_least_privilege/team_member_role_least_privilege.metadata.json
#	prowler/providers/vercel/services/team/team_member_role_least_privilege/team_member_role_least_privilege.py
#	prowler/providers/vercel/services/team/team_no_stale_invitations/team_no_stale_invitations.py
#	tests/providers/vercel/services/authentication/authentication_no_stale_tokens/authentication_no_stale_tokens_test.py
#	tests/providers/vercel/services/authentication/authentication_token_not_expired/authentication_token_not_expired_test.py
#	tests/providers/vercel/services/domain/domain_ssl_certificate_valid/domain_ssl_certificate_valid_test.py
#	tests/providers/vercel/services/project/project_environment_no_secrets_in_plain_type/project_environment_no_secrets_in_plain_type_test.py
#	tests/providers/vercel/services/project/project_password_protection_enabled/project_password_protection_enabled_test.py
#	tests/providers/vercel/services/team/team_member_role_least_privilege/team_member_role_least_privilege_test.py
#	tests/providers/vercel/services/team/team_no_stale_invitations/team_no_stale_invitations_test.py
2026-04-01 14:01:49 +02:00
alejandrobailo
3f428639c9 style(ui): use WizardInputField in Vercel credentials form 2026-03-24 12:13:58 +01:00
Daniel Barranquero
ee31c715c1 fix: aws non-related changes 2026-03-24 11:07:28 +01:00
Daniel Barranquero
7fb5e7eb56 Merge branch 'feat/vercel-api' into 'feat/vercel-ui' 2026-03-24 10:58:03 +01:00
Daniel Barranquero
c3760a8c28 feat: add changelog 2026-03-23 17:05:44 +01:00
Daniel Barranquero
d3d26edb12 feat(vercel): add missing changes for api support 2026-03-23 16:42:54 +01:00
Daniel Barranquero
1c70d911f8 Merge branch 'feat/vercel-sdk' into feat/vercel-api 2026-03-23 14:04:18 +01:00
Daniel Barranquero
776c17ee65 chore: udpate docs 2026-03-23 09:27:38 +01:00
Daniel Barranquero
db18e47467 feat: add docs and modify gh workflows 2026-03-20 16:40:23 +01:00
Daniel Barranquero
ea5ba82333 fix tests 2026-03-20 14:23:55 +01:00
Daniel Barranquero
2d5e948c96 feat: scan all teams when no team is specified 2026-03-20 13:56:46 +01:00
Daniel Barranquero
f9ccc89177 chore: update metadata 2026-03-20 12:25:19 +01:00
Daniel Barranquero
f8bededc9b chore: fix black 2026-03-19 17:36:32 +01:00
Daniel Barranquero
273c8e4318 Merge branch 'master' into feat/vercel-sdk 2026-03-19 16:58:45 +01:00
Daniel Barranquero
274cd07c18 chore: update services format 2026-03-19 16:56:26 +01:00
Daniel Barranquero
cc7fa7d49a chore: update asserts in every unit test 2026-03-18 12:23:09 +01:00
Daniel Barranquero
fb62b81a9b fix: parser tests 2026-03-18 09:11:56 +01:00
Daniel Barranquero
29cc9eae19 fix: remove init from tests files 2026-03-17 16:55:40 +01:00
Daniel Barranquero
0186e9f304 chore: remove cli authentication flags 2026-03-17 16:52:59 +01:00
Daniel Barranquero
6cfa67d007 chore: add vercel to outputs and to html 2026-03-17 16:41:18 +01:00
Daniel Barranquero
e583cfdafd feat(vercel): add example mutelist 2026-03-17 16:34:54 +01:00
Daniel Barranquero
a25c5d4e6a chore: add missing check tests 2026-03-17 16:30:25 +01:00
Daniel Barranquero
109ee80836 chore: update metadata 2026-03-17 16:23:31 +01:00
Daniel Barranquero
a97a8b63af chore: vercel provider revision 2026-03-17 16:22:18 +01:00
Daniel Barranquero
1a1317c89c Merge branch 'master' into feat/vercel-sdk 2026-03-17 11:55:44 +01:00
alejandrobailo
f363e74a3e chore(sdk): remove Vercel compliance files 2026-03-03 15:22:59 +01:00
alejandrobailo
786d00d69c feat(sdk): make stable branches configurable via audit_config 2026-03-03 15:22:40 +01:00
alejandrobailo
67fb058b2a fix(sdk): normalize check metadata format 2026-03-03 15:22:32 +01:00
alejandrobailo
49841dd77a refactor(sdk): rename environment checks to project_environment 2026-03-03 15:22:24 +01:00
alejandrobailo
842dfc19b8 fix(vercel): remove __init__.py from test directories 2026-02-27 14:44:16 +01:00
alejandrobailo
5267ca0710 feat(ui): add Vercel provider UI and documentation
- Add Vercel provider badge, credentials form, and wizard integration
- Add Vercel to provider type selectors, radio groups, and icon mappings
- Add Vercel credential fields, build logic, and Zod form schemas
- Fix provider wizard validation on back navigation (superRefine → refine)
- Add Vercel docs link to provider wizard help text
- Add Vercel getting started and authentication documentation pages
- Register Vercel docs in navigation config
2026-02-27 11:54:14 +01:00
alejandrobailo
4918a9b018 feat(api): integrate Vercel provider into API layer
- Add VERCEL to ProviderChoices enum with validate_vercel_uid validator
- Add PostgreSQL enum migration with RunSQL for forward/reverse
- Add Vercel branch to connection test, provider kwargs, and type hints in utils
- Add VercelProviderSecret serializer for API token + team credentials
2026-02-27 11:53:24 +01:00
alejandrobailo
5c2b51d1bf feat(sdk): add Vercel provider with 30 security checks
- Add Vercel provider with API token authentication and team-scoped support
- Implement 6 services: authentication, deployment, domain, project, security, team
- Add 30 security checks covering token hygiene, deployment protection, WAF,
  SSL certificates, environment variables, SSO enforcement, and member governance
- Add CheckReportVercel to core SDK models and finding output mapping
- Include 5 compliance frameworks: CIS Controls v8, ISO 27001, NIST 800-53, PCI DSS 4.0, SOC 2
- Add unit tests for provider, mutelist, and representative checks per service
2026-02-27 11:52:57 +01:00
alejandrobailo
ba54da28d6 chore: init feature branch for Vercel provider 2026-02-27 11:52:16 +01:00
18 changed files with 164 additions and 8 deletions

View File

@@ -7,6 +7,7 @@ All notable changes to the **Prowler UI** are documented in this file.
### 🚀 Added
- Findings grouped view with drill-down table showing resources per check, resource detail drawer, infinite scroll pagination, and bulk mute support [(#10425)](https://github.com/prowler-cloud/prowler/pull/10425)
- Vercel provider: connect Vercel teams via API token, scan deployments, domains, projects, and team settings [(#10191)](https://github.com/prowler-cloud/prowler/pull/10191)
### 🔄 Changed

View File

@@ -18,6 +18,7 @@ import {
MongoDBAtlasProviderBadge,
OpenStackProviderBadge,
OracleCloudProviderBadge,
VercelProviderBadge,
} from "@/components/icons/providers-badge";
import {
MultiSelect,
@@ -48,6 +49,7 @@ const PROVIDER_ICON: Record<ProviderType, ReactNode> = {
alibabacloud: <AlibabaCloudProviderBadge width={18} height={18} />,
cloudflare: <CloudflareProviderBadge width={18} height={18} />,
openstack: <OpenStackProviderBadge width={18} height={18} />,
vercel: <VercelProviderBadge width={18} height={18} />,
};
/** Common props shared by both batch and instant modes. */

View File

@@ -83,6 +83,11 @@ const GoogleWorkspaceProviderBadge = lazy(() =>
default: m.GoogleWorkspaceProviderBadge,
})),
);
const VercelProviderBadge = lazy(() =>
import("@/components/icons/providers-badge").then((m) => ({
default: m.VercelProviderBadge,
})),
);
type IconProps = { width: number; height: number };
@@ -150,6 +155,10 @@ const PROVIDER_DATA: Record<
label: "OpenStack",
icon: OpenStackProviderBadge,
},
vercel: {
label: "Vercel",
icon: VercelProviderBadge,
},
};
/** Common props shared by both batch and instant modes. */

View File

@@ -13,6 +13,7 @@ import {
MongoDBAtlasProviderBadge,
OpenStackProviderBadge,
OracleCloudProviderBadge,
VercelProviderBadge,
} from "@/components/icons/providers-badge";
import { cn } from "@/lib/utils";
import { ProviderType } from "@/types";
@@ -32,6 +33,7 @@ export const PROVIDER_ICONS = {
alibabacloud: AlibabaCloudProviderBadge,
cloudflare: CloudflareProviderBadge,
openstack: OpenStackProviderBadge,
vercel: VercelProviderBadge,
} as const;
interface ProviderIconCellProps {

View File

@@ -16,6 +16,7 @@ import { M365ProviderBadge } from "./m365-provider-badge";
import { MongoDBAtlasProviderBadge } from "./mongodbatlas-provider-badge";
import { OpenStackProviderBadge } from "./openstack-provider-badge";
import { OracleCloudProviderBadge } from "./oraclecloud-provider-badge";
import { VercelProviderBadge } from "./vercel-provider-badge";
export {
AlibabaCloudProviderBadge,
@@ -32,6 +33,7 @@ export {
MongoDBAtlasProviderBadge,
OpenStackProviderBadge,
OracleCloudProviderBadge,
VercelProviderBadge,
};
// Map provider display names to their icon components
@@ -50,4 +52,5 @@ export const PROVIDER_BADGE_BY_NAME: Record<string, FC<IconSvgProps>> = {
"Alibaba Cloud": AlibabaCloudProviderBadge,
Cloudflare: CloudflareProviderBadge,
OpenStack: OpenStackProviderBadge,
Vercel: VercelProviderBadge,
};

View File

@@ -0,0 +1,23 @@
import { IconSvgProps } from "@/types";
export const VercelProviderBadge: 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 256 256"
width={size || width}
{...props}
>
<rect width="256" height="256" fill="#000000" rx="60" />
<path d="M128 45L217 195H39L128 45Z" fill="#ffffff" />
</svg>
);

View File

@@ -23,6 +23,7 @@ import {
MongoDBAtlasProviderBadge,
OpenStackProviderBadge,
OracleCloudProviderBadge,
VercelProviderBadge,
} from "../icons/providers-badge";
import { FormMessage } from "../ui/form";
@@ -97,6 +98,11 @@ const PROVIDERS = [
label: "OpenStack",
badge: OpenStackProviderBadge,
},
{
value: "vercel",
label: "Vercel",
badge: VercelProviderBadge,
},
] as const;
interface RadioGroupProviderProps {

View File

@@ -32,6 +32,7 @@ import {
OCICredentials,
OpenStackCredentials,
ProviderType,
VercelCredentials,
} from "@/types";
import { ProviderTitleDocs } from "../provider-title-docs";
@@ -60,6 +61,7 @@ import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-for
import { MongoDBAtlasCredentialsForm } from "./via-credentials/mongodbatlas-credentials-form";
import { OpenStackCredentialsForm } from "./via-credentials/openstack-credentials-form";
import { OracleCloudCredentialsForm } from "./via-credentials/oraclecloud-credentials-form";
import { VercelCredentialsForm } from "./via-credentials/vercel-credentials-form";
type BaseCredentialsFormProps = {
providerType: ProviderType;
@@ -272,6 +274,11 @@ export const BaseCredentialsForm = ({
}
/>
)}
{providerType === "vercel" && (
<VercelCredentialsForm
control={form.control as unknown as Control<VercelCredentials>}
/>
)}
{!hideActions && (
<div className="flex w-full justify-end gap-4">

View File

@@ -116,6 +116,11 @@ const getProviderFieldDetails = (providerType?: ProviderType) => {
label: "Customer ID",
placeholder: "e.g. C01234abc",
};
case "vercel":
return {
label: "Team ID",
placeholder: "e.g. team_xxxxxxxxxxxxxxxxxxxxxxxx",
};
default:
return {
label: "Provider UID",
@@ -133,27 +138,31 @@ function applyBackStep({
}: {
prevStep: number;
awsMethod: "single" | null;
form: Pick<UseFormReturn<FormValues>, "setValue">;
form: Pick<UseFormReturn<FormValues>, "setValue" | "clearErrors">;
setPrevStep: Dispatch<SetStateAction<number>>;
setAwsMethod: Dispatch<SetStateAction<"single" | null>>;
}) {
// If in UID form after choosing single, go back to method selector
if (prevStep === 2 && awsMethod === "single") {
setAwsMethod(null);
form.setValue("providerUid", "");
form.setValue("providerAlias", "");
form.setValue("providerUid", "", { shouldValidate: false });
form.setValue("providerAlias", "", { shouldValidate: false });
return;
}
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);
form.setValue("providerType", undefined as unknown as ProviderType, {
shouldValidate: false,
});
setAwsMethod(null);
}
// Reset the providerUid and providerAlias fields when going back
form.setValue("providerUid", "");
form.setValue("providerAlias", "");
form.setValue("providerUid", "", { shouldValidate: false });
form.setValue("providerAlias", "", { shouldValidate: false });
// Clear all validation errors so the radio buttons don't show red borders
form.clearErrors();
}
export const ConnectAccountForm = ({

View File

@@ -0,0 +1,40 @@
import { Control } from "react-hook-form";
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { VercelCredentials } from "@/types";
export const VercelCredentialsForm = ({
control,
}: {
control: Control<VercelCredentials>;
}) => {
return (
<>
<div className="flex flex-col">
<div className="text-md text-default-foreground leading-9 font-bold">
Connect via API Token
</div>
<div className="text-default-500 text-sm">
Provide a Vercel API Token with read permissions to the resources you
want Prowler to assess.
</div>
</div>
<WizardInputField
control={control}
name={ProviderCredentialFields.VERCEL_API_TOKEN}
type="password"
label="API Token"
labelPlacement="inside"
placeholder="Enter your Vercel API Token"
variant="bordered"
isRequired
/>
<div className="text-default-400 text-xs">
Tokens never leave your browser unencrypted and are stored as secrets in
the backend. You can revoke the token from the Vercel dashboard anytime
at vercel.com/account/tokens.
</div>
</>
);
};

View File

@@ -13,6 +13,7 @@ import {
MongoDBAtlasProviderBadge,
OpenStackProviderBadge,
OracleCloudProviderBadge,
VercelProviderBadge,
} from "@/components/icons/providers-badge";
import { ProviderType } from "@/types";
@@ -46,6 +47,8 @@ export const getProviderLogo = (provider: ProviderType) => {
return <CloudflareProviderBadge width={35} height={35} />;
case "openstack":
return <OpenStackProviderBadge width={35} height={35} />;
case "vercel":
return <VercelProviderBadge width={35} height={35} />;
default:
return null;
}
@@ -81,6 +84,8 @@ export const getProviderName = (provider: ProviderType): string => {
return "Cloudflare";
case "openstack":
return "OpenStack";
case "vercel":
return "Vercel";
default:
return "Unknown Provider";
}

View File

@@ -243,6 +243,11 @@ export const useCredentialsForm = ({
[ProviderCredentialFields.IMAGE_FILTER]: "",
[ProviderCredentialFields.TAG_FILTER]: "",
};
case "vercel":
return {
...baseDefaults,
[ProviderCredentialFields.VERCEL_API_TOKEN]: "",
};
default:
return baseDefaults;
}

View File

@@ -92,6 +92,11 @@ export const getProviderHelpText = (provider: string) => {
text: "Need help connecting your Google Workspace account?",
link: "https://goto.prowler.com/provider-googleworkspace",
};
case "vercel":
return {
text: "Need help connecting your Vercel team?",
link: "https://goto.prowler.com/provider-vercel",
};
default:
return {
text: "How to setup a provider?",

View File

@@ -250,6 +250,16 @@ export const buildAlibabaCloudSecret = (
return filterEmptyValues(secret);
};
export const buildVercelSecret = (formData: FormData) => {
const secret = {
[ProviderCredentialFields.VERCEL_API_TOKEN]: getFormValue(
formData,
ProviderCredentialFields.VERCEL_API_TOKEN,
),
};
return filterEmptyValues(secret);
};
export const buildOpenStackSecret = (formData: FormData) => {
const secret = {
[ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: getFormValue(
@@ -499,6 +509,10 @@ export const buildSecretConfig = (
secretType: "static",
secret: buildGoogleWorkspaceSecret(formData),
}),
vercel: () => ({
secretType: "static",
secret: buildVercelSecret(formData),
}),
};
const builder = secretBuilders[providerType];

View File

@@ -88,6 +88,9 @@ export const ProviderCredentialFields = {
GOOGLEWORKSPACE_CUSTOMER_ID: "customer_id",
GOOGLEWORKSPACE_CREDENTIALS_CONTENT: "credentials_content",
GOOGLEWORKSPACE_DELEGATED_USER: "delegated_user",
// Vercel fields
VERCEL_API_TOKEN: "api_token",
} as const;
// Type for credential field values
@@ -146,6 +149,7 @@ export const ErrorPointers = {
GOOGLEWORKSPACE_CREDENTIALS_CONTENT:
"/data/attributes/secret/credentials_content",
GOOGLEWORKSPACE_DELEGATED_USER: "/data/attributes/secret/delegated_user",
VERCEL_API_TOKEN: "/data/attributes/secret/api_token",
} as const;
export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers];

View File

@@ -381,6 +381,11 @@ export type GoogleWorkspaceCredentials = {
[ProviderCredentialFields.PROVIDER_ID]: string;
};
export type VercelCredentials = {
[ProviderCredentialFields.VERCEL_API_TOKEN]: string;
[ProviderCredentialFields.PROVIDER_ID]: string;
};
export type CredentialsFormSchema =
| AWSCredentials
| AWSCredentialsRole
@@ -397,7 +402,8 @@ export type CredentialsFormSchema =
| AlibabaCloudCredentialsRole
| CloudflareCredentials
| OpenStackCredentials
| GoogleWorkspaceCredentials;
| GoogleWorkspaceCredentials
| VercelCredentials;
export interface SearchParamsProps {
[key: string]: string | string[] | undefined;

View File

@@ -156,6 +156,11 @@ export const addProviderFormSchema = z
"Customer ID must start with 'C' followed by alphanumeric characters (e.g., C01234abc)",
),
}),
z.object({
providerType: z.literal("vercel"),
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
providerUid: z.string().trim().min(1, "Team ID is required"),
}),
]),
);
@@ -376,7 +381,15 @@ export const addCredentialsFormSchema = (
.string()
.min(1, "Cloud name is required"),
}
: {}),
: providerType === "vercel"
? {
[ProviderCredentialFields.VERCEL_API_TOKEN]:
z
.string()
.trim()
.min(1, "API Token is required"),
}
: {}),
})
.superRefine((data: Record<string, string | undefined>, ctx) => {
if (providerType === "m365") {

View File

@@ -13,6 +13,7 @@ export const PROVIDER_TYPES = [
"alibabacloud",
"cloudflare",
"openstack",
"vercel",
] as const;
export type ProviderType = (typeof PROVIDER_TYPES)[number];
@@ -32,6 +33,7 @@ export const PROVIDER_DISPLAY_NAMES: Record<ProviderType, string> = {
alibabacloud: "Alibaba Cloud",
cloudflare: "Cloudflare",
openstack: "OpenStack",
vercel: "Vercel",
};
export function getProviderDisplayName(providerId: string): string {