Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Security Policy

## Supported Versions

The table below details which versions of FlowCraft are currently supported with security updates:

| Version | Supported |
| ------- | ------------------ |
| 1.x.x | :white_check_mark: |
| < 1.0.0 | :x: |

## Reporting a Vulnerability

The FlowCraft team takes security and user privacy seriously. If you discover a security vulnerability or potential exploit, we appreciate your help in disclosing it to us responsibly.

### Responsible Disclosure Guidelines

**Please do NOT create a public GitHub issue, pull request, or discussion for security vulnerabilities.**

Instead, report vulnerabilities privately through one of the following channels:

1. **GitHub Private Vulnerability Reporting:** Use the "Report a vulnerability" button under the **Security** tab of the repository.
2. **Email Contact:** Send a private email to the project maintainers at `security@flowcraft.dev`.

### What to Include in Your Report

To help us evaluate and address the issue quickly, please provide:

- A clear summary of the vulnerability and its potential impact.
- Detailed step-by-step instructions, screenshots, or a minimal Proof-of-Concept (PoC) script to reproduce the issue.
- The browser environment, OS, and version of FlowCraft where the vulnerability was observed.
- Any proposed remediation or code fix recommendations (if available).

### Response Timeline

- **Acknowledgment:** We aim to acknowledge receipt of your vulnerability report within **48 hours**.
- **Triage & Fix:** We will keep you informed as we investigate the vulnerability, work on a patch, and verify the resolution.
- **Public Disclosure:** Once a fix is released, we will coordinate public disclosure and credit security researchers who report issues in good faith.

Thank you for contributing to the security and safety of the open-source community!
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CenterCanvas from './components/CenterCanvas';
import RightSidebar from './components/RightSidebar';
import Toast from './components/Toast';
import { Block, ToastConfig, LayoutDirection } from './types';
import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts';

// Default blueprint layout tracking
const initialDemoBlocks: Block[] = [
Expand Down Expand Up @@ -88,6 +89,7 @@ export default function App() {
const [layoutDirection, setLayoutDirection] = useState<LayoutDirection>('vertical');
const [currentWorkspace, setCurrentWorkspace] = useState<string>('Form-Flow Sandbox');
const [workspaces, setWorkspaces] = useState<string[]>(['Form-Flow Sandbox']);
const [showShortcutsHelp, setShowShortcutsHelp] = useState(false);

useEffect(() => {
try {
Expand Down Expand Up @@ -251,6 +253,37 @@ export default function App() {
showToast(`Block "${block.label}" removed`, 'info');
};

// Duplicate block (Ctrl+D / Cmd+D)
const handleDuplicateBlock = (id: string) => {
const original = blocks.find((b) => b.id === id);
if (!original) return;

const newId = `block-${Math.random().toString(36).substring(2, 9)}`;
const duplicatedBlock: Block = {
...original,
id: newId,
label: `${original.label} (Copy)`,
};

setBlocks((prev) => {
const idx = prev.findIndex((b) => b.id === id);
if (idx === -1) return [...prev, duplicatedBlock];

const updated = [...prev];
if (original.type !== 'decision') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Duplicating a decision with populated branches creates a second unconnected source: the copy keeps the original yes/no targets, but this branch never rewires an incoming edge to the new ID. A decision-specific insertion strategy, or disabling duplication when its connection cannot be preserved, would avoid adding an unexpected orphaned flow node.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/App.tsx, line 273:

<comment>Duplicating a decision with populated branches creates a second unconnected source: the copy keeps the original yes/no targets, but this branch never rewires an incoming edge to the new ID. A decision-specific insertion strategy, or disabling duplication when its connection cannot be preserved, would avoid adding an unexpected orphaned flow node.</comment>

<file context>
@@ -251,6 +253,37 @@ export default function App() {
+      if (idx === -1) return [...prev, duplicatedBlock];
+
+      const updated = [...prev];
+      if (original.type !== 'decision') {
+        duplicatedBlock.targetId = original.targetId;
+        updated[idx] = { ...original, targetId: newId };
</file context>

duplicatedBlock.targetId = original.targetId;
updated[idx] = { ...original, targetId: newId };
}

updated.splice(idx + 1, 0, duplicatedBlock);
return updated;
});

setSelectedBlockId(newId);
setActiveParentId(newId);
showToast(`Duplicated "${original.label}"`, 'success');
};

// Select and chain next process block
const handleSelectAndContinue = (parentBlock: Block) => {
setActiveParentId(parentBlock.id);
Expand Down Expand Up @@ -284,6 +317,17 @@ export default function App() {
}
};

useKeyboardShortcuts({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Keyboard navigation is broken once this global listener is mounted: Tab from toolbar, sidebar, or modal buttons is intercepted to cycle blocks instead of moving focus, and Delete/Backspace can remove the selected block while the help dialog is open. Scoping shortcuts to the canvas or excluding all interactive/modal targets before handling these keys would preserve native control behavior and prevent destructive actions behind the dialog.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/App.tsx, line 320:

<comment>Keyboard navigation is broken once this global listener is mounted: Tab from toolbar, sidebar, or modal buttons is intercepted to cycle blocks instead of moving focus, and Delete/Backspace can remove the selected block while the help dialog is open. Scoping shortcuts to the canvas or excluding all interactive/modal targets before handling these keys would preserve native control behavior and prevent destructive actions behind the dialog.</comment>

<file context>
@@ -284,6 +317,17 @@ export default function App() {
     }
   };
 
+  useKeyboardShortcuts({
+    blocks,
+    selectedBlockId,
</file context>

blocks,
selectedBlockId,
currentWorkspace,
onSelectBlock: setSelectedBlockId,
onDeleteBlock: handleDeleteBlock,
onDuplicateBlock: handleDuplicateBlock,
onSaveWorkspace: handleSaveWorkspace,
onToggleShortcutsHelp: () => setShowShortcutsHelp((prev) => !prev),
});

const handleLoadWorkspace = (name: string) => {
try {
const data = localStorage.getItem('flowforge_workspaces');
Expand Down Expand Up @@ -475,6 +519,8 @@ export default function App() {
toggleDarkMode={toggleDarkMode}
layoutDirection={layoutDirection}
onLayoutDirectionChange={setLayoutDirection}
showShortcutsHelp={showShortcutsHelp}
onToggleShortcutsHelp={() => setShowShortcutsHelp((prev) => !prev)}
/>

{/* RIGHT SIDEBAR PROPERTIES */}
Expand Down
Loading
Loading