Skip to content

Conversation

@webdevcody
Copy link
Collaborator

@webdevcody webdevcody commented Dec 21, 2025

This commit updates various modules to utilize the secure file system operations from the secureFs module instead of the native fs module. Key changes include:

  • Replaced fs imports with secureFs in multiple route handlers and services to enhance security and consistency in file operations.
  • Added centralized validation for working directories in the sdk-options module to ensure all AI model invocations are secure.

These changes aim to improve the security and maintainability of file handling across the application.

Summary by CodeRabbit

  • New Features

    • 30+ new dark & light themes with Dark/Light tabs, hover previews and live theme preview
    • Branch tracking now records creation timestamps
    • Enhanced planning workflow with task tracking and approval support
    • Expanded terminal theme options
  • Improvements

    • Centralized runtime validation for file operations
    • Deferred theme application for smoother UI responsiveness
    • Better error handling for file diffs
    • Async package-manager detection for faster dev-server startup

✏️ Tip: You can customize this high-level summary in your review settings.

This commit updates various modules to utilize the secure file system operations from the secureFs module instead of the native fs module. Key changes include:

- Replaced fs imports with secureFs in multiple route handlers and services to enhance security and consistency in file operations.
- Added centralized validation for working directories in the sdk-options module to ensure all AI model invocations are secure.

These changes aim to improve the security and maintainability of file handling across the application.
@coderabbitai
Copy link

coderabbitai bot commented Dec 21, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Centralizes working-directory validation, replaces fs/promises with a secure-fs abstraction across server routes and services, expands the UI theme system with many new themes and theme UI, and normalizes string literal and type literal quoting (double → single) across the codebase.

Changes

Cohort / File(s) Summary
Path validation & SDK options
apps/server/src/lib/sdk-options.ts
Added exported validateWorkingDirectory(cwd: string) and wired it into all create*Options factories; minor public literal/type signature adjustments.
Secure filesystem adoption (server routes & lib)
apps/server/src/lib/worktree-metadata.ts, apps/server/src/routes/fs/routes/*, apps/server/src/routes/templates/routes/clone.ts, apps/server/src/routes/worktree/routes/*, libs/git-utils/src/diff.ts
Replaced Node fs/fs/promises usage with secureFs across many route handlers and utilities; added PathNotAllowedError handling and adjusted I/O call sites.
App-spec flows
apps/server/src/routes/app-spec/generate-features-from-spec.ts, apps/server/src/routes/app-spec/generate-spec.ts, apps/server/src/routes/app-spec/parse-and-create-features.ts
Switched to secureFs I/O, normalized logging/strings, and expanded generateFeaturesFromSpec() signature to accept events: EventEmitter, abortController: AbortController, and optional maxFeatures.
Worktree utilities & tracking
apps/server/src/routes/worktree/common.ts, apps/server/src/routes/worktree/routes/branch-tracking.ts, apps/server/src/routes/worktree/routes/create.ts
Exported new utilities (execAsync, logError, getErrorMessage, AUTOMAKER_INITIAL_COMMIT_MESSAGE); TrackedBranch now includes createdAt; internal I/O moved to secureFs.
Dev server async refactor
apps/server/src/services/dev-server-service.ts, apps/server/tests/unit/services/dev-server-service.test.ts
Converted package-manager detection and dev-command logic to async; added fileExists helper using secureFs; updated tests to mock secureFs.access.
Agent & auto-mode services (types + validation)
apps/server/src/services/agent-service.ts, apps/server/src/services/auto-mode-service.ts
Centralized working-directory validation usage; normalized message/event literal types; extended PlanSpec, ParsedTask, FeatureWithPlanning, and other planning-related types (new fields added).
Workspace routes
apps/server/src/routes/workspace/routes/{config,directories}.ts
Replaced fs with secureFs, added defaultDir handling when ALLOWED_ROOT_DIRECTORY unset, deterministic sorting of directories.
FS route handlers (specific)
apps/server/src/routes/fs/routes/{browse,delete-board-background,delete,exists,image,mkdir,read,readdir,resolve-directory,save-board-background,save-image,stat,validate-path,write}.ts
Replaced I/O with secureFs, adjusted error handling (including PathNotAllowedError → 403) and string normalization.
Worktree git/init/list/status/diffs
apps/server/src/routes/worktree/routes/{init-git,list,status,diffs,file-diff,info}.ts
Used secureFs for existence/access checks, modernized exec/execAsync usage, normalized strings and minor parsing adjustments.
Theme system — config & options
apps/ui/src/config/theme-options.ts, apps/ui/src/config/terminal-themes.ts, libs/types/src/settings.ts
Introduced exported Theme type, expanded themeOptions with isDark/color, darkThemes/lightThemes helpers, extended terminalThemes, and expanded ThemeMode union in shared types.
UI: theme selection & preview
apps/ui/src/components/layout/sidebar.tsx, apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx, apps/ui/src/components/views/setup-view/steps/theme-step.tsx
Added tabbed dark/light theme selection, hover preview behavior, memoized ThemeMenuItem, and two-column theme menu rendering.
UI root & store updates
apps/ui/src/routes/__root.tsx, apps/ui/src/store/app-store.ts, apps/ui/src/components/views/settings-view/shared/types.ts
Added useDeferredValue for theme application, re-exported ThemeMode, and normalized many public type literals to single-quoted forms; Project.theme changed to string.
Theme assets & styles
apps/ui/src/styles/theme-imports.ts, apps/ui/src/styles/global.css, apps/ui/src/styles/themes/{forest,ocean,blossom,mint,lavender,github,gruvboxlight,solarizedlight,nordlight,paper,peach,rose,sand,sepia,sky,snow}.css
Added ~15 new theme CSS files, expanded imports and global theme variant declarations, and adjusted some CSS values.
Misc UI adjustments
apps/ui/src/config/terminal-themes.ts, apps/ui/src/styles/*
Extended terminal theme mapping and added selectionForeground optional property; reorganized theme imports and global CSS variants.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • secureFs integration and PathNotAllowedError handling across critical routes (browse, read, write, delete, mkdir, init-git).
  • New/changed type fields for PlanSpec/ParsedTask/TrackedBranch and their downstream consumers.
  • Async signature changes in DevServerService (ensure all call sites await new promises).
  • generateFeaturesFromSpec new parameters (events, abortController) and wiring where invoked.
  • Theme import surface and UI theme-preview interactions (ensure CSS bundles and runtime theme resolution match new ThemeMode values).

Possibly related PRs

Suggested labels

Refactor

"I hopped through lines of code today — secure paths, themes in bloom, hooray!
New palettes bright, quotes tidy and neat, the burrow's safer, and the UI's sweet 🐇🎨"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: replacing native fs with secureFs for file operations across multiple modules.

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 077a63b and ab65d46.

📒 Files selected for processing (1)
  • apps/server/tests/unit/services/dev-server-service.test.ts (11 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @webdevcody, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the application's file handling mechanisms to prioritize security and maintainability. By replacing direct fs module calls with a custom secureFs implementation and centralizing working directory validation for all AI model invocations, the changes ensure a more robust and secure environment. Additionally, the user interface now offers a greatly expanded selection of themes, enhancing customization and user experience.

Highlights

  • Enhanced File System Security: Replaced direct usage of Node.js's native fs module with a custom secureFs module across various server-side components, including worktree metadata handling, app spec generation, and file system routes. This centralizes file operation security.
  • Centralized Working Directory Validation: Introduced a new validateWorkingDirectory function in sdk-options.ts that is now called by all AI model invocation factory functions. This ensures that all AI models operate strictly within allowed directories, significantly enhancing security by preventing unauthorized file access.
  • Streamlined Path Validation: Removed redundant isPathAllowed checks from individual file system routes and agent services, as the new secureFs module and validateWorkingDirectory function now handle path validation centrally and consistently.
  • Expanded UI Theme Options: Introduced a wide array of new dark and light themes for the user interface, categorized into tabs for easier selection. This includes new terminal themes to match the expanded UI options.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@claude
Copy link

claude bot commented Dec 21, 2025

PR Review: Refactor to use secureFs for improved file handling

Overview

This PR makes significant security and maintainability improvements by replacing native fs with secureFs throughout the codebase, while also adding extensive theme support. The changes span 61 files with 4,867 additions and 3,351 deletions.


✅ Strengths

Security Improvements (Server-Side)

  1. Centralized Path Validation: The addition of validateWorkingDirectory() in sdk-options.ts:35 provides a critical security checkpoint that applies to ALL AI model invocations before they execute. This is excellent defense-in-depth.

  2. Consistent secureFs Usage: Systematic replacement of fs with secureFs across route handlers and services creates a uniform security boundary. All file operations now go through path validation via @automaker/platform.

  3. Well-Documented Security Model: The comments in sdk-options.ts:13-28 and secure-fs.ts:1-7 clearly explain the security rationale and architecture.

Code Quality

  1. Good Abstraction: The secureFs module in libs/platform/src/secure-fs.ts provides clean wrappers with proper TypeScript overloads (e.g., readdir at lines 62-78).

  2. Proper Error Handling: Custom PathNotAllowedError class in security.ts:11-18 provides clear, actionable error messages.

  3. Backward Compatibility: The re-export wrapper in apps/server/src/lib/secure-fs.ts maintains compatibility with existing imports.


⚠️ Issues & Recommendations

Critical: Security Concerns

1. Missing Tests for Security-Critical Code

  • Issue: The new validateWorkingDirectory() function in sdk-options.ts:35 is called by all AI model factory functions but appears to lack dedicated unit tests.
  • Impact: HIGH - This is a critical security boundary that needs comprehensive test coverage
  • Recommendation: Add tests to verify:
    // sdk-options.test.ts - Add these test cases:
    - validateWorkingDirectory() throws PathNotAllowedError for paths outside ALLOWED_ROOT_DIRECTORY
    - validateWorkingDirectory() allows paths within ALLOWED_ROOT_DIRECTORY
    - validateWorkingDirectory() allows DATA_DIR paths (appData exception)
    - All create*Options() functions call validateWorkingDirectory()
  • Files: apps/server/tests/unit/lib/sdk-options.test.ts

2. Path Traversal Edge Cases

  • Issue: While isPathWithinDirectory() in security.ts:104-115 handles basic .. attacks, there may be edge cases with symbolic links, Windows UNC paths, and mixed separators.
  • Recommendation: Add explicit documentation and tests for:
    • Symbolic link handling (note: lstat is available but not explicitly used in validation)
    • Windows UNC paths (\\\\server\\share)
    • Mixed path separators (/ and \\ on Windows)
    • Null byte injection attempts

3. Inconsistent Error Handling in Path Validation

  • Issue: Some routes like apps/server/src/routes/fs/routes/browse.ts use secureFs but may not have explicit try-catch for PathNotAllowedError, relying on global error handlers.
  • Recommendation: Consider adding explicit error handling or documenting the expected error propagation pattern.

Major: Code Quality Issues

4. Magic String in Error Messages

  • Location: Multiple files use string literals for error messages
  • Example: sdk-options.ts:41-45
    throw new PathNotAllowedError(
      `Working directory "${cwd}" (resolved: ${resolvedCwd}) is not allowed. ` +
        (allowedRoot
          ? `Must be within ALLOWED_ROOT_DIRECTORY: ${allowedRoot}`
          : 'ALLOWED_ROOT_DIRECTORY is configured but path is not within allowed directories.')
    );
  • Issue: Violates clean-code.md principle Add customizable keyboard shortcuts in settings #5 (Avoid Magic Strings)
  • Recommendation: Extract error messages to constants:
    const ERROR_MESSAGES = {
      PATH_NOT_ALLOWED: (cwd: string, resolved: string, allowedRoot: string | null) => 
        `Working directory "${cwd}" (resolved: ${resolved}) is not allowed. ${
          allowedRoot 
            ? `Must be within ALLOWED_ROOT_DIRECTORY: ${allowedRoot}` 
            : 'ALLOWED_ROOT_DIRECTORY is configured but path is not within allowed directories.'
        }`
    };

5. Large File Refactors Without Clear Separation

  • Location: apps/ui/src/components/layout/sidebar.tsx (593 additions, 684 deletions)
  • Issue: This PR mixes security refactoring (secureFs) with UI/theme changes. The sidebar.tsx changes appear unrelated to the PR title.
  • Recommendation: Consider splitting into separate PRs:
    • PR 1: Security refactoring (secureFs migration)
    • PR 2: Theme system enhancements
      This makes review easier and rollback safer.

6. Potential Code Duplication in Theme Files

  • Location: 15 new theme CSS files in apps/ui/src/styles/themes/
  • Issue: Each theme file is ~92 lines with similar structure. This violates DRY principle (clean-code.md feat(sidebar): implement trash functionality for project management #1).
  • Recommendation: Consider a theme generation system or CSS variables approach:
    // theme-generator.ts
    function generateThemeCSS(theme: ThemeConfig): string {
      // Generate CSS from config object
    }

Minor: Best Practices

7. Inconsistent Import Formatting

  • Location: Multiple files switch from double quotes to single quotes
  • Example: sdk-options.ts:12-16 changes from "@anthropic-ai/..." to '@anthropic-ai/...'
  • Recommendation: Run a consistent formatter (Prettier) across the codebase to avoid mixing quote styles in a single PR.

8. Missing JSDoc for Public Security Functions

  • Location: validateWorkingDirectory() in sdk-options.ts:35
  • Current: Has a basic multi-line comment
  • Recommendation: Use JSDoc format for better IDE integration:
    /**
     * Validate that a working directory is allowed by ALLOWED_ROOT_DIRECTORY.
     * This is the centralized security check for ALL AI model invocations.
     *
     * @param cwd - The working directory to validate
     * @throws {PathNotAllowedError} if the directory is not within ALLOWED_ROOT_DIRECTORY
     * @example
     * validateWorkingDirectory('/home/user/project'); // OK if within allowed root
     * validateWorkingDirectory('/etc/passwd'); // Throws PathNotAllowedError
     */

🧪 Test Coverage

Current State

  • ✅ Existing tests found: sdk-options.test.ts, worktree-metadata.test.ts
  • ❌ No tests visible for new validateWorkingDirectory() function
  • ❌ No integration tests for secureFs enforcement across routes

Recommended Test Additions

  1. Unit Tests for Security Functions:

    // apps/server/tests/unit/lib/sdk-options.test.ts
    describe('validateWorkingDirectory', () => {
      it('should throw PathNotAllowedError for paths outside allowed root', () => {
        // Test implementation
      });
      
      it('should allow paths within ALLOWED_ROOT_DIRECTORY', () => {
        // Test implementation
      });
      
      it('should allow DATA_DIR paths as exception', () => {
        // Test implementation
      });
    });
  2. Integration Tests for Route Security:

    // apps/server/tests/integration/routes/fs/security.integration.test.ts
    describe('File System Route Security', () => {
      it('should reject read requests outside allowed root', async () => {
        const response = await request(app)
          .post('/api/fs/read')
          .send({ path: '/etc/passwd' });
        expect(response.status).toBe(403);
      });
    });
  3. Tests for Theme System (if keeping in this PR):

    • Theme switching persistence
    • CSS variable application
    • Terminal theme synchronization

🚀 Performance Considerations

  1. Path Resolution Overhead: Every file operation now includes path.resolve() and validation checks. For high-frequency operations, consider:

    • Caching validated paths within a request context
    • Benchmarking impact on file-heavy operations
  2. Theme CSS Bundle Size: 15 new theme files add ~1.4KB each. Consider:

    • Lazy loading theme CSS files
    • Using CSS-in-JS with tree-shaking
    • Current approach: All themes loaded upfront in theme-imports.ts:1-34

📝 Documentation & Maintainability

Good

  • ✅ Clear inline comments explaining security model
  • ✅ Consistent function naming
  • ✅ Type safety maintained throughout

Needs Improvement

  • Add migration guide for developers using fs directly
  • Document the secureFs API in README or docs/
  • Add ADR (Architecture Decision Record) for security model
  • Update CONTRIBUTING.md with secureFs usage guidelines

Summary & Recommendation

Security: ⚠️ NEEDS WORK

While the security improvements are conceptually sound, the lack of comprehensive tests for the critical validateWorkingDirectory() function is concerning. This must be addressed before merge.

Code Quality: ⚠️ MIXED

  • Good: Clean abstractions, proper TypeScript usage
  • Bad: Magic strings, mixed concerns (security + themes), potential DRY violations in themes

Recommendation: REQUEST CHANGES

Required before merge:

  1. Add comprehensive tests for validateWorkingDirectory() and path validation logic
  2. Add integration tests for secureFs enforcement across key routes
  3. Consider splitting theme changes into separate PR

Suggested improvements:
4. Extract magic strings to constants
5. Add JSDoc to public security functions
6. Run consistent formatter to fix quote style inconsistencies
7. Add security documentation (migration guide, ADR)


Positive Notes

This PR represents a significant step toward improving the security posture of AutoMaker. The centralized validation approach is architecturally sound and the consistent application across the codebase shows thorough execution. Once the testing gaps are addressed, this will be a valuable security enhancement.

Great work on the systematic refactoring! 👏


Reviewed by: Claude Sonnet 4.5
Review Date: 2025-12-21

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is a substantial and valuable pull request that significantly enhances the application's security posture by centralizing file system operations through a new secureFs module. The introduction of validateWorkingDirectory in the SDK options is a particularly strong improvement, ensuring all AI agent actions are properly sandboxed. The extensive UI refactoring to introduce many new themes and improve the theme selection experience is also a fantastic addition. I have a couple of suggestions to further improve security and type safety, but overall, this is excellent work.

Comment on lines +26 to +27
await mkdirSafe(path.dirname(path.resolve(filePath)));
await secureFs.writeFile(filePath, content, 'utf-8');
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This call to mkdirSafe could introduce a security vulnerability. The secureFs.writeFile on the next line correctly validates the full filePath, but mkdirSafe operates on the parent directory without the same validation. If filePath were to contain path traversal sequences (e.g., ../), path.resolve would resolve it, and mkdirSafe might then create a directory outside of the ALLOWED_ROOT_DIRECTORY before writeFile has a chance to throw a security error.

For a defense-in-depth approach, this directory creation step should also be secure. I recommend replacing mkdirSafe with a direct call to secureFs.mkdir, which includes the necessary path validation.

Suggested change
await mkdirSafe(path.dirname(path.resolve(filePath)));
await secureFs.writeFile(filePath, content, 'utf-8');
await secureFs.mkdir(path.dirname(filePath), { recursive: true });
await secureFs.writeFile(filePath, content, 'utf-8');

try {
const metadataPath = getWorktreeMetadataPath(projectPath, branch);
const content = await fs.readFile(metadataPath, "utf-8");
const content = (await secureFs.readFile(metadataPath, 'utf-8')) as string;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The type assertion as string is necessary here because the secureFs.readFile wrapper returns Promise<string | Buffer>. To improve type safety and remove the need for casting throughout the codebase, consider adding function overloads to the secureFs.readFile implementation. This would allow TypeScript to correctly infer a Promise<string> return type when an encoding is provided, mirroring the behavior of the native fs.readFile and making the code more robust.

Copy link

@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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/server/src/routes/fs/routes/mkdir.ts (1)

26-31: Symlink handling may be overly permissive.

Line 28 treats both directories and symbolic links as success. However, a symlink could point to a file rather than a directory. If the caller expects a directory to exist, this could cause issues downstream.

Consider verifying the symlink target:

Proposed fix
       const stats = await secureFs.lstat(resolvedPath);
       // Path exists - if it's a directory or symlink, consider it success
-      if (stats.isDirectory() || stats.isSymbolicLink()) {
+      if (stats.isDirectory()) {
         res.json({ success: true });
         return;
       }
+      // For symlinks, verify the target is a directory
+      if (stats.isSymbolicLink()) {
+        const targetStats = await secureFs.stat(resolvedPath);
+        if (targetStats.isDirectory()) {
+          res.json({ success: true });
+          return;
+        }
+      }
       // It's a file - can't create directory
apps/server/src/routes/worktree/routes/branch-tracking.ts (1)

12-16: Consider backward compatibility for the required createdAt field.

The createdAt field is defined as required in the TrackedBranch interface, but existing tracked branch data files (.automaker/active-branches.json) created before this change won't have this field. This could cause issues when reading existing data.

🔎 Suggested fix: Make createdAt optional or add migration logic

Option 1: Make the field optional to handle existing data gracefully:

 export interface TrackedBranch {
   name: string;
-  createdAt: string;
+  createdAt?: string;
   lastActivatedAt?: string;
 }

Option 2: Add migration logic in getTrackedBranches to populate missing createdAt values:

 export async function getTrackedBranches(projectPath: string): Promise<TrackedBranch[]> {
   try {
     const filePath = getBranchTrackingPath(projectPath);
     const content = (await secureFs.readFile(filePath, 'utf-8')) as string;
     const data: BranchTrackingData = JSON.parse(content);
-    return data.branches || [];
+    const branches = data.branches || [];
+    // Migrate old data without createdAt
+    return branches.map(b => ({
+      ...b,
+      createdAt: b.createdAt || new Date().toISOString()
+    }));
   } catch (error: any) {
apps/server/src/routes/worktree/routes/create.ts (1)

149-156: Potential command injection via branchName in git commands.

The branchName from user input is used directly in shell commands without sanitization or quoting. While the sanitizedName is created for directory paths (line 129), the original branchName is used in git commands:

  • Line 151: git worktree add "${worktreePath}" ${branchName}
  • Line 155: git worktree add -b ${branchName} "${worktreePath}" ${base}

A malicious branch name like main; rm -rf / could execute arbitrary commands.

🔎 Proposed fix

Either validate/sanitize the branch name or properly quote it:

       if (branchExists) {
         // Use existing branch
-        createCmd = `git worktree add "${worktreePath}" ${branchName}`;
+        createCmd = `git worktree add "${worktreePath}" "${branchName}"`;
       } else {
         // Create new branch from base or HEAD
         const base = baseBranch || 'HEAD';
-        createCmd = `git worktree add -b ${branchName} "${worktreePath}" ${base}`;
+        createCmd = `git worktree add -b "${branchName}" "${worktreePath}" "${base}"`;
       }

Better yet, validate branchName against a safe pattern (e.g., /^[a-zA-Z0-9_\-\/]+$/) before use.

🧹 Nitpick comments (25)
apps/ui/src/styles/themes/sepia.css (2)

85-87: Inconsistent color format: convert hex colors to OKLCH for consistency.

Lines 86-87 use hex color values while the entire theme uses OKLCH custom properties. For consistency and maintainability, convert these to OKLCH format to match the rest of the theme palette.

 .sepia .animated-outline-gradient {
-  background: conic-gradient(from 90deg at 50% 50%, #92400e 0%, #a16207 50%, #92400e 100%);
+  background: conic-gradient(from 90deg at 50% 50%, oklch(0.45 0.14 50) 0%, oklch(0.5 0.12 50) 50%, oklch(0.45 0.14 50) 100%);
 }

89-92: Avoid !important; prefer CSS specificity or a dedicated override class.

Using !important in lines 90-91 can create maintenance challenges and conflicts with component-level styles. Consider using a more specific selector or a dedicated override class instead.

-.sepia .animated-outline-inner {
-  background: oklch(0.97 0.02 70) !important;
-  color: #92400e !important;
+.sepia .animated-outline-inner {
+  background: oklch(0.97 0.02 70);
+  color: oklch(0.45 0.14 50);
 }

Note: Also convert the hex color to OKLCH format (as mentioned in the previous comment) for full consistency.

apps/server/src/routes/fs/routes/delete-board-background.ts (1)

27-37: Consider more specific error handling in the catch block.

The empty catch block at line 35 swallows all errors, including potentially important ones like PathNotAllowedError or file permission issues. While the comment indicates that a missing directory is acceptable, this broad error handling could mask security violations or other problems.

Consider handling specific error cases:

-      } catch {
-        // Directory may not exist, that's fine
+      } catch (error) {
+        // Directory may not exist, that's fine
+        // But propagate security errors
+        if (error instanceof PathNotAllowedError) {
+          throw error;
+        }
+        // Otherwise, silently continue (directory doesn't exist)
       }

This would ensure security boundaries are respected while maintaining the lenient behavior for missing directories.

apps/server/src/routes/fs/routes/validate-path.ts (1)

24-39: Consider handling PathNotAllowedError in the inner catch block.

The empty catch at line 37 treats all errors as "Path does not exist", including PathNotAllowedError. This could be misleading – a forbidden path would be reported as non-existent rather than as a security violation.

🔎 Recommended improvement for clearer error messaging
       } catch {
-        res.status(400).json({ success: false, error: 'Path does not exist' });
+      } catch (error) {
+        // Re-throw security errors to be handled by outer catch
+        if (error instanceof PathNotAllowedError) {
+          throw error;
+        }
+        res.status(400).json({ success: false, error: 'Path does not exist' });
       }

This would allow the outer catch to return a 403 for security violations (see next comment).

apps/ui/src/styles/themes/solarizedlight.css (1)

85-92: Consider converting hex colors to oklch for consistency.

Lines 86 and 91 use hex color values (#268bd2, #2aa198) while the rest of the theme uses oklch. For consistency with the Paper theme and modern CSS practices, consider converting these to oklch:

 .solarizedlight .animated-outline-gradient {
-  background: conic-gradient(from 90deg at 50% 50%, #268bd2 0%, #2aa198 50%, #268bd2 100%);
+  background: conic-gradient(
+    from 90deg at 50% 50%,
+    oklch(0.55 0.15 230) 0%,
+    oklch(0.6 0.15 180) 50%,
+    oklch(0.55 0.15 230) 100%
+  );
 }

 .solarizedlight .animated-outline-inner {
   background: oklch(0.98 0.01 90) !important;
-  color: #268bd2 !important;
+  color: oklch(0.55 0.15 230) !important;
 }

This maintains the Solarized color identity while aligning with the oklch color space used throughout the theme.

apps/server/src/routes/fs/routes/write.ts (1)

26-27: Potential path inconsistency between directory creation and file write.

mkdirSafe receives path.resolve(filePath) (absolute), but secureFs.writeFile receives the raw filePath (possibly relative). If secureFs.writeFile internally resolves paths differently, this could lead to unexpected behavior or a subtle TOCTOU gap.

Consider using the resolved path consistently:

Proposed fix
+      const resolvedPath = path.resolve(filePath);
       // Ensure parent directory exists (symlink-safe)
-      await mkdirSafe(path.dirname(path.resolve(filePath)));
-      await secureFs.writeFile(filePath, content, 'utf-8');
+      await mkdirSafe(path.dirname(resolvedPath));
+      await secureFs.writeFile(resolvedPath, content, 'utf-8');
apps/server/src/routes/fs/routes/read.ts (1)

18-20: Consider extracting isENOENT to a shared utility.

This function is duplicated from apps/server/src/routes/worktree/common.ts (lines 117-119). Consider importing it from a shared location to reduce duplication.

apps/server/src/routes/fs/routes/save-image.ts (1)

49-52: Consider adding PathNotAllowedError handling for consistency.

Other handlers in this PR (e.g., image.ts, browse.ts) explicitly catch PathNotAllowedError and return a 403 status. This handler catches all errors generically. For consistency, consider adding specific handling:

🔎 Proposed fix
+import { PathNotAllowedError } from '@automaker/platform';
+
 // ... inside the handler
     } catch (error) {
+      if (error instanceof PathNotAllowedError) {
+        res.status(403).json({ success: false, error: 'Path not allowed' });
+        return;
+      }
       logError(error, 'Save image failed');
       res.status(500).json({ success: false, error: getErrorMessage(error) });
     }
apps/server/src/routes/fs/routes/resolve-directory.ts (2)

42-49: Consider extracting the home directory resolution for clarity.

The pattern process.env.HOME || process.env.USERPROFILE || '' is repeated four times. Extracting it would improve readability:

🔎 Proposed refactor
+     const homeDir = process.env.HOME || process.env.USERPROFILE || '';
       const searchPaths: string[] = [
         process.cwd(),
-        process.env.HOME || process.env.USERPROFILE || '',
-        path.join(process.env.HOME || process.env.USERPROFILE || '', 'Documents'),
-        path.join(process.env.HOME || process.env.USERPROFILE || '', 'Desktop'),
-        path.join(process.env.HOME || process.env.USERPROFILE || '', 'Projects'),
+        homeDir,
+        path.join(homeDir, 'Documents'),
+        path.join(homeDir, 'Desktop'),
+        path.join(homeDir, 'Projects'),
       ].filter(Boolean);

112-115: Consider adding PathNotAllowedError handling for consistency.

Similar to other handlers in this PR, explicit handling of PathNotAllowedError would provide consistent 403 responses when accessing restricted paths:

🔎 Proposed fix
+import { PathNotAllowedError } from '@automaker/platform';
+
 // ... inside the handler
     } catch (error) {
+      if (error instanceof PathNotAllowedError) {
+        res.status(403).json({ success: false, error: 'Path not allowed' });
+        return;
+      }
       logError(error, 'Resolve directory failed');
       res.status(500).json({ success: false, error: getErrorMessage(error) });
     }
apps/server/src/routes/fs/routes/browse.ts (1)

89-92: Permission warning is macOS-specific.

The warning message references "System Preferences > Privacy & Security" which is specific to macOS. Consider making this platform-aware since the code also handles Windows drives:

🔎 Proposed fix
+          const warningMessage = os.platform() === 'darwin'
+            ? 'Permission denied - grant Full Disk Access to Terminal in System Preferences > Privacy & Security'
+            : 'Permission denied - check folder permissions';
           res.json({
             success: true,
             currentPath: targetPath,
             parentPath: hasParent ? parentPath : null,
             directories: [],
             drives,
-            warning:
-              'Permission denied - grant Full Disk Access to Terminal in System Preferences > Privacy & Security',
+            warning: warningMessage,
           });
apps/ui/src/styles/themes/lavender.css (1)

85-92: Consider using CSS variables for hard-coded gradient colors.

The animated outline sections use hard-coded hex color values (#a855f7, #8b5cf6) which could reference existing CSS variables (e.g., var(--primary), var(--brand-500)) for better maintainability and consistency with the rest of the theme.

💡 Example refactor using CSS variables
 .lavender .animated-outline-gradient {
-  background: conic-gradient(from 90deg at 50% 50%, #a855f7 0%, #8b5cf6 50%, #a855f7 100%);
+  background: conic-gradient(from 90deg at 50% 50%, var(--primary) 0%, var(--brand-600) 50%, var(--primary) 100%);
 }
 
 .lavender .animated-outline-inner {
   background: oklch(0.98 0.015 285) !important;
-  color: #a855f7 !important;
+  color: var(--primary) !important;
 }
apps/ui/src/styles/themes/blossom.css (1)

85-92: Consider using CSS variables for hard-coded gradient colors.

The animated outline sections use hard-coded hex color values (#ec4899, #f472b6) which could reference existing CSS variables for better maintainability and consistency with the theme system.

💡 Example refactor using CSS variables
 .blossom .animated-outline-gradient {
-  background: conic-gradient(from 90deg at 50% 50%, #ec4899 0%, #f472b6 50%, #ec4899 100%);
+  background: conic-gradient(from 90deg at 50% 50%, var(--primary) 0%, var(--brand-400) 50%, var(--primary) 100%);
 }
 
 .blossom .animated-outline-inner {
   background: oklch(0.99 0.015 330) !important;
-  color: #ec4899 !important;
+  color: var(--primary) !important;
 }
apps/ui/src/styles/themes/forest.css (1)

91-98: Consider using CSS variables for hard-coded gradient colors.

The animated outline sections use hard-coded hex color values (#22c55e, #16a34a, #86efac) which could reference existing CSS variables for better maintainability.

💡 Example refactor using CSS variables
 .forest .animated-outline-gradient {
-  background: conic-gradient(from 90deg at 50% 50%, #22c55e 0%, #16a34a 50%, #22c55e 100%);
+  background: conic-gradient(from 90deg at 50% 50%, var(--primary) 0%, var(--brand-600) 50%, var(--primary) 100%);
 }
 
 .forest .animated-outline-inner {
   background: oklch(0.16 0.025 150) !important;
-  color: #86efac !important;
+  color: var(--brand-400) !important;
 }
apps/ui/src/styles/themes/ocean.css (1)

91-98: Consider using CSS variables for hard-coded gradient colors.

The animated outline sections use hard-coded hex color values (#3b82f6, #0ea5e9, #93c5fd) which could reference existing CSS variables for better maintainability and consistency.

💡 Example refactor using CSS variables
 .ocean .animated-outline-gradient {
-  background: conic-gradient(from 90deg at 50% 50%, #3b82f6 0%, #0ea5e9 50%, #3b82f6 100%);
+  background: conic-gradient(from 90deg at 50% 50%, var(--primary) 0%, var(--chart-2) 50%, var(--primary) 100%);
 }
 
 .ocean .animated-outline-inner {
   background: oklch(0.16 0.035 230) !important;
-  color: #93c5fd !important;
+  color: var(--brand-400) !important;
 }
apps/server/src/routes/worktree/routes/status.ts (1)

12-12: Consider importing execAsync from common.ts to avoid duplication.

The file defines its own execAsync = promisify(exec) on line 12, but according to the relevant code snippets, common.ts already exports execAsync at line 12. Consider importing it instead to maintain a single source of truth.

🔎 Proposed refactor
-import { getErrorMessage, logError } from '../common.js';
+import { execAsync, getErrorMessage, logError } from '../common.js';

-const execAsync = promisify(exec);

Remove the unused imports:

-import { exec } from 'child_process';
-import { promisify } from 'util';
apps/ui/src/styles/themes/gruvboxlight.css (1)

85-92: Consider using CSS variables for hard-coded gradient colors.

The animated outline sections use hard-coded hex color values (#d79921, #b57614) which could reference existing CSS variables for better maintainability and consistency with the theme system.

💡 Example refactor using CSS variables
 .gruvboxlight .animated-outline-gradient {
-  background: conic-gradient(from 90deg at 50% 50%, #d79921 0%, #b57614 50%, #d79921 100%);
+  background: conic-gradient(from 90deg at 50% 50%, var(--primary) 0%, var(--brand-600) 50%, var(--primary) 100%);
 }
 
 .gruvboxlight .animated-outline-inner {
   background: oklch(0.98 0.015 85) !important;
-  color: #b57614 !important;
+  color: var(--brand-600) !important;
 }
apps/ui/src/components/views/setup-view/steps/theme-step.tsx (1)

41-98: Consider extracting a shared ThemeTabGroup component.

The dark/light tab toggle and theme grid rendering logic is nearly identical to appearance-section.tsx. Extracting a reusable component would reduce duplication and ensure consistent behavior across both views.

apps/ui/src/styles/themes/nordlight.css (1)

85-92: Consider using CSS variables instead of hardcoded hex colors.

Lines 86 and 91 use hardcoded hex color values (#5e81ac, #81a1c1) instead of CSS custom properties. For consistency and maintainability, consider using the CSS variables defined earlier (e.g., var(--primary) or var(--sidebar-primary)).

🔎 Example refactor using CSS variables
 .nordlight .animated-outline-gradient {
-  background: conic-gradient(from 90deg at 50% 50%, #5e81ac 0%, #81a1c1 50%, #5e81ac 100%);
+  background: conic-gradient(from 90deg at 50% 50%, var(--primary) 0%, var(--brand-400) 50%, var(--primary) 100%);
 }
 
 .nordlight .animated-outline-inner {
   background: oklch(0.99 0.008 220) !important;
-  color: #5e81ac !important;
+  color: var(--primary) !important;
 }
apps/ui/src/styles/themes/snow.css (1)

85-92: Consider using CSS variables instead of hardcoded hex colors.

Similar to other theme files, lines 86 and 91 use hardcoded hex color values (#6366f1, #8b5cf6) instead of CSS custom properties. For consistency and maintainability across all theme files, consider using the CSS variables defined earlier.

🔎 Example refactor using CSS variables
 .snow .animated-outline-gradient {
-  background: conic-gradient(from 90deg at 50% 50%, #6366f1 0%, #8b5cf6 50%, #6366f1 100%);
+  background: conic-gradient(from 90deg at 50% 50%, var(--primary) 0%, var(--chart-4) 50%, var(--primary) 100%);
 }
 
 .snow .animated-outline-inner {
   background: oklch(1 0 0) !important;
-  color: #6366f1 !important;
+  color: var(--primary) !important;
 }
apps/ui/src/routes/__root.tsx (1)

129-132: Optional: Simplify the type assertion.

The type assertion ('system' as ThemeOption['value']) could be simplified to just 'system' since TypeScript can infer the comparison:

     const themeClasses = themeOptions
       .map((option) => option.value)
-      .filter((theme) => theme !== ('system' as ThemeOption['value']));
+      .filter((theme) => theme !== 'system');
apps/server/src/routes/worktree/routes/create.ts (1)

24-24: Consider importing execAsync from common module.

execAsync is already exported from ../common.js (per the relevant code snippets). Importing it instead of redeclaring locally would reduce duplication:

 import {
   isGitRepo,
   getErrorMessage,
   logError,
   normalizePath,
   ensureInitialCommit,
+  execAsync,
 } from '../common.js';
 import { trackBranch } from './branch-tracking.js';

-const execAsync = promisify(exec);
apps/server/src/routes/templates/routes/clone.ts (1)

96-109: Optional: Simplify parent directory existence check.

The nested try/catch for setting parentExists is verbose. Consider using a helper:

-          let parentExists = false;
-          try {
-            await secureFs.access(parentDir);
-            parentExists = true;
-          } catch {
-            parentExists = false;
-          }
+          const parentExists = await secureFs.access(parentDir).then(() => true, () => false);

           if (!parentExists) {
apps/ui/src/components/layout/sidebar.tsx (1)

196-197: Remove redundant key prop from internal wrapper div.

The key={option.value} on line 197 inside the ThemeMenuItem component is redundant since this component is rendered via .map() and the key is already correctly placed on the <ThemeMenuItem> element at the call sites (lines 1672, 1689). The key prop on internal elements doesn't serve React's reconciliation purpose.

🔎 Proposed fix
   return (
     <div
-      key={option.value}
       onPointerEnter={() => onPreviewEnter(option.value)}
       onPointerLeave={onPreviewLeave}
     >
apps/server/src/routes/app-spec/generate-spec.ts (1)

41-61: Consider extracting the default tech stack configuration.

The default technology stack (lines 53-60) is hardcoded as a multi-line string. Consider extracting this to a configuration constant for easier maintenance and reusability.

📋 Optional refactor: Extract to constant

At the top of the file, add:

const DEFAULT_TECH_STACK = `Default Technology Stack:
- Framework: TanStack Start (React-based full-stack framework)
- Database: PostgreSQL with Drizzle ORM
- UI Components: shadcn/ui
- Styling: Tailwind CSS
- Frontend: React

Use these technologies as the foundation for the specification.`;

Then use:

  } else {
    // Use default tech stack
-   techStackDefaults = `Default Technology Stack:
-   ...
-   Use these technologies as the foundation for the specification.`;
+   techStackDefaults = DEFAULT_TECH_STACK;
  }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b5479a and 077a63b.

📒 Files selected for processing (62)
  • apps/server/src/lib/sdk-options.ts (11 hunks)
  • apps/server/src/lib/worktree-metadata.ts (7 hunks)
  • apps/server/src/routes/app-spec/generate-features-from-spec.ts (4 hunks)
  • apps/server/src/routes/app-spec/generate-spec.ts (5 hunks)
  • apps/server/src/routes/app-spec/parse-and-create-features.ts (2 hunks)
  • apps/server/src/routes/fs/routes/browse.ts (6 hunks)
  • apps/server/src/routes/fs/routes/delete-board-background.ts (4 hunks)
  • apps/server/src/routes/fs/routes/delete.ts (2 hunks)
  • apps/server/src/routes/fs/routes/exists.ts (2 hunks)
  • apps/server/src/routes/fs/routes/image.ts (2 hunks)
  • apps/server/src/routes/fs/routes/mkdir.ts (3 hunks)
  • apps/server/src/routes/fs/routes/read.ts (3 hunks)
  • apps/server/src/routes/fs/routes/readdir.ts (2 hunks)
  • apps/server/src/routes/fs/routes/resolve-directory.ts (5 hunks)
  • apps/server/src/routes/fs/routes/save-board-background.ts (2 hunks)
  • apps/server/src/routes/fs/routes/save-image.ts (2 hunks)
  • apps/server/src/routes/fs/routes/stat.ts (2 hunks)
  • apps/server/src/routes/fs/routes/validate-path.ts (2 hunks)
  • apps/server/src/routes/fs/routes/write.ts (3 hunks)
  • apps/server/src/routes/templates/routes/clone.ts (8 hunks)
  • apps/server/src/routes/workspace/routes/config.ts (3 hunks)
  • apps/server/src/routes/workspace/routes/directories.ts (4 hunks)
  • apps/server/src/routes/worktree/common.ts (5 hunks)
  • apps/server/src/routes/worktree/routes/branch-tracking.ts (4 hunks)
  • apps/server/src/routes/worktree/routes/create.ts (7 hunks)
  • apps/server/src/routes/worktree/routes/diffs.ts (4 hunks)
  • apps/server/src/routes/worktree/routes/file-diff.ts (2 hunks)
  • apps/server/src/routes/worktree/routes/info.ts (3 hunks)
  • apps/server/src/routes/worktree/routes/init-git.ts (2 hunks)
  • apps/server/src/routes/worktree/routes/list.ts (7 hunks)
  • apps/server/src/routes/worktree/routes/status.ts (2 hunks)
  • apps/server/src/services/agent-service.ts (19 hunks)
  • apps/server/src/services/auto-mode-service.ts (86 hunks)
  • apps/server/src/services/dev-server-service.ts (13 hunks)
  • apps/ui/src/components/layout/sidebar.tsx (82 hunks)
  • apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx (3 hunks)
  • apps/ui/src/components/views/settings-view/shared/types.ts (2 hunks)
  • apps/ui/src/components/views/setup-view/steps/theme-step.tsx (4 hunks)
  • apps/ui/src/config/terminal-themes.ts (3 hunks)
  • apps/ui/src/config/theme-options.ts (1 hunks)
  • apps/ui/src/routes/__root.tsx (8 hunks)
  • apps/ui/src/store/app-store.ts (73 hunks)
  • apps/ui/src/styles/global.css (8 hunks)
  • apps/ui/src/styles/theme-imports.ts (1 hunks)
  • apps/ui/src/styles/themes/blossom.css (1 hunks)
  • apps/ui/src/styles/themes/forest.css (1 hunks)
  • apps/ui/src/styles/themes/github.css (1 hunks)
  • apps/ui/src/styles/themes/gruvboxlight.css (1 hunks)
  • apps/ui/src/styles/themes/lavender.css (1 hunks)
  • apps/ui/src/styles/themes/mint.css (1 hunks)
  • apps/ui/src/styles/themes/nordlight.css (1 hunks)
  • apps/ui/src/styles/themes/ocean.css (1 hunks)
  • apps/ui/src/styles/themes/paper.css (1 hunks)
  • apps/ui/src/styles/themes/peach.css (1 hunks)
  • apps/ui/src/styles/themes/rose.css (1 hunks)
  • apps/ui/src/styles/themes/sand.css (1 hunks)
  • apps/ui/src/styles/themes/sepia.css (1 hunks)
  • apps/ui/src/styles/themes/sky.css (1 hunks)
  • apps/ui/src/styles/themes/snow.css (1 hunks)
  • apps/ui/src/styles/themes/solarizedlight.css (1 hunks)
  • libs/git-utils/src/diff.ts (8 hunks)
  • libs/types/src/settings.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (30)
apps/server/src/routes/fs/routes/validate-path.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/server/src/routes/worktree/routes/diffs.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/worktree/common.ts (1)
  • logError (143-143)
apps/server/src/routes/fs/routes/resolve-directory.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/server/src/routes/fs/routes/mkdir.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/server/src/routes/worktree/routes/branch-tracking.ts (1)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/fs/routes/delete.ts (4)
apps/server/src/routes/worktree/routes/delete.ts (1)
  • createDeleteHandler (13-80)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/server/src/routes/fs/common.ts (1)
  • logError (15-15)
apps/ui/src/components/views/setup-view/steps/theme-step.tsx (1)
apps/ui/src/config/theme-options.ts (2)
  • darkThemes (340-340)
  • lightThemes (343-343)
apps/server/src/routes/worktree/routes/create.ts (2)
apps/server/src/routes/worktree/common.ts (3)
  • execAsync (13-13)
  • isGitRepo (105-112)
  • logError (143-143)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/ui/src/config/theme-options.ts (1)
apps/ui/src/components/views/settings-view/shared/types.ts (1)
  • Theme (3-3)
apps/server/src/routes/worktree/routes/list.ts (5)
apps/server/src/routes/worktree/common.ts (3)
  • execAsync (13-13)
  • normalizePath (98-100)
  • logError (143-143)
libs/types/src/settings.ts (1)
  • WorktreeInfo (328-339)
apps/ui/src/types/electron.d.ts (1)
  • WorktreeInfo (578-583)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/ui/src/components/layout/sidebar.tsx (3)
apps/ui/src/config/theme-options.ts (2)
  • darkThemes (340-340)
  • lightThemes (343-343)
apps/ui/src/store/app-store.ts (1)
  • ThemeMode (16-16)
libs/types/src/settings.ts (1)
  • ThemeMode (24-59)
apps/server/src/routes/app-spec/generate-spec.ts (6)
apps/server/src/routes/templates/common.ts (1)
  • logger (11-11)
apps/server/src/lib/sdk-options.ts (1)
  • createSpecGenerationOptions (173-191)
apps/server/src/lib/app-spec-format.ts (3)
  • specOutputSchema (10-10)
  • SpecOutput (9-9)
  • specToXml (27-100)
apps/server/src/routes/app-spec/common.ts (1)
  • logAuthStatus (37-52)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/generate-features-from-spec.ts (1)
  • generateFeaturesFromSpec (18-162)
apps/server/src/routes/fs/routes/browse.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/worktree/common.ts (1)
  • logError (143-143)
apps/server/src/routes/fs/routes/image.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/worktree/common.ts (1)
  • logError (143-143)
apps/server/src/routes/app-spec/generate-features-from-spec.ts (3)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logAuthStatus (37-52)
apps/server/src/routes/app-spec/parse-and-create-features.ts (1)
  • parseAndCreateFeatures (13-94)
apps/server/src/routes/fs/routes/stat.ts (3)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/server/src/routes/fs/common.ts (1)
  • logError (15-15)
apps/server/src/routes/fs/routes/read.ts (3)
apps/server/src/routes/worktree/common.ts (2)
  • isENOENT (118-120)
  • logError (143-143)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/server/src/routes/worktree/routes/status.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/worktree/common.ts (2)
  • execAsync (13-13)
  • logError (143-143)
apps/server/src/services/dev-server-service.ts (1)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/fs/routes/save-board-background.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/server/src/routes/fs/routes/write.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/server/src/routes/fs/routes/delete-board-background.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
apps/server/src/services/agent-service.ts (3)
apps/ui/src/types/electron.d.ts (1)
  • Message (13-20)
apps/server/src/lib/sdk-options.ts (1)
  • validateWorkingDirectory (36-48)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
libs/git-utils/src/diff.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
libs/git-utils/src/types.ts (1)
  • FileStatus (34-38)
apps/server/src/routes/worktree/routes/info.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/worktree/common.ts (2)
  • execAsync (13-13)
  • logError (143-143)
apps/ui/src/config/terminal-themes.ts (2)
apps/ui/src/store/app-store.ts (1)
  • ThemeMode (16-16)
libs/types/src/settings.ts (1)
  • ThemeMode (24-59)
apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx (1)
apps/ui/src/config/theme-options.ts (2)
  • darkThemes (340-340)
  • lightThemes (343-343)
apps/server/src/services/auto-mode-service.ts (8)
apps/server/src/routes/worktree/common.ts (1)
  • execAsync (13-13)
apps/ui/src/store/app-store.ts (4)
  • PlanningMode (216-216)
  • ParsedTask (241-247)
  • PlanSpec (250-261)
  • Feature (231-238)
libs/types/src/settings.ts (1)
  • PlanningMode (65-65)
init.mjs (1)
  • lines (83-83)
.github/scripts/upload-to-r2.js (1)
  • path (7-7)
apps/server/src/lib/sdk-options.ts (1)
  • validateWorkingDirectory (36-48)
apps/server/src/services/feature-loader.ts (2)
  • getFeatureDir (163-165)
  • Feature (20-20)
apps/server/src/providers/provider-factory.ts (1)
  • ProviderFactory (13-115)
apps/ui/src/store/app-store.ts (6)
libs/types/src/settings.ts (3)
  • KanbanCardDetailLevel (62-62)
  • ThemeMode (24-59)
  • AIProfile (128-145)
libs/types/src/index.ts (4)
  • KanbanCardDetailLevel (76-76)
  • ThemeMode (75-75)
  • Feature (20-20)
  • AIProfile (81-81)
apps/ui/src/components/views/settings-view/shared/types.ts (1)
  • Project (24-29)
apps/ui/src/lib/electron.ts (1)
  • Project (2657-2663)
libs/types/src/feature.ts (1)
  • Feature (15-49)
apps/ui/src/hooks/use-settings-migration.ts (2)
  • syncSettingsToServer (202-247)
  • syncProjectSettingsToServer (296-325)
apps/server/src/routes/fs/routes/save-image.ts (2)
apps/server/src/lib/secure-fs.ts (1)
  • secureFs (8-23)
apps/server/src/routes/app-spec/common.ts (1)
  • logError (57-66)
🪛 GitHub Actions: Test Suite
apps/server/src/services/dev-server-service.ts

[error] 1-1: startDevServer: AssertionError in tests — expected spawn to be called with npm run dev (or other package manager) for detection logic. The tests assert specific package manager commands (npm, yarn, pnpm, bun) but the spawn mock did not receive these calls.


[error] 1-1: Overall test suite for dev-server-service.ts failed. The following integration/unit tests did not pass, indicating issues with process spawning logic and state management for dev servers.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: claude-review

Comment on lines 40 to 43
} catch (error) {
logError(error, "Delete board background failed");
logError(error, 'Delete board background failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add PathNotAllowedError handling for consistency with other routes.

This route lacks specific handling for PathNotAllowedError, unlike other routes in this PR (exists.ts, delete.ts, stat.ts) which return 403 for path security violations. Currently, a PathNotAllowedError would result in a 500 response instead of the more appropriate 403 Forbidden.

🔎 Recommended fix for consistent security error handling
     } catch (error) {
+      // Path not allowed - return 403 Forbidden
+      if (error instanceof PathNotAllowedError) {
+        res.status(403).json({ success: false, error: getErrorMessage(error) });
+        return;
+      }
+
       logError(error, 'Delete board background failed');
       res.status(500).json({ success: false, error: getErrorMessage(error) });
     }
📝 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
} catch (error) {
logError(error, "Delete board background failed");
logError(error, 'Delete board background failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
} catch (error) {
// Path not allowed - return 403 Forbidden
if (error instanceof PathNotAllowedError) {
res.status(403).json({ success: false, error: getErrorMessage(error) });
return;
}
logError(error, 'Delete board background failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
🤖 Prompt for AI Agents
In apps/server/src/routes/fs/routes/delete-board-background.ts around lines 40
to 43, the catch block does not treat PathNotAllowedError specially so path
security violations return 500 instead of 403; update the catch to detect if
error is an instance of PathNotAllowedError (import it if not already) and call
res.status(403).json({ success: false, error: getErrorMessage(error) }) for that
case, otherwise keep the existing logError and res.status(500) behavior for
other errors.

Comment on lines 47 to 51
} catch (error) {
logError(error, "Save board background failed");
logError(error, 'Save board background failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add PathNotAllowedError handling for consistency.

Like delete-board-background.ts, this route lacks specific handling for PathNotAllowedError. For consistency with other routes in this PR (exists.ts, delete.ts, stat.ts), consider adding 403 response for path security violations.

🔎 Recommended fix for consistent security error handling
     } catch (error) {
+      // Path not allowed - return 403 Forbidden
+      if (error instanceof PathNotAllowedError) {
+        res.status(403).json({ success: false, error: getErrorMessage(error) });
+        return;
+      }
+
       logError(error, 'Save board background failed');
       res.status(500).json({ success: false, error: getErrorMessage(error) });
     }

Don't forget to import PathNotAllowedError at the top of the file:

+import { PathNotAllowedError } from '@automaker/platform';
📝 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
} catch (error) {
logError(error, "Save board background failed");
logError(error, 'Save board background failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
} catch (error) {
// Path not allowed - return 403 Forbidden
if (error instanceof PathNotAllowedError) {
res.status(403).json({ success: false, error: getErrorMessage(error) });
return;
}
logError(error, 'Save board background failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
🤖 Prompt for AI Agents
In apps/server/src/routes/fs/routes/save-board-background.ts around lines 47 to
51, the catch block only logs and returns a 500; add specific handling for
PathNotAllowedError to return a 403 like other routes. Update the imports to
include PathNotAllowedError, then in the catch: if error is an instance of
PathNotAllowedError respond with res.status(403).json({ success: false, error:
getErrorMessage(error) }); otherwise keep the existing logError and 500
response; ensure the logic order checks the PathNotAllowedError before the
generic error handling.

Comment on lines 40 to 44
} catch (error) {
logError(error, "Validate path failed");
logError(error, 'Validate path failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add PathNotAllowedError handling for consistency.

Like several other board-related routes, this lacks specific handling for PathNotAllowedError in the outer catch. For consistency with other routes in this PR (exists.ts, delete.ts, stat.ts), add a 403 response for path security violations.

🔎 Recommended fix for consistent security error handling
     } catch (error) {
+      // Path not allowed - return 403 Forbidden
+      if (error instanceof PathNotAllowedError) {
+        res.status(403).json({ success: false, error: getErrorMessage(error) });
+        return;
+      }
+
       logError(error, 'Validate path failed');
       res.status(500).json({ success: false, error: getErrorMessage(error) });
     }

Don't forget to import PathNotAllowedError at the top:

+import { PathNotAllowedError } from '@automaker/platform';
📝 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
} catch (error) {
logError(error, "Validate path failed");
logError(error, 'Validate path failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
} catch (error) {
// Path not allowed - return 403 Forbidden
if (error instanceof PathNotAllowedError) {
res.status(403).json({ success: false, error: getErrorMessage(error) });
return;
}
logError(error, 'Validate path failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
🤖 Prompt for AI Agents
In apps/server/src/routes/fs/routes/validate-path.ts around lines 40 to 44, the
outer catch block does not handle PathNotAllowedError specifically; update the
catch to check if error is an instance of PathNotAllowedError and if so return
res.status(403).json({ success: false, error: getErrorMessage(error) }),
otherwise keep the existing logging and 500 response; also add an import for
PathNotAllowedError at the top of the file to ensure the instanceof check works.

Comment on lines +31 to 44
try {
await secureFs.access(gitDirPath);
// .git exists
res.json({
success: true,
result: {
initialized: false,
message: "Git repository already exists",
message: 'Git repository already exists',
},
});
return;
} catch {
// .git doesn't exist, continue with initialization
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Empty catch block may mask security errors from secureFs.

The catch block assumes any error from secureFs.access means ".git doesn't exist", but secureFs may throw security-related errors (e.g., path traversal attempts). Catching all errors and proceeding with initialization could bypass security checks.

Consider checking the error type or code to distinguish "file not found" from security violations:

🔎 Proposed fix
       try {
         await secureFs.access(gitDirPath);
         // .git exists
         res.json({
           success: true,
           result: {
             initialized: false,
             message: 'Git repository already exists',
           },
         });
         return;
-      } catch {
-        // .git doesn't exist, continue with initialization
+      } catch (err) {
+        // Only continue if .git doesn't exist (ENOENT)
+        if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
+          throw err; // Re-throw security or other errors
+        }
+        // .git doesn't exist, continue with initialization
       }

Comment on lines 46 to +49
// Initialize git and create an initial empty commit
await execAsync(
`git init && git commit --allow-empty -m "Initial commit"`,
{ cwd: projectPath }
);
await execAsync(`git init && git commit --allow-empty -m "Initial commit"`, {
cwd: projectPath,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

User-controlled projectPath used in shell execution without validation.

The projectPath is taken directly from req.body and used as cwd for execAsync, bypassing the secureFs security layer. An attacker could specify any directory path (e.g., "/etc", "/var/www") to initialize git repositories in unintended locations.

The PR mentions adding validateWorkingDirectory in sdk-options.ts—consider applying similar validation here before executing shell commands:

🔎 Proposed fix (example pattern)
+import { validateWorkingDirectory } from '../../../lib/validate-path.js'; // adjust import as needed

 export function createInitGitHandler() {
   return async (req: Request, res: Response): Promise<void> => {
     try {
       const { projectPath } = req.body as {
         projectPath: string;
       };

       if (!projectPath) {
         res.status(400).json({
           success: false,
           error: 'projectPath required',
         });
         return;
       }

+      // Validate projectPath is within allowed directories
+      validateWorkingDirectory(projectPath);
+
       // Check if .git already exists
       const gitDirPath = join(projectPath, '.git');

Run the following script to check if other worktree routes validate projectPath before shell execution:

#!/bin/bash
# Check how other routes handle projectPath validation before execAsync/exec calls
rg -n -B5 -A5 'execAsync|exec\(' apps/server/src/routes/worktree/
🤖 Prompt for AI Agents
In apps/server/src/routes/worktree/routes/init-git.ts around lines 46-49, the
code uses the user-provided projectPath directly as cwd for execAsync which
bypasses secureFs and allows initializing git in arbitrary filesystem locations;
before calling execAsync, run the same validateWorkingDirectory (or equivalent
logic from sdk-options.ts) to ensure projectPath is within the allowed workspace
root, normalize and resolve the path, reject or return 400 on validation
failure, and then use the validated/resolved path as cwd (or perform the git
init via the secureFs layer) so no unvalidated user path is passed to shell
execution.


{/* Nav Items - Scrollable */}
<nav className={cn("flex-1 overflow-y-auto px-3 pb-2", sidebarOpen ? "mt-5" : "mt1")}>
<nav className={cn('flex-1 overflow-y-auto px-3 pb-2', sidebarOpen ? 'mt-5' : 'mt1')}>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix Tailwind class typo: mt1 should be mt-1.

The class mt1 is not a valid Tailwind utility. It should be mt-1 to add margin-top.

🔎 Proposed fix
-        <nav className={cn('flex-1 overflow-y-auto px-3 pb-2', sidebarOpen ? 'mt-5' : 'mt1')}>
+        <nav className={cn('flex-1 overflow-y-auto px-3 pb-2', sidebarOpen ? 'mt-5' : 'mt-1')}>
📝 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
<nav className={cn('flex-1 overflow-y-auto px-3 pb-2', sidebarOpen ? 'mt-5' : 'mt1')}>
<nav className={cn('flex-1 overflow-y-auto px-3 pb-2', sidebarOpen ? 'mt-5' : 'mt-1')}>
🤖 Prompt for AI Agents
In apps/ui/src/components/layout/sidebar.tsx around line 1751, the Tailwind
class string uses an invalid utility 'mt1'; replace it with the correct 'mt-1'
so the conditional class becomes sidebarOpen ? 'mt-5' : 'mt-1' to apply the
intended top margin when sidebarOpen is false.

Comment on lines +54 to +78
<button
onClick={() => setActiveTab('dark')}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200',
activeTab === 'dark'
? 'bg-brand-500 text-white shadow-sm'
: 'text-muted-foreground hover:text-foreground'
)}
>
<Moon className="w-3.5 h-3.5" />
Dark
</button>
<button
onClick={() => setActiveTab('light')}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200',
activeTab === 'light'
? 'bg-brand-500 text-white shadow-sm'
: 'text-muted-foreground hover:text-foreground'
)}
>
<Sun className="w-3.5 h-3.5" />
Light
</button>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add type="button" to tab buttons.

Same concern as in theme-step.tsx: these buttons default to type="submit" and could cause issues if the component is placed inside a form.

🔎 Proposed fix
              <button
+               type="button"
                onClick={() => setActiveTab('dark')}
                className={cn(
                  'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200',
                  activeTab === 'dark'
                    ? 'bg-brand-500 text-white shadow-sm'
                    : 'text-muted-foreground hover:text-foreground'
                )}
              >
                <Moon className="w-3.5 h-3.5" />
                Dark
              </button>
              <button
+               type="button"
                onClick={() => setActiveTab('light')}
                className={cn(
📝 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
<button
onClick={() => setActiveTab('dark')}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200',
activeTab === 'dark'
? 'bg-brand-500 text-white shadow-sm'
: 'text-muted-foreground hover:text-foreground'
)}
>
<Moon className="w-3.5 h-3.5" />
Dark
</button>
<button
onClick={() => setActiveTab('light')}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200',
activeTab === 'light'
? 'bg-brand-500 text-white shadow-sm'
: 'text-muted-foreground hover:text-foreground'
)}
>
<Sun className="w-3.5 h-3.5" />
Light
</button>
</div>
<button
type="button"
onClick={() => setActiveTab('dark')}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200',
activeTab === 'dark'
? 'bg-brand-500 text-white shadow-sm'
: 'text-muted-foreground hover:text-foreground'
)}
>
<Moon className="w-3.5 h-3.5" />
Dark
</button>
<button
type="button"
onClick={() => setActiveTab('light')}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200',
activeTab === 'light'
? 'bg-brand-500 text-white shadow-sm'
: 'text-muted-foreground hover:text-foreground'
)}
>
<Sun className="w-3.5 h-3.5" />
Light
</button>
</div>
🤖 Prompt for AI Agents
In apps/ui/src/components/views/settings-view/appearance/appearance-section.tsx
around lines 54 to 78, the Dark/Light tab buttons are missing an explicit type
attribute and will default to type="submit" when inside a form; update both
<button> elements to include type="button" to prevent accidental form submits,
leaving all other props and className logic unchanged.

name: string;
path: string;
theme?: Theme;
theme?: string;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find the types.ts file and examine its current state
cat -n "apps/ui/src/components/views/settings-view/shared/types.ts" | head -50

Repository: AutoMaker-Org/automaker

Length of output: 1041


🏁 Script executed:

#!/bin/bash
# Search for how Project.theme is used in the codebase
rg -n "\.theme\s*=" apps/ui/src --type=ts -A 2 -B 2 | head -100

Repository: AutoMaker-Org/automaker

Length of output: 911


🏁 Script executed:

#!/bin/bash
# Search for where Project objects are created or modified with theme values
rg -n "theme\s*:\s*" apps/ui/src --type=ts -B 2 -A 2 | grep -A 5 -B 5 "Project\|theme:" | head -150

Repository: AutoMaker-Org/automaker

Length of output: 8177


🏁 Script executed:

#!/bin/bash
# Check what ThemeMode and Theme types are
rg -n "type ThemeMode|type Theme|export.*Theme" apps/ui/src --type=ts | head -50

Repository: AutoMaker-Org/automaker

Length of output: 1472


🏁 Script executed:

#!/bin/bash
# Get the full Theme type definition
sed -n '30,40p' apps/ui/src/config/theme-options.ts

Repository: AutoMaker-Org/automaker

Length of output: 247


🏁 Script executed:

#!/bin/bash
# Get the full Theme type definition
sed -n '32,67p' apps/ui/src/config/theme-options.ts

Repository: AutoMaker-Org/automaker

Length of output: 560


🏁 Script executed:

#!/bin/bash
# Check what ThemeMode is
rg -n "type ThemeMode\s*=" apps/ui/src --type=ts -A 5

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Search for ThemeMode definition
rg -n "ThemeMode" apps/ui/src/store/app-store.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 793


🏁 Script executed:

#!/bin/bash
# Check the imports at the top of app-store.ts
head -20 apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 503


🏁 Script executed:

#!/bin/bash
# Check what ThemeMode actually is - search in the electron types
find . -name "*.ts" -type f -exec grep -l "type ThemeMode\s*=" {} \;

Repository: AutoMaker-Org/automaker

Length of output: 95


🏁 Script executed:

#!/bin/bash
# Check the ThemeMode definition
rg -n "type ThemeMode" libs/types/src/settings.ts -A 10

Repository: AutoMaker-Org/automaker

Length of output: 267


🏁 Script executed:

#!/bin/bash
# Now let me check one more thing - see if ThemeMode includes all Theme values
sed -n '24,60p' libs/types/src/settings.ts

Repository: AutoMaker-Org/automaker

Length of output: 577


🏁 Script executed:

#!/bin/bash
# Let me check the casting in settings-view.tsx more closely
sed -n '55,65p' "apps/ui/src/components/views/settings-view.tsx"

Repository: AutoMaker-Org/automaker

Length of output: 305


🏁 Script executed:

#!/bin/bash
# Check if there are any direct string assignments to project.theme (not from ThemeMode)
rg -n "theme\s*:\s*['\"]" apps/ui/src --type=ts | grep -v "theme:" | head -20

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Let me check the exact type that should be used by looking at how project.theme is assigned
rg -n "project\.theme\s*=" apps/ui/src/store/app-store.ts -B 2 -A 2

Repository: AutoMaker-Org/automaker

Length of output: 49


🏁 Script executed:

#!/bin/bash
# Check what values are actually assigned to project.theme in the store
rg -n "p\.id === projectId.*theme" apps/ui/src/store/app-store.ts -B 3 -A 3

Repository: AutoMaker-Org/automaker

Length of output: 372


Use ThemeMode type for proper type safety.

Changing theme from string to a specific type is necessary for type safety. However, the correct type should be ThemeMode (not just Theme), since the store assigns ThemeMode | null to this field. ThemeMode includes all valid theme values plus the 'system' mode:

 export interface Project {
   id: string;
   name: string;
   path: string;
-  theme?: string;
+  theme?: ThemeMode;
 }

Import ThemeMode from '@automaker/types' if not already available, or re-export it alongside Theme.

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

🤖 Prompt for AI Agents
In apps/ui/src/components/views/settings-view/shared/types.ts around line 28,
the theme property is currently typed as string but should be ThemeMode to match
the store which assigns ThemeMode | null; update the type to theme?: ThemeMode
and add an import for ThemeMode from '@automaker/types' (or re-export it
alongside Theme) so the file uses the correct union of valid theme values
including 'system'.

Comment on lines +43 to +67
<button
onClick={() => setActiveTab('dark')}
className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-lg font-medium transition-all duration-200',
activeTab === 'dark'
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
: 'bg-accent/50 text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<Moon className="w-4 h-4" />
Dark Themes
</button>
<button
onClick={() => setActiveTab('light')}
className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-lg font-medium transition-all duration-200',
activeTab === 'light'
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
: 'bg-accent/50 text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<Sun className="w-4 h-4" />
Light Themes
</button>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add type="button" to prevent unintended form submissions.

Native <button> elements default to type="submit". If this component is ever rendered inside a form, these tabs could inadvertently submit it.

🔎 Proposed fix
        <button
+         type="button"
          onClick={() => setActiveTab('dark')}
          className={cn(
            'flex items-center gap-2 px-6 py-2.5 rounded-lg font-medium transition-all duration-200',
            activeTab === 'dark'
              ? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
              : 'bg-accent/50 text-muted-foreground hover:bg-accent hover:text-foreground'
          )}
        >
          <Moon className="w-4 h-4" />
          Dark Themes
        </button>
        <button
+         type="button"
          onClick={() => setActiveTab('light')}
          className={cn(
📝 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
<button
onClick={() => setActiveTab('dark')}
className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-lg font-medium transition-all duration-200',
activeTab === 'dark'
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
: 'bg-accent/50 text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<Moon className="w-4 h-4" />
Dark Themes
</button>
<button
onClick={() => setActiveTab('light')}
className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-lg font-medium transition-all duration-200',
activeTab === 'light'
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
: 'bg-accent/50 text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<Sun className="w-4 h-4" />
Light Themes
</button>
</div>
<button
type="button"
onClick={() => setActiveTab('dark')}
className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-lg font-medium transition-all duration-200',
activeTab === 'dark'
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
: 'bg-accent/50 text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<Moon className="w-4 h-4" />
Dark Themes
</button>
<button
type="button"
onClick={() => setActiveTab('light')}
className={cn(
'flex items-center gap-2 px-6 py-2.5 rounded-lg font-medium transition-all duration-200',
activeTab === 'light'
? 'bg-brand-500 text-white shadow-lg shadow-brand-500/25'
: 'bg-accent/50 text-muted-foreground hover:bg-accent hover:text-foreground'
)}
>
<Sun className="w-4 h-4" />
Light Themes
</button>
</div>
🤖 Prompt for AI Agents
In apps/ui/src/components/views/setup-view/steps/theme-step.tsx around lines 43
to 67, the two <button> elements lack an explicit type, so they default to
type="submit" and can unintentionally submit enclosing forms; update both
buttons to include type="button" to prevent accidental form submission while
preserving their onClick handlers and styles.

Comment on lines +393 to +430
:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.red,
.sunset,
.gray
)
::-webkit-scrollbar {
width: 8px;
height: 8px;
}

:is(.dark, .retro, .dracula, .nord, .monokai, .tokyonight, .solarized, .gruvbox, .catppuccin, .onedark, .synthwave, .red, .sunset, .gray) ::-webkit-scrollbar-track {
:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.red,
.sunset,
.gray
)
::-webkit-scrollbar-track {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing forest and ocean themes in scrollbar selectors.

The new dark themes forest and ocean (declared on lines 19-20) are not included in the scrollbar styling selectors. This may cause inconsistent scrollbar appearance for these themes.

🔎 Proposed fix
 :is(
     .dark,
     .retro,
     .dracula,
     .nord,
     .monokai,
     .tokyonight,
     .solarized,
     .gruvbox,
     .catppuccin,
     .onedark,
     .synthwave,
     .red,
     .sunset,
-    .gray
+    .gray,
+    .forest,
+    .ocean
   )
   ::-webkit-scrollbar {

Apply the same fix to the ::-webkit-scrollbar-track selector on lines 414-430.

📝 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
:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.red,
.sunset,
.gray
)
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
:is(.dark, .retro, .dracula, .nord, .monokai, .tokyonight, .solarized, .gruvbox, .catppuccin, .onedark, .synthwave, .red, .sunset, .gray) ::-webkit-scrollbar-track {
:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.red,
.sunset,
.gray
)
::-webkit-scrollbar-track {
:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.red,
.sunset,
.gray,
.forest,
.ocean
)
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
:is(
.dark,
.retro,
.dracula,
.nord,
.monokai,
.tokyonight,
.solarized,
.gruvbox,
.catppuccin,
.onedark,
.synthwave,
.red,
.sunset,
.gray,
.forest,
.ocean
)
::-webkit-scrollbar-track {
🤖 Prompt for AI Agents
In apps/ui/src/styles/global.css around lines 393 to 430 the ::-webkit-scrollbar
and ::-webkit-scrollbar-track selectors list theme classes but omit the new dark
themes forest and ocean; update both selector lists to include .forest and
.ocean so those themes receive the same scrollbar and scrollbar-track styling as
the other dark themes.

This commit modifies the unit tests in dev-server-service.test.ts to replace the usage of the native fs.existsSync method with secureFs.access for improved security and consistency in file handling. Key changes include:

- Updated all instances of existsSync to utilize secureFs.access, ensuring that file existence checks are performed using the secure file system operations.
- Adjusted mock implementations to reflect the new secureFs methods, enhancing the reliability of the tests.

These changes aim to align the testing strategy with the recent refactor for enhanced security in file operations.
@webdevcody webdevcody merged commit 3b39df4 into main Dec 21, 2025
4 of 6 checks passed
@webdevcody webdevcody deleted the refactoring-themes branch December 21, 2025 06:41
@claude
Copy link

claude bot commented Dec 21, 2025

Code Review - PR #206: Security-focused fs to secureFs Refactor

📊 Executive Summary

This PR implements a significant security improvement by migrating from native fs to a centralized secureFs module with path validation. The architecture is well-designed with defense-in-depth, but critical gaps in migration completeness and test coverage must be addressed before merge.

Recommendation: 🔴 Request Changes - Incomplete migration and missing security tests are blocking issues.


✅ Strengths

1. Excellent Security Architecture

  • Defense-in-depth: 3-layer validation (API middleware → secureFs → AI validation)
  • Centralized validation: validateWorkingDirectory() in sdk-options.ts provides single security checkpoint for ALL AI operations
  • Proper path traversal protection: Correct implementation preventing ../ attacks
  • Clear error messages: PathNotAllowedError provides actionable feedback

2. Consistent Refactoring Pattern

  • Clean migration pattern across 50+ files
  • Proper type assertions for readFile() encoding
  • Error handling preserved during migration
  • TypeScript overloads for readdir() with withFileTypes

3. Theme System Enhancements

  • 32+ new themes with good organization (Dark/Light tabs)
  • Deferred theme application for better UX
  • Expanded terminal theme support

🚨 Critical Issues (Must Fix Before Merge)

1. Incomplete Migration - Security Bypass Risk

HIGH SEVERITY: Critical files still using native fs, bypassing security validation entirely:

Files requiring immediate migration:

  • apps/server/src/services/terminal-service.ts (line 11: import * as fs from "fs")
  • apps/server/src/routes/setup/common.ts (line 7: import fs from "fs/promises")
  • apps/server/src/routes/setup/routes/delete-api-key.ts
  • apps/server/src/routes/setup/routes/gh-status.ts
  • apps/server/src/routes/setup/get-claude-status.ts

Impact: Terminal service and setup routes (API key management!) can access files outside ALLOWED_ROOT_DIRECTORY.

Recommendation:

# Find all remaining fs imports
grep -r "from ['\"]fs['\"]" apps/server/src/
grep -r "from ['\"]fs/promises['\"]" apps/server/src/

Complete migration for all findings.

2. Zero Test Coverage for Critical Security Feature

CRITICAL: validateWorkingDirectory() is the centerpiece of AI security validation but has zero tests.

Missing tests:

describe('validateWorkingDirectory', () => {
  it('should throw PathNotAllowedError when path outside ALLOWED_ROOT_DIRECTORY');
  it('should allow path within ALLOWED_ROOT_DIRECTORY');
  it('should resolve relative paths before validation');
  it('should allow DATA_DIR paths as exception');
  it('should throw with descriptive error message');
});

Also missing:

  • Tests for SDK options factories (createChatOptions, createAutoModeOptions, etc.) verifying security validation
  • Integration tests for full security stack
  • Error handling tests for PathNotAllowedError in routes

Recommendation: Add comprehensive test suite before merge. This is a security-critical feature.

3. PathNotAllowedError Constructor Mismatch

Bug in sdk-options.ts:154:

// libs/platform/src/security.ts - Constructor signature
export class PathNotAllowedError extends Error {
  constructor(filePath: string) {  // Takes single param
    super(`Path not allowed: ${filePath}. Must be within...`);

// apps/server/src/lib/sdk-options.ts - Usage
throw new PathNotAllowedError(  // ❌ Tries to pass custom message
  `Working directory "${cwd}" (resolved: ${resolvedCwd}) is not allowed...`
);

Fix options:

  1. Update PathNotAllowedError to accept optional custom message
  2. Update sdk-options.ts to pass only the filepath parameter

4. Security Errors Swallowed in Error Handlers

In libs/git-utils/src/diff.ts:

} catch (error) {
  logger.error(`Failed to generate synthetic diff for ${fullPath}:`, error);
  return `[Unable to read file content]`;  // ❌ Hides PathNotAllowedError
}

Issue: Users see "Unable to read file content" instead of explicit security violation message.

Fix:

} catch (error) {
  if (error instanceof PathNotAllowedError) {
    throw error;  // Don't swallow security errors
  }
  logger.error(`Failed to generate synthetic diff for ${fullPath}:`, error);
  return `[Unable to read file content]`;
}

⚠️ High Priority Issues

5. Type Safety Issues

a) secureFs.stat() has any return type:

// libs/platform/src/secure-fs.ts
export async function stat(filePath: string): Promise<any> {  // ❌ Should be Promise<Stats>

b) Fragile type casting pattern (16+ occurrences):

const content = (await secureFs.readFile(metadataPath, 'utf-8')) as string;

Better approach: Use function overloads to eliminate casting:

export async function readFile(filePath: string, encoding: BufferEncoding): Promise<string>;
export async function readFile(filePath: string): Promise<Buffer>;

6. Missing Production Safety Checks

Risk: ALLOWED_ROOT_DIRECTORY defaults to allowing ALL paths when not configured.

Recommendations:

  • Add startup validation for ALLOWED_ROOT_DIRECTORY configuration
  • Log prominent WARNING if not configured in production
  • Consider fail-closed mode option for production deployments

🟡 Medium Priority Issues

7. Code Style Inconsistencies

  • Mixed single/double quotes across files
  • sdk-options.ts switched to single quotes, but inconsistently applied
  • Some files use import * as secureFs while others use named imports

Recommendation: Pick a style and apply consistently (consider Prettier/ESLint auto-fix).

8. Performance Considerations

a) Package manager detection has potential race condition:

// dev-server-service.ts - Sequential checks
if (await secureFs.access(...)) // bun
if (await secureFs.access(...)) // pnpm  
if (await secureFs.access(...)) // yarn

Better: Use Promise.all() for atomic detection.

b) Redundant path validation:

  • validateWorkingDirectory() in sdk-options
  • Then secureFs validates again on every file operation
  • Not critical (path.resolve is fast) but could optimize with validation cache

9. Theme Implementation

Without full visibility into theme loading mechanism:

  • Verify deferred loading doesn't cause FOUC (Flash of Unstyled Content)
  • Consider visual regression tests for 32 new themes
  • Validate color contrast ratios for accessibility compliance

🟢 Nice to Have / Future Work

  1. Symbolic link validation: Validate symlink targets are within allowed paths
  2. Audit logging: Log all PathNotAllowedError violations for security monitoring
  3. Environment-specific configs: Different validation rules for dev vs production
  4. Documentation: Add security configuration guide and migration notes to CHANGELOG

📋 Testing Recommendations

Unit Tests Needed:

  • validateWorkingDirectory() comprehensive test suite
  • All create*Options() factories with security validation
  • secureFs wrapper functions
  • PathNotAllowedError error handling in routes

Integration Tests Needed:

  • Full security stack: API → middleware → secureFs → validation
  • AI operations with invalid working directories
  • Terminal service path validation
  • Setup routes security (especially API key operations)

E2E Tests:

  • Theme switching with deferred loading
  • Security error handling in production mode

🎯 Actionable Steps to Merge-Ready

Must Do (Blocking):

  1. ✅ Complete fs → secureFs migration (terminal-service, setup routes)
  2. ✅ Add test coverage for validateWorkingDirectory()
  3. ✅ Fix PathNotAllowedError constructor mismatch
  4. ✅ Fix security error handling in git-utils/diff.ts

Should Do (High Priority):

  1. ⚠️ Fix type safety (secureFs.stat() return type, readFile overloads)
  2. ⚠️ Add startup validation for ALLOWED_ROOT_DIRECTORY
  3. ⚠️ Add integration tests for security stack

Nice to Have:

  1. 📝 Code style consistency pass
  2. 📝 Performance optimizations (Promise.all, validation cache)
  3. 📝 Documentation updates

🏁 Conclusion

This PR implements a well-architected security improvement with solid defense-in-depth design. The validateWorkingDirectory() centralization is particularly elegant. However, the incomplete migration leaves critical security gaps in terminal and setup routes, and the complete lack of tests for the core security feature is a significant risk.

Once the critical issues are resolved, this will be an excellent security enhancement that significantly improves the codebase's security posture.

Estimated effort to address critical issues: 4-6 hours (migration + tests)

Great work on the security architecture! 🎉 Just need to finish the migration and add test coverage.


Questions? Happy to clarify any recommendations or help with implementation.

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