Skip to content

Conversation

@JoshuaRileyDev
Copy link
Contributor

@JoshuaRileyDev JoshuaRileyDev commented Dec 24, 2025

This is a comprehensive template system for scaffolding projects quickly, it allows you to create templates but also work with dynamic values which will show on a form when you create a new project, it also has an inbuilt secrets manager for securely storing your secrets which will be encrypted.

Here's a few screenshots:

Screenshot 2025-12-24 at 16 35 44 Screenshot 2025-12-24 at 16 35 57 Screenshot 2025-12-24 at 16 36 15 Screenshot 2025-12-24 at 16 36 32

Summary by CodeRabbit

  • New Features

    • Template management: create/edit templates, parse parameters, copy templates (with parameter substitution) and in-app Parameter Guide.
    • Encrypted secrets store: manage secret groups & accounts, encrypt/decrypt keys, use secrets when creating projects.
    • Repository cloning: clone Git repositories from the app.
    • NewProjectScreen: unified project creation flow (templates, folders, clone, recent).
  • Settings / UI

    • Templates and Secrets settings, dialogs to add/edit templates/groups/accounts, default projects path, larger dialog close button.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 24, 2025

📝 Walkthrough

Walkthrough

Adds a template system, an encrypted secrets store, and Git clone support across main, preload, and renderer layers; implements filesystem-backed persistence, template parsing/replacement, IPC channels and preload APIs, and new UI for templates, secrets, and project creation.

Changes

Cohort / File(s) Summary
IPC registration & constants
apps/frontend/src/main/ipc-handlers/index.ts, apps/frontend/src/shared/constants/ipc.ts
Register and export template & secrets IPC handlers; add IPC channel constants for templates, secrets, and git:cloneRepository.
Main — utilities
apps/frontend/src/main/template-parser.ts, apps/frontend/src/main/secrets-encryption.ts
New template parsing/replacement utilities and a SecretsEncryption wrapper around Electron safeStorage.
Main — IPC handlers
apps/frontend/src/main/ipc-handlers/template-handlers.ts, apps/frontend/src/main/ipc-handlers/secrets-handlers.ts, apps/frontend/src/main/ipc-handlers/project-handlers.ts
New IPC handlers: template CRUD/copy/parameterized copy, secrets store CRUD/accounts with encryption persisted to JSON, and git clone repository (execSync) with validation and error reporting.
Preload API
apps/frontend/src/preload/api/index.ts, apps/frontend/src/preload/api/template-api.ts, apps/frontend/src/preload/api/secrets-api.ts, apps/frontend/src/preload/api/project-api.ts
Expose TemplateAPI and SecretsAPI via preload; extend ElectronAPI/ProjectAPI to include template, secrets, and cloneRepository methods that call new IPC channels.
Shared types & settings
apps/frontend/src/shared/types/template.ts, apps/frontend/src/shared/types/secrets.ts, apps/frontend/src/shared/types/ipc.ts, apps/frontend/src/shared/types/index.ts, apps/frontend/src/shared/types/settings.ts
Add Template and Secrets type definitions (Template, ParsedTemplate, SecretGroup, SecretAccount, store/input shapes), extend IPC typings, and add projectPath/defaultSecretAccounts to AppSettings.
Renderer — Templates & Secrets UIs
apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx, apps/frontend/src/renderer/components/settings/SecretsManager.tsx, apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx, apps/frontend/src/renderer/components/settings/AddSecretGroupDialog.tsx, apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx, apps/frontend/src/renderer/components/settings/AppSettings.tsx, apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
New Templates settings UI, Secrets Manager, and dialogs for creating/editing templates, secret groups, and accounts; templates navigation and default projects path selector.
Renderer — Project creation flow
apps/frontend/src/renderer/components/NewProjectScreen.tsx, apps/frontend/src/renderer/App.tsx
Add NewProjectScreen and integrate into App for import, clone, and template-based project creation flows (parameter & secret selection, post-create initialization).
Renderer — mocks & minor UI
apps/frontend/src/renderer/lib/browser-mock.ts, apps/frontend/src/renderer/lib/mocks/project-mock.ts, apps/frontend/src/renderer/components/ui/dialog.tsx
Add browser mocks for templates & secrets, project mock cloneRepository, and tweak dialog close button sizing.

Sequence Diagram(s)

sequenceDiagram
  participant UI as Renderer (NewProjectScreen)
  participant Pre as Preload API
  participant Main as Main (IPC)
  participant FS as Filesystem
  participant Enc as SecretsEncryption

  Note over UI,Pre: Create project from template with secret parameters
  UI->>Pre: copyTemplateWithParameters(templateId, dest, name, values)
  Pre->>Main: invoke TEMPLATES_COPY_WITH_PARAMETERS
  Main->>FS: read templates.json & template files
  Main->>Main: parseTemplateDirectory(templatePath)
  alt secret parameters present
    Main->>Main: invoke SECRETS_DECRYPT_ACCOUNT / SECRETS_DECRYPT_ACCOUNT_KEY
    Main->>Enc: decrypt encrypted values
    Enc-->>Main: decrypted values
  end
  Main->>FS: copy files, replace placeholders (replaceTemplateParameters)
  Main-->>Pre: IPCResult<{ path }>
  Pre-->>UI: result / error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Suggested labels

size/L

Poem

🐰 I hopped through folders, copying with care,

I tucked the keys where secrets sleep with flair.
A repo was fetched, a template sewn tight,
Projects awoke beneath the soft moonlight. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% 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 title accurately describes the main change: introducing a comprehensive template system for scaffolding projects, which is the primary feature across all modified files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89f2a85 and 358f4a1.

📒 Files selected for processing (9)
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/i18n/locales/en/settings.json
  • apps/frontend/src/shared/i18n/locales/fr/settings.json
  • apps/frontend/src/shared/types/ipc.ts
  • apps/frontend/src/shared/types/settings.ts
🧰 Additional context used
📓 Path-based instructions (3)
apps/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/frontend/src/**/*.{ts,tsx}: Always use translation keys with useTranslation() for all user-facing text in React/TypeScript frontend components - use format namespace:section.key (e.g., navigation:items.githubPRs)
Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from apps/frontend/src/shared/i18n/locales/

Files:

  • apps/frontend/src/shared/types/settings.ts
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/types/ipc.ts
apps/frontend/**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

apps/frontend/**/*.{ts,tsx}: Review React patterns and TypeScript type safety.
Check for proper state management and component composition.

Files:

  • apps/frontend/src/shared/types/settings.ts
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/types/ipc.ts
apps/frontend/src/shared/i18n/locales/**/*.json

📄 CodeRabbit inference engine (CLAUDE.md)

Add new translation keys to ALL language files (minimum: en/*.json and fr/*.json) in apps/frontend/src/shared/i18n/locales/

Files:

  • apps/frontend/src/shared/i18n/locales/fr/settings.json
  • apps/frontend/src/shared/i18n/locales/en/settings.json
🧠 Learnings (3)
📚 Learning: 2025-12-25T18:29:32.954Z
Learnt from: CR
Repo: AndyMik90/Auto-Claude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-25T18:29:32.954Z
Learning: Applies to apps/frontend/src/shared/i18n/locales/**/*.json : Add new translation keys to ALL language files (minimum: `en/*.json` and `fr/*.json`) in `apps/frontend/src/shared/i18n/locales/`

Applied to files:

  • apps/frontend/src/shared/i18n/locales/fr/settings.json
  • apps/frontend/src/shared/i18n/locales/en/settings.json
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
📚 Learning: 2025-12-25T18:29:32.954Z
Learnt from: CR
Repo: AndyMik90/Auto-Claude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-25T18:29:32.954Z
Learning: Applies to apps/frontend/src/**/*.{ts,tsx} : Always use translation keys with `useTranslation()` for all user-facing text in React/TypeScript frontend components - use format `namespace:section.key` (e.g., `navigation:items.githubPRs`)

Applied to files:

  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
📚 Learning: 2025-12-25T18:29:32.954Z
Learnt from: CR
Repo: AndyMik90/Auto-Claude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-25T18:29:32.954Z
Learning: Applies to apps/frontend/src/**/*.{ts,tsx} : Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from `apps/frontend/src/shared/i18n/locales/`

Applied to files:

  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/shared/types/ipc.ts
🧬 Code graph analysis (4)
apps/frontend/src/renderer/components/settings/SecretsManager.tsx (7)
apps/frontend/src/shared/types/secrets.ts (1)
  • SecretGroup (17-26)
apps/frontend/src/renderer/components/settings/utils/hookProxyFactory.ts (1)
  • error (18-18)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
.design-system/src/lib/icons.ts (3)
  • Plus (30-30)
  • Star (34-34)
  • ChevronRight (23-23)
.design-system/src/components/Card.tsx (1)
  • Card (10-24)
apps/frontend/src/renderer/components/settings/AddSecretGroupDialog.tsx (1)
  • AddSecretGroupDialog (24-266)
apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx (1)
  • AddSecretAccountDialog (23-165)
apps/frontend/src/renderer/App.tsx (4)
apps/frontend/src/main/project-store.ts (1)
  • addProject (74-109)
apps/frontend/src/renderer/stores/project-store.ts (1)
  • loadProjects (237-329)
apps/frontend/src/shared/types/template.ts (1)
  • Template (5-13)
apps/frontend/src/renderer/components/NewProjectScreen.tsx (1)
  • NewProjectScreen (34-777)
apps/frontend/src/renderer/components/settings/GeneralSettings.tsx (3)
apps/frontend/src/renderer/components/settings/utils/hookProxyFactory.ts (1)
  • settings (15-15)
.design-system/src/components/Input.tsx (1)
  • Input (4-24)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx (4)
apps/frontend/src/shared/types/template.ts (1)
  • Template (5-13)
apps/frontend/src/renderer/components/ui/dialog.tsx (6)
  • Dialog (116-116)
  • DialogContent (121-121)
  • DialogHeader (122-122)
  • DialogTitle (124-124)
  • DialogDescription (125-125)
  • DialogFooter (123-123)
.design-system/src/components/Input.tsx (1)
  • Input (4-24)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
🪛 Biome (2.1.2)
apps/frontend/src/renderer/components/settings/SecretsManager.tsx

[error] 17-20: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 312-312: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 312-312: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

⏰ 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: CodeQL (python)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (5)
apps/frontend/src/shared/i18n/locales/fr/settings.json (1)

257-331: LGTM! Translation keys properly added to both language files.

The new templates and secrets sections are complete and correctly structured. The French translations match the English keys in en/settings.json, adhering to the coding guideline that requires adding translation keys to ALL language files.

Based on coding guidelines.

apps/frontend/src/shared/types/settings.ts (1)

91-91: LGTM! Type additions are clean and well-documented.

The new optional properties projectPath and defaultSecretAccounts are properly typed and documented. These additions support the new template and secrets management features without breaking existing code.

Also applies to: 123-125

apps/frontend/src/shared/i18n/locales/en/settings.json (1)

257-331: LGTM! Translation keys properly added.

The new templates and secrets sections provide comprehensive localization support for the new features. The keys match the French translations in fr/settings.json, correctly following the coding guideline requirement.

Based on coding guidelines.

apps/frontend/src/shared/constants/ipc.ts (1)

111-133: LGTM! IPC channel constants are well-organized.

The new channel constants for Templates, Secrets, and Git operations follow the existing naming conventions and are properly namespaced. These additions align with the new IPC handlers and preload APIs introduced in this PR.

Also applies to: 369-369

apps/frontend/src/shared/types/ipc.ts (1)

581-581: Type signature is appropriate; backend security concerns already flagged.

The type signature for cloneRepository is correct and follows the file's conventions. The past review comment comprehensively addresses the security vulnerabilities in the backend handler implementation (shell injection, path traversal, protocol validation, and resource limits).

From a type definition perspective, this signature appropriately defines the API surface.

Ensure the backend handler security improvements from the past review comment are implemented.


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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 28

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e8c530 and 02d811e.

⛔ Files ignored due to path filters (1)
  • apps/frontend/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
📒 Files selected for processing (26)
  • apps/frontend/src/main/ipc-handlers/index.ts
  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
  • apps/frontend/src/main/secrets-encryption.ts
  • apps/frontend/src/main/template-parser.ts
  • apps/frontend/src/preload/api/index.ts
  • apps/frontend/src/preload/api/project-api.ts
  • apps/frontend/src/preload/api/secrets-api.ts
  • apps/frontend/src/preload/api/template-api.ts
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/renderer/components/NewProjectScreen.tsx
  • apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx
  • apps/frontend/src/renderer/components/settings/AddSecretGroupDialog.tsx
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/renderer/components/settings/AppSettings.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx
  • apps/frontend/src/renderer/components/ui/dialog.tsx
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/types/index.ts
  • apps/frontend/src/shared/types/ipc.ts
  • apps/frontend/src/shared/types/secrets.ts
  • apps/frontend/src/shared/types/settings.ts
  • apps/frontend/src/shared/types/template.ts
🧰 Additional context used
📓 Path-based instructions (1)
apps/frontend/**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

apps/frontend/**/*.{ts,tsx}: Review React patterns and TypeScript type safety.
Check for proper state management and component composition.

Files:

  • apps/frontend/src/shared/types/index.ts
  • apps/frontend/src/preload/api/project-api.ts
  • apps/frontend/src/main/ipc-handlers/index.ts
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/shared/types/secrets.ts
  • apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx
  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/main/secrets-encryption.ts
  • apps/frontend/src/renderer/components/settings/AppSettings.tsx
  • apps/frontend/src/shared/types/template.ts
  • apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx
  • apps/frontend/src/preload/api/secrets-api.ts
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/shared/types/settings.ts
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
  • apps/frontend/src/renderer/components/ui/dialog.tsx
  • apps/frontend/src/renderer/components/NewProjectScreen.tsx
  • apps/frontend/src/preload/api/template-api.ts
  • apps/frontend/src/main/template-parser.ts
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
  • apps/frontend/src/renderer/components/settings/AddSecretGroupDialog.tsx
  • apps/frontend/src/shared/types/ipc.ts
  • apps/frontend/src/preload/api/index.ts
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
🧬 Code graph analysis (11)
apps/frontend/src/main/ipc-handlers/index.ts (2)
apps/frontend/src/main/ipc-handlers/template-handlers.ts (1)
  • registerTemplateHandlers (42-317)
apps/frontend/src/main/ipc-handlers/secrets-handlers.ts (1)
  • registerSecretsHandlers (47-372)
apps/frontend/src/renderer/components/settings/GeneralSettings.tsx (2)
.design-system/src/components/Input.tsx (1)
  • Input (4-24)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx (3)
apps/frontend/src/shared/types/template.ts (1)
  • Template (5-12)
apps/frontend/src/renderer/components/settings/SecretsManager.tsx (1)
  • SecretsManager (21-432)
apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx (1)
  • AddTemplateDialog (23-212)
apps/frontend/src/main/ipc-handlers/project-handlers.ts (1)
apps/frontend/src/shared/constants/ipc.ts (1)
  • IPC_CHANNELS (6-327)
apps/frontend/src/renderer/components/settings/AppSettings.tsx (1)
apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx (1)
  • TemplatesSettings (20-447)
apps/frontend/src/shared/types/template.ts (1)
apps/frontend/src/main/template-parser.ts (2)
  • TemplateParameter (4-15)
  • ParsedTemplate (17-21)
apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx (3)
apps/frontend/src/shared/types/secrets.ts (1)
  • SecretGroup (17-26)
.design-system/src/components/Input.tsx (1)
  • Input (4-24)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
apps/frontend/src/preload/api/secrets-api.ts (1)
apps/frontend/src/shared/types/secrets.ts (3)
  • SecretGroup (17-26)
  • SecretGroupInput (34-39)
  • SecretAccountInput (42-45)
apps/frontend/src/main/ipc-handlers/template-handlers.ts (3)
apps/frontend/src/shared/types/template.ts (3)
  • TemplateStore (14-16)
  • Template (5-12)
  • ParsedTemplate (31-35)
apps/frontend/src/main/ipc-handlers/index.ts (1)
  • registerTemplateHandlers (119-119)
apps/frontend/src/main/template-parser.ts (3)
  • ParsedTemplate (17-21)
  • parseTemplateDirectory (149-197)
  • replaceTemplateParameters (202-219)
apps/frontend/src/preload/api/template-api.ts (4)
apps/frontend/src/preload/api/index.ts (2)
  • TemplateAPI (60-60)
  • createTemplateAPI (46-46)
apps/frontend/src/shared/types/template.ts (2)
  • Template (5-12)
  • ParsedTemplate (31-35)
apps/frontend/src/main/template-parser.ts (1)
  • ParsedTemplate (17-21)
apps/frontend/src/shared/constants/ipc.ts (1)
  • IPC_CHANNELS (6-327)
apps/frontend/src/main/template-parser.ts (1)
apps/frontend/src/shared/types/template.ts (2)
  • TemplateParameter (18-29)
  • ParsedTemplate (31-35)
🪛 Biome (2.1.2)
apps/frontend/src/renderer/components/settings/SecretsManager.tsx

[error] 16-19: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 21-21: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)


[error] 313-313: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 313-313: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

apps/frontend/src/renderer/components/NewProjectScreen.tsx

[error] 405-409: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (29)
apps/frontend/src/renderer/components/ui/dialog.tsx (1)

62-67: LGTM! Improved close button styling.

The change from padding-based sizing to explicit dimensions (h-8 w-8) with flexbox centering (inline-flex items-center justify-center) makes the button size predictable and ensures the icon is reliably centered. The addition of disabled:pointer-events-none properly prevents interaction when the button is disabled. The implementation follows Tailwind CSS v4 best practices and maintains accessibility with focus states and screen-reader text.

apps/frontend/src/shared/types/settings.ts (1)

83-83: LGTM! Clean type additions.

The new optional fields projectPath and defaultSecretAccounts are well-typed and appropriately optional. The inline comments clearly explain the purpose of defaultSecretAccounts.

Also applies to: 115-117

apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx (2)

79-126: LGTM! Robust save handler.

The validation, error handling, and state management are well-implemented. The use of imagePath.trim() || undefined ensures empty strings are converted to undefined, which is appropriate for optional fields.


128-211: LGTM! Clean component structure.

The dialog UI is well-structured with proper labels, conditional rendering, and disabled states during saving. The component composition follows React best practices.

apps/frontend/src/shared/types/index.ts (1)

19-20: LGTM! Standard export pattern.

The new exports for template and secrets types follow the existing pattern and are appropriately placed before the IPC types export.

apps/frontend/src/renderer/components/settings/GeneralSettings.tsx (1)

190-205: LGTM! Consistent UI pattern.

The Default Projects Path UI follows the same pattern as other path settings in the component, maintaining consistency.

apps/frontend/src/main/ipc-handlers/index.ts (1)

19-20: LGTM! Consistent handler registration pattern.

The new template and secrets handlers are registered following the existing pattern. The imports, registration calls, and re-exports are all properly structured.

Also applies to: 67-71, 119-120

apps/frontend/src/preload/api/project-api.ts (1)

101-101: LGTM! Correct API integration.

The cloneRepository method is properly typed and correctly invokes the corresponding IPC channel with the expected parameters.

Also applies to: 279-280

apps/frontend/src/preload/api/index.ts (1)

5-6: LGTM!

The TemplateAPI and SecretsAPI are cleanly integrated following the established pattern for other API modules. The interface extension, factory composition, and exports are consistent with the existing codebase conventions.

Also applies to: 18-19, 31-32, 46-47, 60-61

apps/frontend/src/renderer/components/settings/AppSettings.tsx (2)

17-18: LGTM!

The Templates section is cleanly integrated into the settings navigation. The pattern follows existing conventions for adding new settings sections (icon import, nav item, type extension, and render case).

Also applies to: 36-36, 53-53, 67-67, 172-173


345-347: Layout change removes content width constraint.

The removal of max-w-2xl allows the content area to expand to full width. This is appropriate for the Templates grid view but may affect other settings sections that previously had constrained widths. Verify that other settings sections still render correctly with the wider layout.

apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx (1)

23-85: LGTM!

The component correctly handles form state lifecycle, validation, and error handling. The useEffect dependency array is correct, and the early return for null group prevents rendering issues.

apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx (1)

217-265: LGTM!

The template card grid with image preview, edit/delete actions, and responsive layout is well implemented. The conditional rendering for the template image with fallback to folder icon provides good UX.

apps/frontend/src/renderer/components/settings/AddSecretGroupDialog.tsx (1)

81-141: LGTM!

The save handler has comprehensive validation: required title, at least one key ID, and uniqueness check for key IDs. The branching logic for create vs update is clean, and error handling covers both API failures and exceptions.

apps/frontend/src/main/secrets-encryption.ts (2)

11-56: LGTM!

Clean encapsulation of Electron's safeStorage API with proper availability checks and Base64 encoding. The static API pattern is appropriate for this stateless utility.


52-55: Security consideration: reEncrypt briefly holds plaintext in memory.

While this is unavoidable for key rotation, be aware that the decrypted value exists in memory during the re-encryption process. For highly sensitive scenarios, consider clearing the variable after use, though JavaScript's garbage collection makes this non-deterministic.

apps/frontend/src/renderer/components/NewProjectScreen.tsx (1)

59-75: LGTM!

Template loading on mount with proper loading state management and error handling follows good patterns.

apps/frontend/src/main/ipc-handlers/template-handlers.ts (2)

279-306: Potential issue: Parameter replacement uses original template paths.

The parseTemplateDirectory on Line 283 is called on template.folderPath (the original template), but replaceTemplateParameters on Line 303 operates on files in targetPath (the copied template). This is correct behavior, but there's an edge case: if the template source folder is modified between parsing and replacement, the relative paths could become invalid.

More importantly, the current implementation only processes files that contain parameters. Files that exist in the copied template but weren't in the parse result won't cause issues, but if a file was deleted from the original after copying, replaceTemplateParameters would throw on a non-existent file.

Consider adding existence check before replacement:

          if (!filesProcessed.has(targetFilePath)) {
            filesProcessed.add(targetFilePath);
+           if (!existsSync(targetFilePath)) {
+             console.warn(`[TEMPLATES] Skipping missing file: ${targetFilePath}`);
+             continue;
+           }
            const newContent = replaceTemplateParameters(targetFilePath, replacements);
            writeFileSync(targetFilePath, newContent, 'utf-8');
          }

42-317: LGTM - Well-structured IPC handlers.

The handlers follow a consistent pattern with proper error handling, template existence validation, and path collision checks. The IPCResult return type provides a clear success/error contract.

apps/frontend/src/main/ipc-handlers/secrets-handlers.ts (1)

47-372: LGTM - Comprehensive secrets management with proper encryption workflow.

The handlers properly separate group schema (keyIds) from account instances (encrypted values). The on-demand decryption pattern (decryptSecretAccount, decryptSecretAccountKey) minimizes exposure of plaintext secrets.

apps/frontend/src/shared/types/secrets.ts (1)

1-45: LGTM - Well-documented type definitions.

The type structure clearly separates the schema (SecretGroup with keyIds) from instances (SecretAccount with encrypted values). The version field in SecretsStore enables future schema migrations.

apps/frontend/src/preload/api/template-api.ts (1)

1-40: LGTM - Clean preload API wrapper.

The interface methods align correctly with the IPC handler signatures. The type annotations ensure type safety across the IPC boundary.

apps/frontend/src/preload/api/secrets-api.ts (1)

1-64: LGTM - Well-organized preload API.

The interface is logically grouped (encryption check, group operations, account operations, decryption) and aligns with the IPC handler signatures.

apps/frontend/src/renderer/components/settings/SecretsManager.tsx (1)

60-62: Consider adding cleanup or dependency tracking.

The useEffect has an empty dependency array but calls loadGroups which references no external dependencies. This is correct, but if loadGroups ever needs to change based on props/context, this could become stale.

The current implementation is correct for the component's requirements.

apps/frontend/src/renderer/App.tsx (2)

904-913: LGTM - NewProjectScreen integration.

The component is correctly wired up with the necessary handlers for folder import, recent project selection, and template-based creation.


428-465: [rewritten review comment]
[classification tag]

apps/frontend/src/shared/constants/ipc.ts (2)

110-131: LGTM - Well-organized IPC channel additions.

The new template and secrets channels follow the existing naming convention and are logically grouped with clear comments.


305-305: LGTM - Git clone channel added.

The GIT_CLONE_REPOSITORY channel follows the existing git operations naming pattern.

apps/frontend/src/shared/types/ipc.ts (1)

261-262: The decrypt operations intentionally expose secrets to the renderer for the secrets manager feature.

The decryptSecretAccount and decryptSecretAccountKey methods are used as intended in SecretsManager.tsx and NewProjectScreen.tsx to decrypt and display secrets on-demand when users explicitly request them (revealing accounts or copying keys to clipboard). Secrets are not persisted in localStorage or sessionStorage, and the preload API documents them as "secure, on-demand" operations.

While the security concern about exposing plaintext secrets to the renderer process is architecturally valid—the renderer is indeed less trusted than the main process—this appears to be a deliberate design choice for the secrets manager UI feature. The current implementation handles secrets responsibly by decrypting only on-demand and not persisting them in browser storage. If this use case changes or broader security hardening is needed, consider whether sensitive operations (like injecting secrets into code) could be performed entirely in the main process instead of the renderer.

Comment on lines +489 to +536
// Clone a git repository
ipcMain.handle(
IPC_CHANNELS.GIT_CLONE_REPOSITORY,
async (_, repoUrl: string, destinationPath: string): Promise<IPCResult<{ path: string }>> => {
try {
if (!existsSync(destinationPath)) {
return { success: false, error: 'Destination directory does not exist' };
}

// Extract repo name from URL (remove .git extension if present)
const repoName = repoUrl
.split('/')
.pop()
?.replace(/\.git$/, '') || 'repo';

const clonePath = path.join(destinationPath, repoName);

// Check if directory already exists
if (existsSync(clonePath)) {
return { success: false, error: `Directory "${repoName}" already exists at this location` };
}

// Clone the repository
try {
execSync(`git clone "${repoUrl}" "${clonePath}"`, {
stdio: 'pipe',
encoding: 'utf-8'
});
} catch (gitError: any) {
// Parse git error message
const errorMessage = gitError.stderr || gitError.message || 'Git clone failed';
return { success: false, error: errorMessage };
}

// Verify the clone was successful
if (!existsSync(clonePath)) {
return { success: false, error: 'Repository cloned but directory not found' };
}

return { success: true, data: { path: clonePath } };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to clone repository'
};
}
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, verify the file exists and check the actual code at lines 489-536
cat -n apps/frontend/src/main/ipc-handlers/project-handlers.ts | sed -n '485,540p'

Repository: AndyMik90/Auto-Claude

Length of output: 2079


🏁 Script executed:

# Check the imports at the top of the file
head -30 apps/frontend/src/main/ipc-handlers/project-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 1048


🏁 Script executed:

# Search for other execSync calls with potential string interpolation
rg -n "execSync.*\`" apps/frontend/src/main/ -A 2

Repository: AndyMik90/Auto-Claude

Length of output: 13987


🏁 Script executed:

# Search for any git-related execSync calls
rg -n "execSync.*git" apps/frontend/src/main/ -A 2

Repository: AndyMik90/Auto-Claude

Length of output: 29374


🏁 Script executed:

# Check for spawnSync usage patterns in the codebase
rg -n "spawnSync|spawn" apps/frontend/src/main/ -A 2

Repository: AndyMik90/Auto-Claude

Length of output: 36930


Critical command injection vulnerability: user-provided repoUrl is directly interpolated into shell command without sanitization.

Line 513 executes git clone with user input via template literals. A malicious repoUrl like https://github.com/repo.git"; rm -rf / # would execute arbitrary commands.

Replace execSync with spawnSync using array syntax to prevent shell interpretation:

import { execSync, spawnSync } from 'child_process';
        try {
-         execSync(`git clone "${repoUrl}" "${clonePath}"`, {
-           stdio: 'pipe',
-           encoding: 'utf-8'
-         });
+         const result = spawnSync('git', ['clone', repoUrl, clonePath], {
+           stdio: 'pipe',
+           encoding: 'utf-8'
+         });
+         if (result.error) {
+           throw result.error;
+         }
        } catch (gitError: any) {

Also address:

  • Line 517: Change catch (gitError: any) to catch (gitError: unknown) for type safety
  • Lines 499-502: Add validation that repoUrl is a valid Git URL format

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

🤖 Prompt for AI Agents
apps/frontend/src/main/ipc-handlers/project-handlers.ts around lines 489 to 536:
the current handler directly interpolates user-controlled repoUrl into execSync
which causes a command-injection risk; replace execSync with
child_process.spawnSync using argument array form (e.g. spawnSync('git',
['clone', repoUrl, clonePath'], { stdio: 'pipe', encoding: 'utf-8' })) so the
URL is passed as an argument not interpreted by a shell, validate repoUrl before
use (ensure it matches accepted git URL patterns such as HTTPS or SSH formats
and reject/return an error for invalid values), change the git error catch
signature from catch (gitError: any) to catch (gitError: unknown) and extract a
safe message (checking instance of Error or reading stderr if present), and keep
the existing existsSync checks and error returns.

Comment on lines +42 to +45
const writeSecretsFile = (store: SecretsStore): void => {
const secretsPath = getSecretsPath();
writeFileSync(secretsPath, JSON.stringify(store, null, 2), '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.

🧹 Nitpick | 🔵 Trivial

Missing error handling in writeSecretsFile (same as templates).

Consistent with template-handlers.ts, this write function lacks error handling.

🔎 Proposed fix
 const writeSecretsFile = (store: SecretsStore): void => {
   const secretsPath = getSecretsPath();
-  writeFileSync(secretsPath, JSON.stringify(store, null, 2), 'utf-8');
+  try {
+    writeFileSync(secretsPath, JSON.stringify(store, null, 2), 'utf-8');
+  } catch (error) {
+    console.error('[SECRETS] Failed to write secrets file:', error);
+    throw 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
const writeSecretsFile = (store: SecretsStore): void => {
const secretsPath = getSecretsPath();
writeFileSync(secretsPath, JSON.stringify(store, null, 2), 'utf-8');
};
const writeSecretsFile = (store: SecretsStore): void => {
const secretsPath = getSecretsPath();
try {
writeFileSync(secretsPath, JSON.stringify(store, null, 2), 'utf-8');
} catch (error) {
console.error('[SECRETS] Failed to write secrets file:', error);
throw error;
}
};
🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/secrets-handlers.ts around lines 42 to
45, the writeSecretsFile function currently calls writeFileSync without error
handling; wrap the file write in a try/catch, catch any thrown error, log the
error (using the same logger/mechanism used in template-handlers.ts), and either
rethrow or return a failure indicator to match template-handlers behavior so
callers can handle the failure; ensure the logged message includes context
(e.g., path and operation) and the original error details.

Comment on lines 189 to 230
// Add an account to a group
ipcMain.handle(
IPC_CHANNELS.SECRETS_ADD_ACCOUNT,
async (_, groupId: string, accountInput: SecretAccountInput): Promise<IPCResult<SecretGroup>> => {
try {
const store = readSecretsFile();
const group = store.groups.find(g => g.id === groupId);

if (!group) {
return { success: false, error: 'Secret group not found' };
}

const now = Date.now();

// Encrypt all key values
const encryptedKeys: Record<string, string> = {};
for (const [keyId, value] of Object.entries(accountInput.keys)) {
encryptedKeys[keyId] = SecretsEncryption.encrypt(value);
}

const newAccount = {
id: uuidv4(),
title: accountInput.title,
keys: encryptedKeys,
createdAt: now,
updatedAt: now
};

group.accounts.push(newAccount);
group.updatedAt = now;

writeSecretsFile(store);

return { success: true, data: group };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to add account'
};
}
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider checking encryption availability before encrypting.

The SECRETS_ADD_ACCOUNT handler encrypts keys without first verifying that encryption is available. If SecretsEncryption.encrypt() fails because encryption isn't available, the error will be caught but the message may not be clear to users.

🔎 Proposed improvement
       const now = Date.now();

+      // Verify encryption is available before proceeding
+      if (!SecretsEncryption.isEncryptionAvailable()) {
+        return { success: false, error: 'Encryption is not available on this system' };
+      }
+
       // Encrypt all key values
       const encryptedKeys: Record<string, string> = {};

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

Comment on lines +37 to +40
const writeTemplatesFile = (store: TemplateStore): void => {
const templatesPath = getTemplatesPath();
writeFileSync(templatesPath, JSON.stringify(store, null, 2), '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.

🧹 Nitpick | 🔵 Trivial

Missing error handling in writeTemplatesFile.

Unlike readTemplatesFile, this function doesn't wrap the write operation in a try-catch. If writeFileSync fails (disk full, permissions, etc.), the error will propagate up but won't be caught consistently across all callers.

🔎 Proposed fix
 const writeTemplatesFile = (store: TemplateStore): void => {
   const templatesPath = getTemplatesPath();
-  writeFileSync(templatesPath, JSON.stringify(store, null, 2), 'utf-8');
+  try {
+    writeFileSync(templatesPath, JSON.stringify(store, null, 2), 'utf-8');
+  } catch (error) {
+    console.error('[TEMPLATES] Failed to write templates file:', error);
+    throw error;
+  }
 };
🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/template-handlers.ts around lines 37 to
40, the writeTemplatesFile function performs writeFileSync without error
handling; wrap the writeFileSync call in a try-catch, on error log a descriptive
message including the error details (using the same logger used elsewhere in
this module or console.error if no logger is available) and either rethrow the
error or return a consistent failure indicator to match readTemplatesFile's
error handling strategy so callers receive consistent behavior.

Comment on lines +4 to +21
export interface TemplateParameter {
key: string; // Unique key for this parameter (generated from position)
title: string;
type: 'text' | 'dropdown' | 'secret';
options?: string[]; // For dropdown type
default?: string;
group?: string; // For secret type
secretKey?: string; // For secret type (the key within the group)
placeholder?: string; // Original placeholder text in file
filePath: string; // File where this parameter was found
position: number; // Position in file for replacement
}

export interface ParsedTemplate {
parameters: TemplateParameter[];
totalFiles: number;
filesWithParameters: number;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicate type definitions - use shared types instead.

TemplateParameter and ParsedTemplate interfaces are already defined in apps/frontend/src/shared/types/template.ts. This duplication creates a maintenance burden where changes must be synchronized across files.

🔎 Suggested fix
 import { readFileSync, readdirSync, statSync } from 'fs';
 import path from 'path';
+import type { TemplateParameter, ParsedTemplate } from '../shared/types/template';

-export interface TemplateParameter {
-  key: string;
-  title: string;
-  type: 'text' | 'dropdown' | 'secret';
-  options?: string[];
-  default?: string;
-  group?: string;
-  secretKey?: string;
-  placeholder?: string;
-  filePath: string;
-  position: number;
-}
-
-export interface ParsedTemplate {
-  parameters: TemplateParameter[];
-  totalFiles: number;
-  filesWithParameters: number;
-}
+export type { TemplateParameter, ParsedTemplate };
🤖 Prompt for AI Agents
In apps/frontend/src/main/template-parser.ts around lines 4 to 21, the
interfaces TemplateParameter and ParsedTemplate are duplicated here but already
exist in apps/frontend/src/shared/types/template.ts; remove these local type
definitions and instead import the shared types from that file (replace the
local declarations with an import statement for TemplateParameter and
ParsedTemplate), update any local references to use the imported names, remove
any now-unused exports/imports, and run the TypeScript build to ensure there are
no type or import path errors.

Comment on lines +79 to +86
const filteredTemplates = templates.filter(template => {
if (!searchQuery) return true;
const query = searchQuery.toLowerCase();
return (
template.name.toLowerCase().includes(query) ||
template.folderPath.toLowerCase().includes(query)
);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider memoizing filteredTemplates for larger template lists.

The filter operation runs on every render. For a small number of templates this is fine, but for larger lists, wrapping in useMemo would prevent unnecessary recalculations.

🔎 Suggested improvement
+import { useState, useEffect, useMemo } from 'react';
...
-  const filteredTemplates = templates.filter(template => {
+  const filteredTemplates = useMemo(() => templates.filter(template => {
     if (!searchQuery) return true;
     const query = searchQuery.toLowerCase();
     return (
       template.name.toLowerCase().includes(query) ||
       template.folderPath.toLowerCase().includes(query)
     );
-  });
+  }), [templates, searchQuery]);
📝 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
const filteredTemplates = templates.filter(template => {
if (!searchQuery) return true;
const query = searchQuery.toLowerCase();
return (
template.name.toLowerCase().includes(query) ||
template.folderPath.toLowerCase().includes(query)
);
});
const filteredTemplates = useMemo(() => templates.filter(template => {
if (!searchQuery) return true;
const query = searchQuery.toLowerCase();
return (
template.name.toLowerCase().includes(query) ||
template.folderPath.toLowerCase().includes(query)
);
}), [templates, searchQuery]);
🤖 Prompt for AI Agents
In apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx around
lines 79 to 86, the templates.filter(...) runs on every render which can be
expensive for large lists; wrap this computation in React.useMemo so the
filteredTemplates array is only recomputed when templates or searchQuery change,
and ensure useMemo is imported from React at the top of the file.

Comment on lines 241 to 250
// Template operations
getTemplates: () => Promise<IPCResult<import('./template').Template[]>>;
saveTemplate: (template: Omit<import('./template').Template, 'id' | 'createdAt' | 'updatedAt'>) => Promise<IPCResult<import('./template').Template>>;
deleteTemplate: (templateId: string) => Promise<IPCResult>;
updateTemplate: (templateId: string, updates: Partial<Omit<import('./template').Template, 'id' | 'createdAt' | 'updatedAt'>>) => Promise<IPCResult<import('./template').Template>>;
copyTemplate: (templateId: string, destinationPath: string) => Promise<IPCResult<{ path: string }>>;
copyTemplateWithName: (templateId: string, destinationPath: string, customName: string) => Promise<IPCResult<{ path: string }>>;
parseTemplateParameters: (templateId: string) => Promise<IPCResult<import('./template').ParsedTemplate>>;
copyTemplateWithParameters: (templateId: string, destinationPath: string, customName: string, parameterValues: Record<string, string>) => Promise<IPCResult<{ path: string }>>;

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Prefer static imports for consistency and readability.

The Template operations use dynamic inline imports (import('./template').Template), which is inconsistent with the rest of the file where types are imported statically at the top (lines 6-109). This pattern makes the code harder to read and maintain.

🔎 Proposed fix: Add static imports at the top

Add this import at the top of the file with the other type imports:

+import type { Template, ParsedTemplate } from './template';

Then update the method signatures to use the imported types directly:

  // Template operations
- getTemplates: () => Promise<IPCResult<import('./template').Template[]>>;
- saveTemplate: (template: Omit<import('./template').Template, 'id' | 'createdAt' | 'updatedAt'>) => Promise<IPCResult<import('./template').Template>>;
+ getTemplates: () => Promise<IPCResult<Template[]>>;
+ saveTemplate: (template: Omit<Template, 'id' | 'createdAt' | 'updatedAt'>) => Promise<IPCResult<Template>>;
  deleteTemplate: (templateId: string) => Promise<IPCResult>;
- updateTemplate: (templateId: string, updates: Partial<Omit<import('./template').Template, 'id' | 'createdAt' | 'updatedAt'>>) => Promise<IPCResult<import('./template').Template>>;
+ updateTemplate: (templateId: string, updates: Partial<Omit<Template, 'id' | 'createdAt' | 'updatedAt'>>) => Promise<IPCResult<Template>>;
  copyTemplate: (templateId: string, destinationPath: string) => Promise<IPCResult<{ path: string }>>;
  copyTemplateWithName: (templateId: string, destinationPath: string, customName: string) => Promise<IPCResult<{ path: string }>>;
- parseTemplateParameters: (templateId: string) => Promise<IPCResult<import('./template').ParsedTemplate>>;
+ parseTemplateParameters: (templateId: string) => Promise<IPCResult<ParsedTemplate>>;
  copyTemplateWithParameters: (templateId: string, destinationPath: string, customName: string, parameterValues: Record<string, string>) => Promise<IPCResult<{ path: string }>>;

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

🤖 Prompt for AI Agents
In apps/frontend/src/shared/types/ipc.ts around lines 241 to 250, the method
signatures use inline dynamic type imports (import('./template').Template /
ParsedTemplate); replace those with static type imports at the top of the file
to match the rest of the file. Add imports for Template and ParsedTemplate
alongside the existing type imports, then update getTemplates, saveTemplate,
updateTemplate, parseTemplateParameters and any other signatures here to
reference Template and ParsedTemplate directly (and keep other return types like
IPCResult<{ path: string }> unchanged).

Comment on lines +251 to +260
// Secrets operations (Encrypted storage - Group/Account model)
checkSecretsEncryption: () => Promise<IPCResult<boolean>>;
getSecretGroups: () => Promise<IPCResult<import('./secrets').SecretGroup[]>>;
getSecretGroup: (groupId: string) => Promise<IPCResult<import('./secrets').SecretGroup>>;
createSecretGroup: (groupInput: import('./secrets').SecretGroupInput) => Promise<IPCResult<import('./secrets').SecretGroup>>;
updateSecretGroup: (groupId: string, updates: Partial<import('./secrets').SecretGroupInput>) => Promise<IPCResult<import('./secrets').SecretGroup>>;
deleteSecretGroup: (groupId: string) => Promise<IPCResult>;
addSecretAccount: (groupId: string, accountInput: import('./secrets').SecretAccountInput) => Promise<IPCResult<import('./secrets').SecretGroup>>;
updateSecretAccount: (groupId: string, accountId: string, accountInput: import('./secrets').SecretAccountInput) => Promise<IPCResult<import('./secrets').SecretGroup>>;
deleteSecretAccount: (groupId: string, accountId: string) => Promise<IPCResult<import('./secrets').SecretGroup>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Prefer static imports for consistency and readability.

The Secrets operations use dynamic inline imports, creating the same inconsistency as the Template operations above.

🔎 Proposed fix: Add static imports at the top

Add this import at the top of the file:

+import type { SecretGroup, SecretGroupInput, SecretAccountInput } from './secrets';

Then update the method signatures:

  // Secrets operations (Encrypted storage - Group/Account model)
  checkSecretsEncryption: () => Promise<IPCResult<boolean>>;
- getSecretGroups: () => Promise<IPCResult<import('./secrets').SecretGroup[]>>;
- getSecretGroup: (groupId: string) => Promise<IPCResult<import('./secrets').SecretGroup>>;
- createSecretGroup: (groupInput: import('./secrets').SecretGroupInput) => Promise<IPCResult<import('./secrets').SecretGroup>>;
- updateSecretGroup: (groupId: string, updates: Partial<import('./secrets').SecretGroupInput>) => Promise<IPCResult<import('./secrets').SecretGroup>>;
+ getSecretGroups: () => Promise<IPCResult<SecretGroup[]>>;
+ getSecretGroup: (groupId: string) => Promise<IPCResult<SecretGroup>>;
+ createSecretGroup: (groupInput: SecretGroupInput) => Promise<IPCResult<SecretGroup>>;
+ updateSecretGroup: (groupId: string, updates: Partial<SecretGroupInput>) => Promise<IPCResult<SecretGroup>>;
  deleteSecretGroup: (groupId: string) => Promise<IPCResult>;
- addSecretAccount: (groupId: string, accountInput: import('./secrets').SecretAccountInput) => Promise<IPCResult<import('./secrets').SecretGroup>>;
- updateSecretAccount: (groupId: string, accountId: string, accountInput: import('./secrets').SecretAccountInput) => Promise<IPCResult<import('./secrets').SecretGroup>>;
- deleteSecretAccount: (groupId: string, accountId: string) => Promise<IPCResult<import('./secrets').SecretGroup>>;
+ addSecretAccount: (groupId: string, accountInput: SecretAccountInput) => Promise<IPCResult<SecretGroup>>;
+ updateSecretAccount: (groupId: string, accountId: string, accountInput: SecretAccountInput) => Promise<IPCResult<SecretGroup>>;
+ deleteSecretAccount: (groupId: string, accountId: string) => Promise<IPCResult<SecretGroup>>;
🤖 Prompt for AI Agents
In apps/frontend/src/shared/types/ipc.ts around lines 251 to 260, the
secrets-related method signatures use inline dynamic imports
(import('./secrets').Type) which is inconsistent with the rest of the file;
replace those inline imports by adding a top-of-file static type-only import
from the secrets module (e.g., import type { SecretGroup, SecretGroupInput,
SecretAccountInput } from './secrets') and then update each method signature to
reference those imported types (e.g., SecretGroup, SecretGroupInput,
SecretAccountInput) instead of the import(...) forms so the file uses
consistent, readable static imports.

detectMainBranch: (projectPath: string) => Promise<IPCResult<string | null>>;
checkGitStatus: (projectPath: string) => Promise<IPCResult<GitStatus>>;
initializeGit: (projectPath: string) => Promise<IPCResult<InitializationResult>>;
cloneRepository: (repoUrl: string, destinationPath: string) => Promise<IPCResult<{ path: string }>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify security validation in the cloneRepository backend handler

echo "=== Searching for cloneRepository handler implementation ==="
rg -n -A 20 'cloneRepository' apps/frontend/src/main/ipc-handlers/ --type=ts 2>/dev/null || echo "Pattern not found in specified path"

echo -e "\n=== Searching in main directory for ipc handlers ==="
fd -t f -e ts -e js 'ipc' apps/frontend/src/main/ 2>/dev/null | head -20

echo -e "\n=== Check directory structure ==="
ls -la apps/frontend/src/main/ 2>/dev/null || echo "Directory may not exist"

Repository: AndyMik90/Auto-Claude

Length of output: 3174


🏁 Script executed:

#!/bin/bash
# Description: Find cloneRepository handler with broader search

echo "=== List files in ipc-handlers directory ==="
find apps/frontend/src/main/ipc-handlers -type f -name "*.ts" | head -30

echo -e "\n=== Search all files in ipc-handlers for cloneRepository ==="
rg -n 'cloneRepository' apps/frontend/src/main/ipc-handlers/ --type=ts

echo -e "\n=== Search entire apps/frontend/src/main for cloneRepository ==="
rg -n 'cloneRepository' apps/frontend/src/main/ --type=ts

echo -e "\n=== Check ipc-setup.ts for handler registration ==="
cat -n apps/frontend/src/main/ipc-setup.ts

Repository: AndyMik90/Auto-Claude

Length of output: 4468


🏁 Script executed:

#!/bin/bash
# Description: Check the actual IPC type definition and search for clone/git functionality

echo "=== Check ipc.ts file around line 569 ==="
sed -n '560,580p' apps/frontend/src/shared/types/ipc.ts

echo -e "\n=== Search entire codebase for 'clone' ==="
rg -n 'clone' apps/frontend/src/main/ --type=ts -i | head -30

echo -e "\n=== Search for git operations in main ==="
rg -n 'execSync.*git|spawn.*git|exec.*git' apps/frontend/src/main/ --type=ts | head -20

echo -e "\n=== Check if cloneRepository is used anywhere in frontend ==="
rg -rn 'cloneRepository' apps/frontend/ --type=ts --type=tsx

Repository: AndyMik90/Auto-Claude

Length of output: 4853


🏁 Script executed:

#!/bin/bash
# Description: Get complete cloneRepository handler implementation

echo "=== Get full cloneRepository handler ==="
sed -n '489,535p' apps/frontend/src/main/ipc-handlers/project-handlers.ts

echo -e "\n=== Check project-handlers.ts imports and utilities ==="
head -50 apps/frontend/src/main/ipc-handlers/project-handlers.ts

echo -e "\n=== Check if there's any URL validation utility ==="
rg -n 'validateUrl|validatePath|sanitize' apps/frontend/src/main/ --type=ts | head -20

Repository: AndyMik90/Auto-Claude

Length of output: 4152


🏁 Script executed:

#!/bin/bash
# Verify shell execution risk and check if timeout is set elsewhere

echo "=== Check for timeout in execSync call ==="
sed -n '510,518p' apps/frontend/src/main/ipc-handlers/project-handlers.ts

echo -e "\n=== Check if there are any constants for validation ==="
rg -n 'GIT_CLONE_REPOSITORY|ALLOWED.*PROTOCOL|MAX.*SIZE' apps/frontend/src/shared/constants --type=ts

echo -e "\n=== Check api-validation-service for URL validation ==="
grep -n 'url\|protocol\|validate' apps/frontend/src/main/api-validation-service.ts | head -20

Repository: AndyMik90/Auto-Claude

Length of output: 1195


Implement URL and path validation in the cloneRepository handler.

The backend handler at apps/frontend/src/main/ipc-handlers/project-handlers.ts:513 directly executes git clone with user-supplied parameters without validation, creating multiple security vulnerabilities:

  1. Shell injection risk: execSync(git clone "${repoUrl}" "${clonePath}", ...) allows arbitrary command injection through repoUrl (e.g., malicious.com"; rm -rf /; ")
  2. No URL protocol validation: Accepts file://, ftp://, or other protocols unintended for git operations
  3. No path traversal protection: repoName extracted from URL is not sanitized; crafted URLs like https://example.com/../../../sensitive could write outside intended directories
  4. No timeout or resource limits: execSync without a timeout parameter can hang indefinitely; large repositories can exhaust disk space

Required fixes:

  • Validate repoUrl against allowed protocols (https, http, git, ssh) using URL parsing
  • Sanitize repoName to prevent directory traversal
  • Restrict clonePath resolution to stay within the parent destinationPath
  • Add timeout to execSync (e.g., timeout: 300000 for 5 minutes)
  • Consider adding repository size or clone time limits
🤖 Prompt for AI Agents
In apps/frontend/src/shared/types/ipc.ts around line 569, the cloneRepository
IPC signature is fine but the backend handler currently clones user-supplied
repoUrl without validation, enabling shell injection, unsupported protocols,
path traversal, and hangs; update the handler at
apps/frontend/src/main/ipc-handlers/project-handlers.ts to: validate repoUrl by
parsing it and only allow protocols https:, http:, git:, ssh:; reject or
normalize unsafe inputs; derive repoName by extracting only the base name and
sanitizing/removing any .. or path separators to prevent traversal; resolve the
clonePath and ensure the final resolved path is inside the allowed
destinationPath parent (reject if it escapes); call git clone using a safe
child_process API (spawn with args array) or at minimum avoid shell
interpolation and set execSync/exec options with a timeout (e.g., 300000 ms) and
resource limits; and add checks for repository size or clone duration (abort if
limits exceeded) and return clear errors on validation failures.

Comment on lines +18 to +35
export interface TemplateParameter {
key: string;
title: string;
type: 'text' | 'dropdown' | 'secret';
options?: string[];
default?: string;
group?: string;
secretKey?: string;
placeholder?: string;
filePath: string;
position: number;
}

export interface ParsedTemplate {
parameters: TemplateParameter[];
totalFiles: number;
filesWithParameters: number;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all definitions of TemplateParameter and ParsedTemplate
echo "=== Searching for TemplateParameter interface definitions ==="
rg -n "interface TemplateParameter" apps/frontend/src/

echo -e "\n=== Searching for ParsedTemplate interface definitions ==="
rg -n "interface ParsedTemplate" apps/frontend/src/

echo -e "\n=== Searching for imports of TemplateParameter ==="
rg -n "import.*TemplateParameter" apps/frontend/src/

echo -e "\n=== Searching for imports of ParsedTemplate ==="
rg -n "import.*ParsedTemplate" apps/frontend/src/

echo -e "\n=== Searching for type references to TemplateParameter ==="
rg -n "TemplateParameter" apps/frontend/src/ -t ts -t tsx | head -30

echo -e "\n=== Searching for type references to ParsedTemplate ==="
rg -n "ParsedTemplate" apps/frontend/src/ -t ts -t tsx | head -30

Repository: AndyMik90/Auto-Claude

Length of output: 1803


🏁 Script executed:

#!/bin/bash
# Read both definitions to compare them
echo "=== Definition in shared/types/template.ts ==="
sed -n '18,35p' apps/frontend/src/shared/types/template.ts

echo -e "\n=== Definition in main/template-parser.ts ==="
sed -n '4,20p' apps/frontend/src/main/template-parser.ts

# Check if template-parser.ts is importing from shared or defining locally
echo -e "\n=== First 10 lines of template-parser.ts to check imports ==="
head -n 10 apps/frontend/src/main/template-parser.ts

Repository: AndyMik90/Auto-Claude

Length of output: 1592


Consolidate duplicate type definitions to shared types.

The TemplateParameter and ParsedTemplate interfaces are defined in both apps/frontend/src/shared/types/template.ts (lines 18-35) and apps/frontend/src/main/template-parser.ts (lines 4-20). This creates a maintenance burden and inconsistency—NewProjectScreen.tsx imports from shared types while template-handlers.ts imports from the local definitions.

Move these interface definitions to a single source of truth in shared/types/template.ts and update template-parser.ts to import from that shared location.

🤖 Prompt for AI Agents
In apps/frontend/src/shared/types/template.ts (around lines 18-35) and
apps/frontend/src/main/template-parser.ts (around lines 4-20), the
TemplateParameter and ParsedTemplate interfaces are duplicated; remove the
duplicated type definitions from apps/frontend/src/main/template-parser.ts and
instead export the interfaces from apps/frontend/src/shared/types/template.ts
(ensure both interfaces are exported), then update
apps/frontend/src/main/template-parser.ts to import { TemplateParameter,
ParsedTemplate } from 'apps/frontend/src/shared/types/template' (or the correct
relative path) and update any other files (e.g., template-handlers.ts and
NewProjectScreen.tsx) to import these shared types so there is a single source
of truth.

JoshuaRileyDev and others added 3 commits December 24, 2025 16:47
Add new "Default Projects Path" setting in Settings > Paths to configure a default location for new projects and templates. Includes folder picker UI following existing patterns.

- Add projectPath field to AppSettings type
- Add folder picker with Browse button to GeneralSettings
- Leverage existing selectDirectory IPC API
- Include package-lock.json update (electron 39.2.6 → 39.2.7)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
This commit introduces a comprehensive template and secrets management system for project creation:

Template System:
- Add template creation, editing, and deletion UI in settings
- Implement template parameter parsing with support for text, dropdown, and secret types
- Support smart quotes (curly quotes) in template parameter syntax
- Parse parameters using regex: {{title="Name",type=text,default="value"}}
- Add template image preview support
- Create new project screen with template grid and search

Secrets Management:
- Add encrypted secrets storage with OS-level encryption
- Create secret groups (schemas) with multiple accounts per group
- Support secret parameters in templates with account selection dropdown
- Group secret parameters by group name (single dropdown per group)
- Add default secret account preferences in settings
- Auto-select account when only one exists in a group

New Project Flow:
- Add full-screen new project modal with multiple options
- Support importing folders, cloning repositories, and creating from templates
- Show recent projects for quick access
- Display template parameters with interactive form
- Auto-initialize git and Auto Claude on project creation

UI Improvements:
- Standardize dialog close button size to match add project button (h-8 w-8)
- Add proper icon sizing consistency across modals
- Improve template parameter display (strip quotes from titles)

Technical Details:
- Add IPC handlers for templates and secrets operations
- Implement template parser with recursive file scanning
- Add parameter replacement during template copy
- Integrate with existing settings and project systems

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
…ter guide

UI Improvements:
- Remove max-width constraint from settings content area for full-width display
- Add template search box with real-time filtering by name/path
- Show "no results" state with clear search option
- Make template tile action buttons always visible (remove hover-only effect)

Documentation:
- Add comprehensive parameter guide dialog in Templates area
- Document all parameter types: text, dropdown, and secret
- Include syntax examples, .env file example, and best practices
- Update Templates description to mention dynamic parameters
- Simplify Secrets Manager description to focus on credentials

Template Search Features:
- Search icon positioned inside input field
- Case-insensitive filtering
- Searches both template name and folder path
- Clear visual feedback for empty search results

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (28)
apps/frontend/src/main/ipc-handlers/project-handlers.ts (1)

489-536: Critical command injection vulnerability: Replace execSync with spawnSync to prevent shell interpretation.

The git clone handler at line 513 directly interpolates user-provided repoUrl into a shell command via execSync, creating a critical command injection risk. A malicious URL could execute arbitrary commands.

Replace with spawnSync using array arguments, validate the URL format, and fix the type safety issue at line 517.

This issue was previously identified in past reviews. Please address the recommended fix using spawnSync with argument arrays.

apps/frontend/src/shared/types/template.ts (1)

18-35: Consolidate duplicate type definitions to eliminate maintenance burden.

The TemplateParameter and ParsedTemplate interfaces are defined both here and in apps/frontend/src/main/template-parser.ts (lines 3-20). This duplication creates maintenance risks and potential inconsistencies.

As noted in previous reviews, update template-parser.ts to import these interfaces from the shared types instead of defining them locally.

apps/frontend/src/renderer/components/settings/GeneralSettings.tsx (1)

30-39: Improve error handling to distinguish user cancellation from real failures.

The empty catch block silently swallows all errors, including permission failures and I/O errors that users should be aware of. Only user cancellations should be silently ignored.

As noted in previous reviews, catch the error and log non-cancellation failures to improve debuggability.

apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx (1)

129-136: Add autocomplete attribute to prevent browser credential caching.

Password inputs for encrypted secret keys should include autoComplete="off" or autoComplete="new-password" to prevent browsers from offering to save these credentials, which is appropriate for encrypted secrets managed by the app's secure storage.

🔎 Suggested improvement
               <Input
                 id={`key-${keyId}`}
                 type="password"
                 placeholder={`Enter ${keyId}`}
                 value={keyValues[keyId] || ''}
                 onChange={(e) => handleKeyValueChange(keyId, e.target.value)}
                 className="font-mono"
+                autoComplete="off"
               />
apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx (1)

64-73: Wrong API used for image file selection.

The handleSelectImage function calls selectDirectory() which opens a directory picker. This prevents users from selecting image files. A dedicated file picker API with image file filters is needed instead.

apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx (3)

29-45: Consider wrapping loadTemplates in useCallback for stability.

The loadTemplates function is recreated on every render. While the current empty dependency array works, wrapping it in useCallback would prevent potential stale closure issues if it's later used in other effects or added as a dependency.


57-70: Replace window.confirm with consistent dialog component.

Using window.confirm creates a browser-native dialog that may look inconsistent with the application's UI. Consider using a custom confirmation dialog component for better UX consistency.


79-86: Consider memoizing filteredTemplates for larger template lists.

The filter operation runs on every render. For larger template lists, wrapping in useMemo would prevent unnecessary recalculations.

apps/frontend/src/renderer/components/settings/AddSecretGroupDialog.tsx (2)

52-61: Wrong API used for image file selection.

The handleSelectImage function calls selectDirectory() which opens a directory picker. This is semantically incorrect for selecting an image file. A proper file selection method with image file type filters should be implemented and used instead.


218-239: Use stable keys instead of array indices for dynamic key ID list.

Using array index as the key prop can cause issues when items are removed from the middle of the list, as React may incorrectly reuse DOM elements. Consider using stable unique identifiers (UUID or similar) for each key ID entry.

apps/frontend/src/renderer/components/NewProjectScreen.tsx (4)

143-224: Consider extracting handleOpenCreateFromTemplate into smaller functions.

This ~80-line function handles multiple responsibilities: loading settings, loading secret groups, parsing parameters, and initializing state. Breaking it into focused helper functions would improve readability and testability.


267-285: Handle secret decryption failures to prevent incomplete project creation.

When secret decryption fails (lines 279-281), the error is silently ignored and the parameter value is not set. This could result in template files containing unresolved placeholders or missing credentials, causing runtime issues in the created project. Handle failures by logging errors, surfacing them to the user, and aborting project creation.


332-332: Remove unnecessary await on non-async callback.

onSelectRecentProject is typed as (projectId: string) => void, not an async function. The await keyword here does nothing but may mislead readers.

🔎 Suggested fix
-      await onSelectRecentProject(project.data.id);
+      onSelectRecentProject(project.data.id);

405-409: Add explicit type="button" to prevent unintended form submission.

As flagged by static analysis, buttons default to type="submit" which can cause unintended form submissions. Add explicit type="button" for action buttons that shouldn't submit forms.

🔎 Suggested fix
                       <button
                         key={project.id}
+                        type="button"
                         onClick={() => onSelectRecentProject(project.id)}
                         className="w-full flex items-center gap-2 rounded-md px-2 py-2 text-left transition-colors hover:bg-accent/50 group"
                       >
apps/frontend/src/main/ipc-handlers/secrets-handlers.ts (2)

42-45: Missing error handling in writeSecretsFile.

This issue was flagged in a previous review. The write operation should include error handling consistent with readSecretsFile.


189-230: Consider checking encryption availability before encrypting.

This was flagged in a previous review. Adding a pre-check for SecretsEncryption.isEncryptionAvailable() before the encryption loop would provide clearer error messages to users.

apps/frontend/src/renderer/components/settings/SecretsManager.tsx (3)

17-21: Address static analysis issues: empty interface and empty pattern.

This was flagged in a previous review. Remove the empty interface and empty destructuring pattern.


119-128: Missing user feedback on copy success/failure.

This was flagged in a previous review. Users should receive visual feedback when copying a key to clipboard.


313-337: Accessibility: Add keyboard event handlers to the interactive div.

This was flagged in a previous review. The onClick handler on line 313 needs keyboard equivalents for accessibility compliance.

apps/frontend/src/main/ipc-handlers/template-handlers.ts (1)

37-40: Missing error handling in writeTemplatesFile.

This was flagged in a previous review. The write operation should include error handling consistent with readTemplatesFile.

apps/frontend/src/main/template-parser.ts (4)

4-21: Remove duplicate type definitions - use shared types instead.

This was flagged in a previous review. TemplateParameter and ParsedTemplate are already defined in apps/frontend/src/shared/types/template.ts.


72-76: Validate type value before casting.

This was flagged in a previous review. The unsafe cast accepts any string value, which may cause unexpected behavior downstream.


26-98: Consider reducing verbose console.log statements for production.

This was flagged in a previous review. The parsing function contains many console.log statements that will pollute logs in production.


185-188: Silent error swallowing may hide issues.

This was flagged in a previous review. Consider logging at debug level for troubleshooting.

apps/frontend/src/renderer/App.tsx (1)

437-474: Unused variable defaultDir and missing error notification.

  1. The defaultDir variable on line 440 is fetched but never used. Either remove it or use it as the default selection for the directory picker.

  2. The TODO on line 469 for showing errors to users was flagged in a previous review.

🔎 Proposed fix for unused variable
   const handleCreateFromTemplate = async (template: Template) => {
     try {
-      // Get the default projects location
-      const defaultDir = await window.electronAPI.getDefaultProjectLocation();
-
       // Ask user to select where to create the project
       const path = await window.electronAPI.selectDirectory();
       if (!path) return;

Or, if you intend to use it as a default:

   const handleCreateFromTemplate = async (template: Template) => {
     try {
       // Get the default projects location
       const defaultDir = await window.electronAPI.getDefaultProjectLocation();

       // Ask user to select where to create the project
-      const path = await window.electronAPI.selectDirectory();
+      const path = await window.electronAPI.selectDirectory(defaultDir);
       if (!path) return;
apps/frontend/src/shared/types/ipc.ts (3)

569-569: Ensure backend implementation includes security validation (already flagged).

The type signature is appropriate, but as documented in the previous review, the backend handler requires comprehensive security validation including URL protocol validation, path traversal prevention, shell injection protection, and timeout handling.

Please refer to the detailed security requirements in the previous review comment, which comprehensively documents all necessary fixes for the handler implementation at apps/frontend/src/main/ipc-handlers/project-handlers.ts.


241-250: Replace dynamic imports with static imports (already flagged).

The template operations use dynamic inline imports (import('./template').Template), which is inconsistent with the static imports at the top of the file (lines 6-109). As noted in the previous review, this pattern reduces readability and makes navigation harder.

The previous review already provided a comprehensive fix. Please refer to that suggestion to add static imports at the top and update these method signatures accordingly.


251-263: Replace dynamic imports with static imports (already flagged).

The secrets operations use the same dynamic inline import pattern as the template operations, creating identical inconsistency. The previous review already documented this issue and provided a fix.

Please refer to the previous review's suggestion to add static imports from the secrets module and update these method signatures to reference the imported types directly.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f843811 and 4c7ff2f.

📒 Files selected for processing (26)
  • apps/frontend/src/main/ipc-handlers/index.ts
  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
  • apps/frontend/src/main/secrets-encryption.ts
  • apps/frontend/src/main/template-parser.ts
  • apps/frontend/src/preload/api/index.ts
  • apps/frontend/src/preload/api/project-api.ts
  • apps/frontend/src/preload/api/secrets-api.ts
  • apps/frontend/src/preload/api/template-api.ts
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/renderer/components/NewProjectScreen.tsx
  • apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx
  • apps/frontend/src/renderer/components/settings/AddSecretGroupDialog.tsx
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/renderer/components/settings/AppSettings.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx
  • apps/frontend/src/renderer/components/ui/dialog.tsx
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/types/index.ts
  • apps/frontend/src/shared/types/ipc.ts
  • apps/frontend/src/shared/types/secrets.ts
  • apps/frontend/src/shared/types/settings.ts
  • apps/frontend/src/shared/types/template.ts
🧰 Additional context used
📓 Path-based instructions (1)
apps/frontend/**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

apps/frontend/**/*.{ts,tsx}: Review React patterns and TypeScript type safety.
Check for proper state management and component composition.

Files:

  • apps/frontend/src/renderer/components/ui/dialog.tsx
  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/preload/api/project-api.ts
  • apps/frontend/src/main/secrets-encryption.ts
  • apps/frontend/src/shared/types/settings.ts
  • apps/frontend/src/renderer/components/settings/AddSecretGroupDialog.tsx
  • apps/frontend/src/shared/types/index.ts
  • apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/main/ipc-handlers/index.ts
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
  • apps/frontend/src/shared/types/template.ts
  • apps/frontend/src/main/template-parser.ts
  • apps/frontend/src/renderer/components/NewProjectScreen.tsx
  • apps/frontend/src/renderer/components/settings/AppSettings.tsx
  • apps/frontend/src/preload/api/template-api.ts
  • apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx
  • apps/frontend/src/preload/api/index.ts
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/preload/api/secrets-api.ts
  • apps/frontend/src/shared/types/ipc.ts
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/types/secrets.ts
🧬 Code graph analysis (11)
apps/frontend/src/renderer/components/settings/AddSecretAccountDialog.tsx (3)
apps/frontend/src/shared/types/secrets.ts (1)
  • SecretGroup (17-26)
.design-system/src/components/Input.tsx (1)
  • Input (4-24)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
apps/frontend/src/renderer/components/settings/GeneralSettings.tsx (2)
.design-system/src/components/Input.tsx (1)
  • Input (4-24)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx (4)
apps/frontend/src/shared/types/template.ts (1)
  • Template (5-12)
apps/frontend/src/renderer/components/ui/dialog.tsx (6)
  • Dialog (116-116)
  • DialogContent (121-121)
  • DialogHeader (122-122)
  • DialogTitle (124-124)
  • DialogDescription (125-125)
  • DialogFooter (123-123)
.design-system/src/components/Input.tsx (1)
  • Input (4-24)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
apps/frontend/src/main/ipc-handlers/template-handlers.ts (4)
apps/frontend/src/shared/types/template.ts (3)
  • TemplateStore (14-16)
  • Template (5-12)
  • ParsedTemplate (31-35)
apps/frontend/src/main/ipc-handlers/index.ts (1)
  • registerTemplateHandlers (119-119)
apps/frontend/src/shared/constants/ipc.ts (1)
  • IPC_CHANNELS (6-327)
apps/frontend/src/main/template-parser.ts (3)
  • ParsedTemplate (17-21)
  • parseTemplateDirectory (149-197)
  • replaceTemplateParameters (202-219)
apps/frontend/src/shared/types/template.ts (1)
apps/frontend/src/main/template-parser.ts (2)
  • TemplateParameter (4-15)
  • ParsedTemplate (17-21)
apps/frontend/src/main/template-parser.ts (1)
apps/frontend/src/shared/types/template.ts (2)
  • TemplateParameter (18-29)
  • ParsedTemplate (31-35)
apps/frontend/src/renderer/components/NewProjectScreen.tsx (7)
apps/frontend/src/shared/types/template.ts (2)
  • Template (5-12)
  • ParsedTemplate (31-35)
apps/frontend/src/main/template-parser.ts (1)
  • ParsedTemplate (17-21)
apps/frontend/src/shared/types/secrets.ts (1)
  • SecretGroup (17-26)
apps/frontend/src/renderer/components/ui/dialog.tsx (6)
  • Dialog (116-116)
  • DialogContent (121-121)
  • DialogHeader (122-122)
  • DialogTitle (124-124)
  • DialogDescription (125-125)
  • DialogFooter (123-123)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
.design-system/src/lib/icons.ts (2)
  • ChevronRight (23-23)
  • Plus (30-30)
.design-system/src/components/Input.tsx (1)
  • Input (4-24)
apps/frontend/src/renderer/components/settings/AppSettings.tsx (1)
apps/frontend/src/renderer/components/settings/TemplatesSettings.tsx (1)
  • TemplatesSettings (20-447)
apps/frontend/src/preload/api/template-api.ts (2)
apps/frontend/src/shared/types/template.ts (2)
  • Template (5-12)
  • ParsedTemplate (31-35)
apps/frontend/src/main/template-parser.ts (1)
  • ParsedTemplate (17-21)
apps/frontend/src/renderer/App.tsx (3)
apps/frontend/src/main/project-store.ts (1)
  • addProject (74-109)
apps/frontend/src/shared/types/template.ts (1)
  • Template (5-12)
apps/frontend/src/renderer/components/NewProjectScreen.tsx (1)
  • NewProjectScreen (34-777)
apps/frontend/src/preload/api/secrets-api.ts (3)
apps/frontend/src/preload/api/index.ts (2)
  • SecretsAPI (61-61)
  • createSecretsAPI (47-47)
apps/frontend/src/shared/types/secrets.ts (3)
  • SecretGroup (17-26)
  • SecretGroupInput (34-39)
  • SecretAccountInput (42-45)
apps/frontend/src/shared/constants/ipc.ts (1)
  • IPC_CHANNELS (6-327)
🪛 Biome (2.1.2)
apps/frontend/src/renderer/components/settings/SecretsManager.tsx

[error] 16-19: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 21-21: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)


[error] 313-313: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)


[error] 313-313: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)

apps/frontend/src/renderer/components/NewProjectScreen.tsx

[error] 405-409: Provide an explicit type prop for the button element.

The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset

(lint/a11y/useButtonType)

🔇 Additional comments (24)
apps/frontend/src/renderer/components/ui/dialog.tsx (1)

62-62: LGTM! Clean UI improvement for the close button.

The addition of explicit dimensions (h-8 w-8) and flexbox centering (inline-flex items-center justify-center) ensures consistent button sizing and proper icon alignment. The 32×32px button size is appropriate for desktop interaction, and all existing accessibility features (focus states, sr-only text, keyboard navigation) are preserved.

apps/frontend/src/shared/types/index.ts (1)

19-20: LGTM!

The new exports for template and secrets types follow the established pattern and properly integrate the new modules into the shared types index.

apps/frontend/src/shared/types/settings.ts (2)

84-84: LGTM!

The addition of the optional projectPath field to AppSettings is straightforward and enables the default projects path feature.


116-118: LGTM!

The defaultSecretAccounts field is well-documented with clear inline comments explaining its purpose for mapping secret group titles to default account IDs.

apps/frontend/src/shared/constants/ipc.ts (2)

110-132: LGTM!

The new IPC channel constants for templates and secrets follow the established naming convention and are well-organized by feature area.


305-305: LGTM!

The GIT_CLONE_REPOSITORY constant follows the existing pattern for Git operations.

apps/frontend/src/renderer/components/settings/GeneralSettings.tsx (1)

194-209: LGTM!

The Default Projects Path UI section is well-structured with clear labels and a Browse button for folder selection. The implementation properly binds to the settings state.

apps/frontend/src/main/ipc-handlers/index.ts (3)

19-20: LGTM!

The imports for the new template and secrets handler modules follow the established pattern.


67-71: LGTM!

The registration of template and secrets handlers is properly integrated into the centralized setup function with clear comments.


119-120: LGTM!

The new handler registration functions are properly exported for potential custom usage, maintaining consistency with other exports.

apps/frontend/src/renderer/components/settings/AppSettings.tsx (4)

19-20: LGTM! Clean imports for templates feature.

The new imports for LayoutTemplate icon and TemplatesSettings component are correctly added to support the templates section.

Also applies to: 39-39


56-56: LGTM! Type extension correctly adds templates section.

The AppSection type is properly extended to include the new 'templates' section, maintaining type safety across the navigation system.


69-69: LGTM! Templates navigation item correctly configured.

The templates navigation item is properly added with the LayoutTemplate icon, following the same pattern as other navigation items.


177-178: LGTM! Templates rendering and full-width layout correctly implemented.

The switch case correctly renders TemplatesSettings for the templates section, and the container styling change (line 350) removes the max-width constraint to support full-width templates grid layout as intended by the PR.

Also applies to: 350-350

apps/frontend/src/preload/api/project-api.ts (1)

101-101: LGTM! Git clone API correctly exposed.

The cloneRepository method is properly added to the ProjectAPI interface and implemented in createProjectAPI, enabling repository cloning functionality through the IPC channel GIT_CLONE_REPOSITORY.

Also applies to: 279-280

apps/frontend/src/preload/api/index.ts (1)

5-6: LGTM! Template and Secrets APIs cleanly integrated.

The ElectronAPI interface and factory correctly compose TemplateAPI and SecretsAPI, exposing the full template and secrets management surface to the renderer. All imports, exports, and type definitions are properly structured.

Also applies to: 18-19, 31-32, 46-47, 60-61

apps/frontend/src/shared/types/secrets.ts (1)

1-45: LGTM! Well-structured type definitions.

The interfaces clearly model the secrets hierarchy with good documentation explaining the group/account relationship. The separation between input types (for creation) and full types (with metadata) follows best practices.

apps/frontend/src/preload/api/secrets-api.ts (1)

10-64: LGTM! Clean preload API implementation.

The API interface is well-typed and the factory pattern provides a consistent implementation. All IPC calls follow the established pattern with proper channel constants and return types.

apps/frontend/src/preload/api/template-api.ts (1)

5-40: LGTM! Consistent preload API pattern.

The TemplateAPI mirrors the structure of SecretsAPI, maintaining consistency across the preload layer. Good use of Omit to exclude auto-generated fields from input types.

apps/frontend/src/main/ipc-handlers/secrets-handlers.ts (1)

47-372: Good overall structure with comprehensive error handling.

The IPC handlers follow a consistent pattern with proper validation, error handling, and IPCResult returns. The separation of concerns between CRUD operations is clean.

apps/frontend/src/renderer/App.tsx (1)

47-47: Good integration of NewProjectScreen component.

The new project flow is cleanly integrated with proper state management and handlers. The component composition follows React best practices.

Also applies to: 61-61, 90-91, 399-435, 913-922

apps/frontend/src/main/ipc-handlers/template-handlers.ts (1)

42-174: LGTM! Well-structured template CRUD handlers.

The handlers follow consistent patterns with proper validation, existence checks, and error handling. Good collision detection for copy operations.

apps/frontend/src/main/template-parser.ts (2)

122-134: Extension-less files are included - verify this is intentional.

Files without extensions (line 132) are included in scanning if they're not in the binary list. This captures files like Dockerfile, Makefile, etc., which is likely intentional. Just flagging for awareness.


149-197: LGTM! Core parsing and replacement logic is sound.

The parseTemplateDirectory and replaceTemplateParameters functions implement the template parameter workflow correctly. The regex-based placeholder detection and replacement is appropriate for this use case.

Also applies to: 202-219

Comment on lines +249 to +255
const now = Date.now();

// Encrypt all key values
const encryptedKeys: Record<string, string> = {};
for (const [keyId, value] of Object.entries(accountInput.keys)) {
encryptedKeys[keyId] = SecretsEncryption.encrypt(value);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add encryption availability check before re-encrypting keys.

Similar to SECRETS_ADD_ACCOUNT, this handler encrypts keys without first verifying encryption availability. Consider adding the same pre-check for consistency:

       const now = Date.now();

+      // Verify encryption is available before proceeding
+      if (!SecretsEncryption.isEncryptionAvailable()) {
+        return { success: false, error: 'Encryption is not available on this system' };
+      }
+
       // Encrypt all key values
       const encryptedKeys: Record<string, string> = {};
📝 Committable suggestion

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

Suggested change
const now = Date.now();
// Encrypt all key values
const encryptedKeys: Record<string, string> = {};
for (const [keyId, value] of Object.entries(accountInput.keys)) {
encryptedKeys[keyId] = SecretsEncryption.encrypt(value);
}
const now = Date.now();
// Verify encryption is available before proceeding
if (!SecretsEncryption.isEncryptionAvailable()) {
return { success: false, error: 'Encryption is not available on this system' };
}
// Encrypt all key values
const encryptedKeys: Record<string, string> = {};
for (const [keyId, value] of Object.entries(accountInput.keys)) {
encryptedKeys[keyId] = SecretsEncryption.encrypt(value);
}
🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/secrets-handlers.ts around lines 249 to
255, the handler re-encrypts account keys without first checking that the
encryption subsystem is available; add the same pre-check used in the
SECRETS_ADD_ACCOUNT flow (call the existing encryption availability check/helper
before attempting to encrypt), and if encryption is not available, bail out
consistently (throw or return the same error/IPC response used elsewhere)
instead of proceeding to call SecretsEncryption.encrypt.

Comment on lines 279 to 306
// First, copy the template folder recursively
cpSync(template.folderPath, targetPath, { recursive: true });

// Parse parameters to get file paths and placeholders
const parsed = parseTemplateDirectory(template.folderPath);

// Create replacement map (placeholder -> value)
const replacements = new Map<string, string>();
for (const param of parsed.parameters) {
const value = parameterValues[param.key];
if (value !== undefined && param.placeholder) {
replacements.set(param.placeholder, value);
}
}

// Replace parameters in copied files
const filesProcessed = new Set<string>();
for (const param of parsed.parameters) {
const relativePath = path.relative(template.folderPath, param.filePath);
const targetFilePath = path.join(targetPath, relativePath);

// Only process each file once
if (!filesProcessed.has(targetFilePath)) {
filesProcessed.add(targetFilePath);
const newContent = replaceTemplateParameters(targetFilePath, replacements);
writeFileSync(targetFilePath, newContent, '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.

🧹 Nitpick | 🔵 Trivial

Consider cleanup on copy failure and handle partial writes.

If parameter replacement fails after the folder has been copied, the destination folder is left in an inconsistent state. Consider wrapping the operation in a transaction-like pattern:

🔎 Suggested improvement
       // First, copy the template folder recursively
       cpSync(template.folderPath, targetPath, { recursive: true });

+      try {
         // Parse parameters to get file paths and placeholders
         const parsed = parseTemplateDirectory(template.folderPath);

         // Create replacement map (placeholder -> value)
         const replacements = new Map<string, string>();
         for (const param of parsed.parameters) {
           const value = parameterValues[param.key];
           if (value !== undefined && param.placeholder) {
             replacements.set(param.placeholder, value);
           }
         }

         // Replace parameters in copied files
         const filesProcessed = new Set<string>();
         for (const param of parsed.parameters) {
           const relativePath = path.relative(template.folderPath, param.filePath);
           const targetFilePath = path.join(targetPath, relativePath);

           // Only process each file once
           if (!filesProcessed.has(targetFilePath)) {
             filesProcessed.add(targetFilePath);
             const newContent = replaceTemplateParameters(targetFilePath, replacements);
             writeFileSync(targetFilePath, newContent, 'utf-8');
           }
         }
+      } catch (error) {
+        // Clean up the partially copied folder on failure
+        try {
+          rmSync(targetPath, { recursive: true, force: true });
+        } catch (cleanupError) {
+          console.error('[TEMPLATES] Failed to clean up after error:', cleanupError);
+        }
+        throw error;
+      }

       return { success: true, data: { path: targetPath } };

Note: You'll need to import rmSync from 'fs'.

🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/template-handlers.ts around lines 279 to
306, the code copies the template folder then performs in-place parameter
replacements which can leave a partially-written/inconsistent target if
replacement or writing fails; change to a transactional pattern: copy the
template into a temporary staging directory (or copy then perform replacements
into temp files), perform all replacements and writes atomically (write to temp
files then rename/move into final locations), and on any error remove the
temp/staging directory and any partially written files using rmSync (ensure
rmSync is imported) before rethrowing the error so the destination is not left
in a bad state.

Comment on lines +38 to +45
static decrypt(encryptedValue: string): string {
if (!this.isEncryptionAvailable()) {
throw new Error('Encryption is not available on this system');
}

const buffer = Buffer.from(encryptedValue, 'base64');
return safeStorage.decryptString(buffer);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider validating base64 input before decryption.

The decrypt method calls Buffer.from(encryptedValue, 'base64') without validating the input format. Invalid base64 strings will cause safeStorage.decryptString to fail with cryptic errors.

🔎 Suggested defensive validation
  static decrypt(encryptedValue: string): string {
    if (!this.isEncryptionAvailable()) {
      throw new Error('Encryption is not available on this system');
    }

+   // Validate base64 format
+   if (!/^[A-Za-z0-9+/]*={0,2}$/.test(encryptedValue)) {
+     throw new Error('Invalid encrypted value format');
+   }

    const buffer = Buffer.from(encryptedValue, 'base64');
    return safeStorage.decryptString(buffer);
  }
📝 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
static decrypt(encryptedValue: string): string {
if (!this.isEncryptionAvailable()) {
throw new Error('Encryption is not available on this system');
}
const buffer = Buffer.from(encryptedValue, 'base64');
return safeStorage.decryptString(buffer);
}
static decrypt(encryptedValue: string): string {
if (!this.isEncryptionAvailable()) {
throw new Error('Encryption is not available on this system');
}
// Validate base64 format
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(encryptedValue)) {
throw new Error('Invalid encrypted value format');
}
const buffer = Buffer.from(encryptedValue, 'base64');
return safeStorage.decryptString(buffer);
}
🤖 Prompt for AI Agents
In apps/frontend/src/main/secrets-encryption.ts around lines 38 to 45, the
decrypt method calls Buffer.from(encryptedValue, 'base64') and
safeStorage.decryptString without validating the input; add a defensive check
that the input is a non-empty string and matches a safe base64 pattern (or
perform a safe decode-validate roundtrip) and throw a clear error if it fails,
then wrap the Buffer.from and safeStorage.decryptString calls in a try/catch to
rethrow a descriptive error (e.g., "Invalid base64 encrypted value" or
"Decryption failed: <err.message>") so callers get actionable messages instead
of cryptic failures.

Comment on lines +34 to +58
const loadGroups = async () => {
setIsLoading(true);
try {
// Check encryption availability
const encryptionResult = await window.electronAPI.checkSecretsEncryption();
if (encryptionResult.success && encryptionResult.data !== undefined) {
setEncryptionAvailable(encryptionResult.data);
}

const result = await window.electronAPI.getSecretGroups();
if (result.success && result.data) {
setGroups(result.data);
}

// Load default accounts from settings
const settingsResult = await window.electronAPI.getSettings();
if (settingsResult.success && settingsResult.data?.defaultSecretAccounts) {
setDefaultAccounts(settingsResult.data.defaultSecretAccounts);
}
} catch (error) {
console.error('Failed to load secret groups:', error);
} finally {
setIsLoading(false);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider showing error state to users when loading fails.

The loadGroups function logs errors to console but doesn't set an error state that could be displayed to users. Consider adding error state handling:

 const [encryptionAvailable, setEncryptionAvailable] = useState(true);
 const [defaultAccounts, setDefaultAccounts] = useState<Record<string, string>>({});
+const [loadError, setLoadError] = useState<string | null>(null);

 const loadGroups = async () => {
   setIsLoading(true);
+  setLoadError(null);
   try {
     // ... existing code
   } catch (error) {
     console.error('Failed to load secret groups:', error);
+    setLoadError('Failed to load secret groups. Please try again.');
   } finally {
     setIsLoading(false);
   }
 };

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

Comment on lines 130 to 143
const handleDeleteGroup = async (groupId: string) => {
if (!confirm('Are you sure you want to delete this secret group and all its accounts? This action cannot be undone.')) {
return;
}

try {
const result = await window.electronAPI.deleteSecretGroup(groupId);
if (result.success) {
await loadGroups();
}
} catch (error) {
console.error('Failed to delete group:', error);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using a custom confirmation dialog instead of confirm().

The native browser confirm() dialog breaks visual consistency with the app's UI and has limited accessibility features. Consider using a custom dialog component that matches the app's design system.

🤖 Prompt for AI Agents
In apps/frontend/src/renderer/components/settings/SecretsManager.tsx around
lines 130 to 143, replace the native confirm() call with the app's custom
confirmation dialog so the prompt matches the design system and improves
accessibility; import and invoke the existing Modal/ConfirmationDialog component
(or create one if missing), present the message asynchronously to the user,
await the user's response (confirm/cancel), and only proceed to call
window.electronAPI.deleteSecretGroup(groupId) when the user explicitly confirms;
ensure to handle the cancel path (no-op) and preserve the try/catch error
logging around the deletion call.

@AndyMik90 AndyMik90 self-assigned this Dec 25, 2025
@MikeeBuilds MikeeBuilds added feature New feature or request area/fullstack This is Frontend + Backend labels Dec 26, 2025
Copy link
Owner

@AndyMik90 AndyMik90 left a comment

Choose a reason for hiding this comment

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

🤖 Auto Claude PR Review

Merge Verdict: 🔴 BLOCKED

Blocked by 2 security vulnerabilities

Risk Assessment

Factor Level Notes
Complexity High Based on lines changed
Security Impact Critical Based on security findings
Scope Coherence Good Based on structural review

🚨 Blocking Issues (Must Fix)

  • Security: Potential Command Injection in Template Scaffolding (apps/frontend/src/main/ipc-handlers/template-handlers.ts:0)
  • Security: Encryption Implementation Requires Review (apps/frontend/src/main/secrets-encryption.ts:0)

Findings Summary

  • Critical: 2 issue(s)
  • High: 3 issue(s)
  • Medium: 5 issue(s)
  • Low: 1 issue(s)

Generated by Auto Claude PR Review

Findings (5 selected of 11 total)

🔴 [CRITICAL] Potential Command Injection in Template Scaffolding

📁 apps/frontend/src/main/ipc-handlers/template-handlers.ts:0

If template commands are passed from renderer to main process for shell execution, this creates a command injection attack surface. Template commands should be validated against an allowlist or looked up from a trusted server-side registry.

Suggested fix:

1. Never accept shell commands directly from renderer process. 2. Use template IDs and lookup commands server-side. 3. Use execFile with argument arrays instead of shell execution. 4. Validate commands against allowlist of scaffolding tools.

🔴 [CRITICAL] Encryption Implementation Requires Review

📁 apps/frontend/src/main/secrets-encryption.ts:0

Secrets encryption must use secure algorithms (AES-256-GCM, not ECB mode), proper key derivation (PBKDF2/Argon2 with sufficient iterations), and secure random IV generation. The actual implementation must be verified.

Suggested fix:

Verify: 1) AES-256-GCM or similar AEAD cipher used. 2) PBKDF2 with 100k+ iterations or Argon2id for key derivation. 3) Random IV/nonce per encryption. 4) No hardcoded keys/secrets.

🟠 [HIGH] Secrets Storage Security Verification Needed

📁 apps/frontend/src/main/ipc-handlers/secrets-handlers.ts:0

Secrets handlers must not store master keys in plaintext, must use OS keychain (Electron safeStorage) where available, and must validate all inputs to prevent injection attacks.

Suggested fix:

Verify: 1) Master key stored in OS keychain. 2) Fallback encryption when keychain unavailable. 3) Input validation on all secret operations. 4) No secrets logged to console/files.

🟠 [HIGH] Template Parsing Injection Risk

📁 apps/frontend/src/main/template-parser.ts:0

Template parsers that use eval(), Function(), or vm module for variable substitution are vulnerable to code injection. Dynamic value substitution should use safe string replacement only.

Suggested fix:

Verify: 1) No eval(), Function(), or vm module usage. 2) Template variables use simple string.replace() or template literals with sanitized values. 3) No user input flows into executable code.

🟠 [HIGH] Shell Execution Pattern Review

📁 apps/frontend/src/main/ipc-handlers/template-handlers.ts:0

If spawn() or exec() is used with shell=true and user-controllable input, command injection is possible. Project names and paths must be properly sanitized.

Suggested fix:

1) Sanitize project names: allow only [a-z0-9-_]. 2) Use spawn with argument arrays, not shell execution. 3) Validate all paths are within expected directories.

This review was generated by Auto Claude.

JoshuaRileyDev and others added 2 commits December 27, 2025 18:46
Security Fixes:
- Fix path traversal vulnerability in template-handlers.ts
  - Add sanitizeName() to prevent "../" path escaping
  - Add validateDestinationPath() to validate destination paths
  - Apply to all template copy handlers

- Add comprehensive input validation in secrets-handlers.ts
  - Validate UUID format for all ID parameters
  - Add string length validation to prevent DoS
  - Add object size validation for key collections
  - Add validation to all 10+ handlers

- Document encryption security in secrets-encryption.ts
  - Confirm AES-256-GCM encryption (NIST approved)
  - Document OS-managed key storage (Keychain/DPAPI/libsecret)
  - Verify no hardcoded keys or secrets

Resolves critical path traversal and high-priority input validation issues
identified in PR security review.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Resolved conflicts:
- apps/frontend/src/renderer/App.tsx: Kept both handleOpenInbuiltTerminal and handleImportFolder functions
- apps/frontend/src/shared/types/index.ts: Merged exports for template, secrets, and cli types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (2)
apps/frontend/src/main/ipc-handlers/template-handlers.ts (2)

93-96: Consider adding error handling for consistency.

As noted in the previous review, wrapping the write operation in try-catch would make error handling consistent with readTemplatesFile and provide better visibility into write failures.


346-373: Add cleanup on failure to prevent inconsistent state.

As noted in the previous review, if parameter replacement or file writes fail after the folder copy (line 347), the destination is left partially modified. The previous suggestion to add cleanup logic with rmSync remains valid and should be implemented.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c7ff2f and ab21ae9.

📒 Files selected for processing (3)
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
  • apps/frontend/src/main/secrets-encryption.ts
🧰 Additional context used
📓 Path-based instructions (2)
apps/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/frontend/src/**/*.{ts,tsx}: Always use translation keys with useTranslation() for all user-facing text in React/TypeScript frontend components - use format namespace:section.key (e.g., navigation:items.githubPRs)
Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from apps/frontend/src/shared/i18n/locales/

Files:

  • apps/frontend/src/main/secrets-encryption.ts
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
apps/frontend/**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

apps/frontend/**/*.{ts,tsx}: Review React patterns and TypeScript type safety.
Check for proper state management and component composition.

Files:

  • apps/frontend/src/main/secrets-encryption.ts
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
🧬 Code graph analysis (2)
apps/frontend/src/main/ipc-handlers/template-handlers.ts (3)
apps/frontend/src/shared/types/template.ts (3)
  • TemplateStore (14-16)
  • Template (5-12)
  • ParsedTemplate (31-35)
apps/frontend/src/main/ipc-handlers/index.ts (1)
  • registerTemplateHandlers (119-119)
apps/frontend/src/main/template-parser.ts (3)
  • ParsedTemplate (17-21)
  • parseTemplateDirectory (149-197)
  • replaceTemplateParameters (202-219)
apps/frontend/src/main/ipc-handlers/secrets-handlers.ts (2)
apps/frontend/src/shared/types/secrets.ts (4)
  • SecretsStore (28-31)
  • SecretGroup (17-26)
  • SecretGroupInput (34-39)
  • SecretAccountInput (42-45)
apps/frontend/src/main/secrets-encryption.ts (1)
  • SecretsEncryption (33-78)
🔇 Additional comments (15)
apps/frontend/src/main/secrets-encryption.ts (3)

1-39: Excellent security documentation and implementation.

The comprehensive security documentation clearly explains the encryption approach, platform-specific implementations, and compliance details. The isEncryptionAvailable() method correctly delegates to Electron's safeStorage API.


41-53: LGTM!

The encrypt() method correctly checks encryption availability before proceeding and returns a base64-encoded ciphertext suitable for storage.


69-77: LGTM!

The reEncrypt() method correctly implements key rotation by decrypting with the old key and re-encrypting with a fresh IV/nonce.

apps/frontend/src/main/ipc-handlers/secrets-handlers.ts (12)

1-22: LGTM!

The imports and UUID validation helper are well-implemented. The validateId() function correctly uses the uuid library's validation and provides clear error messages.


24-31: LGTM!

The validateStringLength() helper correctly prevents DoS attacks via oversized string inputs.


43-68: LGTM!

Both getSecretsPath() and readSecretsFile() implement proper error handling. The read function gracefully handles missing files and parse errors by returning a sensible default.


75-106: LGTM!

Both handlers follow the correct IPC pattern with comprehensive error handling and return the expected IPCResult type.


108-131: LGTM!

The SECRETS_GET_GROUP handler properly validates the UUID input and returns appropriate errors for invalid IDs or missing groups.


133-173: LGTM with validation concern noted.

The SECRETS_CREATE_GROUP handler correctly validates inputs, generates UUIDs, and persists the new group. Note that line 146's usage of validateObjectSize with the keyIds array is addressed in the earlier comment on the validation helper.


175-220: LGTM with validation concern noted.

The SECRETS_UPDATE_GROUP handler correctly implements partial updates with appropriate validation. Note that line 194's usage of validateObjectSize with the keyIds array is addressed in the earlier comment on the validation helper.


222-248: LGTM!

The SECRETS_DELETE_GROUP handler properly validates inputs and safely removes groups from the store.


250-304: LGTM!

The SECRETS_ADD_ACCOUNT handler implements comprehensive input validation for title, keys object, and individual key-value pairs before encrypting and persisting the new account.


306-360: LGTM!

The SECRETS_UPDATE_ACCOUNT handler correctly validates both UUID parameters and all input fields before re-encrypting and updating the account.


362-396: LGTM!

The SECRETS_DELETE_ACCOUNT handler properly validates both UUID parameters and safely removes accounts from groups.


398-476: Excellent secure decryption implementation.

Both decryption handlers properly validate inputs and include security-conscious comments about avoiding logging of decrypted values. The implementation correctly separates full-account decryption from single-key decryption use cases.

const realPath = realpathSync(absolutePath);

// Ensure it's a directory (not a file)
const stats = require('fs').statSync(realPath);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Use the imported statSync instead of require('fs').

Line 2 already imports filesystem functions. Import statSync there and use it here for consistency.

🔎 Proposed fix

Update the import on line 2:

-import { existsSync, writeFileSync, readFileSync, mkdirSync, cpSync, realpathSync } from 'fs';
+import { existsSync, writeFileSync, readFileSync, mkdirSync, cpSync, realpathSync, statSync } from 'fs';

Then update line 54:

-    const stats = require('fs').statSync(realPath);
+    const stats = statSync(realPath);
🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/template-handlers.ts around line 54, the
code uses require('fs').statSync(realPath) despite fs functions being imported
at the top; add statSync to the existing import on line 2 (e.g., include
statSync in the destructured import from 'fs') and replace the require call on
line 54 with a direct call to statSync(realPath) to keep imports consistent and
remove the inline require.

// Delete template
ipcMain.handle(
IPC_CHANNELS.TEMPLATES_DELETE,
async (_, templateId: string): Promise<IPCResult> => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add UUID validation for templateId parameters.

Per the PR's security hardening (UUID validation in secrets-handlers.ts), validate that templateId is a valid UUID format before processing. This prevents unnecessary store operations and potential confusion from malformed IDs.

🔎 Suggested validation helper

Add a validation function at the top of the file:

const isValidUUID = (id: string): boolean => {
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  return uuidRegex.test(id);
};

Then in each handler accepting templateId:

 async (_, templateId: string): Promise<IPCResult> => {
   try {
+    if (!isValidUUID(templateId)) {
+      return { success: false, error: 'Invalid template ID format' };
+    }
+
     const store = readTemplatesFile();

Also applies to: 170-170, 197-197, 238-238, 279-279, 319-319

🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/template-handlers.ts around lines 145,
170, 197, 238, 279 and 319, the handlers accepting templateId lack UUID format
validation; add a small isValidUUID helper at the top of the file using the
provided regex and call it at the start of each handler that takes templateId,
returning an IPC error/result (same shape as other validation errors)
immediately when validation fails to avoid further store operations; ensure you
use the same error/result pattern used in secrets-handlers.ts for consistency.

if (value) {
// Remove straight quotes: ' "
// Remove smart quotes: " " ' ' (Unicode \u201C \u201D \u2018 \u2019)
value = value.replace(/['""\u201C\u201D''\u2018\u2019]/g, '');

Check warning

Code scanning / CodeQL

Duplicate character in character class Warning

Character ''' is
repeated in the same character class
.
Character ''' is
repeated in the same character class
.
if (value) {
// Remove straight quotes: ' "
// Remove smart quotes: " " ' ' (Unicode \u201C \u201D \u2018 \u2019)
value = value.replace(/['""\u201C\u201D''\u2018\u2019]/g, '');

Check warning

Code scanning / CodeQL

Duplicate character in character class Warning

Character '"' is
repeated in the same character class
.
}

if (pairs.type === 'dropdown' && pairs.options) {
param.options = pairs.options.split(',').map(o => o.trim().replace(/['""\u201C\u201D''\u2018\u2019]/g, ''));

Check warning

Code scanning / CodeQL

Duplicate character in character class Warning

Character ''' is
repeated in the same character class
.
Character ''' is
repeated in the same character class
.
}

if (pairs.type === 'dropdown' && pairs.options) {
param.options = pairs.options.split(',').map(o => o.trim().replace(/['""\u201C\u201D''\u2018\u2019]/g, ''));

Check warning

Code scanning / CodeQL

Duplicate character in character class Warning

Character '"' is
repeated in the same character class
.
const handleCreateFromTemplate = async (template: Template) => {
try {
// Get the default projects location
const defaultDir = await window.electronAPI.getDefaultProjectLocation();

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable defaultDir.
import { Card, CardContent } from './ui/card';
import { Input } from './ui/input';
import { Label } from './ui/label';
import { ScrollArea } from './ui/scroll-area';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import ScrollArea.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (5)
apps/frontend/src/renderer/App.tsx (1)

487-490: TODO comment indicates missing user feedback.

The error case logs to console but the TODO indicates user notification is needed. Consider tracking this as a follow-up.

Would you like me to open an issue to track adding user-facing error notifications for template creation failures?

🤖 Prompt for AI Agents
In apps/frontend/src/renderer/App.tsx around lines 458 to 461, the error branch
only logs to console and leaves a TODO to show the user an error; replace the
TODO by invoking the app's user-facing notification mechanism (e.g., dispatch a
toast/alert, set an error state that renders a visible error banner/modal, or
call the existing notification utility) and include a concise, user-friendly
message plus the error detail for debugging; ensure the UI notification is
accessible and dismissible, and if there's no notification system yet, create a
small reusable helper (or open a follow-up issue) so template copy failures
surface to users rather than only to the console.
apps/frontend/src/renderer/components/settings/GeneralSettings.tsx (1)

122-131: Improve error handling in directory selection.

The empty catch block (lines 128-130) silently swallows all errors, not just user cancellations. If the API fails for other reasons (e.g., permissions), the user won't know.

🔎 Proposed improvement
  const handleSelectProjectPath = async () => {
    try {
      const path = await window.electronAPI.selectDirectory();
      if (path) {
        onSettingsChange({ ...settings, projectPath: path });
      }
-   } catch {
-     // User cancelled - ignore
+   } catch (error) {
+     // User cancellation returns null/undefined, not an error
+     // If we reach here, log unexpected errors
+     console.error('Failed to select project path:', error);
    }
  };
🤖 Prompt for AI Agents
In apps/frontend/src/renderer/components/settings/GeneralSettings.tsx around
lines 27 to 36 the catch block for handleSelectProjectPath is empty and swallows
all errors; change it to catch the error (e.g., catch (err)), distinguish
user-cancellation from real failures (check err.message or a cancellation flag
returned by window.electronAPI), and for non-cancellation errors log the error
(console.error) and surface it to the user (call your existing
notification/toast API or window.electronAPI.showError) so permission/IO errors
are visible while still silently ignoring explicit user-cancel actions.
apps/frontend/src/main/ipc-handlers/project-handlers.ts (1)

467-514: Critical command injection vulnerability: user-provided repoUrl is directly interpolated into shell command without sanitization.

Line 491 executes git clone with user input via template literals and execSync. A malicious repoUrl like https://github.com/repo.git"; rm -rf / # would execute arbitrary commands. This is a critical security issue.

Replace execSync with execFileSync (already imported) using array syntax to prevent shell interpretation:

🔎 Proposed fix
        // Clone the repository
        try {
-         execSync(`git clone "${repoUrl}" "${clonePath}"`, {
+         execFileSync(getToolPath('git'), ['clone', repoUrl, clonePath], {
            stdio: 'pipe',
            encoding: 'utf-8'
          });
-       } catch (gitError: any) {
+       } catch (gitError: unknown) {
          // Parse git error message
-         const errorMessage = gitError.stderr || gitError.message || 'Git clone failed';
+         const errorMessage = gitError instanceof Error ? gitError.message : 'Git clone failed';
          return { success: false, error: errorMessage };
        }

Additionally, validate repoUrl before use to ensure it matches accepted Git URL patterns (HTTPS or SSH formats) and reject invalid values.

🤖 Prompt for AI Agents
apps/frontend/src/main/ipc-handlers/project-handlers.ts around lines 489 to 536:
the current handler directly interpolates user-controlled repoUrl into execSync
which causes a command-injection risk; replace execSync with
child_process.execFileSync (already imported) using argument array form (e.g.
execFileSync(getToolPath('git'), ['clone', repoUrl, clonePath], { stdio: 'pipe',
encoding: 'utf-8' })) so the URL is passed as an argument not interpreted by a
shell, validate repoUrl before use (ensure it matches accepted git URL patterns
such as HTTPS or SSH formats and reject/return an error for invalid values),
change the git error catch signature from catch (gitError: any) to catch
(gitError: unknown) and extract a safe message (checking instanceof Error),
and keep the existing existsSync checks and error returns.
apps/frontend/src/shared/types/ipc.ts (2)

245-254: Prefer static imports for consistency and readability.

The Template operations continue to use inline dynamic imports (import('./template').Template, import('./template').ParsedTemplate), which remains inconsistent with the static import pattern used throughout lines 6-109.

Add static imports at the top:

import type { Template, ParsedTemplate } from './template';

Then reference Template and ParsedTemplate directly in method signatures.


255-267: Prefer static imports for consistency and readability.

The Secrets operations use inline dynamic imports (import('./secrets').SecretGroup, import('./secrets').SecretGroupInput, import('./secrets').SecretAccountInput), creating the same inconsistency as the Template operations.

Add static imports at the top:

import type { SecretGroup, SecretGroupInput, SecretAccountInput } from './secrets';

Then reference these types directly in all method signatures (lines 255-267).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab21ae9 and 5eb12b6.

📒 Files selected for processing (9)
  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/preload/api/index.ts
  • apps/frontend/src/preload/api/project-api.ts
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/types/index.ts
  • apps/frontend/src/shared/types/ipc.ts
  • apps/frontend/src/shared/types/settings.ts
🧰 Additional context used
📓 Path-based instructions (2)
apps/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/frontend/src/**/*.{ts,tsx}: Always use translation keys with useTranslation() for all user-facing text in React/TypeScript frontend components - use format namespace:section.key (e.g., navigation:items.githubPRs)
Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from apps/frontend/src/shared/i18n/locales/

Files:

  • apps/frontend/src/shared/types/index.ts
  • apps/frontend/src/preload/api/project-api.ts
  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/preload/api/index.ts
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/types/settings.ts
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/shared/types/ipc.ts
apps/frontend/**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

apps/frontend/**/*.{ts,tsx}: Review React patterns and TypeScript type safety.
Check for proper state management and component composition.

Files:

  • apps/frontend/src/shared/types/index.ts
  • apps/frontend/src/preload/api/project-api.ts
  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/preload/api/index.ts
  • apps/frontend/src/renderer/components/settings/GeneralSettings.tsx
  • apps/frontend/src/shared/constants/ipc.ts
  • apps/frontend/src/shared/types/settings.ts
  • apps/frontend/src/renderer/App.tsx
  • apps/frontend/src/shared/types/ipc.ts
🧠 Learnings (1)
📚 Learning: 2025-12-25T18:29:32.954Z
Learnt from: CR
Repo: AndyMik90/Auto-Claude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-25T18:29:32.954Z
Learning: Applies to apps/frontend/src/**/*.{ts,tsx} : Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from `apps/frontend/src/shared/i18n/locales/`

Applied to files:

  • apps/frontend/src/shared/types/ipc.ts
🧬 Code graph analysis (5)
apps/frontend/src/preload/api/project-api.ts (1)
apps/frontend/src/shared/constants/ipc.ts (1)
  • IPC_CHANNELS (6-390)
apps/frontend/src/main/ipc-handlers/project-handlers.ts (1)
apps/frontend/src/shared/constants/ipc.ts (1)
  • IPC_CHANNELS (6-390)
apps/frontend/src/preload/api/index.ts (2)
apps/frontend/src/preload/api/template-api.ts (2)
  • TemplateAPI (5-14)
  • createTemplateAPI (16-40)
apps/frontend/src/preload/api/secrets-api.ts (2)
  • SecretsAPI (10-29)
  • createSecretsAPI (31-64)
apps/frontend/src/renderer/components/settings/GeneralSettings.tsx (3)
apps/frontend/src/renderer/components/settings/utils/hookProxyFactory.ts (1)
  • settings (15-15)
.design-system/src/components/Input.tsx (1)
  • Input (4-24)
.design-system/src/components/Button.tsx (1)
  • Button (10-44)
apps/frontend/src/renderer/App.tsx (3)
apps/frontend/src/main/project-store.ts (1)
  • addProject (74-109)
apps/frontend/src/shared/types/template.ts (1)
  • Template (5-12)
apps/frontend/src/renderer/components/NewProjectScreen.tsx (1)
  • NewProjectScreen (34-777)
🪛 GitHub Check: CodeQL
apps/frontend/src/renderer/App.tsx

[notice] 460-460: Unused variable, import, function or class
Unused variable defaultDir.

🔇 Additional comments (10)
apps/frontend/src/shared/types/index.ts (1)

19-20: LGTM!

The new type exports for templates and secrets follow the existing barrel export pattern and integrate cleanly with the type system.

apps/frontend/src/renderer/App.tsx (5)

48-48: LGTM!

The imports and state setup for the new project screen integration are clean and follow React best practices.

Also applies to: 63-63, 92-92


419-421: LGTM!

The refactored handleAddProject cleanly delegates to the new project screen modal, simplifying the flow.


423-447: LGTM!

The import folder handler correctly orchestrates project addition, tab management, and initialization flows with appropriate error handling.


449-455: LGTM!

The recent project selection handler correctly refreshes the project list and opens the selected project.


946-954: LGTM!

The NewProjectScreen component is correctly integrated with proper prop passing and event handling.

apps/frontend/src/shared/constants/ipc.ts (1)

110-132: LGTM!

The new IPC channel constants for templates, secrets, and git cloning follow the established naming conventions and integrate cleanly with the existing channel definitions.

Also applies to: 368-368

apps/frontend/src/shared/types/settings.ts (1)

90-90: LGTM!

The new settings fields are properly typed as optional and include helpful inline documentation explaining their purpose.

Also applies to: 122-124

apps/frontend/src/preload/api/project-api.ts (1)

100-100: LGTM!

The cloneRepository method is cleanly integrated into the ProjectAPI with proper typing and IPC channel usage.

Also applies to: 275-276

apps/frontend/src/preload/api/index.ts (1)

5-6: LGTM!

The template and secrets APIs are cleanly integrated following the established composition pattern for the ElectronAPI surface.

Also applies to: 19-20, 34-35, 50-51, 65-66

const handleCreateFromTemplate = async (template: Template) => {
try {
// Get the default projects location
const defaultDir = await window.electronAPI.getDefaultProjectLocation();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused variable.

The defaultDir variable is declared but never used. Either remove it or use it to set a default directory for the picker.

🔎 Proposed fix
-    const defaultDir = await window.electronAPI.getDefaultProjectLocation();
-
     // Ask user to select where to create the project
     const path = await window.electronAPI.selectDirectory();
🧰 Tools
🪛 GitHub Check: CodeQL

[notice] 460-460: Unused variable, import, function or class
Unused variable defaultDir.

🤖 Prompt for AI Agents
In apps/frontend/src/renderer/App.tsx around line 460, the const defaultDir
returned from window.electronAPI.getDefaultProjectLocation() is declared but
never used; remove the unused variable or apply it to the folder/file picker.
Fix by either deleting the await call and variable if not needed, or pass
defaultDir into the picker initialization (or set it as the initial directory
state before opening the picker) and remove any linter suppression so the
variable is used.

Comment on lines +329 to +344
<div className="space-y-3">
<Label htmlFor="projectPath" className="text-sm font-medium text-foreground">Default Projects Path</Label>
<p className="text-sm text-muted-foreground">Default location for new projects and templates</p>
<div className="flex gap-2 max-w-lg">
<Input
id="projectPath"
placeholder="Select a folder..."
className="flex-1"
value={settings.projectPath || ''}
onChange={(e) => onSettingsChange({ ...settings, projectPath: e.target.value })}
/>
<Button variant="outline" onClick={handleSelectProjectPath}>
Browse
</Button>
</div>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use translation keys for all user-facing text.

Lines 330, 331, 335, and 341 contain hardcoded English strings that violate the coding guidelines. All user-facing text in React/TypeScript frontend components must use useTranslation() with translation keys in the format namespace:section.key.

As per coding guidelines: Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from apps/frontend/src/shared/i18n/locales/.

🔎 Proposed fix
        <div className="space-y-3">
-         <Label htmlFor="projectPath" className="text-sm font-medium text-foreground">Default Projects Path</Label>
-         <p className="text-sm text-muted-foreground">Default location for new projects and templates</p>
+         <Label htmlFor="projectPath" className="text-sm font-medium text-foreground">{t('general.projectPath')}</Label>
+         <p className="text-sm text-muted-foreground">{t('general.projectPathDescription')}</p>
          <div className="flex gap-2 max-w-lg">
            <Input
              id="projectPath"
-             placeholder="Select a folder..."
+             placeholder={t('general.projectPathPlaceholder')}
              className="flex-1"
              value={settings.projectPath || ''}
              onChange={(e) => onSettingsChange({ ...settings, projectPath: e.target.value })}
            />
            <Button variant="outline" onClick={handleSelectProjectPath}>
-             Browse
+             {t('common:buttons.browse', { ns: 'common' })}
            </Button>
          </div>
        </div>
📝 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
<div className="space-y-3">
<Label htmlFor="projectPath" className="text-sm font-medium text-foreground">Default Projects Path</Label>
<p className="text-sm text-muted-foreground">Default location for new projects and templates</p>
<div className="flex gap-2 max-w-lg">
<Input
id="projectPath"
placeholder="Select a folder..."
className="flex-1"
value={settings.projectPath || ''}
onChange={(e) => onSettingsChange({ ...settings, projectPath: e.target.value })}
/>
<Button variant="outline" onClick={handleSelectProjectPath}>
Browse
</Button>
</div>
</div>
<div className="space-y-3">
<Label htmlFor="projectPath" className="text-sm font-medium text-foreground">{t('general:projectPath')}</Label>
<p className="text-sm text-muted-foreground">{t('general:projectPathDescription')}</p>
<div className="flex gap-2 max-w-lg">
<Input
id="projectPath"
placeholder={t('general:projectPathPlaceholder')}
className="flex-1"
value={settings.projectPath || ''}
onChange={(e) => onSettingsChange({ ...settings, projectPath: e.target.value })}
/>
<Button variant="outline" onClick={handleSelectProjectPath}>
{t('common:buttons.browse')}
</Button>
</div>
</div>
🤖 Prompt for AI Agents
In apps/frontend/src/renderer/components/settings/GeneralSettings.tsx around
lines 329-344 the component uses hardcoded English strings for
labels/placeholders/buttons; replace those with translation keys via
useTranslation. Import and call useTranslation with the appropriate namespace
(e.g. const { t } = useTranslation('settings')), then replace "Default Projects
Path", "Default location for new projects and templates", "Select a folder..."
and "Browse" with t('settings:general.defaultProjectsPath'),
t('settings:general.defaultProjectsPathDescription'),
t('settings:general.selectFolderPlaceholder'), and t('settings:general.browse')
respectively (or equivalent keys under apps/frontend/src/shared/i18n/locales/),
keeping existing props and handlers unchanged.

…tems

Security Enhancements:

1. Fixed validateObjectSize to handle both arrays and objects
   - Updated type signature to accept Record<string, any> | unknown[]
   - Auto-detects type and validates accordingly
   - Provides clear error messages with item type (items vs keys)

2. Added comprehensive input validation for template fields
   - Validate name (1-255 chars, required)
   - Validate folderPath (1-1024 chars, required, must exist)
   - Validate imagePath (max 1024 chars, optional)
   - Type checking for all fields

3. Implemented optimistic locking for template updates
   - Added version field to Template interface
   - TEMPLATES_UPDATE now requires expectedVersion parameter
   - Prevents concurrent modification issues
   - Returns clear conflict error when version mismatch detected
   - Auto-increments version on successful update

4. Fixed TOCTOU race conditions in template copy operations
   - Replaced existsSync checks with atomic mkdirSync operations
   - Handles EEXIST errors properly to detect existing folders
   - Added cleanup logic if copy/parameter replacement fails
   - Prevents race conditions in concurrent copy attempts
   - Applied to: TEMPLATES_COPY, TEMPLATES_COPY_WITH_NAME, TEMPLATES_COPY_WITH_PARAMETERS

5. Added comprehensive parameterValues validation
   - Validates type (must be object, not array)
   - Limits to 100 parameters max
   - Validates each key length (max 100 chars)
   - Validates each value length (max 10,000 chars)
   - Validates total payload size (max 1MB)
   - Prevents DoS attacks via memory exhaustion

All changes tested and verified with successful build.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (7)
apps/frontend/src/main/ipc-handlers/secrets-handlers.ts (3)

73-76: Missing error handling in writeSecretsFile.

This was previously flagged: the function lacks try/catch error handling and context logging. While callers have try/catch blocks, adding error handling here would provide better diagnostic context.


253-307: Two issues: missing encryption check and schema validation.

  1. Missing encryption availability check (duplicate): This was previously flagged - encryption should be verified available before attempting to encrypt at line 283.

  2. Account keys don't validate against group schema: The handler doesn't verify that accountInput.keys match the group's keyIds schema. This allows:

    • Adding keys not defined in the schema
    • Missing required keys from the schema
🔎 Proposed fix for schema validation
       const now = Date.now();

+      // Verify encryption is available before proceeding
+      if (!SecretsEncryption.isEncryptionAvailable()) {
+        return { success: false, error: 'Encryption is not available on this system' };
+      }
+
+      // Validate that provided keys match the group schema
+      const providedKeyIds = new Set(Object.keys(accountInput.keys));
+      const requiredKeyIds = new Set(group.keyIds);
+      
+      // Check for missing required keys
+      for (const requiredKey of requiredKeyIds) {
+        if (!providedKeyIds.has(requiredKey)) {
+          return { success: false, error: `Missing required key: ${requiredKey}` };
+        }
+      }
+      
+      // Check for extra keys not in schema
+      for (const providedKey of providedKeyIds) {
+        if (!requiredKeyIds.has(providedKey)) {
+          return { success: false, error: `Key not in schema: ${providedKey}` };
+        }
+      }
+
       // Encrypt all key values
       const encryptedKeys: Record<string, string> = {};

309-363: Two issues: missing encryption check and schema validation.

  1. Missing encryption availability check (duplicate): This was previously flagged - encryption should be verified available before attempting to encrypt at line 345.

  2. Account keys don't validate against group schema: Similar to SECRETS_ADD_ACCOUNT, this handler doesn't verify that updated keys match the group's keyIds schema.

🔎 Proposed fix
       const now = Date.now();

+      // Verify encryption is available before proceeding
+      if (!SecretsEncryption.isEncryptionAvailable()) {
+        return { success: false, error: 'Encryption is not available on this system' };
+      }
+
+      // Validate that provided keys match the group schema
+      const providedKeyIds = new Set(Object.keys(accountInput.keys));
+      const requiredKeyIds = new Set(group.keyIds);
+      
+      for (const requiredKey of requiredKeyIds) {
+        if (!providedKeyIds.has(requiredKey)) {
+          return { success: false, error: `Missing required key: ${requiredKey}` };
+        }
+      }
+      
+      for (const providedKey of providedKeyIds) {
+        if (!requiredKeyIds.has(providedKey)) {
+          return { success: false, error: `Key not in schema: ${providedKey}` };
+        }
+      }
+
       // Encrypt all key values
       const encryptedKeys: Record<string, string> = {};
apps/frontend/src/main/ipc-handlers/template-handlers.ts (4)

7-7: Consolidate duplicate type definitions to shared types.

The types TemplateParameter and ParsedTemplate imported here are duplicated across shared/types/template.ts and template-parser.ts. This creates maintenance burden and inconsistency.

As noted in past review comments, move these interface definitions to a single source of truth in shared/types/template.ts and update both template-parser.ts and this file to import from that shared location.


54-54: Use the imported statSync instead of require('fs').

Line 2 already imports filesystem functions. Import statSync there and use it here for consistency.

This issue was previously flagged and remains unaddressed.


93-96: Missing error handling in writeTemplatesFile.

Unlike readTemplatesFile, this function doesn't wrap the write operation in a try-catch. If writeFileSync fails (disk full, permissions, etc.), the error will propagate up but won't be caught consistently across all callers.

This issue was previously flagged and remains unaddressed.


177-177: Add UUID validation for templateId parameters.

Per past review comments, validate that templateId is a valid UUID format before processing. This prevents unnecessary store operations and potential confusion from malformed IDs.

This issue was previously flagged for handlers at lines 177, 204, 285, 397, and 437 and remains unaddressed.

Also applies to: 204-204, 285-285, 397-397, 437-437

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eb12b6 and 31778b8.

📒 Files selected for processing (3)
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
  • apps/frontend/src/shared/types/template.ts
🧰 Additional context used
📓 Path-based instructions (2)
apps/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/frontend/src/**/*.{ts,tsx}: Always use translation keys with useTranslation() for all user-facing text in React/TypeScript frontend components - use format namespace:section.key (e.g., navigation:items.githubPRs)
Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from apps/frontend/src/shared/i18n/locales/

Files:

  • apps/frontend/src/shared/types/template.ts
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
apps/frontend/**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

apps/frontend/**/*.{ts,tsx}: Review React patterns and TypeScript type safety.
Check for proper state management and component composition.

Files:

  • apps/frontend/src/shared/types/template.ts
  • apps/frontend/src/main/ipc-handlers/secrets-handlers.ts
  • apps/frontend/src/main/ipc-handlers/template-handlers.ts
🧬 Code graph analysis (1)
apps/frontend/src/main/ipc-handlers/secrets-handlers.ts (3)
apps/frontend/src/shared/types/secrets.ts (4)
  • SecretsStore (28-31)
  • SecretGroup (17-26)
  • SecretGroupInput (34-39)
  • SecretAccountInput (42-45)
apps/frontend/src/main/ipc-handlers/index.ts (1)
  • registerSecretsHandlers (120-120)
apps/frontend/src/main/secrets-encryption.ts (1)
  • SecretsEncryption (33-78)
⏰ 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: CodeQL (javascript-typescript)
  • GitHub Check: CodeQL (python)
🔇 Additional comments (11)
apps/frontend/src/main/ipc-handlers/secrets-handlers.ts (7)

33-44: LGTM! Validation helper properly handles both arrays and objects.

The updated validateObjectSize function now correctly handles both arrays (by length) and objects (by key count) with appropriate type signatures and error messages. This addresses the previous feedback about semantic clarity.


57-71: LGTM! Proper error handling with safe fallback.

The function correctly handles missing files and parse errors, providing a safe default store and logging errors for debugging.


78-93: LGTM! Proper encryption availability check.

The handler correctly checks encryption availability and returns a well-structured result with error handling.


95-134: LGTM! Group retrieval handlers are well-implemented.

Both handlers properly validate inputs, handle errors, and return appropriate results.


225-251: LGTM! Group deletion properly implemented.

The handler correctly validates the group ID and removes the group (including all its accounts).


365-399: LGTM! Account deletion properly implemented.

The handler correctly validates IDs, removes the account, and updates timestamps.


401-479: LGTM! Decryption handlers are secure and well-implemented.

Both decryption handlers:

  • Properly validate all IDs and inputs
  • Have comprehensive error handling
  • Include security-conscious comments about not logging decrypted values
  • Handle encryption unavailability through the SecretsEncryption.decrypt() error path
apps/frontend/src/main/ipc-handlers/template-handlers.ts (4)

120-149: Excellent input validation implementation.

The comprehensive validation of template fields (name, folderPath, imagePath) addresses the security hardening approach mentioned in past reviews. The checks prevent:

  • Empty or oversized field values
  • Non-existent folder paths
  • Type confusion attacks

This is a significant security improvement.


255-268: Well-implemented optimistic locking mechanism.

The version-based concurrency control prevents lost updates when multiple processes modify the same template. The implementation correctly:

  • Checks the version before applying updates
  • Provides a clear error message with version details
  • Increments the version after successful update

This addresses the past concern about concurrent template modifications.


305-328: Excellent TOCTOU race condition mitigation.

The atomic directory creation pattern (lines 305-314) with mkdirSync(targetPath, { recursive: false }) correctly prevents the time-of-check to time-of-use vulnerability. If the directory exists, the operation fails immediately with EEXIST, preventing concurrent operations from corrupting each other.

The cleanup-on-failure pattern (lines 316-328) ensures the destination is not left in an inconsistent state when copy operations fail.

This addresses the critical security concern raised in past reviews.


458-497: Comprehensive DoS prevention through parameter validation.

The multi-layered validation of parameterValues effectively prevents denial-of-service attacks through:

  • Maximum parameter count (100)
  • Per-value size limits (10,000 characters)
  • Per-key size limits (100 characters)
  • Total payload size limit (1,000,000 bytes)
  • Type checking to prevent object injection

This addresses the security hardening concern raised in past reviews and aligns with the validation approach used in secrets-handlers.ts.

Comment on lines +136 to +176
// Create a new secret group (schema only, no accounts)
ipcMain.handle(
IPC_CHANNELS.SECRETS_CREATE_GROUP,
async (_, groupInput: SecretGroupInput): Promise<IPCResult<SecretGroup>> => {
try {
// Security: Validate input lengths
validateStringLength(groupInput.title, 'title', 200);
if (groupInput.description) {
validateStringLength(groupInput.description, 'description', 1000);
}
if (groupInput.imagePath) {
validateStringLength(groupInput.imagePath, 'imagePath', 500);
}
validateObjectSize(groupInput.keyIds, 'keyIds', 50);

const store = readSecretsFile();
const now = Date.now();

const newGroup: SecretGroup = {
id: uuidv4(),
title: groupInput.title,
description: groupInput.description,
imagePath: groupInput.imagePath,
keyIds: groupInput.keyIds,
accounts: [],
createdAt: now,
updatedAt: now
};

store.groups.push(newGroup);
writeSecretsFile(store);

return { success: true, data: newGroup };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to create secret group'
};
}
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider validating keyIds for uniqueness and non-empty schema.

While the array size is validated, there's no check that:

  • The keyIds array is non-empty (a group with no schema might not be useful)
  • The keyIds contain unique values (duplicates could cause confusion)
🔎 Proposed validation enhancement
         validateStringLength(groupInput.imagePath, 'imagePath', 500);
       }
       validateObjectSize(groupInput.keyIds, 'keyIds', 50);
+
+      // Validate keyIds schema
+      if (groupInput.keyIds.length === 0) {
+        return { success: false, error: 'Group must have at least one key in schema' };
+      }
+      const uniqueKeyIds = new Set(groupInput.keyIds);
+      if (uniqueKeyIds.size !== groupInput.keyIds.length) {
+        return { success: false, error: 'Duplicate keyIds in schema' };
+      }

       const store = readSecretsFile();
📝 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
// Create a new secret group (schema only, no accounts)
ipcMain.handle(
IPC_CHANNELS.SECRETS_CREATE_GROUP,
async (_, groupInput: SecretGroupInput): Promise<IPCResult<SecretGroup>> => {
try {
// Security: Validate input lengths
validateStringLength(groupInput.title, 'title', 200);
if (groupInput.description) {
validateStringLength(groupInput.description, 'description', 1000);
}
if (groupInput.imagePath) {
validateStringLength(groupInput.imagePath, 'imagePath', 500);
}
validateObjectSize(groupInput.keyIds, 'keyIds', 50);
const store = readSecretsFile();
const now = Date.now();
const newGroup: SecretGroup = {
id: uuidv4(),
title: groupInput.title,
description: groupInput.description,
imagePath: groupInput.imagePath,
keyIds: groupInput.keyIds,
accounts: [],
createdAt: now,
updatedAt: now
};
store.groups.push(newGroup);
writeSecretsFile(store);
return { success: true, data: newGroup };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to create secret group'
};
}
}
);
// Create a new secret group (schema only, no accounts)
ipcMain.handle(
IPC_CHANNELS.SECRETS_CREATE_GROUP,
async (_, groupInput: SecretGroupInput): Promise<IPCResult<SecretGroup>> => {
try {
// Security: Validate input lengths
validateStringLength(groupInput.title, 'title', 200);
if (groupInput.description) {
validateStringLength(groupInput.description, 'description', 1000);
}
if (groupInput.imagePath) {
validateStringLength(groupInput.imagePath, 'imagePath', 500);
}
validateObjectSize(groupInput.keyIds, 'keyIds', 50);
// Validate keyIds schema
if (groupInput.keyIds.length === 0) {
return { success: false, error: 'Group must have at least one key in schema' };
}
const uniqueKeyIds = new Set(groupInput.keyIds);
if (uniqueKeyIds.size !== groupInput.keyIds.length) {
return { success: false, error: 'Duplicate keyIds in schema' };
}
const store = readSecretsFile();
const now = Date.now();
const newGroup: SecretGroup = {
id: uuidv4(),
title: groupInput.title,
description: groupInput.description,
imagePath: groupInput.imagePath,
keyIds: groupInput.keyIds,
accounts: [],
createdAt: now,
updatedAt: now
};
store.groups.push(newGroup);
writeSecretsFile(store);
return { success: true, data: newGroup };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to create secret group'
};
}
}
);
🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/secrets-handlers.ts around lines 136 to
176, add validation that groupInput.keyIds is non-empty and contains only unique
values before creating the group: check that keyIds exists and keyIds.length > 0
and throw or return a clear validation error if empty, then verify uniqueness by
comparing new Set(keyIds).size to keyIds.length and throw/return an error if
duplicates are found; perform these checks after the existing validateObjectSize
call and before constructing newGroup so the handler returns a helpful
validation message instead of allowing empty or duplicated schema keys.

Comment on lines +178 to +223
// Update a secret group (schema only)
ipcMain.handle(
IPC_CHANNELS.SECRETS_UPDATE_GROUP,
async (_, groupId: string, updates: Partial<SecretGroupInput>): Promise<IPCResult<SecretGroup>> => {
try {
// Security: Validate UUID format
validateId(groupId, 'groupId');

// Security: Validate input lengths
if (updates.title !== undefined) {
validateStringLength(updates.title, 'title', 200);
}
if (updates.description !== undefined) {
validateStringLength(updates.description, 'description', 1000);
}
if (updates.imagePath !== undefined) {
validateStringLength(updates.imagePath, 'imagePath', 500);
}
if (updates.keyIds !== undefined) {
validateObjectSize(updates.keyIds, 'keyIds', 50);
}

const store = readSecretsFile();
const group = store.groups.find(g => g.id === groupId);

if (!group) {
return { success: false, error: 'Secret group not found' };
}

if (updates.title !== undefined) group.title = updates.title;
if (updates.description !== undefined) group.description = updates.description;
if (updates.imagePath !== undefined) group.imagePath = updates.imagePath;
if (updates.keyIds !== undefined) group.keyIds = updates.keyIds;

group.updatedAt = Date.now();
writeSecretsFile(store);

return { success: true, data: group };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to update secret group'
};
}
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Schema updates may create inconsistent account data.

When keyIds are updated (line 210), existing accounts in the group may have keys that no longer match the schema. This can lead to:

  • Accounts with keys not in the updated schema
  • Accounts missing keys required by the new schema

Consider adding validation to prevent schema changes when accounts exist, or implement migration logic.

🔎 Proposed fix: Prevent schema changes when accounts exist
       if (updates.keyIds !== undefined) {
         validateObjectSize(updates.keyIds, 'keyIds', 50);
+        
+        // Prevent schema changes if accounts exist
+        if (group.accounts.length > 0) {
+          return { 
+            success: false, 
+            error: 'Cannot modify keyIds schema when accounts exist. Delete accounts first or create a new group.' 
+          };
+        }
       }

       const store = readSecretsFile();
📝 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
// Update a secret group (schema only)
ipcMain.handle(
IPC_CHANNELS.SECRETS_UPDATE_GROUP,
async (_, groupId: string, updates: Partial<SecretGroupInput>): Promise<IPCResult<SecretGroup>> => {
try {
// Security: Validate UUID format
validateId(groupId, 'groupId');
// Security: Validate input lengths
if (updates.title !== undefined) {
validateStringLength(updates.title, 'title', 200);
}
if (updates.description !== undefined) {
validateStringLength(updates.description, 'description', 1000);
}
if (updates.imagePath !== undefined) {
validateStringLength(updates.imagePath, 'imagePath', 500);
}
if (updates.keyIds !== undefined) {
validateObjectSize(updates.keyIds, 'keyIds', 50);
}
const store = readSecretsFile();
const group = store.groups.find(g => g.id === groupId);
if (!group) {
return { success: false, error: 'Secret group not found' };
}
if (updates.title !== undefined) group.title = updates.title;
if (updates.description !== undefined) group.description = updates.description;
if (updates.imagePath !== undefined) group.imagePath = updates.imagePath;
if (updates.keyIds !== undefined) group.keyIds = updates.keyIds;
group.updatedAt = Date.now();
writeSecretsFile(store);
return { success: true, data: group };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to update secret group'
};
}
}
);
// Update a secret group (schema only)
ipcMain.handle(
IPC_CHANNELS.SECRETS_UPDATE_GROUP,
async (_, groupId: string, updates: Partial<SecretGroupInput>): Promise<IPCResult<SecretGroup>> => {
try {
// Security: Validate UUID format
validateId(groupId, 'groupId');
// Security: Validate input lengths
if (updates.title !== undefined) {
validateStringLength(updates.title, 'title', 200);
}
if (updates.description !== undefined) {
validateStringLength(updates.description, 'description', 1000);
}
if (updates.imagePath !== undefined) {
validateStringLength(updates.imagePath, 'imagePath', 500);
}
if (updates.keyIds !== undefined) {
validateObjectSize(updates.keyIds, 'keyIds', 50);
}
const store = readSecretsFile();
const group = store.groups.find(g => g.id === groupId);
if (!group) {
return { success: false, error: 'Secret group not found' };
}
if (updates.keyIds !== undefined) {
// Prevent schema changes if accounts exist
if (group.accounts.length > 0) {
return {
success: false,
error: 'Cannot modify keyIds schema when accounts exist. Delete accounts first or create a new group.'
};
}
}
if (updates.title !== undefined) group.title = updates.title;
if (updates.description !== undefined) group.description = updates.description;
if (updates.imagePath !== undefined) group.imagePath = updates.imagePath;
if (updates.keyIds !== undefined) group.keyIds = updates.keyIds;
group.updatedAt = Date.now();
writeSecretsFile(store);
return { success: true, data: group };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to update secret group'
};
}
}
);

} catch (copyErr: any) {
// If copy fails, try to clean up the created directory
try {
require('fs').rmSync(targetPath, { recursive: true, force: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Import rmSync for consistency.

Lines 323, 379, and 546 use require('fs').rmSync, but other fs functions are imported at the top of the file. For consistency, add rmSync to the import statement on line 2.

🔎 Proposed fix

Update the import on line 2:

-import { existsSync, writeFileSync, readFileSync, mkdirSync, cpSync, realpathSync } from 'fs';
+import { existsSync, writeFileSync, readFileSync, mkdirSync, cpSync, realpathSync, rmSync } from 'fs';

Then update lines 323, 379, and 546:

-            require('fs').rmSync(targetPath, { recursive: true, force: true });
+            rmSync(targetPath, { recursive: true, force: true });
📝 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
require('fs').rmSync(targetPath, { recursive: true, force: true });
rmSync(targetPath, { recursive: true, force: true });
🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/template-handlers.ts around line 2 and
usages at lines 323, 379, and 546, the code uses require('fs').rmSync inline
while other fs functions are imported at the top; add rmSync to the existing fs
import on line 2 (so rmSync is imported alongside the other fs functions) and
replace the three require('fs').rmSync calls at lines 323, 379, and 546 with
direct calls to rmSync to keep imports consistent.

JoshuaRileyDev and others added 3 commits December 27, 2025 19:03
Changed empty object pattern destructuring to _props parameter to satisfy linter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
TypeScript Fixes:
1. Added missing execSync import in project-handlers.ts
   - Import execSync from child_process for git clone operations

2. Updated template type signatures for optimistic locking
   - Modified saveTemplate to exclude 'version' from input type
   - Modified updateTemplate to require expectedVersion parameter
   - Updated AddTemplateDialog to pass template.version when updating

3. Added missing template and secrets API methods to browser mocks
   - Added 8 template operation mocks (getTemplates, saveTemplate, etc.)
   - Added 11 secrets operation mocks (checkSecretsEncryption, getSecretGroups, etc.)
   - Fixed SecretGroup.keyIds type (changed from {} to [])
   - Added cloneRepository mock to project-mock.ts

All TypeScript compilation errors resolved. Build and typecheck pass successfully.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx (1)

64-73: Wrong API used for image file selection.

selectDirectory() only allows selecting directories (configured with properties: ['openDirectory']). For selecting image files, a dedicated file selection API is needed with appropriate file type filters (e.g., .png, .jpg, .svg).

apps/frontend/src/main/ipc-handlers/project-handlers.ts (1)

467-514: Critical security vulnerabilities in git clone handler.

All critical issues in this handler have been documented in the previous review comment, including command injection via execSync with string interpolation, lack of URL validation, missing path sanitization, and absence of timeout controls.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31778b8 and 89f2a85.

📒 Files selected for processing (6)
  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/lib/browser-mock.ts
  • apps/frontend/src/renderer/lib/mocks/project-mock.ts
  • apps/frontend/src/shared/types/ipc.ts
🧰 Additional context used
📓 Path-based instructions (2)
apps/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/frontend/src/**/*.{ts,tsx}: Always use translation keys with useTranslation() for all user-facing text in React/TypeScript frontend components - use format namespace:section.key (e.g., navigation:items.githubPRs)
Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from apps/frontend/src/shared/i18n/locales/

Files:

  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/renderer/lib/mocks/project-mock.ts
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/lib/browser-mock.ts
  • apps/frontend/src/shared/types/ipc.ts
apps/frontend/**/*.{ts,tsx}

⚙️ CodeRabbit configuration file

apps/frontend/**/*.{ts,tsx}: Review React patterns and TypeScript type safety.
Check for proper state management and component composition.

Files:

  • apps/frontend/src/main/ipc-handlers/project-handlers.ts
  • apps/frontend/src/renderer/lib/mocks/project-mock.ts
  • apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx
  • apps/frontend/src/renderer/components/settings/SecretsManager.tsx
  • apps/frontend/src/renderer/lib/browser-mock.ts
  • apps/frontend/src/shared/types/ipc.ts
🧠 Learnings (1)
📚 Learning: 2025-12-25T18:29:32.954Z
Learnt from: CR
Repo: AndyMik90/Auto-Claude PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-25T18:29:32.954Z
Learning: Applies to apps/frontend/src/**/*.{ts,tsx} : Never use hardcoded strings in JSX/TSX files for user-facing text - always reference translation keys from `apps/frontend/src/shared/i18n/locales/`

Applied to files:

  • apps/frontend/src/shared/types/ipc.ts
🧬 Code graph analysis (2)
apps/frontend/src/main/ipc-handlers/project-handlers.ts (1)
apps/frontend/src/shared/constants/ipc.ts (1)
  • IPC_CHANNELS (6-390)
apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx (3)
apps/frontend/src/shared/types/template.ts (1)
  • Template (5-13)
apps/frontend/src/renderer/components/settings/utils/hookProxyFactory.ts (4)
  • isSaving (17-17)
  • error (18-18)
  • setError (19-19)
  • handleSave (52-52)
apps/frontend/src/renderer/components/ui/dialog.tsx (6)
  • Dialog (116-116)
  • DialogContent (121-121)
  • DialogHeader (122-122)
  • DialogTitle (124-124)
  • DialogDescription (125-125)
  • DialogFooter (123-123)
🪛 Biome (2.1.2)
apps/frontend/src/renderer/components/settings/SecretsManager.tsx

[error] 16-19: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 313-313: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event.

Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation.

(lint/a11y/useKeyWithClickEvents)


[error] 313-313: Static Elements should not be interactive.

To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value.

(lint/a11y/noStaticElementInteractions)

⏰ 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). (1)
  • GitHub Check: CodeQL (javascript-typescript)
🔇 Additional comments (3)
apps/frontend/src/renderer/components/settings/AddTemplateDialog.tsx (1)

79-130: LGTM on save logic with optimistic locking.

The validation, optimistic locking implementation with template.version, and error handling are well-structured. The early returns on validation failure and API errors prevent unnecessary state updates.

apps/frontend/src/renderer/lib/mocks/project-mock.ts (1)

110-113: LGTM!

The mock implementation is consistent with other mocks in the file and correctly returns the expected shape for testing repository cloning functionality.

apps/frontend/src/renderer/lib/browser-mock.ts (1)

175-196: LGTM!

The mock implementations for template and secrets operations are well-structured and consistent with the existing mock patterns in the file. Each method returns appropriate mock data that matches the expected API contract.

- Replace all hardcoded strings in AddTemplateDialog with translation keys
- Replace all hardcoded strings in SecretsManager with translation keys
- Add templates and secrets sections to English settings.json
- Add complete French translations for templates and secrets
- Use settings namespace with descriptive key structure (e.g., templates.form.name.label)
- Preserve dynamic error messages while using translation fallbacks
@JoshuaRileyDev JoshuaRileyDev changed the title Added comprehensive template system for scaffolding projects feat: comprehensive template system for scaffolding projects Dec 27, 2025
@AlexMadera AlexMadera self-assigned this Dec 29, 2025
Copy link
Collaborator

@AlexMadera AlexMadera left a comment

Choose a reason for hiding this comment

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

🤖 Auto Claude PR Review

Merge Verdict: 🔴 BLOCKED

Blocked by 1 security vulnerabilities

Risk Assessment

Factor Level Notes
Complexity High Based on lines changed
Security Impact Critical Based on security findings
Scope Coherence Good Based on structural review

🚨 Blocking Issues (Must Fix)

  • Security: Command Injection Vulnerability in Git Clone (apps/frontend/src/main/ipc-handlers/project-handlers.ts:489)

Findings Summary

  • Critical: 1 issue(s)
  • High: 2 issue(s)
  • Medium: 4 issue(s)
  • Low: 2 issue(s)

Generated by Auto Claude PR Review

Findings (9 selected of 9 total)

🔴 [CRITICAL] Command Injection Vulnerability in Git Clone

📁 apps/frontend/src/main/ipc-handlers/project-handlers.ts:489

The git clone handler uses execSync with template string interpolation: execSync(\git clone "${repoUrl}" "${clonePath}"`). This allows shell metacharacter injection through the repoUrl parameter. An attacker could craft a malicious URL like https://example.com"; rm -rf /; "to execute arbitrary commands. The rest of the codebase correctly usesexecFileSync` with argument arrays to prevent this.

Suggested fix:

Replace execSync with execFileSync and pass arguments as an array:

```javascript
execFileSync(getToolPath('git'), ['clone', repoUrl, clonePath], {
  stdio: 'pipe',
  encoding: 'utf-8'
});

#### 🟠 [HIGH] API Signature Mismatch - updateTemplate missing version parameter
📁 `apps/frontend/src/preload/api/template-api.ts:23`

The TemplateAPI.updateTemplate signature is `(templateId, updates) => ...` but the backend handler TEMPLATES_UPDATE expects 3 parameters: `(templateId, updates, expectedVersion)`. This breaks the optimistic locking mechanism implemented in template-handlers.ts. The frontend updateTemplate call will pass only 2 args, causing the expectedVersion to be undefined.

**Suggested fix:**

Update the TemplateAPI interface and implementation to include expectedVersion:

updateTemplate: (templateId: string, updates: Partial<...>, expectedVersion: number) => Promise<IPCResult<Template>>

And the implementation:

updateTemplate: (templateId, updates, expectedVersion) =>
  ipcRenderer.invoke(IPC_CHANNELS.TEMPLATES_UPDATE, templateId, updates, expectedVersion)

#### 🟠 [HIGH] Missing error handling for failed clone operations
📁 `apps/frontend/src/renderer/components/NewProjectScreen.tsx:104`

When cloneRepository fails, the error is set via setCloneError but there's no visual feedback if the user quickly closes and reopens the dialog - the error state persists. More critically, if selectDirectory returns a path but cloneRepository fails partially (e.g., clone starts but errors midway), there's no cleanup of the partial clone directory.

**Suggested fix:**

Add cleanup logic for failed clone operations. Consider showing a toast notification instead of inline error, and clear error state when dialog opens.


#### 🟡 [MEDIUM] Template saveTemplate signature excludes 'version' but type includes it
📁 `apps/frontend/src/shared/types/ipc.ts:248`

The saveTemplate IPC type signature is `Omit<Template, 'id' | 'version' | 'createdAt' | 'updatedAt'>` but the Template type definition shows version should be excluded. However, the actual implementation in template-handlers.ts correctly initializes version to 1. This is a minor type documentation inconsistency but could confuse developers.

**Suggested fix:**

The types are technically correct but add a comment explaining that version is server-managed for optimistic locking.


#### 🟡 [MEDIUM] validateId throws Error that could expose internal implementation
📁 `apps/frontend/src/main/ipc-handlers/secrets-handlers.ts:24`

The validateId function throws with message `Invalid ${fieldName}: must be a valid UUID`. While this is helpful for debugging, in a security-sensitive context, providing specific error messages about validation failures can help attackers understand the expected format and craft targeted attacks.

**Suggested fix:**

Consider using a generic error message like 'Invalid request parameters' or ensuring error messages are sanitized before being sent to the client.


#### 🟡 [MEDIUM] scanDirectory silently ignores errors without logging
📁 `apps/frontend/src/main/template-parser.ts:105`

The scanDirectory function catches errors when reading directories but only logs to console.error. If a template folder has permission issues, the user won't know why some files weren't scanned. This could lead to incomplete parameter detection.

**Suggested fix:**

Consider returning an errors array alongside the files array, or at minimum logging which specific directories/files failed to be read.


#### 🟡 [MEDIUM] Race condition in handleSetDefaultAccount
📁 `apps/frontend/src/renderer/components/settings/SecretsManager.tsx:71`

The handleSetDefaultAccount function reads settings, modifies them, then saves. If two calls happen in quick succession (e.g., user rapidly changes dropdowns), the second call could overwrite the first's changes because it read stale data. This is a TOCTOU issue.

**Suggested fix:**

Use optimistic locking or a debounce mechanism. Alternatively, make the setting update atomic by sending only the specific key-value pair to update.


#### 🔵 [LOW] validateDestinationPath uses require() inside function
📁 `apps/frontend/src/main/ipc-handlers/template-handlers.ts:51`

The validateDestinationPath function uses `require('fs').statSync` instead of the already-imported fs module. This is inconsistent with the rest of the file and slightly less efficient (though negligible). Same pattern appears in cleanup code with `require('fs').rmSync`.

**Suggested fix:**

Import rmSync at the top of the file and use the imported module consistently throughout.


#### 🔵 [LOW] Mock template version doesn't match type
📁 `apps/frontend/src/renderer/lib/browser-mock.ts:182`

The browser mock for saveTemplate returns a template with `version: 1` which is correct, but the mock data structure is missing proper TypeScript typing. If the Template type changes, the mock won't fail at compile time.

**Suggested fix:**

Add explicit typing: { success: true, data: { ...template, id: '1', version: 1, createdAt: Date.now(), updatedAt: Date.now() } as Template }


---
*This review was generated by Auto Claude.*

@AlexMadera AlexMadera added this to the Release 2.8 milestone Dec 30, 2025
@CLAassistant
Copy link

CLAassistant commented Dec 30, 2025

CLA assistant check
All committers have signed the CLA.

@MikeeBuilds MikeeBuilds added ❌ MERGE CONFLICTS stability-hold Feature on hold during Stability First initiative labels Jan 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/fullstack This is Frontend + Backend feature New feature or request ❌ MERGE CONFLICTS stability-hold Feature on hold during Stability First initiative

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants