feat(techniques): add external_references storage and display MITRE sources with links
Aegis CI / lint-and-test (push) Waiting to run
Snyk Security Scan / Python vulnerabilities (backend) (push) Waiting to run
Snyk Security Scan / npm vulnerabilities (frontend) (push) Waiting to run
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Waiting to run

This commit is contained in:
kitos
2026-06-18 16:26:11 +02:00
parent 263823f290
commit a58f9fd357
6 changed files with 96 additions and 0 deletions
@@ -151,6 +151,12 @@ function TechniqueJiraSection({ technique }: { technique: ReturnType<typeof Obje
);
}
function getMitreUrl(mitreId: string, externalRefs?: Array<{ source_name: string; url?: string }>): string {
const ref = externalRefs?.find((r) => r.source_name === "mitre-attack" && r.url);
if (ref?.url) return ref.url;
return `https://attack.mitre.org/techniques/${mitreId.replace(".", "/")}/`;
}
export default function TechniqueDetailPage() {
const { mitreId } = useParams<{ mitreId: string }>();
const navigate = useNavigate();
@@ -349,6 +355,20 @@ export default function TechniqueDetailPage() {
<dt className="text-xs font-medium uppercase text-gray-500">MITRE Version</dt>
<dd className="mt-1 text-sm text-gray-300">{technique.mitre_version || "—"}</dd>
</div>
<div>
<dt className="text-xs font-medium uppercase text-gray-500">MITRE ATT&amp;CK</dt>
<dd className="mt-1">
<a
href={getMitreUrl(technique.mitre_id, technique.external_references)}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-sm text-cyan-400 hover:underline"
>
<ExternalLink className="h-3.5 w-3.5" />
View on MITRE ATT&amp;CK
</a>
</dd>
</div>
</dl>
</div>
</div>
@@ -655,6 +675,39 @@ export default function TechniqueDetailPage() {
</div>
)}
{/* External References */}
{technique.external_references && technique.external_references.filter((r) => r.source_name !== "mitre-attack" && r.url).length > 0 && (
<div className="rounded-xl border border-gray-800 bg-gray-900 p-6">
<h2 className="mb-4 text-lg font-semibold text-white">References</h2>
<div className="space-y-2">
{technique.external_references
.filter((r) => r.source_name !== "mitre-attack" && r.url)
.map((ref, i) => (
<div
key={i}
className="flex items-start justify-between rounded-lg border border-gray-800 bg-gray-800/30 p-3"
>
<div className="min-w-0 flex-1">
<p className="text-sm font-medium text-gray-200">{ref.source_name}</p>
{ref.description && (
<p className="mt-0.5 line-clamp-2 text-xs text-gray-500">{ref.description}</p>
)}
</div>
<a
href={safeUrl(ref.url!)}
target="_blank"
rel="noopener noreferrer"
className="ml-3 flex shrink-0 items-center gap-1 text-sm text-cyan-400 hover:underline"
>
View
<ExternalLink className="h-3.5 w-3.5" />
</a>
</div>
))}
</div>
</div>
)}
{/* Jira Integration — shows tickets linked to tests of this technique */}
{technique && <TechniqueJiraSection technique={technique} />}
+8
View File
@@ -11,6 +11,13 @@ export interface User {
// ── Techniques ─────────────────────────────────────────────────────
export interface TechniqueExternalReference {
source_name: string;
url?: string;
external_id?: string;
description?: string;
}
export interface Technique {
id: string;
mitre_id: string;
@@ -25,6 +32,7 @@ export interface Technique {
status_global: TechniqueStatus;
review_required: boolean;
last_review_date: string | null;
external_references?: TechniqueExternalReference[];
}
export type TechniqueStatus =