feat(audit): P1 cross-tenant/RBAC tests, P3 test fixes, P2/P3 frontend fixes
Check Cross-Plugin Imports / check (push) Has been cancelled

- P1-Tests: 12 test files with new cross-tenant isolation + RBAC tests
- P3-Tests: 8 fixes (duplicate fixtures, sys.path.insert, unused imports, KeyError)
- P3-Frontend: LucideIcons → ICON_MAP (2 files), inline styles → Tailwind (2 files)
- P3-Frontend: DOMPurify for iframe XSS, redundant regex removed, console.log → console.debug
- P2-Frontend: 2 notification API TODOs retained (requires larger refactor)
- conftest.py: create_no_perm_user helper added
- pyproject.toml: pythonpath for scripts/ added
- All checks green: ruff 0, F821 0, tsc 0, app 495 routes, cross-plugin 0
This commit is contained in:
Agent Zero
2026-08-16 01:30:02 +02:00
parent abbe7a18fc
commit db97a39133
29 changed files with 618 additions and 52 deletions
+3 -4
View File
@@ -1,4 +1,3 @@
// TODO: P3-F5 — Replace inline styles with Tailwind classes
import React, { useState, useRef, useEffect } from 'react';
import clsx from 'clsx';
import ReactMarkdown from 'react-markdown';
@@ -37,9 +36,9 @@ function ActivityIndicator({ status }: { status: string | null }) {
return (
<div className="flex items-center gap-2 px-4 py-2 text-xs text-secondary-500">
<div className="flex gap-1">
<span className="w-1.5 h-1.5 rounded-full bg-primary-400 animate-bounce" style={{ animationDelay: '0ms' }} />
<span className="w-1.5 h-1.5 rounded-full bg-primary-400 animate-bounce" style={{ animationDelay: '150ms' }} />
<span className="w-1.5 h-1.5 rounded-full bg-primary-400 animate-bounce" style={{ animationDelay: '300ms' }} />
<span className="w-1.5 h-1.5 rounded-full bg-primary-400 animate-bounce [animation-delay:0ms]" />
<span className="w-1.5 h-1.5 rounded-full bg-primary-400 animate-bounce [animation-delay:150ms]" />
<span className="w-1.5 h-1.5 rounded-full bg-primary-400 animate-bounce [animation-delay:300ms]" />
</div>
<span>{status}</span>
</div>
@@ -1,4 +1,3 @@
// TODO: P3-F21 — Remove redundant regex before DOMPurify
import React from 'react';
import DOMPurify from 'dompurify';
import type { MessageBlock } from '@/store/commStore';
@@ -14,13 +13,7 @@ const HtmlBlock: React.FC<HtmlBlockProps> = ({ block }) => {
return null;
}
// Replace javascript: URLs in href attributes before sanitizing
const safeHtml = rawHtml.replace(
/href\s*=\s*(["'])\s*javascript:[^"']*\1/gi,
'href=$1#$1'
);
const sanitized = DOMPurify.sanitize(safeHtml);
const sanitized = DOMPurify.sanitize(rawHtml);
return (
<div
@@ -1,4 +1,3 @@
// TODO: P3-F3 — Replace import * as LucideIcons with explicit icon imports
import React, { useState, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button';
@@ -8,8 +7,16 @@ import { Modal } from '@/components/ui/Modal';
import { Input } from '@/components/ui/Input';
import { useToast } from '@/components/ui/Toast';
import { Loader2 } from 'lucide-react';
import * as LucideIcons from 'lucide-react';
import { Loader2, FileText, FolderOpen, Mail, Calendar, Link, Tag } from 'lucide-react';
const ICON_MAP: Record<string, React.ComponentType<{ className?: string }>> = {
FileText,
FolderOpen,
Mail,
Calendar,
Link,
Tag,
};
import { HistoryViewer } from '@/components/HistoryViewer';
import { usePluginStore } from '@/store/pluginStore';
import { useAIUIControlStore } from '@/store/aiUIControlStore';
@@ -154,8 +161,8 @@ function ContactPersonModal({
}
function getIcon(name: string): React.ReactNode {
const Icon = (LucideIcons as any)[name];
return Icon ? <Icon className="h-4 w-4" /> : <LucideIcons.FileText className="h-4 w-4" />;
const Icon = ICON_MAP[name] ?? FileText;
return <Icon className="h-4 w-4" />;
}
export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId = 'contact-detail' }: ContactDetailProps) {
+3 -2
View File
@@ -1,4 +1,3 @@
// TODO: P3-F7 — Sanitize iframe HTML rendering to prevent XSS
/**
* Mail detail reading pane.
* Shows mail headers, sanitized HTML body, and attachments.
@@ -13,6 +12,7 @@ import { Button } from '@/components/ui/Button';
import { EmptyState } from '@/components/ui/EmptyState';
import { FileText, Loader2 } from 'lucide-react';
import { formatDateTime } from '@/utils/date';
import DOMPurify from 'dompurify';
export interface MailDetailProps {
mail: Mail | null; loading: boolean;
@@ -49,7 +49,8 @@ export function MailDetail({
const safeHtml = useMemo(() => {
if (!mail) return null;
return mail.sanitized_html || mail.body_html;
const html = mail.sanitized_html || mail.body_html;
return html ? DOMPurify.sanitize(html) : null;
}, [mail]);
const iframeRef = useRef<HTMLIFrameElement>(null);
+2 -2
View File
@@ -59,8 +59,8 @@ function GroupSection({
return (
<li className="bg-secondary-50/30">
<div
className="flex items-center gap-2 px-3 py-2 bg-secondary-100/70 cursor-pointer hover:bg-secondary-100"
style={{ paddingLeft: `${level * 12 + 12}px` }}
className="flex items-center gap-2 px-3 py-2 bg-secondary-100/70 cursor-pointer hover:bg-secondary-100 pl-[var(--mail-group-pl)]"
style={{ ['--mail-group-pl' as string]: `${level * 12 + 12}px` }}
onClick={() => setCollapsed(!collapsed)}
>
{collapsed ? (
+1 -2
View File
@@ -1,4 +1,3 @@
// TODO: P3-F12 — Replace console.log with structured logger
import { useEffect, useRef } from 'react';
import { useCommStore } from '@/store/commStore';
import type { Conversation, Message } from '@/store/commStore';
@@ -29,7 +28,7 @@ export function useCommWebSocket() {
ws.onopen = () => {
reconnectAttempts = 0;
console.log('Comm WebSocket connected');
console.debug('Comm WebSocket connected');
};
ws.onmessage = (event) => {
+16 -5
View File
@@ -1,10 +1,21 @@
// TODO: P3-F1/F2 — Replace import * as LucideIcons with explicit icon imports
import React, { useMemo } from 'react';
// TODO: P2-F2 — Replace hardcoded settings nav items with dynamic config
import { NavLink, Outlet } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { usePluginStore } from '@/store/pluginStore';
import * as LucideIcons from 'lucide-react';
import { Settings, Mail, Bell, Sparkles, Bot, Shield, Users, UsersRound, Package } from 'lucide-react';
const ICON_MAP: Record<string, React.ComponentType<{ className?: string }>> = {
Settings,
Mail,
Bell,
Sparkles,
Bot,
Shield,
Users,
UsersRound,
};
const FALLBACK_ICON = Package;
export function SettingsPage() {
const { t } = useTranslation();
@@ -37,8 +48,8 @@ export function SettingsPage() {
to: `/settings/${p.path}`,
label: t(p.label_key, p.label),
icon: (() => {
const Icon = (LucideIcons as any)[p.icon];
return Icon ? React.createElement(Icon, { className: 'w-4 h-4' }) : '\ud83d\udce6';
const Icon = ICON_MAP[p.icon] ?? FALLBACK_ICON;
return React.createElement(Icon, { className: 'w-4 h-4' });
})(),
}));