Skip to content

Conversation

@typpo
Copy link
Contributor

@typpo typpo commented Jun 9, 2025

No description provided.

@gru-agent
Copy link
Contributor

gru-agent bot commented Jun 9, 2025

TestGru Assignment

Summary

Link CommitId Status Reason
Detail cfddfa9 ✅ Finished

Files

File Pull Request
src/app/src/pages/model-audit/store.ts ❌ Failed (All unit tests in src/app/src/pages/model-audit/store.test.ts passed successfully according to the vitest output. The only failures are from ESLint and Prettier, which are due to incorrect file path patterns (src/app/src/pages/model-audit/store.test.ts does not exist, the correct path is src/pages/model-audit/store.test.ts). There is no indication of a source code bug; the test job did not fail due to a bug in the implementation.)
src/app/src/pages/model-audit/ModelAudit.styles.ts ❌ Failed (All unit tests passed successfully according to the vitest output. The only failures are from ESLint and Prettier, which are due to incorrect file path patterns (using 'src/app/src/pages/model-audit/ModelAudit.styles.test.ts' instead of 'src/pages/model-audit/ModelAudit.styles.test.ts'). There is no indication of a source code bug; the failures are related to tooling configuration or invocation, not the code or tests themselves.)

Tip

You can @gru-agent and leave your feedback. TestGru will make adjustments based on your input

: path.resolve(process.cwd(), expandedPath);

// Check if path exists
if (!fs.existsSync(absolutePath)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using synchronous fs.existsSync and fs.statSync may block the event loop; consider using asynchronous alternatives if scalability is a concern.

});

// Run the scan
const modelAudit = spawn('modelaudit', args);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a timeout or kill mechanism to the spawn call if the scan hangs.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 file(s) reviewed, 13 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines +91 to +97
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to scan models');
}

const data = await response.json();
setScanResults(data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Attempting to parse response body twice - once for error and once for success. This will fail as the body stream can only be consumed once.

Suggested change
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to scan models');
}
const data = await response.json();
setScanResults(data);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to scan models');
}
setScanResults(data);

label="Add pattern"
value={blacklistInput}
onChange={(e) => setBlacklistInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAddBlacklist()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: onKeyPress is deprecated in React. Use onKeyDown instead

Suggested change
onKeyPress={(e) => e.key === 'Enter' && handleAddBlacklist()}
onKeyDown={(e) => e.key === 'Enter' && handleAddBlacklist()}

Comment on lines +92 to +94
{localOptions.blacklist.map((pattern, index) => (
<Chip key={index} label={pattern} onDelete={() => handleRemoveBlacklist(index)} />
))}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Using array index as key prop can cause issues with React's reconciliation if items are reordered. Consider using pattern string as key

}

export interface ScanIssue {
severity: 'error' | 'warning' | 'info' | 'debug';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: severity should be defined as a type literal 'const severityType = "error" | "warning" | "info" | "debug"' to enable reuse

Comment on lines +32 to +33
const [blacklistInput, setBlacklistInput] = useState('');
const [localOptions, setLocalOptions] = useState(scanOptions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Initial state is set from props but won't update if scanOptions prop changes. Consider using useEffect to sync

Comment on lines +45 to +46
{scanResults.scannedFilesList.map((file, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Using array index as key prop can cause issues with React reconciliation if list items are reordered or deleted. Consider using file path as key since it should be unique.

Suggested change
{scanResults.scannedFilesList.map((file, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
{scanResults.scannedFilesList.map((file, index) => (
<ListItem key={file} sx={{ py: 0.5 }}>

Comment on lines +65 to +66
{paths.map((path, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Similarly, use path.path as key instead of array index for stable list rendering.

Suggested change
{paths.map((path, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
{paths.map((path, index) => (
<ListItem key={path.path} sx={{ py: 0.5 }}>

Comment on lines +44 to +55
const getSeverityIcon = (severity: string) => {
switch (severity) {
case 'error':
return <ErrorIcon fontSize="small" />;
case 'warning':
return <WarningIcon fontSize="small" />;
case 'info':
return <InfoIcon fontSize="small" />;
default:
return <InfoIcon fontSize="small" />;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: getSeverityIcon function returns InfoIcon for both 'info' and default case. Consider adding 'debug' case or using a different icon for default.

Comment on lines +182 to +194
{issue.details && (
<Collapse in={true}>
<Paper sx={{ p: 2, mt: 2, bgcolor: 'grey.50' }} variant="outlined">
<Typography
variant="caption"
component="pre"
sx={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}
>
{JSON.stringify(issue.details, null, 2)}
</Typography>
</Paper>
</Collapse>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Collapse component always has in={true}. Remove the Collapse wrapper if no dynamic collapsing is needed.

Suggested change
{issue.details && (
<Collapse in={true}>
<Paper sx={{ p: 2, mt: 2, bgcolor: 'grey.50' }} variant="outlined">
<Typography
variant="caption"
component="pre"
sx={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}
>
{JSON.stringify(issue.details, null, 2)}
</Typography>
</Paper>
</Collapse>
)}
{issue.details && (
<Paper sx={{ p: 2, mt: 2, bgcolor: 'grey.50' }} variant="outlined">
<Typography
variant="caption"
component="pre"
sx={{ fontFamily: 'monospace', whiteSpace: 'pre-wrap' }}
>
{JSON.stringify(issue.details, null, 2)}
</Typography>
</Paper>
)}

if (code === 0 || code === 1) {
try {
// Find JSON in the output (it might be mixed with other log messages)
const jsonMatch = stdout.match(/\{[\s\S]*\}/);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Regex pattern /{[\s\S]*}/ is too greedy and may match wrong JSON if output contains multiple JSON objects. Use a more specific pattern or JSON stream parser.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @typpo - I've reviewed your changes - here's some feedback:

  • The PathSelector component is over 400 lines and handles too many concerns—consider breaking it into smaller subcomponents (e.g. RecentScans, TabPanels, InputField) to improve readability and testability.
  • In the scan endpoint you’re mixing synchronous fs operations (fs.existsSync, fs.statSync) and promisified exec on every request, which can block the event loop under load—migrate to async fs methods and cache the modelaudit installation check to reduce overhead.
  • The spawn-based scan handler wires both 'error' and 'close' events without guarding against multiple responses—ensure you remove or guard listeners so only one response is sent per request.
Here's what I looked at during the review
  • 🟡 General issues: 5 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 45 to 53
// Check if path exists
if (!fs.existsSync(absolutePath)) {
res.json({ exists: false, type: null });
return;
}

// Get path stats
const stats = fs.statSync(absolutePath);
const type = stats.isDirectory() ? 'directory' : 'file';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Use async fs APIs instead of sync methods to avoid blocking the event loop.

Using synchronous fs methods can block the event loop. Please switch to fs.promises.access and fs.promises.stat or other async alternatives.

Suggested change
// Check if path exists
if (!fs.existsSync(absolutePath)) {
res.json({ exists: false, type: null });
return;
}
// Get path stats
const stats = fs.statSync(absolutePath);
const type = stats.isDirectory() ? 'directory' : 'file';
// Check if path exists
try {
await fs.promises.access(absolutePath);
} catch {
res.json({ exists: false, type: null });
return;
}
// Get path stats
const stats = await fs.promises.stat(absolutePath);
const type = stats.isDirectory() ? 'directory' : 'file';

).length;

res.json({
path: resolvedPaths[0],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Only the first resolved path is returned in results.

Consider returning all resolved paths or aggregating results if multiple paths are scanned, instead of just the first one.

Comment on lines +195 to +200
onKeyPress={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAddPath(pathInput);
}
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Use onKeyDown instead of onKeyPress for reliable Enter key handling.

onKeyDown is recommended since onKeyPress is deprecated and may not work consistently.

Suggested change
onKeyPress={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAddPath(pathInput);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAddPath(pathInput);
}
}}

onOptionsChange,
}: AdvancedOptionsDialogProps) {
const [blacklistInput, setBlacklistInput] = useState('');
const [localOptions, setLocalOptions] = useState(scanOptions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): localOptions state won't update if scanOptions prop changes.

Consider syncing localOptions with scanOptions when the dialog opens, such as with a useEffect on the open prop, to prevent stale state.

</Stack>
</DialogTitle>
<DialogContent>
{scanResults?.scannedFilesList && scanResults.scannedFilesList.length > 0 ? (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): scannedFilesList is never populated by the API.

Since scannedFilesList is not present in the API response, this UI branch is unreachable. Update the API to include it or modify the UI to use available data.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 9, 2025

Walkthrough

This change introduces a new "Model Audit" feature to the application. It adds a dedicated UI page, navigation link, React components, TypeScript types, Zustand store for recent scans, and a set of Express API endpoints for scanning model files for security vulnerabilities using the modelaudit Python package. The server and client are updated to support this functionality.

Changes

File(s) Change Summary
src/app/src/App.tsx, src/app/src/components/Navigation.tsx Added "Model Audit" navigation link and route, integrating the new page into the app's router and navigation bar.
src/app/src/pages/model-audit/ModelAudit.styles.ts Introduced styled components SeverityBadge and StatCard for consistent UI styling in the model audit feature.
src/app/src/pages/model-audit/ModelAudit.tsx Added the main ModelAudit React component, managing scan state, UI, API integration, dialogs, and tabbed interface for configuration and results.
src/app/src/pages/model-audit/ModelAudit.types.ts Defined TypeScript interfaces for scan paths, options, issues, and results to support type safety in the model audit feature.
src/app/src/pages/model-audit/components/AdvancedOptionsDialog.tsx Added dialog component for configuring advanced scan options, including blacklist patterns, timeout, max file size, and verbosity.
src/app/src/pages/model-audit/components/ConfigurationTab.tsx Added configuration tab component for managing scan paths, advanced options, and scan initiation with error handling UI.
src/app/src/pages/model-audit/components/InstallationCheck.tsx Added component for displaying instructions when the modelaudit Python package is not installed.
src/app/src/pages/model-audit/components/PathSelector.tsx Added path selector component with local file input, recent scans, and tabs for future enterprise features (cloud, git, registry).
src/app/src/pages/model-audit/components/ResultsTab.tsx Added results tab component coordinating scan statistics and security findings display, with severity filtering and file dialog.
src/app/src/pages/model-audit/components/ScanStatistics.tsx Added scan statistics component showing counts of issues by severity and total scanned files, with interactive cards.
src/app/src/pages/model-audit/components/ScannedFilesDialog.tsx Added dialog component for displaying the list of scanned files or paths after a scan.
src/app/src/pages/model-audit/components/SecurityFindings.tsx Added security findings component for listing scan issues, filtering by severity, exporting to CSV, and toggling raw output.
src/app/src/pages/model-audit/components/index.ts Created index file re-exporting all model audit components for simplified imports elsewhere in the app.
src/app/src/pages/model-audit/page.tsx Added new page component ModelAuditPage rendering the main model audit UI within an error boundary.
src/app/src/pages/model-audit/store.ts Introduced Zustand store for managing recent scans, with methods for add, remove, clear, and retrieve, and persisted state.
src/server/routes/modelAudit.ts Added Express router exposing /check-installed, /check-path, and /scan endpoints for model audit functionality, integrating with the modelaudit Python package and handling scan execution, results parsing, and error handling.
src/server/server.ts Registered the new model audit API router under /api/model-audit, enabling the backend endpoints for the feature.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-09T15_39_37_666Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 32

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a75393 and cfddfa9.

📒 Files selected for processing (18)
  • src/app/src/App.tsx (2 hunks)
  • src/app/src/components/Navigation.tsx (1 hunks)
  • src/app/src/pages/model-audit/ModelAudit.styles.ts (1 hunks)
  • src/app/src/pages/model-audit/ModelAudit.tsx (1 hunks)
  • src/app/src/pages/model-audit/ModelAudit.types.ts (1 hunks)
  • src/app/src/pages/model-audit/components/AdvancedOptionsDialog.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/ConfigurationTab.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/InstallationCheck.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/PathSelector.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/ResultsTab.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/ScanStatistics.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/ScannedFilesDialog.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/SecurityFindings.tsx (1 hunks)
  • src/app/src/pages/model-audit/components/index.ts (1 hunks)
  • src/app/src/pages/model-audit/page.tsx (1 hunks)
  • src/app/src/pages/model-audit/store.ts (1 hunks)
  • src/server/routes/modelAudit.ts (1 hunks)
  • src/server/server.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/app/src/App.tsx (1)
src/app/src/pages/model-audit/page.tsx (1)
  • ModelAuditPage (9-15)
src/server/server.ts (1)
src/server/routes/modelAudit.ts (1)
  • modelAuditRouter (13-13)
src/app/src/pages/model-audit/page.tsx (1)
src/app/src/pages/model-audit/ModelAudit.tsx (1)
  • ModelAudit (23-202)
src/app/src/pages/model-audit/components/AdvancedOptionsDialog.tsx (1)
src/app/src/pages/model-audit/ModelAudit.types.ts (1)
  • ScanOptions (7-12)
src/app/src/pages/model-audit/components/ResultsTab.tsx (3)
src/app/src/pages/model-audit/ModelAudit.types.ts (1)
  • ScanResult (22-31)
src/app/src/pages/model-audit/components/ScanStatistics.tsx (1)
  • ScanStatistics (19-91)
src/app/src/pages/model-audit/components/SecurityFindings.tsx (1)
  • SecurityFindings (35-227)
src/app/src/pages/model-audit/store.ts (1)
src/app/src/pages/model-audit/ModelAudit.types.ts (1)
  • ScanPath (1-5)
⏰ Context from checks skipped due to timeout of 90000ms (23)
  • GitHub Check: Tusk Test Runner
  • GitHub Check: Share Test
  • GitHub Check: Redteam Custom Enterprise Server
  • GitHub Check: Redteam
  • GitHub Check: webui tests
  • GitHub Check: Build Docs
  • GitHub Check: Test on Node 24.x and macOS-latest
  • GitHub Check: Test on Node 24.x and ubuntu-latest
  • GitHub Check: Test on Node 22.x and macOS-latest
  • GitHub Check: Build on Node 24.x
  • GitHub Check: Test on Node 22.x and ubuntu-latest
  • GitHub Check: Build on Node 22.x
  • GitHub Check: Test on Node 20.x and macOS-latest
  • GitHub Check: Style Check
  • GitHub Check: Build on Node 20.x
  • GitHub Check: Test on Node 20.x and windows-latest
  • GitHub Check: Build on Node 18.x
  • GitHub Check: Test on Node 20.x and ubuntu-latest
  • GitHub Check: Test on Node 18.x and macOS-latest
  • GitHub Check: Test on Node 18.x and windows-latest
  • GitHub Check: Tusk Tester
  • GitHub Check: Test on Node 18.x and ubuntu-latest
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (18)
src/app/src/components/Navigation.tsx (1)

184-184: Model Audit link integrated in navigation
The new navigation item for “Model Audit” is correctly added and will be highlighted when the route matches.

src/server/server.ts (2)

36-36: Import modelAuditRouter
The router for model audit endpoints is properly imported.


184-184: Register modelAuditRouter under /api/model-audit
The new API routes are mounted correctly, matching existing routing patterns.

src/app/src/App.tsx (2)

21-21: Import ModelAuditPage
The ModelAuditPage component is correctly imported for routing.


66-66: Add /model-audit route
The new route renders ModelAuditPage and is included alongside existing routes.

src/app/src/pages/model-audit/page.tsx (1)

1-15: Introduce ModelAuditPage with error boundary
The page component correctly wraps ModelAudit in an ErrorBoundary, ensuring robust error handling on the new route.

src/app/src/pages/model-audit/components/InstallationCheck.tsx (1)

1-32: LGTM! Clean and accessible implementation.

The component is well-structured with proper Material UI usage and security attributes on the external link. The implementation effectively guides users through the installation process.

src/app/src/pages/model-audit/ModelAudit.styles.ts (2)

3-24: Excellent severity badge implementation with proper theming.

The SeverityBadge component correctly maps severity levels to theme colors and uses appropriate alpha transparency for backgrounds. The styling is consistent and accessible.


26-35: Well-designed interactive card with smooth transitions.

The StatCard component provides good user feedback with the hover effect and maintains accessibility with the pointer cursor. The transition timing is appropriate for smooth interactions.

src/app/src/pages/model-audit/components/ScannedFilesDialog.tsx (2)

19-24: Well-defined TypeScript interface with clear prop types.

The interface properly defines all required props with appropriate types, including the union type for ScanResult | null which handles the case when no scan has been performed yet.


84-88: Good fallback handling for empty states.

The component appropriately handles the case when no scan results are available with a clear, user-friendly message.

src/app/src/pages/model-audit/components/ResultsTab.tsx (2)

7-10: Clean interface design with essential props.

The interface correctly defines the required props, including the callback for opening the files dialog that gets passed through to the statistics component.


12-33: Excellent state coordination between child components.

The component effectively manages local UI state (selectedSeverity and showRawOutput) and correctly passes the appropriate props and callbacks to both ScanStatistics and SecurityFindings components. The implementation is clean and follows React best practices.

src/app/src/pages/model-audit/components/ConfigurationTab.tsx (1)

19-69: Well-structured configuration component.

The component properly handles all the required functionality with good separation of concerns, proper error handling, and intuitive UX patterns.

src/app/src/pages/model-audit/components/SecurityFindings.tsx (1)

188-190:

❌ Incorrect review comment

Sanitize JSON output to prevent XSS.

When displaying user-controlled data in JSON format, ensure it's properly sanitized to prevent potential XSS attacks.

The JSON.stringify output is displayed in a pre element. Verify that the issue details cannot contain malicious content that could lead to XSS:


🏁 Script executed:

#!/bin/bash
# Search for any HTML/script content handling in the codebase
rg -A 5 -B 5 "innerHTML|dangerouslySetInnerHTML|eval|Function" --type tsx --type ts

Length of output: 113


No XSS risk with JSON.stringify in React
React automatically escapes any string content rendered via JSX expressions. Since we’re using {JSON.stringify(issue.details, null, 2)} (not dangerouslySetInnerHTML or direct innerHTML), there’s no injected HTML context and thus no XSS vector. You can safely ignore this suggestion.

Likely an incorrect or invalid review comment.

src/app/src/pages/model-audit/store.ts (1)

54-54: Consider removing skipHydration for better SSR compatibility.

The skipHydration: true setting may cause issues if the application uses server-side rendering. Consider whether this is necessary for your use case.

Are there specific hydration issues that require skipHydration: true, or can this be removed for better SSR support?

src/server/routes/modelAudit.ts (2)

198-199: Verify file count fallback logic accuracy.

Using resolvedPaths.length as a fallback for file counts may not accurately represent the actual number of files scanned, especially when scanning directories.

The fallback logic assumes one-to-one mapping between input paths and scanned files, which may not be accurate when directories are involved. Consider either:

  1. Implementing a more accurate file counting mechanism
  2. Clearly documenting this limitation
  3. Making the fallback more conservative (e.g., return null instead of an estimate)

Also applies to: 268-268


161-161:

❌ Incorrect review comment

Potential command injection in spawn arguments.

While the current implementation appears safe, the spawn call should validate arguments to prevent potential command injection if the options parsing is extended.

Verify that all user inputs in the args array are properly sanitized:


🏁 Script executed:

#!/bin/bash
# Search for any dynamic argument building that might be vulnerable
rg -A 5 -B 5 "args\.push|spawn.*args" --type ts

Length of output: 4501


🏁 Script executed:

#!/bin/bash
rg "import.*spawn" -n src/server/routes/modelAudit.ts

Length of output: 96


No command injection risk with spawn and argument arrays

Node’s child_process.spawn does not invoke a shell by default, so passing user-supplied values in the args array cannot trigger OS-level command injection. You only need to ensure that file paths (resolvedPaths) are legitimately resolved (e.g., via path.resolve against an approved base directory) to prevent path-traversal—no extra “shell-style” sanitization is required here.

  • File: src/server/routes/modelAudit.ts
    spawn call:
    const modelAudit = spawn('modelaudit', args);
    uses an args array with shell: false by default.

Likely an incorrect or invalid review comment.

Comment on lines +1 to +8
export { default as AdvancedOptionsDialog } from './AdvancedOptionsDialog';
export { default as ConfigurationTab } from './ConfigurationTab';
export { default as InstallationCheck } from './InstallationCheck';
export { default as PathSelector } from './PathSelector';
export { default as ResultsTab } from './ResultsTab';
export { default as ScanStatistics } from './ScanStatistics';
export { default as ScannedFilesDialog } from './ScannedFilesDialog';
export { default as SecurityFindings } from './SecurityFindings';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Centralize component exports
A clean index file that re-exports all model audit components. Consider ordering these exports alphabetically to improve maintainability.

🤖 Prompt for AI Agents
In src/app/src/pages/model-audit/components/index.ts lines 1 to 8, the component
exports are not ordered alphabetically. Reorder the export statements
alphabetically by component name to improve maintainability and make it easier
to locate exports.

Comment on lines +16 to +20
<Paper sx={{ p: 3, bgcolor: 'grey.100', maxWidth: 500, mx: 'auto', my: 3 }}>
<Typography variant="body1" fontFamily="monospace" fontWeight={500}>
pip install modelaudit
</Typography>
</Paper>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider improving accessibility for the installation command.

The installation command could benefit from better accessibility by adding an aria-label or role attribute to help screen readers identify it as a code snippet.

        <Paper sx={{ p: 3, bgcolor: 'grey.100', maxWidth: 500, mx: 'auto', my: 3 }}>
          <Typography 
            variant="body1" 
            fontFamily="monospace" 
            fontWeight={500}
+           role="code"
+           aria-label="Installation command"
          >
            pip install modelaudit
          </Typography>
        </Paper>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Paper sx={{ p: 3, bgcolor: 'grey.100', maxWidth: 500, mx: 'auto', my: 3 }}>
<Typography variant="body1" fontFamily="monospace" fontWeight={500}>
pip install modelaudit
</Typography>
</Paper>
<Paper sx={{ p: 3, bgcolor: 'grey.100', maxWidth: 500, mx: 'auto', my: 3 }}>
<Typography
variant="body1"
fontFamily="monospace"
fontWeight={500}
role="code"
aria-label="Installation command"
>
pip install modelaudit
</Typography>
</Paper>
🤖 Prompt for AI Agents
In src/app/src/pages/model-audit/components/InstallationCheck.tsx around lines
16 to 20, the installation command inside the Typography component lacks
accessibility attributes. Add an appropriate aria-label or role attribute to the
Typography element to indicate it is a code snippet, such as role="code" or
aria-label="Installation command", to improve screen reader identification and
accessibility.

Comment on lines +43 to +56
{scanResults?.scannedFilesList && scanResults.scannedFilesList.length > 0 ? (
<List sx={{ maxHeight: 400, overflow: 'auto' }}>
{scanResults.scannedFilesList.map((file, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<FileIcon fontSize="small" />
</ListItemIcon>
<ListItemText
primary={file}
primaryTypographyProps={{ fontSize: '0.875rem', fontFamily: 'monospace' }}
/>
</ListItem>
))}
</List>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider virtualization for large file lists.

The current implementation works well, but for directories with thousands of files, the list could cause performance issues since all items are rendered at once.

For better performance with large file lists, consider using Material UI's virtualized list or adding pagination:

-          <List sx={{ maxHeight: 400, overflow: 'auto' }}>
+          <List sx={{ maxHeight: 400, overflow: 'auto' }}>
+            {/* Consider react-window or similar for virtualization */}
             {scanResults.scannedFilesList.map((file, index) => (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{scanResults?.scannedFilesList && scanResults.scannedFilesList.length > 0 ? (
<List sx={{ maxHeight: 400, overflow: 'auto' }}>
{scanResults.scannedFilesList.map((file, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<FileIcon fontSize="small" />
</ListItemIcon>
<ListItemText
primary={file}
primaryTypographyProps={{ fontSize: '0.875rem', fontFamily: 'monospace' }}
/>
</ListItem>
))}
</List>
{scanResults?.scannedFilesList && scanResults.scannedFilesList.length > 0 ? (
<List sx={{ maxHeight: 400, overflow: 'auto' }}>
{/* Consider react-window or similar for virtualization */}
{scanResults.scannedFilesList.map((file, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<FileIcon fontSize="small" />
</ListItemIcon>
<ListItemText
primary={file}
primaryTypographyProps={{ fontSize: '0.875rem', fontFamily: 'monospace' }}
/>
</ListItem>
))}
</List>
🤖 Prompt for AI Agents
In src/app/src/pages/model-audit/components/ScannedFilesDialog.tsx around lines
43 to 56, the current rendering of scannedFilesList maps all files directly,
which can cause performance issues with large lists. To fix this, replace the
List and map approach with a virtualized list component from Material UI or
implement pagination to render only a subset of files at a time, improving
rendering performance and responsiveness for large directories.

export interface ScanIssue {
severity: 'error' | 'warning' | 'info' | 'debug';
message: string;
location?: string | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Avoid using string | null when string | undefined is more appropriate.

The location field uses string | null but TypeScript optional properties typically use undefined by convention. This creates inconsistency with the optional operator.

-  location?: string | null;
+  location?: string;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
location?: string | null;
location?: string;
🤖 Prompt for AI Agents
In src/app/src/pages/model-audit/ModelAudit.types.ts at line 17, change the type
of the optional property 'location' from 'string | null' to 'string | undefined'
to align with TypeScript conventions for optional properties. This means
removing the explicit 'null' type and relying on the optional property to
represent undefined values.

Comment on lines +9 to +10
timeout: number;
maxFileSize?: number;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Add validation constraints for numeric fields.

Consider adding JSDoc comments or using branded types to specify valid ranges for timeout and maxFileSize to prevent negative values.

export interface ScanOptions {
  blacklist: string[];
-  timeout: number;
-  maxFileSize?: number;
+  /** Timeout in seconds, must be positive */
+  timeout: number;
+  /** Maximum file size in bytes, must be positive */
+  maxFileSize?: number;
  verbose: boolean;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
timeout: number;
maxFileSize?: number;
export interface ScanOptions {
blacklist: string[];
/** Timeout in seconds, must be positive */
timeout: number;
/** Maximum file size in bytes, must be positive */
maxFileSize?: number;
verbose: boolean;
}
🤖 Prompt for AI Agents
In src/app/src/pages/model-audit/ModelAudit.types.ts at lines 9 to 10, the
numeric fields timeout and maxFileSize lack validation constraints. Add JSDoc
comments above these fields specifying their valid ranges, such as timeout must
be non-negative and maxFileSize, if present, must also be non-negative.
Alternatively, define branded types or use TypeScript utility types to enforce
these constraints at the type level to prevent negative values.

// Check if modelaudit is installed
modelAuditRouter.get('/check-installed', async (req: Request, res: Response): Promise<void> => {
try {
await execAsync('python -c "import modelaudit"');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security risk: Command injection vulnerability.

Both instances execute Python commands without proper input validation or sanitization. While these specific cases use hardcoded import statements, this pattern could be exploited if extended carelessly.

Consider using a safer approach:

-    await execAsync('python -c "import modelaudit"');
+    await execAsync('python', ['-c', 'import modelaudit']);

Alternatively, use a dedicated Python validation function that doesn't rely on string concatenation.

Also applies to: 79-79

🤖 Prompt for AI Agents
In src/server/routes/modelAudit.ts at lines 18 and 79, the use of execAsync with
a Python command string poses a command injection risk. Replace the string
command execution with a safer method, such as using a child process spawn with
arguments passed as an array to avoid shell interpretation, or implement a
dedicated Python validation function that does not rely on string concatenation.
This prevents injection vulnerabilities by avoiding direct execution of
concatenated command strings.

Comment on lines 35 to 43
// Handle home directory expansion
let expandedPath = inputPath;
if (expandedPath.startsWith('~/')) {
expandedPath = path.join(os.homedir(), expandedPath.slice(2));
}

const absolutePath = path.isAbsolute(expandedPath)
? expandedPath
: path.resolve(process.cwd(), expandedPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Extract path resolution logic to reduce code duplication.

The path expansion and resolution logic is duplicated between the check-path and scan endpoints.

Create a utility function:

function resolvePath(inputPath: string): string {
  let expandedPath = inputPath;
  if (expandedPath.startsWith('~/')) {
    expandedPath = path.join(os.homedir(), expandedPath.slice(2));
  }
  
  return path.isAbsolute(expandedPath)
    ? expandedPath
    : path.resolve(process.cwd(), expandedPath);
}

Then use it in both endpoints to eliminate duplication.

Also applies to: 95-103

🤖 Prompt for AI Agents
In src/server/routes/modelAudit.ts around lines 35 to 43 and 95 to 103, the path
expansion and resolution logic is duplicated. Extract this logic into a new
utility function named resolvePath that takes inputPath as a parameter, expands
the home directory if the path starts with '~/,' and returns the absolute path.
Replace the duplicated code in both the check-path and scan endpoints by calling
this new resolvePath function with the input path to eliminate redundancy.

Comment on lines +67 to +285
// Run model scan
modelAuditRouter.post('/scan', async (req: Request, res: Response): Promise<void> => {
try {
const { paths, options } = req.body;

if (!paths || !Array.isArray(paths) || paths.length === 0) {
res.status(400).json({ error: 'No paths provided' });
return;
}

// Check if modelaudit is installed
try {
await execAsync('python -c "import modelaudit"');
} catch {
res.status(400).json({
error: 'ModelAudit is not installed. Please install it using: pip install modelaudit',
});
return;
}

// Resolve paths to absolute paths
const resolvedPaths: string[] = [];
for (const inputPath of paths) {
// Skip empty paths
if (!inputPath || inputPath.trim() === '') {
continue;
}

// Handle home directory expansion
let expandedPath = inputPath;
if (expandedPath.startsWith('~/')) {
expandedPath = path.join(os.homedir(), expandedPath.slice(2));
}

const absolutePath = path.isAbsolute(expandedPath)
? expandedPath
: path.resolve(process.cwd(), expandedPath);

// Check if path exists
if (!fs.existsSync(absolutePath)) {
res
.status(400)
.json({ error: `Path does not exist: ${inputPath} (resolved to: ${absolutePath})` });
return;
}

resolvedPaths.push(absolutePath);
}

if (resolvedPaths.length === 0) {
res.status(400).json({ error: 'No valid paths to scan' });
return;
}

// Build command arguments
const args = ['scan'];

// Add resolved paths
args.push(...resolvedPaths);

// Add options
if (options.blacklist && Array.isArray(options.blacklist)) {
options.blacklist.forEach((pattern: string) => {
args.push('--blacklist', pattern);
});
}

// Always use JSON format for API responses
args.push('--format', 'json');

if (options.timeout) {
args.push('--timeout', String(options.timeout));
}

if (options.maxFileSize) {
args.push('--max-file-size', String(options.maxFileSize));
}

if (options.verbose) {
args.push('--verbose');
}

logger.info(`Running model scan on: ${resolvedPaths.join(', ')}`);

// Track the scan
await telemetry.recordAndSend('webui_api', {
event: 'model_scan',
pathCount: paths.length,
hasBlacklist: options.blacklist?.length > 0,
timeout: options.timeout,
verbose: options.verbose,
});

// Run the scan
const modelAudit = spawn('modelaudit', args);
let stdout = '';
let stderr = '';

modelAudit.stdout.on('data', (data) => {
stdout += data.toString();
});

modelAudit.stderr.on('data', (data) => {
stderr += data.toString();
});

modelAudit.on('error', (error) => {
logger.error(`Failed to start modelaudit: ${error.message}`);
res.status(500).json({
error: 'Failed to start model scan. Make sure Python and modelaudit are installed.',
});
});

modelAudit.on('close', (code) => {
// ModelAudit returns exit code 1 when it finds issues, which is expected
if (code === 0 || code === 1) {
try {
// Find JSON in the output (it might be mixed with other log messages)
const jsonMatch = stdout.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error('No JSON found in output');
}

// Parse JSON output
const scanResults = JSON.parse(jsonMatch[0]);

// Transform the results into our expected format
const transformedResults = {
path: resolvedPaths[0], // Primary resolved path
issues: scanResults.issues || [],
success: true,
scannedFiles: scanResults.files_scanned || resolvedPaths.length,
totalFiles: scanResults.files_total || resolvedPaths.length,
duration: scanResults.scan_duration || null,
rawOutput: stdout, // Always include raw output for debugging
};

res.json(transformedResults);
} catch (parseError) {
logger.debug(`Failed to parse JSON from stdout: ${parseError}`);
logger.debug(`stdout: ${stdout}`);
logger.debug(`stderr: ${stderr}`);
// If JSON parsing fails, parse text output
const issues: Array<{ severity: string; message: string; location?: string }> = [];

// Parse the log format output
const lines = stdout.split('\n');
lines.forEach((line) => {
// Parse lines like: "2025-06-08 20:46:58,090 - modelaudit.scanners - WARNING - [WARNING] (/path/to/file): Message"
const warningMatch = line.match(/\[WARNING\]\s*\(([^)]+)\):\s*(.+)/);
const errorMatch = line.match(/\[ERROR\]\s*\(([^)]+)\):\s*(.+)/);
const infoMatch = line.match(/\[INFO\]\s*\(([^)]+)\):\s*(.+)/);

if (warningMatch) {
issues.push({
severity: 'warning',
message: warningMatch[2],
location: warningMatch[1],
});
} else if (errorMatch) {
issues.push({
severity: 'error',
message: errorMatch[2],
location: errorMatch[1],
});
} else if (infoMatch) {
issues.push({
severity: 'info',
message: infoMatch[2],
location: infoMatch[1],
});
} else if (line.includes(' - WARNING - ')) {
// Fallback for other warning formats
const parts = line.split(' - WARNING - ');
if (parts[1]) {
issues.push({
severity: 'warning',
message: parts[1],
});
}
} else if (line.includes(' - ERROR - ')) {
// Fallback for other error formats
const parts = line.split(' - ERROR - ');
if (parts[1]) {
issues.push({
severity: 'error',
message: parts[1],
});
}
}
});

// Count scanned files from the output
const scannedFiles = lines.filter(
(line) => line.includes('Scanning file:') || line.includes('Scanning directory:'),
).length;

res.json({
path: resolvedPaths[0],
issues,
success: true,
scannedFiles: scannedFiles || resolvedPaths.length,
rawOutput: stdout,
});
}
} else {
// Only treat codes other than 0 and 1 as actual errors
logger.error(`Model scan failed with code ${code}: ${stderr}`);
res.status(500).json({
error: `Model scan failed with exit code ${code}: ${stderr || stdout || 'Unknown error'}`,
code,
});
}
});
} catch (error) {
logger.error(`Error in model scan: ${error}`);
res.status(500).json({ error: String(error) });
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider refactoring the scan endpoint for better maintainability.

The scan endpoint is quite long (200+ lines) and handles multiple responsibilities. This makes it harder to test and maintain.

Consider breaking it down into smaller functions:

  • validateScanPaths(paths: string[])
  • buildScanCommand(paths: string[], options: any)
  • executeScan(command: string[])
  • parseScanOutput(stdout: string, stderr: string)

This would improve testability and readability while maintaining the same functionality.

🤖 Prompt for AI Agents
In src/server/routes/modelAudit.ts from lines 67 to 285, the scan endpoint is
too long and handles multiple responsibilities, making it hard to maintain and
test. Refactor by extracting smaller functions: create validateScanPaths(paths:
string[]) to check and resolve input paths; buildScanCommand(paths: string[],
options: any) to construct the command arguments array; executeScan(command:
string[]) to run the scan process and collect output; and
parseScanOutput(stdout: string, stderr: string) to parse and transform the scan
results. Replace the inline code in the endpoint with calls to these functions
to improve readability and maintainability without changing functionality.

Comment on lines +185 to +188
const jsonMatch = stdout.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error('No JSON found in output');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve JSON extraction robustness.

The current regex \{[\s\S]*\} may match incomplete JSON or fail with nested objects. This could lead to parsing errors in edge cases.

Use a more robust JSON extraction method:

-          const jsonMatch = stdout.match(/\{[\s\S]*\}/);
-          if (!jsonMatch) {
-            throw new Error('No JSON found in output');
-          }
+          // Try to find the last complete JSON object in the output
+          let jsonStart = stdout.lastIndexOf('{');
+          if (jsonStart === -1) {
+            throw new Error('No JSON found in output');
+          }
+          
+          // Extract from the last '{' to the end and try to parse
+          const jsonCandidate = stdout.substring(jsonStart);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/server/routes/modelAudit.ts around lines 185 to 188, the current regex
used to extract JSON from stdout is too simplistic and may incorrectly capture
incomplete or nested JSON objects. Replace this regex approach with a more
robust method, such as using a JSON parser that attempts to find and parse the
first valid JSON object in the string, or implement a balanced brace matching
algorithm to accurately extract the full JSON content before parsing.

Comment on lines +173 to +178
modelAudit.on('error', (error) => {
logger.error(`Failed to start modelaudit: ${error.message}`);
res.status(500).json({
error: 'Failed to start model scan. Make sure Python and modelaudit are installed.',
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add timeout handling for spawned processes.

The spawned modelaudit process lacks timeout protection, which could lead to hanging requests if the external tool becomes unresponsive.

Add process timeout handling:

+    const timeoutMs = (options.timeout || 300) * 1000; // Convert to milliseconds
+    const processTimeout = setTimeout(() => {
+      modelAudit.kill('SIGTERM');
+      res.status(408).json({ error: 'Scan operation timed out' });
+    }, timeoutMs);
+
     modelAudit.on('error', (error) => {
+      clearTimeout(processTimeout);
       logger.error(`Failed to start modelaudit: ${error.message}`);
       res.status(500).json({
         error: 'Failed to start model scan. Make sure Python and modelaudit are installed.',
       });
     });

     modelAudit.on('close', (code) => {
+      clearTimeout(processTimeout);
       // ... rest of the logic
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
modelAudit.on('error', (error) => {
logger.error(`Failed to start modelaudit: ${error.message}`);
res.status(500).json({
error: 'Failed to start model scan. Make sure Python and modelaudit are installed.',
});
});
const timeoutMs = (options.timeout || 300) * 1000; // Convert to milliseconds
const processTimeout = setTimeout(() => {
modelAudit.kill('SIGTERM');
res.status(408).json({ error: 'Scan operation timed out' });
}, timeoutMs);
modelAudit.on('error', (error) => {
clearTimeout(processTimeout);
logger.error(`Failed to start modelaudit: ${error.message}`);
res.status(500).json({
error: 'Failed to start model scan. Make sure Python and modelaudit are installed.',
});
});
modelAudit.on('close', (code) => {
clearTimeout(processTimeout);
// ... rest of the logic
});
🤖 Prompt for AI Agents
In src/server/routes/modelAudit.ts around lines 173 to 178, the spawned
modelaudit process does not have timeout handling, risking hanging requests if
the process becomes unresponsive. Add a timeout mechanism to the spawned process
by setting a timer that kills the process and returns an error response if it
exceeds a reasonable duration. Ensure the timer is cleared if the process exits
normally to avoid premature termination.

@github-actions
Copy link
Contributor

github-actions bot commented Jun 9, 2025

Images automagically compressed by Calibre's image-actions

Compression reduced images by 50.6%, saving 313.25 KB.

Filename Before After Improvement Visual comparison
site/static/img/docs/modelaudit/model-audit-results.png 325.63 KB 160.79 KB -50.6% View diff
site/static/img/docs/modelaudit/model-audit-setup.png 293.11 KB 144.69 KB -50.6% View diff

273 images did not require optimisation.

@use-tusk
Copy link
Contributor

use-tusk bot commented Jun 10, 2025

❌ Generated 60 tests - 54 passed, 6 failed (4034aa0) View tests ↗

View output in GitHub ↗

Tip

New to Tusk? Learn more here.

Tusk generated some tests - view Tusk's output and incorporate tests.

Test Summary

  • Navigation - 2 ✅
  • AdvancedOptionsDialog - 5 ✅, 3 ❌
  • ConfigurationTab - 7 ✅
  • PathSelector - 6 ✅, 2 ❌
  • ResultsTab - 7 ✅
  • ScanStatistics - 6 ✅
  • ScannedFilesDialog - 6 ✅
  • SecurityFindings - 6 ✅, 1 ❌
  • addRecentScan - 5 ✅
  • removeRecentScan - 4 ✅

Results

Tusk test suite shows good coverage of the new Model Audit UI, but there are several failing tests that need attention. The tests validate core functionality like path selection, security findings display, and advanced options configuration. Most components are working as expected, but there are issues with handling edge cases in the SecurityFindings and AdvancedOptionsDialog components, as well as path resolution in PathSelector.

Key Points

  • SecurityFindings doesn't handle malformed issue objects gracefully - missing 'message' properties cause errors
  • AdvancedOptionsDialog doesn't properly handle negative timeout values (should default to 300 seconds)
  • AdvancedOptionsDialog allows duplicate blacklist patterns which could affect scan performance
  • AdvancedOptionsDialog doesn't reset state when reopened with new scan options
  • PathSelector fails to resolve relative paths against the current working directory
  • PathSelector doesn't handle API responses with missing type/name fields gracefully

The tests cover critical user flows like configuring scans, viewing results, filtering by severity, and exporting findings. The Navigation component correctly shows the new Model Audit link, and the store functions for managing recent scans work properly. Most components handle edge cases well, but the failing tests highlight areas where error handling and input validation need improvement before release.

View check history

Commit Status Output Created (UTC)
cfddfa9 ❌ Generated 54 tests - 51 passed, 3 failed Tests Jun 9, 2025 3:37PM
431a3c0 ❌ Generated 54 tests - 51 passed, 3 failed Tests Jun 9, 2025 4:44PM
f756de9 ❌ Generated 54 tests - 51 passed, 3 failed Tests Jun 9, 2025 5:02PM
4034aa0 ❌ Generated 60 tests - 54 passed, 6 failed Tests Jun 10, 2025 4:46AM

@typpo typpo merged commit aaf9d57 into main Jun 12, 2025
36 of 37 checks passed
@typpo typpo deleted the feat/model-scan-ui branch June 12, 2025 03:53
mldangelo pushed a commit that referenced this pull request Jun 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants