diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md
index 8ea4ee3b73..c622279475 100644
--- a/ui/CHANGELOG.md
+++ b/ui/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to the **Prowler UI** are documented in this file.
## [1.27.0] (Prowler UNRELEASED)
+### 🚀 Added
+
+- AWS findings and resource details now expose a "View in AWS Console" link that opens the resource directly in the AWS Console via the universal `/go/view` ARN resolver. The per-provider external link is rendered by a new shared `ExternalResourceLink` component, which also covers the existing IaC repository link [(#9172)](https://github.com/prowler-cloud/prowler/pull/9172)
+
### 🔄 Changed
- Trimmed unused npm dependencies [(#11115)](https://github.com/prowler-cloud/prowler/pull/11115)
diff --git a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx
index b5c011b00c..5cc3b7b298 100644
--- a/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx
+++ b/ui/components/findings/table/resource-detail-drawer/resource-detail-drawer-content.tsx
@@ -44,6 +44,10 @@ import {
TooltipTrigger,
} from "@/components/shadcn/tooltip";
import { EventsTimeline } from "@/components/shared/events-timeline/events-timeline";
+import {
+ ExternalResourceLink,
+ resolveExternalTarget,
+} from "@/components/shared/external-resource-link";
import {
QUERY_EDITOR_LANGUAGE,
QueryCodeEditor,
@@ -429,6 +433,16 @@ export function ResourceDetailDrawerContent({
const resourceDetailHref = f?.resourceId
? buildResourceDetailHref(f.resourceId)
: null;
+ const externalResourceTarget = resolveExternalTarget({
+ providerType,
+ resourceUid,
+ providerUid,
+ resourceName,
+ findingUid: f?.uid,
+ region: resourceRegion,
+ });
+ const hasIdAction =
+ Boolean(resourceDetailHref) || Boolean(externalResourceTarget);
const findingRecommendationUrl = f?.remediation.recommendation.url;
const checkRecommendationUrl = checkMeta.remediation.recommendation.url;
const recommendationUrl = isNonEmptyString(findingRecommendationUrl)
@@ -699,17 +713,31 @@ export function ResourceDetailDrawerContent({
entityId={resourceUid}
idLabel="UID"
idAction={
- resourceDetailHref ? (
-
+ hasIdAction ? (
+
+ {resourceDetailHref && (
+
+ )}
+ {externalResourceTarget && (
+
+ )}
+
) : undefined
}
/>
diff --git a/ui/components/resources/table/resource-detail-content.tsx b/ui/components/resources/table/resource-detail-content.tsx
index e9596e77c9..81979db0c5 100644
--- a/ui/components/resources/table/resource-detail-content.tsx
+++ b/ui/components/resources/table/resource-detail-content.tsx
@@ -1,7 +1,7 @@
"use client";
import { Row, RowSelectionState } from "@tanstack/react-table";
-import { Container, CornerDownRight, ExternalLink, Link } from "lucide-react";
+import { Container, CornerDownRight, Link } from "lucide-react";
import { useState } from "react";
import { FloatingMuteButton } from "@/components/findings/floating-mute-button";
@@ -22,6 +22,7 @@ import {
} from "@/components/shadcn/info-field/info-field";
import { LoadingState } from "@/components/shadcn/spinner/loading-state";
import { EventsTimeline } from "@/components/shared/events-timeline/events-timeline";
+import { ExternalResourceLink } from "@/components/shared/external-resource-link";
import {
QUERY_EDITOR_LANGUAGE,
QueryCodeEditor,
@@ -31,7 +32,6 @@ import { DateWithTime } from "@/components/ui/entities/date-with-time";
import { EntityInfo } from "@/components/ui/entities/entity-info";
import { DataTable } from "@/components/ui/table";
import { getGroupLabel } from "@/lib/categories";
-import { buildGitFileUrl } from "@/lib/iac-utils";
import { getRegionFlag } from "@/lib/region-flags";
import { ProviderType, ResourceProps } from "@/types";
@@ -190,16 +190,6 @@ export const ResourceDetailContent = ({
handleMuteComplete,
);
- const gitUrl =
- providerData.provider === "iac"
- ? buildGitFileUrl(
- providerData.uid,
- attributes.name,
- "",
- attributes.region,
- )
- : null;
-
const findingTitle =
findingDetails?.attributes?.check_metadata?.checktitle || "Finding Detail";
const resourceName =
@@ -268,25 +258,13 @@ export const ResourceDetailContent = ({
Copy resource link to clipboard
- {providerData.provider === "iac" && gitUrl && (
-
-
-
-
- View in Repository
-
-
-
- Go to Resource in the Repository
-
-
- )}
+
diff --git a/ui/components/shared/external-resource-link/external-resource-link.test.tsx b/ui/components/shared/external-resource-link/external-resource-link.test.tsx
new file mode 100644
index 0000000000..c4f5629710
--- /dev/null
+++ b/ui/components/shared/external-resource-link/external-resource-link.test.tsx
@@ -0,0 +1,71 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import { ExternalResourceLink } from "./external-resource-link";
+
+describe("ExternalResourceLink", () => {
+ it("renders an AWS Console link for AWS resources with a valid ARN", () => {
+ const arn = "arn:aws:s3:::example-bucket";
+ render();
+
+ const link = screen.getByRole("link", {
+ name: /open resource in aws console/i,
+ });
+ expect(link).toHaveAttribute(
+ "href",
+ `https://console.aws.amazon.com/go/view?arn=${encodeURIComponent(arn)}`,
+ );
+ expect(link).toHaveAttribute("target", "_blank");
+ expect(link).toHaveAttribute("rel", "noopener noreferrer");
+ expect(link).toHaveTextContent("View in AWS Console");
+ });
+
+ it("renders a repository link for IaC resources", () => {
+ render(
+ ,
+ );
+
+ const link = screen.getByRole("link", {
+ name: /open resource in the repository/i,
+ });
+ expect(link).toHaveAttribute(
+ "href",
+ "https://github.com/example/repo/blob/develop/main.tf#L10-L15",
+ );
+ expect(link).toHaveTextContent("View in Repository");
+ });
+
+ it("renders nothing for AWS resources without a valid ARN", () => {
+ const { container } = render(
+ ,
+ );
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ it("renders nothing for IaC resources missing repo url or filename", () => {
+ const { container } = render(
+ ,
+ );
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ it("renders nothing for providers without external link support", () => {
+ const { container } = render(
+ ,
+ );
+ expect(container).toBeEmptyDOMElement();
+ });
+});
diff --git a/ui/components/shared/external-resource-link/external-resource-link.tsx b/ui/components/shared/external-resource-link/external-resource-link.tsx
new file mode 100644
index 0000000000..e08544ea19
--- /dev/null
+++ b/ui/components/shared/external-resource-link/external-resource-link.tsx
@@ -0,0 +1,94 @@
+import { ExternalLink } from "lucide-react";
+
+import { Button } from "@/components/shadcn";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "@/components/shadcn/tooltip";
+import { buildAwsConsoleUrl } from "@/lib/aws-utils";
+import { buildGitFileUrl, extractLineRangeFromUid } from "@/lib/iac-utils";
+
+interface ExternalResourceLinkProps {
+ providerType: string | null | undefined;
+ resourceUid?: string | null;
+ providerUid?: string | null;
+ resourceName?: string | null;
+ findingUid?: string | null;
+ region?: string | null;
+ className?: string;
+}
+
+interface ExternalResourceTarget {
+ url: string;
+ label: string;
+ tooltip: string;
+}
+
+export const resolveExternalTarget = ({
+ providerType,
+ resourceUid,
+ providerUid,
+ resourceName,
+ findingUid,
+ region,
+}: ExternalResourceLinkProps): ExternalResourceTarget | null => {
+ if (providerType === "aws" && resourceUid) {
+ const url = buildAwsConsoleUrl(resourceUid);
+ if (!url) return null;
+ return {
+ url,
+ label: "View in AWS Console",
+ tooltip: "Open resource in AWS Console",
+ };
+ }
+
+ if (providerType === "iac" && providerUid && resourceName) {
+ const lineRange = findingUid
+ ? (extractLineRangeFromUid(findingUid) ?? "")
+ : "";
+ const url = buildGitFileUrl(
+ providerUid,
+ resourceName,
+ lineRange,
+ region ?? undefined,
+ );
+ if (!url) return null;
+ return {
+ url,
+ label: "View in Repository",
+ tooltip: "Open resource in the repository",
+ };
+ }
+
+ return null;
+};
+
+export const ExternalResourceLink = (props: ExternalResourceLinkProps) => {
+ const target = resolveExternalTarget(props);
+ if (!target) return null;
+
+ return (
+
+
+
+
+ {target.tooltip}
+
+ );
+};
diff --git a/ui/components/shared/external-resource-link/index.ts b/ui/components/shared/external-resource-link/index.ts
new file mode 100644
index 0000000000..3c1b341060
--- /dev/null
+++ b/ui/components/shared/external-resource-link/index.ts
@@ -0,0 +1,4 @@
+export {
+ ExternalResourceLink,
+ resolveExternalTarget,
+} from "./external-resource-link";
diff --git a/ui/lib/aws-utils.test.ts b/ui/lib/aws-utils.test.ts
new file mode 100644
index 0000000000..32957a18ad
--- /dev/null
+++ b/ui/lib/aws-utils.test.ts
@@ -0,0 +1,26 @@
+import { describe, expect, it } from "vitest";
+
+import { buildAwsConsoleUrl } from "./aws-utils";
+
+describe("buildAwsConsoleUrl", () => {
+ it("returns a `/go/view` URL with the ARN URL-encoded", () => {
+ const arn = "arn:aws:s3:::my-bucket";
+ expect(buildAwsConsoleUrl(arn)).toBe(
+ `https://console.aws.amazon.com/go/view?arn=${encodeURIComponent(arn)}`,
+ );
+ });
+
+ it("preserves regional and account scoping in the encoded ARN", () => {
+ const arn =
+ "arn:aws:iam::123456789012:role/MyRole-with+special/chars and spaces";
+ const url = buildAwsConsoleUrl(arn);
+ expect(url).not.toBeNull();
+ expect(url).toContain(encodeURIComponent(arn));
+ });
+
+ it("returns null for missing or non-ARN inputs", () => {
+ expect(buildAwsConsoleUrl("")).toBeNull();
+ expect(buildAwsConsoleUrl("not-an-arn")).toBeNull();
+ expect(buildAwsConsoleUrl("https://example.com")).toBeNull();
+ });
+});
diff --git a/ui/lib/aws-utils.ts b/ui/lib/aws-utils.ts
new file mode 100644
index 0000000000..96041481ce
--- /dev/null
+++ b/ui/lib/aws-utils.ts
@@ -0,0 +1,9 @@
+// Uses the AWS Console's universal `/go/view` redirect so we don't have to
+// special-case each service — the console resolves the ARN to the right page.
+export const buildAwsConsoleUrl = (resourceArn: string): string | null => {
+ if (!resourceArn || !resourceArn.startsWith("arn:")) {
+ return null;
+ }
+
+ return `https://console.aws.amazon.com/go/view?arn=${encodeURIComponent(resourceArn)}`;
+};