Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
f41f006
chore: merge release v3.101.2 back to main [skip ci]
github-actions[bot] Jul 14, 2026
0f7581b
fix(device-agent): implement the installer-cleanup fix (#3381)
github-actions[bot] Jul 14, 2026
421826b
CS-408 [Bug] - org chart image doesn't update after upload, delete, o…
github-actions[bot] Jul 14, 2026
7577a37
fix(deps): remediate dependency security vulnerabilities (#3403)
Marfuen Jul 14, 2026
d403e9c
fix(deps): remediate dependency security vulnerabilities (165→16, 4 c…
Marfuen Jul 14, 2026
029e899
chore(deps): bump semantic-release to v25 (clears sigstore advisory, …
Marfuen Jul 15, 2026
dcec288
fix(security): prevent SSRF in task-automation enterprise API calls (…
Marfuen Jul 15, 2026
59a6b0f
fix(deps): patch tmp and js-yaml in mcp-server (Dependabot #58/#59/#7…
Marfuen Jul 15, 2026
a25f56e
ci: submit resolved bun tree to GitHub dependency graph (accurate SBO…
Marfuen Jul 15, 2026
e47e6fd
fix(ci): scan bun.lock via file: input so the SBOM isn't empty (#3414)
Marfuen Jul 15, 2026
5f47024
fix(ci): pin Syft 1.46.0 in SBOM action (1.42.3 can't parse bun.lock)…
Marfuen Jul 15, 2026
598ff8c
fix(deps): override uuid to ^11.1.1 (Dependabot #85) (#3418)
Marfuen Jul 15, 2026
32329be
fix(security): resolve open CodeQL alerts (XSS, sanitization, workflo…
Marfuen Jul 15, 2026
67c9f4a
feat(framework-editor): raise requirement description limit to 100,00…
tofikwest Jul 15, 2026
35487c2
Merge pull request #3420 from trycompai/tofik/frame-2-requirement-des…
tofikwest Jul 15, 2026
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
8 changes: 4 additions & 4 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@
"@aws-sdk/lib-storage": "3.1013.0",
"@aws-sdk/s3-request-presigner": "3.1013.0",
"@browserbasehq/sdk": "2.6.0",
"@browserbasehq/stagehand": "^3.2.1",
"@browserbasehq/stagehand": "^3.7.0",
"@inference/tracing": "^0.0.21",
"@maced/api-client": "^0.9.2",
"@mendable/firecrawl-js": "^4.9.3",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.1.5",
"@nestjs/swagger": "^11.2.0",
"@nestjs/swagger": "^11.4.5",
"@nestjs/throttler": "^6.5.0",
"@prisma/adapter-pg": "7.6.0",
"@prisma/client": "7.6.0",
Expand All @@ -92,8 +92,8 @@
"adm-zip": "^0.5.16",
"ai": "^6.0.175",
"archiver": "^7.0.1",
"axios": "^1.12.2",
"better-auth": "^1.4.22",
"axios": "^1.16.0",
"better-auth": "^1.6.13",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"docx": "^9.7.1",
Expand Down
10 changes: 5 additions & 5 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@aws-sdk/s3-request-presigner": "3.1013.0",
"@azure/core-rest-pipeline": "^1.21.0",
"@browserbasehq/sdk": "2.6.0",
"@browserbasehq/stagehand": "^3.0.5",
"@browserbasehq/stagehand": "^3.7.0",
"@date-fns/tz": "^1.2.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
Expand Down Expand Up @@ -85,8 +85,8 @@
"@xyflow/react": "^12.10.0",
"ai": "^6.0.175",
"ai-elements": "^1.6.1",
"axios": "^1.9.0",
"better-auth": "^1.4.22",
"axios": "^1.16.0",
"better-auth": "^1.6.13",
"botid": "^1.5.5",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
Expand All @@ -102,7 +102,7 @@
"mammoth": "^1.11.0",
"motion": "^12.35.0",
"nanoid": "^5.1.6",
"next": "^16.2.0",
"next": "^16.2.6",
"next-safe-action": "^8.0.3",
"next-themes": "^0.4.4",
"nuqs": "^2.4.3",
Expand Down Expand Up @@ -171,7 +171,7 @@
"trigger.dev": "4.4.3",
"typescript": "^5.8.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
"vitest": "^3.2.6"
},
"exports": {
"./src/lib/encryption": "./src/lib/encryption.ts"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,26 @@ interface OrgChartData {
interface OrgChartContentProps {
chartData: OrgChartData | null;
members: OrgChartMember[];
onChartChange: () => void | Promise<unknown>;
}

export function OrgChartContent({ chartData, members }: OrgChartContentProps) {
export function OrgChartContent({
chartData,
members,
onChartChange,
}: OrgChartContentProps) {
if (!chartData) {
return <OrgChartEmptyState members={members} />;
return (
<OrgChartEmptyState members={members} onChartChange={onChartChange} />
);
}

if (chartData.type === 'uploaded' && chartData.signedImageUrl) {
return (
<OrgChartImageView
imageUrl={chartData.signedImageUrl}
chartName={chartData.name}
onChartChange={onChartChange}
/>
);
}
Expand All @@ -57,6 +65,7 @@ export function OrgChartContent({ chartData, members }: OrgChartContentProps) {
initialEdges={chartData.edges}
members={members}
updatedAt={chartData.updatedAt ?? null}
onChartChange={onChartChange}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { formatDistanceToNow } from 'date-fns';
import { useRouter } from 'next/navigation';
import { useCallback, useMemo, useRef, useState } from 'react';
import { toast } from 'sonner';
import type { OrgChartMember } from '../types';
Expand All @@ -30,16 +29,17 @@ interface OrgChartEditorProps {
initialEdges: Edge[];
members: OrgChartMember[];
updatedAt: string | null;
onChartChange: () => void | Promise<unknown>;
}

export function OrgChartEditor({
initialNodes,
initialEdges,
members,
updatedAt,
onChartChange,
}: OrgChartEditorProps) {
const api = useApi();
const router = useRouter();
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [reactFlowInstance, setReactFlowInstance] = useState<ReactFlowInstance | null>(null);

Expand Down Expand Up @@ -161,7 +161,12 @@ export function OrgChartEditor({
setLastSavedAt(new Date().toISOString());
}
toast.success('Org chart saved');
router.refresh();

try {
await onChartChange();
} catch {
// Save already succeeded; a refresh failure shouldn't surface as a save error.
}
} catch {
toast.error('Failed to save org chart');
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import type { OrgChartMember } from '../types';

interface OrgChartEmptyStateProps {
members: OrgChartMember[];
onChartChange: () => void | Promise<unknown>;
}

export function OrgChartEmptyState({ members }: OrgChartEmptyStateProps) {
export function OrgChartEmptyState({
members,
onChartChange,
}: OrgChartEmptyStateProps) {
const [mode, setMode] = useState<'empty' | 'create' | 'upload'>('empty');

if (mode === 'create') {
Expand All @@ -21,12 +25,18 @@ export function OrgChartEmptyState({ members }: OrgChartEmptyStateProps) {
initialEdges={[]}
members={members}
updatedAt={null}
onChartChange={onChartChange}
/>
);
}

if (mode === 'upload') {
return <UploadOrgChartDialog onClose={() => setMode('empty')} />;
return (
<UploadOrgChartDialog
onClose={() => setMode('empty')}
onUploaded={onChartChange}
/>
);
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import { Button } from '@trycompai/design-system';
import { TrashCan, Upload } from '@trycompai/design-system/icons';
import { useApi } from '@/hooks/use-api';
import { toast } from 'sonner';
import { useRouter } from 'next/navigation';
import { UploadOrgChartDialog } from './UploadOrgChartDialog';

interface OrgChartImageViewProps {
imageUrl: string;
chartName: string;
onChartChange: () => void | Promise<unknown>;
}

export function OrgChartImageView({
imageUrl,
chartName,
onChartChange,
}: OrgChartImageViewProps) {
const api = useApi();
const router = useRouter();
const [isDeleting, setIsDeleting] = useState(false);
const [showReplace, setShowReplace] = useState(false);

Expand All @@ -33,7 +33,12 @@ export function OrgChartImageView({
}

toast.success('Org chart deleted');
router.refresh();

try {
await onChartChange();
} catch {
// Delete already succeeded; a refresh failure shouldn't surface as a delete error.
}
} catch {
toast.error('Failed to delete org chart');
} finally {
Expand All @@ -42,7 +47,12 @@ export function OrgChartImageView({
};

if (showReplace) {
return <UploadOrgChartDialog onClose={() => setShowReplace(false)} />;
return (
<UploadOrgChartDialog
onClose={() => setShowReplace(false)}
onUploaded={onChartChange}
/>
);
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface OrgChartTabContentProps {
}

export function OrgChartTabContent({ organizationId }: OrgChartTabContentProps) {
const { orgChart } = useOrgChart();
const { orgChart, mutate } = useOrgChart();
const { members } = useTeamMembers({ organizationId });

const chartMembers: OrgChartMember[] = members
Expand All @@ -25,5 +25,11 @@ export function OrgChartTabContent({ organizationId }: OrgChartTabContentProps)
jobTitle: m.jobTitle ?? null,
}));

return <OrgChartContent chartData={orgChart} members={chartMembers} />;
return (
<OrgChartContent
chartData={orgChart}
members={chartMembers}
onChartChange={mutate}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import { Button } from '@trycompai/design-system';
import { Upload, Close } from '@trycompai/design-system/icons';
import { useApi } from '@/hooks/use-api';
import { toast } from 'sonner';
import { useRouter } from 'next/navigation';

interface UploadOrgChartDialogProps {
onClose: () => void;
onUploaded: () => void | Promise<unknown>;
}

export function UploadOrgChartDialog({ onClose }: UploadOrgChartDialogProps) {
export function UploadOrgChartDialog({
onClose,
onUploaded,
}: UploadOrgChartDialogProps) {
const api = useApi();
const router = useRouter();
const [isUploading, setIsUploading] = useState(false);
const [selectedFile, setSelectedFile] = useState<File | null>(null);

Expand Down Expand Up @@ -60,8 +62,13 @@ export function UploadOrgChartDialog({ onClose }: UploadOrgChartDialogProps) {
}

toast.success('Org chart uploaded');
router.refresh();
onClose();

try {
await onUploaded();
} catch {
// Upload already succeeded; a refresh failure shouldn't surface as an upload error.
}
} catch {
toast.error('Failed to upload org chart');
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ async function callEnterpriseApi<T>(
): Promise<T> {
const { enterpriseApiUrl, enterpriseApiKey } = getEnterpriseConfig();

const url = new URL(endpoint, enterpriseApiUrl);
const baseUrl = new URL(enterpriseApiUrl);
const url = new URL(endpoint, baseUrl);

// SSRF guard: `endpoint` and params can carry user-derived values, so pin the
// request to the configured enterprise API origin — never allow it to be
// redirected to another host.
if (url.origin !== baseUrl.origin) {

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: A redirect from an enterprise endpoint still bypasses this origin check because fetch follows redirects by default. Disable redirects or validate each redirect target so this helper cannot reach another host after the initial request.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/tasks/[taskId]/automation/[automationId]/actions/task-automation-actions.ts, line 64:

<comment>A redirect from an enterprise endpoint still bypasses this origin check because `fetch` follows redirects by default. Disable redirects or validate each redirect target so this helper cannot reach another host after the initial request.</comment>

<file context>
@@ -55,7 +55,15 @@ async function callEnterpriseApi<T>(
+  // SSRF guard: `endpoint` and params can carry user-derived values, so pin the
+  // request to the configured enterprise API origin — never allow it to be
+  // redirected to another host.
+  if (url.origin !== baseUrl.origin) {
+    throw new EnterpriseApiError('Invalid enterprise API endpoint', 400);
+  }
</file context>
Fix with cubic

throw new EnterpriseApiError('Invalid enterprise API endpoint', 400);
}

if (options.params) {
Object.entries(options.params).forEach(([key, value]) => {
Expand Down Expand Up @@ -243,8 +251,10 @@ export async function analyzeAutomationWorkflow(scriptContent: string) {

export const getAutomationRunStatus = async (runId: string) => {
try {
const result = await callEnterpriseApi(`/api/tasks-automations/runs/${runId}`, {
});
const result = await callEnterpriseApi(
`/api/tasks-automations/runs/${encodeURIComponent(runId)}`,
{},
);

return {
success: true,
Expand Down
7 changes: 5 additions & 2 deletions apps/app/src/lib/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,18 @@ export function resolveBuiltInPermissions(roleString: string | null | undefined)
if (BUILT_IN_ROLE_NAMES.includes(roleName)) {
const role = allRoles[roleName as RoleName];
if (role) {
mergePermissions(combined, role.statements as Record<string, string[]>);
mergePermissions(combined, role.statements as Record<string, readonly string[]>);
}
}
}

return { permissions: combined, customRoleNames };
}

export function mergePermissions(target: UserPermissions, source: Record<string, string[]>): void {
export function mergePermissions(
target: UserPermissions,
source: Record<string, readonly string[]>,
): void {
for (const [resource, actions] of Object.entries(source)) {
if (!target[resource]) {
target[resource] = [];
Expand Down
6 changes: 3 additions & 3 deletions apps/framework-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
"@trycompai/company": "workspace:*",
"@trycompai/db": "workspace:*",
"@trycompai/ui": "workspace:*",
"better-auth": "^1.4.22",
"better-auth": "^1.6.13",
"framer-motion": "^12.23.9",
"lucide-react": "^1.7.0",
"next": "^16.2.0",
"next": "^16.2.6",
"next-themes": "^0.4.4",
"nuqs": "^2.4.3",
"react": "^19.0.0",
Expand All @@ -41,7 +41,7 @@
"prisma": "7.6.0",
"tailwindcss": "^4.1.8",
"typescript": "^5.8.3",
"vitest": "^3.2.4"
"vitest": "^3.2.6"
},
"private": true,
"scripts": {
Expand Down
Loading
Loading