Skip to content

Commit 4602221

Browse files
icecrasher321claude
andcommitted
feat(db): regenerate copilot tool-permission migration on top of staging
Replaces the branch's old 0264/0265 (dropped pre-merge so staging's 0264-0270 chain could apply cleanly) with a single 0271 generated against staging's schema: the permission-decision enum, the two copilot_async_tool_calls decision columns, and copilot_chats.auto_allowed_tools. Deliberately does NOT drop copilot_chats.plan_artifact. The branch removed every reader, but the currently-deployed code still SELECTs that column, so dropping it in the same deploy breaks the old app version during blue/green overlap — `check:migrations` flags it for exactly this reason, and the honest fix is to defer rather than annotate around it. The column is retained in schema.ts marked @deprecated; drop it in a follow-up once this has rolled out. Also in this commit, all fallout from the merge itself: - pinned-fetch/revoke tests: their private-IP stub moved to @sim/security/ssrf alongside the source change. Worth noting the stub exists because the suite's 203.0.113.10 is TEST-NET-3, which the real classifier correctly calls reserved — the old stub had been quietly disagreeing with production. - materialize-file test: dropped the reserved-system-folder case, which covered the workflow-alias backing folders this branch deleted. - api-validation route ratchet 977 -> 983 (this branch's new routes). Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b804c24 commit 4602221

8 files changed

Lines changed: 17781 additions & 32 deletions

File tree

apps/sim/lib/copilot/tools/handlers/materialize-file.test.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -446,32 +446,6 @@ describe('executeMaterializeFile - extract operation', () => {
446446
expect(mockDecompress).toHaveBeenCalledTimes(1)
447447
})
448448

449-
it('folds reserved system folder names into the "archive" fallback folder', async () => {
450-
// '.changelogs' / '.plans' back workflow changelog/plan aliases; extraction
451-
// must never write into them (and the already-extracted lookup hides them,
452-
// so a second extract would silently duplicate).
453-
mockFindUpload.mockResolvedValue(
454-
zipRow({ displayName: '.changelogs.zip', originalName: '.changelogs.zip' })
455-
)
456-
mockFetchBuffer.mockResolvedValue(Buffer.from('zip-bytes'))
457-
mockDecompress.mockResolvedValue({
458-
extracted: [{ id: 'f1', name: 'a.txt', url: '/x', size: 1, type: 'text/plain', key: 'k1' }],
459-
skipped: 0,
460-
skippedUnsafePaths: [],
461-
})
462-
463-
const result = await executeMaterializeFile(
464-
{ fileNames: ['.changelogs.zip'], operation: 'extract' },
465-
context
466-
)
467-
468-
expect(result.success).toBe(true)
469-
expect(mockDecompress).toHaveBeenCalledWith(
470-
expect.any(Buffer),
471-
expect.objectContaining({ rootFolderSegments: ['archive'] })
472-
)
473-
})
474-
475449
it('folds degenerate archive names into the "archive" fallback folder', async () => {
476450
mockFindUpload.mockResolvedValue(zipRow({ displayName: '..zip', originalName: '..zip' }))
477451
mockFetchBuffer.mockResolvedValue(Buffer.from('zip-bytes'))

apps/sim/lib/mcp/oauth/revoke.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,19 @@ const {
3030
}))
3131

3232
vi.mock('@/lib/core/security/input-validation.server', () => ({
33-
isPrivateOrReservedIP: (ip: string) =>
34-
ip.startsWith('127.') || ip.startsWith('10.') || ip === '::1',
3533
createSsrfGuardedFetchWithDispatcher: vi.fn(() => ({
3634
fetch: mockUndiciFetch,
3735
dispatcher: { destroy: vi.fn(() => Promise.resolve()) },
3836
})),
3937
}))
38+
/**
39+
* Stubbed so the suite's `203.0.113.10` reads as an ordinary public address.
40+
* The real classifier treats TEST-NET-3 as reserved, which would route every
41+
* "public IP" case down the pinned-private branch instead.
42+
*/
43+
vi.mock('@sim/security/ssrf', () => ({
44+
isPrivateIp: (ip: string) => ip.startsWith('127.') || ip.startsWith('10.') || ip === '::1',
45+
}))
4046
vi.mock('@/lib/mcp/domain-check', () => ({
4147
validateMcpServerSsrf: mockValidateMcpServerSsrf,
4248
}))

apps/sim/lib/mcp/pinned-fetch.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@ const {
2121
vi.mock('@/lib/core/security/input-validation.server', () => ({
2222
createSsrfGuardedFetchWithDispatcher: mockCreateGuardedFetchWithDispatcher,
2323
createPinnedFetchWithDispatcher: mockCreatePinnedFetchWithDispatcher,
24-
isPrivateOrReservedIP: (ip: string) =>
25-
ip.startsWith('127.') || ip.startsWith('10.') || ip === '::1',
24+
}))
25+
/**
26+
* Stubbed so the suite's `203.0.113.10` reads as an ordinary public address.
27+
* The real classifier treats TEST-NET-3 as reserved, which would route every
28+
* "public IP" case down the pinned-private branch instead.
29+
*/
30+
vi.mock('@sim/security/ssrf', () => ({
31+
isPrivateIp: (ip: string) => ip.startsWith('127.') || ip.startsWith('10.') || ip === '::1',
2632
}))
2733
vi.mock('@/lib/mcp/domain-check', () => ({
2834
validateMcpServerSsrf: mockValidateMcpServerSsrf,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CREATE TYPE "public"."copilot_tool_permission_decision" AS ENUM('allow', 'allow_chat', 'always_allow', 'skip');--> statement-breakpoint
2+
ALTER TABLE "copilot_async_tool_calls" ADD COLUMN "permission_decision" "copilot_tool_permission_decision";--> statement-breakpoint
3+
ALTER TABLE "copilot_async_tool_calls" ADD COLUMN "permission_decided_at" timestamp;--> statement-breakpoint
4+
ALTER TABLE "copilot_chats" ADD COLUMN "auto_allowed_tools" jsonb DEFAULT '[]' NOT NULL;

0 commit comments

Comments
 (0)