* fix(import): dedup workflow name (#3813)
* feat(concurrency): bullmq based concurrency control system (#3605)
* feat(concurrency): bullmq based queueing system
* fix bun lock
* remove manual execs off queues
* address comments
* fix legacy team limits
* cleanup enterprise typing code
* inline child triggers
* fix status check
* address more comments
* optimize reconciler scan
* remove dead code
* add to landing page
* Add load testing framework
* update bullmq
* fix
* fix headless path
---------
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
* fix(linear): add default null for after cursor (#3814)
* fix(knowledge): reject non-alphanumeric file extensions from document names (#3816)
* fix(knowledge): reject non-alphanumeric file extensions from document names
* fix(knowledge): improve error message when extension is non-alphanumeric
* fix(security): SSRF, access control, and info disclosure (#3815)
* fix(security): scope copilot feedback GET endpoint to authenticated user
Add WHERE clause to filter feedback records by the authenticated user's
ID, preventing any authenticated user from reading all users' copilot
interactions, queries, and workflow YAML (IDOR / CWE-639).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(smtp): add SSRF validation and genericize network error messages
Prevent SSRF via user-controlled smtpHost by validating with
validateDatabaseHost before creating the nodemailer transporter.
Collapse distinct network error messages (ECONNREFUSED, ECONNRESET,
ETIMEDOUT) into a single generic message to prevent port-state leakage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): add SSRF validation to SFTP/SSH and access control to workspace invitations
Add `validateDatabaseHost` checks to SFTP and SSH connection utilities to
block connections to private/reserved IPs and localhost, matching the
existing pattern used by all database tools. Add authorization check to
the workspace invitation GET endpoint so only the invitee or a workspace
admin can view invitation details.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(smtp): restore SMTP response code handling for post-connection errors
SMTP 4xx/5xx response codes are application-level errors (invalid
recipient, mailbox full, server error) unrelated to the SSRF hardening
goal. Restore response code differentiation and logging to preserve
actionable user-facing error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): use session email directly instead of extra DB query
Addresses PR review feedback — align with the workspace invitation
route pattern by using session.user.email instead of re-fetching
from the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(auth): revert lint autofix that broke hasExternalApiCredentials return type
Biome auto-fixed `return auth !== null && auth.startsWith(...)` to
`return auth?.startsWith(...)` which returns `boolean | undefined`,
not `boolean`, causing a TypeScript build failure.
* fix(smtp): pin resolved IP to prevent DNS rebinding (TOCTOU)
Use the pre-resolved IP from validateDatabaseHost instead of the
original hostname when creating the nodemailer transporter. Set
servername to the original hostname to preserve TLS SNI validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(security): extract createPinnedLookup helper for DNS rebinding prevention
Extract reusable createPinnedLookup from secureFetchWithPinnedIP so
non-HTTP transports (SSH, SFTP, IMAP) can pin resolved IPs at the
socket level. SMTP route uses host+servername pinning instead since
nodemailer doesn't reliably pass lookup to both secure/plaintext paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): pin IMAP connections to validated resolved IP
Pass the resolved IP from validateDatabaseHost to ImapFlow as host,
with the original hostname as servername for TLS SNI verification.
Closes the DNS TOCTOU rebinding window.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(auth): revert lint autofix on hasExternalApiCredentials return type
Also pin SFTP/SSH connections to validated resolved IP to prevent DNS rebinding.
* fix(security): short-circuit admin check when caller is invitee
Skip the hasWorkspaceAdminAccess DB query when the caller is already
the invitee, avoiding an unnecessary round-trip. Aligns with the org
invitation route pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(worker): dockerfile + helm updates (#3818)
* fix(worker): dockerfile + helm updates
* address comments
* update dockerfile (#3819)
* fix dockerfile
* fix(security): pentest remediation — condition escaping, SSRF hardening, ReDoS protection (#3820)
* fix(executor): escape newline characters in condition expression strings
Unescaped newline/carriage-return characters in resolved string values
cause unterminated string literals in generated JS, crashing condition
evaluation with a SyntaxError.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): prevent ReDoS in guardrails regex validation
Add safe-regex2 to reject catastrophic backtracking patterns before
execution and cap input length at 10k characters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): SSRF localhost hardening and regex DoS protection
Block localhost/loopback URLs in hosted environments using isHosted flag
instead of allowHttp. Add safe-regex2 validation and input length limits
to regex guardrails to prevent catastrophic backtracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): validate regex syntax before safety check
Move new RegExp() before safe() so invalid patterns get a proper syntax
error instead of a misleading "catastrophic backtracking" message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): address PR review feedback
- Hoist isLocalhost && isHosted guard to single early-return before
protocol checks, removing redundant duplicate block
- Move regex syntax validation (new RegExp) before safe-regex2 check
so invalid patterns get proper syntax error instead of misleading
"catastrophic backtracking" message
* fix(security): remove input length cap from regex validation
The 10k character cap would block legitimate guardrail checks on long
LLM outputs. Input length doesn't affect ReDoS risk — the safe-regex2
pattern check already prevents catastrophic backtracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tests): mock isHosted in input-validation and function-execute tests
Tests that assert self-hosted localhost behavior need isHosted=false,
which is not guaranteed in CI where NEXT_PUBLIC_APP_URL is set to the
hosted domain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* improvement(worker): configuration defaults (#3821)
* improvement(worker): configuration defaults
* update readmes
* realtime curl import
* improvement(tour): remove auto-start, only trigger on explicit user action (#3823)
* fix(mcp): use correct modal for creating workflow MCP servers in deploy (#3822)
* fix(mcp): use correct modal for creating workflow MCP servers in deploy
* fix(mcp): show workflows field during loading and when empty
* mock course
* fix(db): use bigint for token counter columns in user_stats (#3755)
* mock course
* updates
* updated X handle for emir
* cleanup: audit and clean academy implementation
* fix(academy): add label to ValidationRule, fix quiz gating, simplify getRuleMessage
* cleanup: remove unnecessary comments across academy files
* refactor(academy): simplify abstractions and fix perf issues
* perf(academy): convert course detail page to server component with client island
* fix(academy): null-safe canAdvance, render exercise instructions, remove stale comments
* fix(academy): remove orphaned migration, fix getCourseById, clean up comments
- Delete 0181_academy_certificate.sql (orphaned duplicate not in journal)
- Add getCourseById() to content/index.ts; use it in certificates API
(was using getCourse which searches by slug, not stable id)
- Remove JSX comments from catalog page
- Remove redundant `passed` recomputation in LessonQuiz
* chore(db): regenerate academy_certificate migration with drizzle-kit
* chore: include blog mdx and components changes
* fix(blog): correct cn import path
* fix(academy): constrain progress bar to max-w-3xl with proper padding
* feat(academy): show back-to-course button on first lesson
* fix(academy): force dark theme on all /academy routes
* content(academy): rewrite sim-foundations course with full 6-module curriculum
* fix(academy): correct edge handles, quiz explanation, and starter mock outputs
- Fix Exercise 2 initial edge handles: 'starter-1-source'/'agent-1-target' → 'source'/'target' (React Flow actual IDs)
- Fix M1-L4 Q4 quiz explanation: remove non-existent Ctrl/Cmd+D and Alt+drag shortcuts
- Add starter mock output to all exercises so run animation shows feedback on the first block
* refine(academy): fix inaccurate content and improve exercise clarity
- Fix Exercise 3: replace hardcoded <agent-1.content> (invalid UUID-based ref) with reference picker instructions
- Fix M4 Quiz Q5: Loop block (subflow container) is correct answer, not the Workflow block
- Fix M4 Quiz Q4: clarify fan-out vs Parallel block distinction in explanation
- Fix M4-L2 video description: accurately describe Loop and Parallel subflow blocks
- Fix M2 Quiz Q3: make response format question conceptual rather than syntax-specific
- Improve Exercise 4 branching instructions: clarify top=true / bottom=false output handles
- Improve Final Project instructions: step-by-step numbered flow
* fix(academy): remove double border on quiz question cards
* fix(academy): single scroll container on lesson pages — remove nested flex scroll
* fix(academy): remove min-h-screen from root layout — fixes double scrollbar on lesson pages
* fix(academy): use fixed inset-0 on lesson page to eliminate document-level scrollbar
* fix(academy): replace sr-only radio/checkbox inputs with buttons to prevent scroll-on-focus; restore layout min-h-screen
* improvement(academy): polish, security hardening, and certificate claim UI
- Replace raw localStorage with BrowserStorage utility in local-progress
- Pre-compute slug/id Maps in content/index for O(1) course lookups
- Move blockMap construction into edge_exists branch only in validation
- Extract navBtnClass constant and MetaRow/formatDate helpers in UI
- Add rate limiting, server-side completion verification, audit logging, and nanoid cert numbers to certificate issuance endpoint
- Add useIssueCertificate mutation hook with completedLessonIds
- Wire certificate claim UI into CourseProgress: sign-in prompt, claim button with loading state, and post-issuance view with link to certificate page
- Fix lesson page scroll container and quiz scroll-on-focus bug
* fix(academy): validate condition branch handles in edge_exists rules
- Add sourceHandle field to edge_exists ValidationRule type
- Check sourceHandle in validation.ts when specified
- Require both condition-if and condition-else branches to be connected in the branching and final project exercises
* fix(academy): address PR review — isHosted regression, stuck isExecuting, revoked cert 500, certificate SSR
- Restore env-var-based isHosted check (was hardcoded true, breaking self-hosted deployments)
- Fix isExecuting stuck at true when mock run fails validation — set isMockRunningRef immediately and reset both flags on early exit
- Fix revoked/expired certificate causing 500 — any existing record (not just active) now returns 409 instead of falling through to INSERT
- Convert certificate verification page from client component to server component — direct DB fetch, notFound() on missing cert, generateMetadata for SEO/social previews
* fix(auth): restore hybrid.ts from staging to fix CI type error
* fix(academy): mark video lessons complete on visit and fix sign-in path
* fix(academy): replace useEffect+setState with lazy useState initializer in CourseProgress
* fix(academy): reset exerciseComplete on lesson navigation, remove unused useAcademyCertificate hook
* fix(academy): useState for slug-change reset, cache() for cert page, handleMockRunRef for stale closure
* fix(academy): replace shadcn theme vars with explicit hex in LessonVideo fallback
* fix(academy): reset completedRef on exercise change, conditional verified badge, multi-select empty guard
* fix(academy): type safety fixes — null metadata fallbacks, returning() guard, exhaustive union, empty catch
* fix(academy): reset ExerciseView completed banner on nav; fix CourseProgress hydration mismatch
* fix(lightbox): guard effect body with isOpen to prevent spurious overflow reset
* fix(academy): reset LessonQuiz state on lesson change to prevent stale answers persisting
* fix(academy): course not-found metadata title; try-finally guard in mock run loop
* fix(academy): type safety, cert persistence, regex guard, mixed-lesson video, shorts support
- Derive AcademyCertificate from db $inferSelect to prevent schema drift
- Add useCourseCertificate query hook; GET /api/academy/certificates now accepts courseId for authenticated lookup
- Use useCourseCertificate in CourseProgress so certificate state survives page refresh
- Guard new RegExp(valuePattern) in validation.ts with try/catch; log warn on invalid pattern
- Add logger.warn for custom validation rules so content authors are alerted
- Add YouTube Shorts URL support to LessonVideo (youtube.com/shorts/VIDEO_ID)
- Fix mixed-lesson video gap: render videoUrl above quiz when mixed has quiz but no exercise
- Add academy-scoped not-found.tsx with link back to /academy
* fix(academy): reset hintIndex when exercise changes
* chore: remove ban-spam-accounts script (wrong branch)
* fix(academy): enforce availableBlocks in toolbar; fix mixed exercise+quiz rendering
- Add useSandboxBlockConstraints context; SandboxCanvasProvider provides exerciseConfig.availableBlocks so the toolbar only shows permitted block types. Empty array hides all blocks (configure-only exercises); non-null array restricts to listed types; triggers always hidden in sandbox.
- Fix mixed lesson with both exerciseConfig and quizConfig: exercise renders first, quiz reveals after exercise completes (sequential pedagogy). canAdvance now requires both exerciseComplete && quizComplete when both are present.
* chore(academy): remove extraneous inline comments
* fix(academy): blank mixed lesson, quiz canAdvance flag, empty-array valueNotEmpty
* prep for merge
* chore(db): regenerate academy certificate migration after staging merge
* fix(academy): disable auto-connect in sandbox mode
* fix(academy): render video in mixed lesson with no exercise or quiz
* fix(academy): mark mixed video-only lessons complete; handle cert insert race
* fix(canvas): add sandbox and embedded to nodes useMemo deps
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Theodore Li <teddy@zenobiapay.com>
* fix(knowledge): give users choice to keep or delete documents when removing connector
* refactor(knowledge): clean up connector delete and extract shared extension validator
- Extract `isAlphanumericExtension` helper to deduplicate regex across parser-extension.ts and validation.ts
- Extract `closeDeleteModal` callback to eliminate 4x scattered state resets
- Add archivedAt/deletedAt filters to UPDATE query in keep-docs delete path
- Parallelize storage file cleanup and tag definition cleanup with Promise.all
- Deduplicate URL construction in delete connector hook
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(knowledge): remove duplicate extension list from parser-extension
Use SUPPORTED_DOCUMENT_EXTENSIONS and isSupportedExtension from
validation.ts instead of maintaining a separate identical list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(db): change document.connectorId FK from cascade to set null
The cascade behavior meant deleting a connector would always delete
its documents, contradicting the "keep documents" option. With set null,
the database automatically nullifies connectorId when a connector is
removed, and we only need explicit deletion when the user opts in.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore(db): add migration metadata for connectorId FK change
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): fix connector delete test and use URL-safe searchParams
Use `new URL(request.url).searchParams` instead of `request.nextUrl.searchParams`
for compatibility with test mocks. Add missing `connectorType` to test fixture.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* spacing
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(executor): escape newline characters in condition expression strings
Unescaped newline/carriage-return characters in resolved string values
cause unterminated string literals in generated JS, crashing condition
evaluation with a SyntaxError.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): prevent ReDoS in guardrails regex validation
Add safe-regex2 to reject catastrophic backtracking patterns before
execution and cap input length at 10k characters.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): SSRF localhost hardening and regex DoS protection
Block localhost/loopback URLs in hosted environments using isHosted flag
instead of allowHttp. Add safe-regex2 validation and input length limits
to regex guardrails to prevent catastrophic backtracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): validate regex syntax before safety check
Move new RegExp() before safe() so invalid patterns get a proper syntax
error instead of a misleading "catastrophic backtracking" message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): address PR review feedback
- Hoist isLocalhost && isHosted guard to single early-return before
protocol checks, removing redundant duplicate block
- Move regex syntax validation (new RegExp) before safe-regex2 check
so invalid patterns get proper syntax error instead of misleading
"catastrophic backtracking" message
* fix(security): remove input length cap from regex validation
The 10k character cap would block legitimate guardrail checks on long
LLM outputs. Input length doesn't affect ReDoS risk — the safe-regex2
pattern check already prevents catastrophic backtracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tests): mock isHosted in input-validation and function-execute tests
Tests that assert self-hosted localhost behavior need isHosted=false,
which is not guaranteed in CI where NEXT_PUBLIC_APP_URL is set to the
hosted domain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): scope copilot feedback GET endpoint to authenticated user
Add WHERE clause to filter feedback records by the authenticated user's
ID, preventing any authenticated user from reading all users' copilot
interactions, queries, and workflow YAML (IDOR / CWE-639).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(smtp): add SSRF validation and genericize network error messages
Prevent SSRF via user-controlled smtpHost by validating with
validateDatabaseHost before creating the nodemailer transporter.
Collapse distinct network error messages (ECONNREFUSED, ECONNRESET,
ETIMEDOUT) into a single generic message to prevent port-state leakage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): add SSRF validation to SFTP/SSH and access control to workspace invitations
Add `validateDatabaseHost` checks to SFTP and SSH connection utilities to
block connections to private/reserved IPs and localhost, matching the
existing pattern used by all database tools. Add authorization check to
the workspace invitation GET endpoint so only the invitee or a workspace
admin can view invitation details.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(smtp): restore SMTP response code handling for post-connection errors
SMTP 4xx/5xx response codes are application-level errors (invalid
recipient, mailbox full, server error) unrelated to the SSRF hardening
goal. Restore response code differentiation and logging to preserve
actionable user-facing error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): use session email directly instead of extra DB query
Addresses PR review feedback — align with the workspace invitation
route pattern by using session.user.email instead of re-fetching
from the database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(auth): revert lint autofix that broke hasExternalApiCredentials return type
Biome auto-fixed `return auth !== null && auth.startsWith(...)` to
`return auth?.startsWith(...)` which returns `boolean | undefined`,
not `boolean`, causing a TypeScript build failure.
* fix(smtp): pin resolved IP to prevent DNS rebinding (TOCTOU)
Use the pre-resolved IP from validateDatabaseHost instead of the
original hostname when creating the nodemailer transporter. Set
servername to the original hostname to preserve TLS SNI validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(security): extract createPinnedLookup helper for DNS rebinding prevention
Extract reusable createPinnedLookup from secureFetchWithPinnedIP so
non-HTTP transports (SSH, SFTP, IMAP) can pin resolved IPs at the
socket level. SMTP route uses host+servername pinning instead since
nodemailer doesn't reliably pass lookup to both secure/plaintext paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(security): pin IMAP connections to validated resolved IP
Pass the resolved IP from validateDatabaseHost to ImapFlow as host,
with the original hostname as servername for TLS SNI verification.
Closes the DNS TOCTOU rebinding window.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(auth): revert lint autofix on hasExternalApiCredentials return type
Also pin SFTP/SSH connections to validated resolved IP to prevent DNS rebinding.
* fix(security): short-circuit admin check when caller is invitee
Skip the hasWorkspaceAdminAccess DB query when the caller is already
the invitee, avoiding an unnecessary round-trip. Aligns with the org
invitation route pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(knowledge): scope sync/update state per-connector to prevent race conditions
* feat(knowledge): add connectors column to knowledge base list
* refactor(knowledge): extract set helpers, handleTogglePause, and filter-before-map
* refactor(knowledge): use onSettled for syncingIds cleanup, consistent with updatingIds
* feat(generic): add generic resource tab, refactor home structure, and UI polish
* reverted hardcoded ff
* fix build
* styling consistency
* styling
* fix(auth): extract shared auth button class and align SSO primary style
- Extract AUTH_SUBMIT_BTN constant to (auth)/components/auth-button-classes.ts,
replacing 10 copy-pasted identical className strings across 7 files
- Update SSOLoginButton primary variant to use AUTH_SUBMIT_BTN instead of
hardcoded purple gradient, making it consistent with all other auth form
submit buttons
- Fix missing isEphemeralResource import in lib/copilot/resources.ts
(was re-exported but not available in local scope)
* fix(auth): replace inline button class in chat auth components with AUTH_SUBMIT_BTN
* fix send button hover state
* feat(search): add tables, files, knowledge bases, and jobs to cmd-k search
* fix(search): address PR feedback — drop files/jobs, add onSelect to memo
* fix(search): add files back with per-file deep links, keep jobs out
* fix(search): remove onSelect from memo comparator to match existing pattern
* fix(knowledge): enqueue connector docs per-batch to survive sync timeouts
* fix(connectors): convert all connectors to contentDeferred pattern and fix validation issues
All 10 connectors now use contentDeferred: true in listDocuments, returning
lightweight metadata stubs instead of downloading content during listing.
Content is fetched lazily via getDocument only for new/changed documents,
preventing Trigger.dev task timeouts on large syncs.
Connector-specific fixes from validation audit:
- Google Drive: metadata-based contentHash, orderBy for deterministic pagination,
precise maxFiles, byte-length size check with truncation warning
- OneDrive: metadata-based contentHash, orderBy for deterministic pagination
- SharePoint: metadata-based contentHash, byte-length size check
- Dropbox: metadata-based contentHash using content_hash field
- Notion: code/equation block extraction, empty page fallback to title,
reduced CHILD_PAGE_CONCURRENCY to 5, syncContext parameter
- Confluence: syncContext caching for cloudId, reduced label concurrency to 5
- Gmail: use joinTagArray for label tags
- Obsidian: syncRunId-based stub hash for forced re-fetch, mtime-based hash
in getDocument, .trim() on vaultUrl, lightweight validateConfig
- Evernote: retryOptions threaded through apiFindNotesMetadata and apiGetNote
- GitHub: added contentDeferred: false to getDocument, syncContext parameter
Infrastructure:
- sync-engine: added syncRunId to syncContext for Obsidian change detection
- confluence/utils: replaced raw fetch with fetchWithRetry, added retryOptions
- oauth: added supportsRefreshTokenRotation: false for Dropbox
- Updated add-connector and validate-connector skills with contentDeferred docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(connectors): address PR review comments - metadata merge, retryOptions, UTF-8 safety
- Sync engine: merge metadata from getDocument during deferred hydration,
so Gmail/Obsidian/Confluence tags and metadata survive the stub→full transition
- Evernote: pass retryOptions {retries:3, backoff:500} from listDocuments and
getDocument callers into apiFindNotesMetadata and apiGetNote
- Google Drive + SharePoint: safe UTF-8 truncation that walks back to the last
complete character boundary instead of splitting multi-byte chars
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(evernote): use correct RetryOptions property names
maxRetries/initialDelayMs instead of retries/backoff to match the
RetryOptions interface from lib/knowledge/documents/utils.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sync-engine): merge title from getDocument and skip unchanged docs after hydration
- Merge title from getDocument during deferred hydration so Gmail
documents get the email Subject header instead of the snippet text
- After hydration, compare the hydrated contentHash against the stored
DB hash — if they match, skip the update. This prevents Obsidian
(and any connector with a force-refresh stub hash) from re-uploading
and re-processing unchanged documents every sync
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sync-engine): dedup externalIds, enable deletion reconciliation, merge sourceUrl
Three sync engine gaps identified during audit:
1. Duplicate externalId guard: if a connector returns the same externalId
across pages (pagination overlap), skip the second occurrence to prevent
unique constraint violations on add and double-uploads on update.
2. Deletion reconciliation: previously required explicit fullSync or
syncMode='full', meaning docs deleted from the source accumulated in
the KB forever. Now runs on all non-incremental syncs (which return
ALL docs). Includes a safety threshold: if >50% of existing docs
(and >5 docs) would be deleted, skip and warn — protects against
partial listing failures. Explicit fullSync bypasses the threshold.
3. sourceUrl merge: hydration now picks up sourceUrl from getDocument,
falling back to the stub's sourceUrl if getDocument doesn't set one.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(connectors): confluence version metadata fallback and google drive maxFiles guard
- Confluence: use `version?.number` directly (undefined) in metadata instead
of `?? ''` (empty string) to prevent Number('') = 0 passing NaN check in
mapTags. Hash still uses `?? ''` for string interpolation.
- Google Drive: add early return when previouslyFetched >= maxFiles to prevent
effectivePageSize <= 0 which violates the API's pageSize requirement (1-1000).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(connectors): blogpost labels and capped listing deletion reconciliation
- Confluence: fetchLabelsForPages now tries both /pages/{id}/labels and
/blogposts/{id}/labels, preventing label loss when getDocument hydrates
blogpost content (previously returned empty labels on 404).
- Sync engine: skip deletion reconciliation when listing was capped
(maxFiles/maxThreads). Connectors signal this via syncContext.listingCapped.
Prevents incorrect deletion of docs beyond the cap that still exist in source.
fullSync override still forces deletion for explicit cleanup.
- Google Drive & Gmail: set syncContext.listingCapped = true when cap is hit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(connectors): set syncContext.listingCapped in all connectors with caps
OneDrive, Dropbox, SharePoint, Confluence (v2 + CQL), and Notion (3 listing
functions) now set syncContext.listingCapped = true when their respective
maxFiles/maxPages limit is hit. Without this, the sync engine's deletion
reconciliation would run against an incomplete listing and incorrectly
hard-delete documents that exist in the source but fell outside the cap window.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(evernote): thread retryOptions through apiListTags and apiListNotebooks
All calls to apiListTags and apiListNotebooks in both listDocuments and
getDocument now pass retryOptions for consistent retry protection across
all Thrift RPC calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent auth bypass via user-controlled context query param in file serve
The /api/files/serve endpoint trusted a user-supplied `context` query
parameter to skip authentication. An attacker could append
`?context=profile-pictures` to any file URL and download files without
auth. Now the public access gate checks the key prefix instead of the
query param, and `og-images/` is added to `inferContextFromKey`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use randomized heredoc delimiter in SSH execute-script route
Prevents accidental heredoc termination if script content contains
the delimiter string on its own line.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: escape workingDirectory in SSH execute-command route
Use escapeShellArg() with single quotes for the workingDirectory
parameter, consistent with all other SSH routes (execute-script,
create-directory, delete-file, move-rename).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: harden chat/form deployment auth (OTP brute-force, CSPRNG, HMAC tokens)
- Add brute-force protection to OTP verification with attempt tracking (CWE-307)
- Replace Math.random() with crypto.randomInt() for OTP generation (CWE-338)
- Replace unsigned Base64 auth tokens with HMAC-SHA256 signed tokens (CWE-327)
- Use shared isEmailAllowed utility in OTP route instead of inline duplicate
- Simplify Redis OTP update to single KEEPTTL call
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: harden SSRF protections and input validation across API routes
Add DNS-based SSRF validation for MCP server URLs, secure OIDC discovery
with IP-pinned fetch, strengthen OTP/chat/form input validation, sanitize
1Password vault parameters, and tighten deployment security checks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix(file-serve): remove user-controlled context param from authenticated path
The `?context` query param was still being passed to `handleCloudProxy`
in the authenticated code path, allowing any logged-in user to spoof
context as `profile-pictures` and bypass ownership checks in
`verifyFileAccess`. Now always use `inferContextFromKey` from the
server-controlled key prefix.
* fix: handle legacy OTP format in decodeOTPValue for deploy-time compat
Add guard for OTP values without colon separator (pre-deploy format)
to avoid misparse that would lock out users with in-flight OTPs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(mcp): distinguish DNS resolution failures from SSRF policy blocks
DNS lookup failures now throw McpDnsResolutionError (502) instead of
McpSsrfError (403), so transient DNS hiccups surface as retryable
upstream errors rather than confusing permission rejections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make OTP attempt counting atomic to prevent TOCTOU race
Redis path: use Lua script for atomic read-increment-conditional-delete.
DB path: use optimistic locking (UPDATE WHERE value = currentValue) with
re-read fallback on conflict. Prevents concurrent wrong guesses from
each counting as a single attempt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: check attempt count before OTP comparison to prevent bypass
Reject OTPs that have already reached max failed attempts before
comparing the code, closing a race window where a correct guess
could bypass brute-force protection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate OIDC discovered endpoints against SSRF
The discovery URL itself was SSRF-validated, but endpoint URLs returned
in the discovery document (tokenEndpoint, userInfoEndpoint, jwksEndpoint)
were stored without validation. A malicious OIDC issuer on a public IP
could return internal network URLs in the discovery response.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove duplicate OIDC endpoint SSRF validation block
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate OIDC discovered endpoints and pin DNS for 1Password Connect
- SSRF-validate all endpoint URLs returned by OIDC discovery documents
before storing them (authorization, token, userinfo, jwks endpoints)
- Pin DNS resolution in 1Password Connect requests using
secureFetchWithPinnedIP to prevent TOCTOU DNS rebinding attacks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* lint
* fix: replace KEEPTTL with TTL+EX for Redis <6.0 compat, add DB retry loop
- Lua script now reads TTL and uses SET...EX instead of KEEPTTL
- DB optimistic locking now retries up to 3 times on conflict
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review feedback on OTP atomicity and 1Password fetch
- Replace Redis KEEPTTL with TTL+SET EX for Redis <6.0 compatibility
- Add retry loop to DB optimistic lock path so concurrent OTP attempts
are actually counted instead of silently dropped
- Remove unreachable fallback fetch in 1Password Connect; make
validateConnectServerUrl return non-nullable string
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: treat Lua nil return as locked when OTP key is missing
When the Redis key is deleted/expired between getOTP and
incrementOTPAttempts, the Lua script returns nil. Handle this
as 'locked' instead of silently treating it as 'incremented'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle Lua nil as locked OTP and add SSRF check to MCP env resolution
- Treat Redis Lua nil return (expired/deleted key) as 'locked' instead
of silently treating it as a successful increment
- Add validateMcpServerSsrf to MCP service resolveConfigEnvVars so
env-var URLs are SSRF-validated after resolution at execution time
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: narrow resolvedIP type guard instead of non-null assertion
Replace urlValidation.resolvedIP! with proper type narrowing by adding
!urlValidation.resolvedIP to the guard clause, so TypeScript can infer
the string type without a fragile assertion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bind auth tokens to deployment password for immediate revocation
Include a SHA-256 hash of the encrypted password in the HMAC-signed
token payload. Changing the deployment password now immediately
invalidates all existing auth cookies, restoring the pre-HMAC behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bind auth tokens to deployment password and remove resolvedIP non-null assertion
- Include SHA-256 hash of encryptedPassword in HMAC token payload so
changing a deployment's password immediately invalidates all sessions
- Pass encryptedPassword through setChatAuthCookie/setFormAuthCookie
and validateAuthToken at all call sites
- Replace non-null assertion on resolvedIP with proper narrowing guard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update test assertions for new encryptedPassword parameter
Tests now expect the encryptedPassword arg passed to validateAuthToken
and setDeploymentAuthCookie after the password-binding change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: format long lines in chat/form test assertions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: pass encryptedPassword through OTP route cookie generation
Select chat.password in PUT handler DB query and pass it to
setChatAuthCookie so OTP-issued tokens include the correct
password slot for subsequent validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(copilot): expand tool metadata, fix thinking text rendering, clean up display logic
* fix(copilot): guard null reasoning data, use ensureTextBlock for thinking end
* fix(copilot): restore displayTitle precedence so cancelled tools show 'Stopped by user'
* feat: skills import, MCP modal updates, wordmark icon, tool-input improvements
- Add skills import functionality (route + components + utils)
- Update MCP deploy modal
- Add Wordmark emcn icon + logo SVG assets
- Improve tool-input component
- Update README branding to new wordmark
- Add ban-spam-accounts admin script
* fix: resolve build error and audit findings from simplify review
- Add BUILT_IN_TOOL_TYPES export to blocks/utils.ts (was removed from
tool-input.tsx but never added to the new import target — caused build
error "Export BUILT_IN_TOOL_TYPES doesn't exist in target module")
- Export Wordmark from emcn icons barrel (index.ts)
- Derive isDragging from dragCounter in skill-import.tsx instead of
maintaining redundant state that could desync
- Replace manual AbortController/setTimeout with AbortSignal.timeout()
in skills import API route (Node 17.3+ supported, cleaner no-cleanup)
- Use useId() for SVG gradient ID in wordmark.tsx to prevent duplicate
ID collisions if rendered multiple times on the same page
* fix(scripts): fix docs mismatch and N+1 query in ban-spam-accounts
- Fix comment: default pattern is @vapu.xyz, not @sharebot.net
- Replace per-user stats loop with a single aggregated JOIN query
* feat: wire wordmark into sidebar, fix credential selector modal dispatch
- Show Wordmark (icon + text) in the expanded sidebar instead of the
bare Sim icon; collapsed state keeps the small Sim icon unchanged
- Untrack scripts/ban-spam-accounts.ts (gitignored; one-off script)
- Credential selector: open OAuthRequiredModal inline instead of
navigating to Settings → Integrations (matches MCP/tool-input pattern)
- Credential selector: update billing import from getSubscriptionAccessState
to getSubscriptionStatus; drop writePendingCredentialCreateRequest and
useSettingsNavigation dependencies
* feat(misc): misc UX/UI improvements
* more random fixes
* more random fixes
* fix: address PR review findings from cursor bugbot
- settings-sidebar: use getSubscriptionAccessState instead of getSubscriptionStatus
so billingBlocked and status validity are checked; add requiresMax gating so
max-plan-only nav items (inbox) are hidden for lower-tier users
- credential-selector: same getSubscriptionAccessState migration for credential sets
visibility check
- mothership chats PATCH: change else if to if for isUnread so both title and
isUnread can be updated in a single request
- skills import: check Content-Length header before reading response body to avoid
loading oversized files into memory
* fix(skills): add ZIP file size guard before extraction
Checks file.size > 5 MB before calling extractSkillFromZip to prevent
zip bombs from exhausting browser memory at the client-side upload path.
* feat(settings-sidebar): show locked upsell items with plan badge
Sim Mailer (requiresMax) and Email Polling (requiresTeam) now always
appear in the settings sidebar when billing is enabled and the
deployment is hosted. If the user lacks the required plan they see a
small MAX / TEAM badge next to the label and are taken to the page
which already contains the upgrade prompt.
Enterprise (Access Control, SSO) and Team management stay hard-hidden
for lower tiers. Admin/superuser items stay truly hidden.
* fix(settings-sidebar): remove flex-1 from label span to fix text centering
* feat(settings-sidebar): remove team gate from email polling, keep only mailer max gate
* feat(subscription): billing details layout and Enterprise card improvements
- Move Enterprise plan card into the plan grid (auto-fit columns) instead
of a separate standalone section below billing details
- Refactor billing details section: remove outer border/background,
separate each row with top border + padding for cleaner separation
- Update button variants: Add Credits → active, Invoices → active
* fix(mothership): prevent lastSeenAt conflict when both title and isUnread are patched together
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sidebar): prevent double-save race in flyout inline rename on Enter+blur
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(skills): normalize CRLF line endings before parsing SKILL.md frontmatter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>