690419:1831 feat: update CI/CD to use SSH key authentication #05
CI / CD Pipeline / build (push) Failing after 4m57s
CI / CD Pipeline / deploy (push) Has been skipped

This commit is contained in:
2026-04-19 18:31:30 +07:00
parent 733f3c3987
commit 13745e5874
61 changed files with 6709 additions and 1241 deletions
+54
View File
@@ -0,0 +1,54 @@
'use client';
import { Bot } from 'lucide-react';
import { useRagQuery } from '../../../hooks/use-rag';
import { useProjectStore } from '../../../lib/stores/project-store';
import { RagSearchBar } from '../../../components/rag/rag-search-bar';
import { RagResultCard } from '../../../components/rag/rag-result-card';
export default function RagPage() {
const { selectedProjectId } = useProjectStore();
const { mutate, data, isPending, error, isIdle } = useRagQuery();
const handleSearch = (question: string) => {
if (!selectedProjectId) return;
mutate({ question, projectPublicId: selectedProjectId });
};
return (
<div className="container mx-auto max-w-3xl py-8 space-y-6">
<div className="flex items-center gap-2">
<Bot className="h-6 w-6 text-primary" />
<h1 className="text-xl font-semibold">RAG </h1>
</div>
{!selectedProjectId && (
<div className="rounded-md border border-yellow-300 bg-yellow-50 px-4 py-3 text-sm text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400 dark:border-yellow-700">
RAG
</div>
)}
<RagSearchBar onSearch={handleSearch} isLoading={isPending} />
{isPending && (
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground animate-pulse">
...
</div>
)}
{error && (
<div className="rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">
: {error.message}
</div>
)}
{data && !isPending && <RagResultCard result={data} />}
{isIdle && !error && (
<p className="text-center text-sm text-muted-foreground pt-4">
</p>
)}
</div>
);
}
@@ -0,0 +1,12 @@
'use client';
import { AlertTriangle } from 'lucide-react';
export function RagFallbackBadge() {
return (
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-100 px-2.5 py-0.5 text-xs font-medium text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400">
<AlertTriangle className="h-3 w-3" />
local model
</span>
);
}
@@ -0,0 +1,74 @@
'use client';
import { FileText } from 'lucide-react';
import type { RagQueryResponse, RagCitation } from '../../hooks/use-rag';
import { RagFallbackBadge } from './rag-fallback-badge';
interface RagResultCardProps {
result: RagQueryResponse;
}
function ConfidenceBar({ score }: { score: number }) {
const pct = Math.round(score * 100);
const color =
pct >= 80 ? 'bg-green-500' : pct >= 50 ? 'bg-yellow-500' : 'bg-red-500';
return (
<div className="flex items-center gap-2">
<div className="h-2 w-24 rounded-full bg-muted overflow-hidden">
<div className={`h-full ${color} transition-all`} style={{ width: `${pct}%` }} />
</div>
<span className="text-xs text-muted-foreground">{pct}%</span>
</div>
);
}
function CitationItem({ citation }: { citation: RagCitation }) {
return (
<div className="rounded border p-3 text-sm space-y-1">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1.5 font-medium text-foreground">
<FileText className="h-4 w-4 text-muted-foreground" />
<span>{citation.docType}</span>
{citation.docNumber && (
<span className="text-muted-foreground"> {citation.docNumber}</span>
)}
{citation.revision && (
<span className="rounded bg-muted px-1 text-xs">Rev. {citation.revision}</span>
)}
</div>
<ConfidenceBar score={citation.score} />
</div>
<p className="text-muted-foreground line-clamp-3">{citation.snippet}</p>
</div>
);
}
export function RagResultCard({ result }: RagResultCardProps) {
return (
<div className="rounded-lg border bg-card p-6 space-y-4">
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<h3 className="font-semibold text-base mb-1"></h3>
<p className="text-sm leading-relaxed whitespace-pre-wrap">{result.answer}</p>
</div>
<div className="flex flex-col items-end gap-1.5 shrink-0">
<ConfidenceBar score={result.confidence} />
{result.usedFallbackModel && <RagFallbackBadge />}
</div>
</div>
{result.citations.length > 0 && (
<div className="space-y-2">
<h4 className="text-sm font-medium text-muted-foreground">
({result.citations.length} )
</h4>
<div className="space-y-2">
{result.citations.map((c) => (
<CitationItem key={c.chunkId} citation={c} />
))}
</div>
</div>
)}
</div>
);
}
@@ -0,0 +1,64 @@
'use client';
import { useState } from 'react';
import { Loader2, Search } from 'lucide-react';
import { z } from 'zod';
const schema = z.object({
question: z.string().min(1, 'กรุณาระบุคำถาม').max(500, 'คำถามต้องไม่เกิน 500 ตัวอักษร'),
});
interface RagSearchBarProps {
onSearch: (question: string) => void;
isLoading: boolean;
}
export function RagSearchBar({ onSearch, isLoading }: RagSearchBarProps) {
const [question, setQuestion] = useState('');
const [error, setError] = useState<string | null>(null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const result = schema.safeParse({ question });
if (!result.success) {
setError(result.error.issues[0]?.message ?? 'ข้อมูลไม่ถูกต้อง');
return;
}
setError(null);
onSearch(question);
};
return (
<form onSubmit={handleSubmit} className="w-full">
<div className="flex gap-2">
<div className="flex-1">
<input
type="text"
value={question}
onChange={(e) => setQuestion(e.target.value)}
placeholder="ถามคำถามเกี่ยวกับเอกสารโครงการ..."
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
disabled={isLoading}
maxLength={500}
/>
{error && <p className="mt-1 text-sm text-destructive">{error}</p>}
<p className="mt-1 text-xs text-muted-foreground text-right">
{question.length}/500
</p>
</div>
<button
type="submit"
disabled={isLoading || question.trim().length === 0}
className="inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
{isLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Search className="h-4 w-4" />
)}
</button>
</div>
</form>
);
}
File diff suppressed because one or more lines are too long
@@ -91,9 +91,39 @@ describe('useWorkflowAction — T027a error handling (Clarify Q1+Q2)', () => {
);
});
it('403: should show unauthorized toast', async () => {
it('M1 (403): should use backend-provided message instead of hardcoded string', async () => {
// backend ส่ง message แบบ contextual (cross-contract) — frontend ต้อง preserve
vi.mocked(workflowEngineService.transition).mockRejectedValue(
makeApiError(403, 'ไม่มีสิทธิ์', ['ติดต่อ Admin'])
makeApiError(
403,
'คุณไม่มีสิทธิ์เข้าถึง Workflow ของสัญญานี้',
['ตรวจสอบสิทธิ์กับ Project Admin']
)
);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowAction('inst-1'), { wrapper });
await act(async () => {
result.current.mutate({ action: 'APPROVE' });
});
await waitFor(() => {
expect(result.current.isError).toBe(true);
});
// ✓ toast ต้องใช้ backend message (ไม่ใช่ "คุณไม่มีสิทธิ์ดำเนินการในขั้นตอนนี้" generic)
expect(toast.error).toHaveBeenCalledWith(
'คุณไม่มีสิทธิ์เข้าถึง Workflow ของสัญญานี้',
expect.objectContaining({
description: 'ตรวจสอบสิทธิ์กับ Project Admin',
})
);
});
it('M1 (403): should fallback to generic message when backend message missing', async () => {
vi.mocked(workflowEngineService.transition).mockRejectedValue(
makeApiError(403, '', ['ติดต่อ Admin'])
);
const { wrapper } = createTestQueryClient();
@@ -115,6 +145,81 @@ describe('useWorkflowAction — T027a error handling (Clarify Q1+Q2)', () => {
);
});
it('M3 (409): should reset idempotency key after 409 so retry uses fresh key', async () => {
// First call → 409
vi.mocked(workflowEngineService.transition).mockRejectedValueOnce(
makeApiError(409, 'ไม่สามารถอัปโหลดในสถานะนี้ได้', ['รีเฟรชหน้า'])
);
// Second call → success
vi.mocked(workflowEngineService.transition).mockResolvedValueOnce({
success: true,
nextState: 'APPROVED',
});
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowAction('inst-1'), { wrapper });
// First mutate → 409
await act(async () => {
result.current.mutate({ action: 'APPROVE' });
});
await waitFor(() => expect(result.current.isError).toBe(true));
const firstKey = vi
.mocked(workflowEngineService.transition)
.mock.calls[0][2];
// Second mutate → success
await act(async () => {
result.current.mutate({ action: 'APPROVE' });
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
const secondKey = vi
.mocked(workflowEngineService.transition)
.mock.calls[1][2];
// ✓ Key ต้องแตกต่างกัน (reset แล้วหลัง 409)
expect(firstKey).not.toBe(secondKey);
expect(firstKey).toMatch(/^[0-9a-f-]{36}$/);
expect(secondKey).toMatch(/^[0-9a-f-]{36}$/);
});
it('M3 (503): should NOT reset idempotency key (user can retry with same key)', async () => {
// First call → 503
vi.mocked(workflowEngineService.transition).mockRejectedValueOnce(
makeApiError(503, 'ระบบยุ่ง')
);
// Second call → success
vi.mocked(workflowEngineService.transition).mockResolvedValueOnce({
success: true,
});
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowAction('inst-1'), { wrapper });
await act(async () => {
result.current.mutate({ action: 'APPROVE' });
});
await waitFor(() => expect(result.current.isError).toBe(true));
const firstKey = vi
.mocked(workflowEngineService.transition)
.mock.calls[0][2];
await act(async () => {
result.current.mutate({ action: 'APPROVE' });
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
const secondKey = vi
.mocked(workflowEngineService.transition)
.mock.calls[1][2];
// ✓ Key ต้องเหมือนเดิม (503 = retryable, same intent)
expect(firstKey).toBe(secondKey);
});
it('should show success toast on 200', async () => {
vi.mocked(workflowEngineService.transition).mockResolvedValue({
success: true,
+36
View File
@@ -0,0 +1,36 @@
import { useMutation } from '@tanstack/react-query';
import apiClient from '../lib/api/client';
export interface RagCitation {
chunkId: string;
docNumber: string | null;
docType: string;
revision: string | null;
snippet: string;
score: number;
}
export interface RagQueryRequest {
question: string;
projectPublicId: string;
}
export interface RagQueryResponse {
answer: string;
citations: RagCitation[];
confidence: number;
usedFallbackModel: boolean;
cachedAt?: string;
}
export function useRagQuery() {
return useMutation<RagQueryResponse, Error, RagQueryRequest>({
mutationFn: async (payload) => {
const idempotencyKey = `rag-${Date.now()}-${Math.random().toString(36).slice(2)}`;
const res = await apiClient.post<{ data: RagQueryResponse }>('/rag/query', payload, {
headers: { 'Idempotency-Key': idempotencyKey },
});
return res.data.data;
},
});
}
+10 -8
View File
@@ -12,13 +12,13 @@ import type { ApiErrorResponse } from '@/lib/api/client';
import type { WorkflowTransitionWithAttachmentsDto } from '@/types/dto/workflow-engine/workflow-engine.dto';
// Type guard — ตรวจสอบว่า error ที่ได้มาเป็น ApiErrorResponse (จาก parseApiError interceptor)
// S3: ป้องกัน edge case `{ error: null }` ซึ่ง typeof null === 'object' แต่ destructure จะ throw
function isApiErrorResponse(err: unknown): err is ApiErrorResponse {
return (
typeof err === 'object' &&
err !== null &&
'error' in err &&
typeof (err as ApiErrorResponse).error === 'object'
);
if (typeof err !== 'object' || err === null || !('error' in err)) {
return false;
}
const inner = (err as { error: unknown }).error;
return typeof inner === 'object' && inner !== null;
}
export function useWorkflowAction(instanceId: string | undefined) {
@@ -69,15 +69,17 @@ export function useWorkflowAction(instanceId: string | undefined) {
// Clarify Q1: 409 Conflict (ไม่อยู่ในสถานะที่อนุญาตให้อัปโหลด)
if (statusCode === 409) {
// M3: reset idempotency key — user intent กับ state เดิมใช้ไม่ได้แล้ว
setIdempotencyKey(uuidv4());
toast.error(message || 'ไม่สามารถดำเนินการในสถานะนี้ได้', {
description: recoveryActions?.[0],
});
return;
}
// 403 Forbidden — ไม่มีสิทธิ์
// 403 Forbidden — ไม่มีสิทธิ์ (M1: ใช้ message จาก backend เพื่อคง context)
if (statusCode === 403) {
toast.error('คุณไม่มีสิทธิ์ดำเนินการในขั้นตอนนี้', {
toast.error(message || 'คุณไม่มีสิทธิ์ดำเนินการในขั้นตอนนี้', {
description: recoveryActions?.[0],
});
return;
+794
View File
@@ -0,0 +1,794 @@
{
"auditReportVersion": 2,
"vulnerabilities": {
"@next/eslint-plugin-next": {
"name": "@next/eslint-plugin-next",
"severity": "high",
"isDirect": false,
"via": [
"glob"
],
"effects": [
"eslint-config-next"
],
"range": "14.0.5-canary.0 - 15.0.0-rc.1",
"nodes": [
"node_modules/@next/eslint-plugin-next"
],
"fixAvailable": true
},
"ajv": {
"name": "ajv",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1113714,
"name": "ajv",
"dependency": "ajv",
"title": "ajv has ReDoS when using `$data` option",
"url": "https://github.com/advisories/GHSA-2g4f-4pwh-qvx6",
"severity": "moderate",
"cwe": [
"CWE-400",
"CWE-1333"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<6.14.0"
}
],
"effects": [],
"range": "<6.14.0",
"nodes": [
"node_modules/ajv"
],
"fixAvailable": true
},
"axios": {
"name": "axios",
"severity": "high",
"isDirect": true,
"via": [
{
"source": 1113275,
"name": "axios",
"dependency": "axios",
"title": "Axios is Vulnerable to Denial of Service via __proto__ Key in mergeConfig",
"url": "https://github.com/advisories/GHSA-43fc-jf86-j433",
"severity": "high",
"cwe": [
"CWE-754"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=1.0.0 <=1.13.4"
},
{
"source": 1116673,
"name": "axios",
"dependency": "axios",
"title": "Axios has a NO_PROXY Hostname Normalization Bypass that Leads to SSRF",
"url": "https://github.com/advisories/GHSA-3p68-rc4w-qgx5",
"severity": "moderate",
"cwe": [
"CWE-441",
"CWE-918"
],
"cvss": {
"score": 4.8,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N"
},
"range": ">=1.0.0 <1.15.0"
},
{
"source": 1116675,
"name": "axios",
"dependency": "axios",
"title": "Axios has Unrestricted Cloud Metadata Exfiltration via Header Injection Chain",
"url": "https://github.com/advisories/GHSA-fvcv-3m26-pcqx",
"severity": "moderate",
"cwe": [
"CWE-113",
"CWE-444",
"CWE-918"
],
"cvss": {
"score": 4.8,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N"
},
"range": ">=1.0.0 <1.15.0"
}
],
"effects": [],
"range": "1.0.0 - 1.14.0",
"nodes": [
"node_modules/axios"
],
"fixAvailable": true
},
"brace-expansion": {
"name": "brace-expansion",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1115540,
"name": "brace-expansion",
"dependency": "brace-expansion",
"title": "brace-expansion: Zero-step sequence causes process hang and memory exhaustion",
"url": "https://github.com/advisories/GHSA-f886-m6hf-6m8v",
"severity": "moderate",
"cwe": [
"CWE-400"
],
"cvss": {
"score": 6.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"
},
"range": "<1.1.13"
},
{
"source": 1115541,
"name": "brace-expansion",
"dependency": "brace-expansion",
"title": "brace-expansion: Zero-step sequence causes process hang and memory exhaustion",
"url": "https://github.com/advisories/GHSA-f886-m6hf-6m8v",
"severity": "moderate",
"cwe": [
"CWE-400"
],
"cvss": {
"score": 6.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"
},
"range": ">=2.0.0 <2.0.3"
}
],
"effects": [],
"range": "<1.1.13 || >=2.0.0 <2.0.3",
"nodes": [
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion",
"node_modules/brace-expansion",
"node_modules/glob/node_modules/brace-expansion"
],
"fixAvailable": true
},
"dompurify": {
"name": "dompurify",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1115529,
"name": "dompurify",
"dependency": "dompurify",
"title": "DOMPurify is vulnerable to mutation-XSS via Re-Contextualization ",
"url": "https://github.com/advisories/GHSA-h8r8-wccr-v5f2",
"severity": "moderate",
"cwe": [
"CWE-79"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<3.3.2"
},
{
"source": 1115668,
"name": "dompurify",
"dependency": "dompurify",
"title": "DOMPurify contains a Cross-site Scripting vulnerability",
"url": "https://github.com/advisories/GHSA-v2wj-7wpq-c8vv",
"severity": "moderate",
"cwe": [
"CWE-79"
],
"cvss": {
"score": 6.1,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
},
"range": ">=3.1.3 <=3.3.1"
},
{
"source": 1115921,
"name": "dompurify",
"dependency": "dompurify",
"title": "DOMPurify ADD_ATTR predicate skips URI validation",
"url": "https://github.com/advisories/GHSA-cjmm-f4jc-qw8r",
"severity": "moderate",
"cwe": [
"CWE-183"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<=3.3.1"
},
{
"source": 1115922,
"name": "dompurify",
"dependency": "dompurify",
"title": "DOMPurify USE_PROFILES prototype pollution allows event handlers",
"url": "https://github.com/advisories/GHSA-cj63-jhhr-wcxv",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<=3.3.1"
},
{
"source": 1116663,
"name": "dompurify",
"dependency": "dompurify",
"title": "DOMPurify's ADD_TAGS function form bypasses FORBID_TAGS due to short-circuit evaluation",
"url": "https://github.com/advisories/GHSA-39q2-94rc-95cp",
"severity": "moderate",
"cwe": [
"CWE-783"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<=3.3.3"
}
],
"effects": [
"monaco-editor"
],
"range": "<=3.3.3",
"nodes": [
"node_modules/dompurify"
],
"fixAvailable": true
},
"eslint-config-next": {
"name": "eslint-config-next",
"severity": "high",
"isDirect": true,
"via": [
"@next/eslint-plugin-next"
],
"effects": [],
"range": "14.0.5-canary.0 - 15.0.0-rc.1",
"nodes": [
"node_modules/eslint-config-next"
],
"fixAvailable": true
},
"flatted": {
"name": "flatted",
"severity": "high",
"isDirect": false,
"via": [
{
"source": 1114526,
"name": "flatted",
"dependency": "flatted",
"title": "flatted vulnerable to unbounded recursion DoS in parse() revive phase",
"url": "https://github.com/advisories/GHSA-25h7-pfq9-p65f",
"severity": "high",
"cwe": [
"CWE-674"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": "<3.4.0"
},
{
"source": 1115357,
"name": "flatted",
"dependency": "flatted",
"title": "Prototype Pollution via parse() in NodeJS flatted",
"url": "https://github.com/advisories/GHSA-rf6f-7fwh-wjgh",
"severity": "high",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<=3.4.1"
}
],
"effects": [],
"range": "<=3.4.1",
"nodes": [
"node_modules/flatted"
],
"fixAvailable": true
},
"follow-redirects": {
"name": "follow-redirects",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1116560,
"name": "follow-redirects",
"dependency": "follow-redirects",
"title": "follow-redirects leaks Custom Authentication Headers to Cross-Domain Redirect Targets",
"url": "https://github.com/advisories/GHSA-r4q5-vmmm-2653",
"severity": "moderate",
"cwe": [
"CWE-200"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<=1.15.11"
}
],
"effects": [],
"range": "<=1.15.11",
"nodes": [
"node_modules/follow-redirects"
],
"fixAvailable": true
},
"glob": {
"name": "glob",
"severity": "high",
"isDirect": false,
"via": [
{
"source": 1109842,
"name": "glob",
"dependency": "glob",
"title": "glob CLI: Command injection via -c/--cmd executes matches with shell:true",
"url": "https://github.com/advisories/GHSA-5j98-mcp5-4vw2",
"severity": "high",
"cwe": [
"CWE-78"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H"
},
"range": ">=10.2.0 <10.5.0"
}
],
"effects": [
"@next/eslint-plugin-next"
],
"range": "10.2.0 - 10.4.5",
"nodes": [
"node_modules/glob"
],
"fixAvailable": true
},
"minimatch": {
"name": "minimatch",
"severity": "high",
"isDirect": false,
"via": [
{
"source": 1113459,
"name": "minimatch",
"dependency": "minimatch",
"title": "minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern",
"url": "https://github.com/advisories/GHSA-3ppc-4f35-3m26",
"severity": "high",
"cwe": [
"CWE-1333"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<3.1.3"
},
{
"source": 1113465,
"name": "minimatch",
"dependency": "minimatch",
"title": "minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern",
"url": "https://github.com/advisories/GHSA-3ppc-4f35-3m26",
"severity": "high",
"cwe": [
"CWE-1333"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": ">=9.0.0 <9.0.6"
},
{
"source": 1113538,
"name": "minimatch",
"dependency": "minimatch",
"title": "minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments",
"url": "https://github.com/advisories/GHSA-7r86-cg39-jmmj",
"severity": "high",
"cwe": [
"CWE-407"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": "<3.1.3"
},
{
"source": 1113544,
"name": "minimatch",
"dependency": "minimatch",
"title": "minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments",
"url": "https://github.com/advisories/GHSA-7r86-cg39-jmmj",
"severity": "high",
"cwe": [
"CWE-407"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=9.0.0 <9.0.7"
},
{
"source": 1113546,
"name": "minimatch",
"dependency": "minimatch",
"title": "minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions",
"url": "https://github.com/advisories/GHSA-23c5-xmqv-rm74",
"severity": "high",
"cwe": [
"CWE-1333"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": "<3.1.4"
},
{
"source": 1113552,
"name": "minimatch",
"dependency": "minimatch",
"title": "minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions",
"url": "https://github.com/advisories/GHSA-23c5-xmqv-rm74",
"severity": "high",
"cwe": [
"CWE-1333"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=9.0.0 <9.0.7"
}
],
"effects": [],
"range": "<=3.1.3 || 9.0.0 - 9.0.6",
"nodes": [
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch",
"node_modules/glob/node_modules/minimatch",
"node_modules/minimatch"
],
"fixAvailable": true
},
"monaco-editor": {
"name": "monaco-editor",
"severity": "moderate",
"isDirect": false,
"via": [
"dompurify"
],
"effects": [],
"range": ">=0.54.0-dev-20250909",
"nodes": [
"node_modules/monaco-editor"
],
"fixAvailable": true
},
"next": {
"name": "next",
"severity": "high",
"isDirect": true,
"via": [
{
"source": 1111374,
"name": "next",
"dependency": "next",
"title": "Next Server Actions Source Code Exposure ",
"url": "https://github.com/advisories/GHSA-w37m-7fhw-fmv9",
"severity": "moderate",
"cwe": [
"CWE-497",
"CWE-502",
"CWE-1395"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"
},
"range": ">=16.0.0-beta.0 <16.0.9"
},
{
"source": 1111383,
"name": "next",
"dependency": "next",
"title": "Next Vulnerable to Denial of Service with Server Components",
"url": "https://github.com/advisories/GHSA-mwv6-3258-q52c",
"severity": "high",
"cwe": [
"CWE-400",
"CWE-502",
"CWE-1395"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=16.0.0-beta.0 <16.0.9"
},
{
"source": 1112592,
"name": "next",
"dependency": "next",
"title": "Next.js self-hosted applications vulnerable to DoS via Image Optimizer remotePatterns configuration",
"url": "https://github.com/advisories/GHSA-9g9p-9gw9-jx7f",
"severity": "moderate",
"cwe": [
"CWE-400",
"CWE-770"
],
"cvss": {
"score": 5.9,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=15.6.0-canary.0 <16.1.5"
},
{
"source": 1112646,
"name": "next",
"dependency": "next",
"title": "Next.js HTTP request deserialization can lead to DoS when using insecure React Server Components",
"url": "https://github.com/advisories/GHSA-h25m-26qc-wcjf",
"severity": "high",
"cwe": [
"CWE-400",
"CWE-502"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=16.0.0-beta.0 <16.0.11"
},
{
"source": 1114898,
"name": "next",
"dependency": "next",
"title": "Next.js: HTTP request smuggling in rewrites",
"url": "https://github.com/advisories/GHSA-ggv3-7p47-pfv8",
"severity": "moderate",
"cwe": [
"CWE-444"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": ">=16.0.0-beta.0 <16.1.7"
},
{
"source": 1114941,
"name": "next",
"dependency": "next",
"title": "Next.js: Unbounded next/image disk cache growth can exhaust storage",
"url": "https://github.com/advisories/GHSA-3x4c-7xq6-9pq8",
"severity": "moderate",
"cwe": [
"CWE-400"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": ">=16.0.0-beta.0 <16.1.7"
},
{
"source": 1114942,
"name": "next",
"dependency": "next",
"title": "Next.js: Unbounded postponed resume buffering can lead to DoS",
"url": "https://github.com/advisories/GHSA-h27x-g6w4-24gq",
"severity": "moderate",
"cwe": [
"CWE-770"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": ">=16.0.1 <16.1.7"
},
{
"source": 1114943,
"name": "next",
"dependency": "next",
"title": "Next.js: null origin can bypass Server Actions CSRF checks",
"url": "https://github.com/advisories/GHSA-mq59-m269-xvcx",
"severity": "moderate",
"cwe": [
"CWE-352"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": ">=16.0.1 <16.1.7"
},
{
"source": 1115360,
"name": "next",
"dependency": "next",
"title": "Next.js: null origin can bypass dev HMR websocket CSRF checks",
"url": "https://github.com/advisories/GHSA-jcc7-9wpm-mj36",
"severity": "low",
"cwe": [
"CWE-1385"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": ">=16.0.1 <16.1.7"
},
{
"source": 1116305,
"name": "next",
"dependency": "next",
"title": "Next.js has Unbounded Memory Consumption via PPR Resume Endpoint ",
"url": "https://github.com/advisories/GHSA-5f7q-jpqc-wp7h",
"severity": "moderate",
"cwe": [
"CWE-400",
"CWE-409",
"CWE-770"
],
"cvss": {
"score": 5.9,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=16.0.0-beta.0 <16.1.5"
},
{
"source": 1116375,
"name": "next",
"dependency": "next",
"title": "Next.js has a Denial of Service with Server Components",
"url": "https://github.com/advisories/GHSA-q4gf-8mx6-v5v3",
"severity": "high",
"cwe": [
"CWE-770"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=16.0.0-beta.0 <16.2.3"
}
],
"effects": [],
"range": "15.6.0-canary.0 - 16.2.2",
"nodes": [
"node_modules/next"
],
"fixAvailable": true
},
"picomatch": {
"name": "picomatch",
"severity": "high",
"isDirect": false,
"via": [
{
"source": 1115549,
"name": "picomatch",
"dependency": "picomatch",
"title": "Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching",
"url": "https://github.com/advisories/GHSA-3v7f-55p6-f55p",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
"range": "<2.3.2"
},
{
"source": 1115551,
"name": "picomatch",
"dependency": "picomatch",
"title": "Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching",
"url": "https://github.com/advisories/GHSA-3v7f-55p6-f55p",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
"range": ">=4.0.0 <4.0.4"
},
{
"source": 1115552,
"name": "picomatch",
"dependency": "picomatch",
"title": "Picomatch has a ReDoS vulnerability via extglob quantifiers",
"url": "https://github.com/advisories/GHSA-c2c7-rcm5-vvqj",
"severity": "high",
"cwe": [
"CWE-1333"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": "<2.3.2"
},
{
"source": 1115554,
"name": "picomatch",
"dependency": "picomatch",
"title": "Picomatch has a ReDoS vulnerability via extglob quantifiers",
"url": "https://github.com/advisories/GHSA-c2c7-rcm5-vvqj",
"severity": "high",
"cwe": [
"CWE-1333"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=4.0.0 <4.0.4"
}
],
"effects": [],
"range": "<=2.3.1 || 4.0.0 - 4.0.3",
"nodes": [
"node_modules/picomatch",
"node_modules/tinyglobby/node_modules/picomatch"
],
"fixAvailable": true
}
},
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 5,
"high": 8,
"critical": 0,
"total": 13
},
"dependencies": {
"prod": 300,
"dev": 301,
"optional": 63,
"peer": 5,
"peerOptional": 0,
"total": 641
}
}
}
+4 -3
View File
@@ -34,13 +34,13 @@
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.91.2",
"@tanstack/react-table": "^8.21.3",
"axios": "^1.13.6",
"axios": "1.15.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"lucide-react": "^0.577.0",
"next": "^16.2.0",
"next": "16.2.4",
"next-auth": "5.0.0-beta.30",
"next-themes": "^0.4.6",
"react": "^19.2.4",
@@ -67,7 +67,7 @@
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.57.1",
"@typescript-eslint/parser": "^8.57.1",
"@vitejs/plugin-react": "^5.1.2",
"@vitejs/plugin-react": "^5.2.0",
"autoprefixer": "^10.4.27",
"baseline-browser-mapping": "^2.10.8",
"eslint": "^9.39.1",
@@ -80,6 +80,7 @@
"postcss": "^8.5.8",
"tailwindcss": "3.4.3",
"typescript": "^5.9.3",
"vite": "7.3.2",
"vitest": "^4.1.0"
}
}