Files
sim/apps
WaleedandClaude Opus 4.7 64642d4f65 fix(connectors): harden 10 KB connectors after audit (#4410)
* fix(connectors): harden 10 KB connectors after audit

Validated each issue against provider docs before fixing.

- jira: migrate from deprecated /rest/api/3/search (Atlassian sunset
  May 2025) to /rest/api/3/search/jql with nextPageToken pagination
- confluence: unify stub hash across v1 CQL (`when`) and v2
  (`createdAt`) paths via shared pageToStub helper using version.number
- salesforce: replace hardcoded login.salesforce.com userinfo with
  host fallback so sandbox-issued tokens (test.salesforce.com) work
- servicenow: validate sys_id against /^[a-f0-9]{32}$/ and switch
  getDocument to path-based /api/now/table/{table}/{sys_id} to close
  encoded-query injection; reject `^` in kbCategory filter
- zendesk: URL-encode Search API query via URLSearchParams; whitelist
  ticket statuses; encode locale path segment
- github: add 10MB cap and /git/blobs/{sha} fallback for files >1MB
  that /contents/ returns with encoding:"none"
- slack: replace SHA-256 over formatted-message window with metadata
  hash slack:{channelId}:{latestTs}:{count} so list and getDocument
  agree; cache auth.test team_id on syncContext
- obsidian: drop syncRunId from stub hash (Local REST API has no
  HEAD/Last-Modified per OpenAPI spec); fall back to path-only stub
  so engine two-stage check short-circuits unchanged notes
- evernote: title fallback for attachments-only notes — breaks
  infinite hydration loop where empty plaintext returned null
- google-docs: drop residual `error instanceof Error` pattern

Confluence and Slack hash format changes self-heal with a one-time
re-sync; no data loss.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(connectors/slack): include oldestTs in hash to catch window-shift edits

Audit caught that the metadata hash slack:{channel}:{latestTs}:{count}
misses edits to the oldest message in the rolling window — count and
latestTs both stay constant. Adding oldestTs catches window-shift
when a new message arrives and pushes the oldest out without changing
the newest.

Also drop residual error instanceof Error pattern in validateConfig.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(connectors): address remaining audit findings

- jira: thread VALIDATE_RETRY_OPTIONS through getJiraCloudId so the
  validate path uses the tighter retry budget
- confluence: thread VALIDATE_RETRY_OPTIONS through getConfluenceCloudId;
  drop residual error instanceof Error pattern
- zendesk: log a warning when statusFilter is set and limit exceeds
  the Search API 1000-result cap; add priority to tagDefinitions and
  mapTags so the metadata isn't orphaned

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(connectors): address PR review comments

- github: skip blob fetch for empty 0-byte files
- salesforce: add fallback message for empty error
- zendesk: warn on invalid statusFilter instead of silent fallthrough
- evernote: remove dead content.trim() guard

* fix(zendesk): add fallback message for empty validateConfig error

* docs(obsidian): clarify hash strategy and re-hydration behavior

* fix(connectors): address parallel audit findings

- servicenow: case-insensitive sys_id pattern; validate workflowState/incidentState/incidentPriority as numeric, kbCategory via allowlist
- salesforce: include 400 in userinfo host fallthrough for sandbox tokens
- zendesk: add missing 'new' and 'hold' options to ticketStatus dropdown

* fix(servicenow): allow string workflowState values (published/draft/review/retired)

* fix(obsidian): validateConfig must check authenticated field, not just status

GET / is the only public endpoint per the Local REST API spec — it returns
200 regardless of auth and exposes the real signal via response.authenticated.
The prior status-based check let invalid API keys pass validation.

* fix(jira): encode collected count in cursor so maxIssues cap works without syncContext

* fix(connectors): address remaining audit findings

- slack: detect message edits and new threaded replies via edited.ts/latest_reply in contentHash
- github: skip binary files via NUL-byte sniff and filter oversized files at tree-list time
- servicenow: add 'outdated' workflow state to allowlist and dropdown; add Canceled (8) to incident state dropdown
- salesforce: drop 400 from userinfo host fallthrough (Salesforce returns 401/403 for cross-env tokens)

* fix(github): apply binary check to blob fallback path

fetchBlobContent now runs isBinaryBuffer on base64-decoded data and
returns null for binaries; getDocument propagates the null so 1-10MB
binary files are skipped instead of being decoded into garbled UTF-8.

* fix(confluence): reuse pageToStub in getDocument to keep hashes in sync

Replace duplicated versionKey/contentHash logic with a single call to
pageToStub so listDocuments and getDocument can never drift.

* fix(connectors): address audit findings across 7 connectors

- github: branch with slashes encoded per-segment in sourceUrl; treat 403
  Contents API responses (>100MB or restricted) as null instead of
  throwing; fetchBlobContent throws on unexpected encoding rather than
  returning empty content
- jira: ADF extractor handles hardBreak, mention, and emoji nodes
- google-docs: paragraph join uses newline so heading/body boundaries
  are preserved
- servicenow: drop legacy 'wiki' field from kb_knowledge requests; only
  the 'text' field is reliably present on modern instances
- salesforce: KnowledgeArticleVersion query adds IsLatestVersion=true to
  avoid duplicate historical versions; encode externalId in sObject GET
- slack: subtype filter switches to a denylist (SLACK_NOISE_SUBTYPES) so
  thread_broadcast / me_message / reminder_add are kept; reply_count
  folded into contentHash to detect threaded reply deletes
- confluence: drop bare-string cursor fallback; unparseable cursors now
  restart the listing instead of silently re-listing blogposts from 0

* fix(servicenow): allow non-ASCII KB category names

Switch from a \w-based allowlist (ASCII only) to a denylist that
rejects only encoded-query-meaningful characters (^, control chars,
quotes). International category names like 'Général' or 'Ação' are
now accepted.

* fix(servicenow): restore wiki field fallback for kb_knowledge

Wiki-template KB articles populate the wiki field and leave text empty;
HTML-template articles do the opposite. Removing wiki caused wiki-format
articles to resolve to empty content and be silently skipped.

* fix(connectors): jira pagination signal and google-docs paragraph join

- jira: rely solely on absence of nextPageToken for end-of-results;
  data.isLast on /rest/api/3/search/jql is unreliable (JRACLOUD-95477)
  and the OR-with-isLast logic could truncate pagination early
- google-docs: strip trailing newline from each paragraph before
  joining so heading->body produces a single newline, not two

* fix(jira): rely solely on nextPageToken for end-of-results

data.isLast on /rest/api/3/search/jql is unreliable (JRACLOUD-95477)
and the previous OR-with-isLast logic could truncate pagination early
if Jira ever returned isLast=true alongside a valid nextPageToken.

* fix(slack): exclude DM channel IDs from direct lookup

DM (D...) channel IDs require im:*/mpim:* OAuth scopes, which the
connector does not request. The regex now matches only public (C) and
private (G) channel IDs to fail fast with a clearer error instead of
hitting missing_scope from Slack.

* fix(slack): align validateConfig channel ID regex with resolveChannel

The previous fix narrowed the regex in resolveChannel to [CG] but missed
the duplicate in validateConfig, which still admitted DM IDs and would
fall through to a name-based search returning 'Channel not found.'

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 17:53:57 -07:00
..