diff --git a/src/agentkit/server/app.py b/src/agentkit/server/app.py index 133131b..511dd0a 100644 --- a/src/agentkit/server/app.py +++ b/src/agentkit/server/app.py @@ -696,11 +696,13 @@ def create_app( @app.get("/{path:path}", response_class=HTMLResponse, include_in_schema=False) async def spa_fallback(path: str): """Serve index.html for SPA client-side routing.""" - # Don't intercept API routes - if path.startswith("api/"): + # Don't intercept API routes or docs + if path.startswith("api/") or path.startswith("docs") or path == "openapi.json": return HTMLResponse("

Not Found

", status_code=404) - # Try to serve a real static file first - file_path = _static_dir / path + # Try to serve a real static file first (with path traversal protection) + file_path = (_static_dir / path).resolve() + if not str(file_path).startswith(str(_static_dir.resolve())): + return HTMLResponse("

Forbidden

", status_code=403) if file_path.is_file(): return FileResponse(str(file_path)) # Fallback to index.html for SPA routing diff --git a/src/agentkit/server/frontend/package-lock.json b/src/agentkit/server/frontend/package-lock.json index 09600c8..390f6f7 100644 --- a/src/agentkit/server/frontend/package-lock.json +++ b/src/agentkit/server/frontend/package-lock.json @@ -13,12 +13,14 @@ "@vue-flow/controls": "^1.1.0", "@vue-flow/core": "^1.41.0", "ant-design-vue": "^4.2.0", + "dompurify": "^3.4.10", "markdown-it": "^14.2.0", "pinia": "^2.2.0", "vue": "^3.5.0", "vue-router": "^4.4.0" }, "devDependencies": { + "@types/dompurify": "^3.0.5", "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^5.1.0", "echarts": "^6.1.0", @@ -933,6 +935,16 @@ "nanopop": "^2.1.0" } }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", @@ -965,6 +977,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", @@ -1511,6 +1530,15 @@ "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==", "license": "MIT" }, + "node_modules/dompurify": { + "version": "3.4.10", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.4.10.tgz", + "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/echarts": { "version": "6.1.0", "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.1.0.tgz", diff --git a/src/agentkit/server/frontend/package.json b/src/agentkit/server/frontend/package.json index c17e88a..e24e515 100644 --- a/src/agentkit/server/frontend/package.json +++ b/src/agentkit/server/frontend/package.json @@ -14,12 +14,14 @@ "@vue-flow/controls": "^1.1.0", "@vue-flow/core": "^1.41.0", "ant-design-vue": "^4.2.0", + "dompurify": "^3.4.10", "markdown-it": "^14.2.0", "pinia": "^2.2.0", "vue": "^3.5.0", "vue-router": "^4.4.0" }, "devDependencies": { + "@types/dompurify": "^3.0.5", "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^5.1.0", "echarts": "^6.1.0", diff --git a/src/agentkit/server/frontend/src/api/skills.ts b/src/agentkit/server/frontend/src/api/skills.ts index d3bdbbb..10889cd 100644 --- a/src/agentkit/server/frontend/src/api/skills.ts +++ b/src/agentkit/server/frontend/src/api/skills.ts @@ -77,28 +77,21 @@ class SkillsApiClient extends BaseApiClient { source?: string ): Promise<{ status: string; name: string; path: string }> { // The install endpoint is on /api/v1/skills/install, not under skill-management - const url = `/api/v1/skills/install` - const resp = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, source }), - }) - if (!resp.ok) { - const err = await resp.json().catch(() => ({ detail: resp.statusText })) - throw new Error(err.detail ?? resp.statusText) - } - return resp.json() + return this.request<{ status: string; name: string; path: string }>( + '/api/v1/skills/install', + { + method: 'POST', + body: JSON.stringify({ name, source }), + } + ) } /** Uninstall a skill */ async uninstallSkill(skillName: string): Promise<{ status: string; name: string }> { - const url = `/api/v1/skills/${encodeURIComponent(skillName)}` - const resp = await fetch(url, { method: 'DELETE' }) - if (!resp.ok) { - const err = await resp.json().catch(() => ({ detail: resp.statusText })) - throw new Error(err.detail ?? resp.statusText) - } - return resp.json() + return this.request<{ status: string; name: string }>( + `/api/v1/skills/${encodeURIComponent(skillName)}`, + { method: 'DELETE' } + ) } /** Reload a skill */ diff --git a/src/agentkit/server/frontend/src/components/chat/ChatMessage.vue b/src/agentkit/server/frontend/src/components/chat/ChatMessage.vue index ff31436..296197a 100644 --- a/src/agentkit/server/frontend/src/components/chat/ChatMessage.vue +++ b/src/agentkit/server/frontend/src/components/chat/ChatMessage.vue @@ -54,6 +54,7 @@