Skip to content

fix(integrations): close defects found by an independent cold audit - #6767

Open
waleedlatif1 wants to merge 11 commits into
stagingfrom
chore/integration-clean-audit
Open

fix(integrations): close defects found by an independent cold audit#6767
waleedlatif1 wants to merge 11 commits into
stagingfrom
chore/integration-clean-audit

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Eight cold readers — one per integration, given no prior findings and no history — audited the eight integrations merged to staging. Every one returned a "production-ready" verdict, and between them they still found six real functional defects, two of which broke an operation outright. This PR closes all of them.

Every fix has a regression test, and each test was individually verified to fail when its fix is reverted.

Broke an operation outright

  • Datadog — Update SLO was unusable on non-metric SLOs. The SLO Type dropdown carried a metric default and its condition covered both create and update, so an untouched control reached the merge as an edit and rewrote the stored type. A metric SLO requires query; the merged body carries monitor_ids/sli_specification instead, so Datadog rejected it. There was no way to express "keep the current type" — update now has its own control defaulting to "Keep current".
  • Entra — advanced $filter returned 400. list_users, list_groups and list_service_principals emitted $count=true only alongside $search, but Graph requires it with ConsistencyLevel: eventual for ne, not, endsWith, and startsWith on non-indexed properties. list_devices already did this correctly; the other three now match it.

Silently wrong results

  • ServiceNow — attachmentLimit and limit overwrote each other on all 12 paginated operations. Neither assignment was scoped to an operation, so List Incidents with a limit of 100 could send 5. This defeated the block's own design, which gave attachmentLimit a unique id specifically to avoid it.
  • Okta — get_logs advertised hasMore: true forever. A System Log query with no until is a polling query, and Okta always returns a next link for one, even on an empty page. The default config is exactly that shape, so any loop driven by hasMore never terminated — including the one our own shipped skill instructs the agent to run.
  • Splunk — 7 of 12 operations published wrong output tables. The docs generator parses tool source and resolves shared consts only from types.ts, so Splunk's helpers in utils.ts were invisible: Run Search and Get Search Results each published ~50 phantom rows (savedSearches, indexes, apps…) they never return. Fixed by inlining the literals and deleting the helpers, which removes the mechanism rather than relocating it. docs:check passed throughout — the generator deterministically reproduces the wrong table.
  • Cloudflare — DNS Analytics emitted fabricated telemetry. min/max were declared as seven numeric fields each mapped ?? 0, though Cloudflare documents both as "currently always an empty object". An agent reading that got plausible-looking numbers instead of obviously-absent data.

Also

Splunk gained an error extractor (a bad SPL string previously reported only "Bad Request"), a bounded oneshot run_search, <msg> parsing so a cancel reports its confirmation, and asNumber on the epoch time bounds. Okta surfaces errorCauses, so a failed write reports the real reason rather than "Api validation failed: profile". MSSQL added WRITETEXT/UPDATETEXT-class statements to the read-only screen, row and byte caps, introspection collapsed from 4N+2 to 6 fixed queries, and guards that run before the connection opens so rejections are 400 rather than 500. CrowdStrike corrected an IOC sort placeholder naming a field that does not exist, and relabelled an unsourced cap as Sim's own. Datadog trims path IDs in all 20 URL builders rather than 2.

Notes for review

  • The MSSQL guard bypass attempt failed. The auditor ran ~55 payloads — doubled quotes, N'...' literals, bracketed quotes, comments, backticks, FETCH abuse, Service Broker — and broke none, then proved why: a masker desync requires a backslash immediately before a quote, which is exactly the rejected pattern.
  • One Entra scope narrowed, not removed. Directory.Read.AllLicenseAssignment.Read.All. Two earlier analyses proposed removing it outright; that breaks list_subscribed_skus, whose least-privileged permission is the narrower scope. A tripwire test asserts GroupMember.ReadWrite.All stays, since group-post-members does not accept Group.ReadWrite.All.
  • A known follow-up: teaching the docs generator to resolve consts from utils.ts was measured and would change nine other integrations (ashby, context_dev, daytona, mintlify, okta, onepassword, persona, rabbitmq, trigger_dev), every inspected change a correction. Those are likely publishing wrong rows today. Tracked on docs generator: tool descriptions past a 600-char window publish as empty strings, silently #6760.

Verification

type-check clean, biome clean, 42 test files / 965 tests passing, and tool-metadata:check, integration-catalog:check, docs:check, check:api-validation all pass. Artifacts regenerated with no unrelated drift.

An independent audit — eight cold readers, one per integration, given no
prior findings — checked the eight integrations merged to staging today.
Two defects broke an operation outright; both are fixed here.

datadog: Update SLO rewrote every non-metric SLO to `metric`. The SLO Type
dropdown carried a `metric` default and its condition covered both create and
update, so an untouched control reached mergeSloUpdatePayload as an edit. A
metric SLO requires `query`, and the merged body carries monitor_ids or
sli_specification instead, so Datadog rejected it — Update SLO was unusable on
monitor-based and time-slice SLOs, with no way to express "keep the current
type". Update now has its own control defaulting to "Keep current".

microsoft_ad: list_users, list_groups and list_service_principals emitted
$count=true only alongside $search, so any $filter using an advanced operator
(ne, not, endsWith, startsWith on non-indexed properties) returned 400. Graph
requires $count=true with ConsistencyLevel: eventual for those. list_devices
already did this correctly; the other three now match it.

Also from the same audit: Datadog path IDs are trimmed before encoding in all
20 URL builders rather than 2, matching the existing get_monitor test.
From the independent cold audit. Cloudflare: DNS analytics no longer emits
fabricated min/max telemetry (Cloudflare documents both as always empty);
purge_everything defaults to specific-purge and errors when combined with
target lists; three unsourced description claims corrected; Array.isArray
guards on four older list transforms.

CrowdStrike: IOC sort placeholder corrected to the dot form (created_on.desc,
not the nonexistent created_timestamp); the 500-indicator cap relabelled as a
Sim bound rather than a CrowdStrike one; credential failures return 401 rather
than 500; prevent_no_ui noted as unenumerated.

MSSQL: introspect no longer lets the model choose the database; row and byte
caps on reads; introspection collapsed from 4N+2 to 6 fixed queries; WHERE and
identifier guards run before the connection opens so rejections are 400 not
500; SAVE TRANSACTION, OPEN/CLOSE key, DEALLOCATE and ADD SIGNATURE added to
the statement screen as two-token phrases; encrypt wording corrected to say
TDS 7.4 encryption is negotiated, not guaranteed.
…k sends

The docs generator parses tool source text and resolves a shared `outputs`
const only from the family's `types.ts`, so Splunk's helpers in `utils.ts` were
invisible to it: seven operations published the block's union of every output
instead of their own. Run Search and Get Search Results each shipped a ~50-row
table naming savedSearches, alerts, indexes, and apps they never return, Cancel
Search Job lost `messages`, and the four list tools lost `total`/`offset`.
Inline the four helpers into each consuming tool and delete them, since
relocating a shared const only moves the trap.

Also:

- Add a `splunk-errors` extractor for the documented `{messages: [{type, text}]}`
  envelope and set it on all twelve tools. A rejected SPL string, the most common
  failure, previously fell through to the status text and reported "Bad Request".
- Read `searchEarliestTime`/`searchLatestTime` with `asNumber`. The job entry
  documents them as bare epoch numbers, so `asString` returned null for every
  `output_mode=json` response.
- Project the `<messages>` block of the XML job-control response. It is the only
  payload that endpoint returns, so `cancel_search_job.messages` was always empty.
- Mark the nullable job outputs optional, matching the transform.
- Default `run_search` to `max_count=1000`. A oneshot search has no paging escape
  hatch and Splunk's own default is 10000 rows in one buffered response.
- Add suggested skills to `SplunkBlockMeta`, grounded in `tools.access`.

The regenerated tool metadata also picks up the Cloudflare and MSSQL output
changes from the previous commit, which were never synced.
Cherry-picked from fix/okta-servicenow-audit-followups (6db0f54), whose base
predated the earlier audit round; the duplicate isOktaFlagEnabled that produced
is resolved in favour of the existing richer helper, which already accepts
'yes'/1/'on' as well as true/'true'.

okta: get_logs no longer advertises hasMore forever. A System Log query with no
'until' is a polling query, and Okta always returns a next link for one, even on
an empty page — so any loop driven by hasMore never terminated, including the
one our own shipped skill instructs the agent to run. errorCauses is now
surfaced, so a failed write reports the real reason instead of the useless
'Api validation failed: profile'. sendEmail routes through one coercion helper
across all four lifecycle tools. update_group's declarative fallback throws
rather than silently truncating an extensible group profile.

servicenow: attachmentLimit and limit no longer overwrite each other. Neither
assignment was scoped to an operation, so all 12 paginated operations could
silently return a row count the user never asked for — defeating the block's own
design, which gave attachmentLimit a unique id precisely to avoid this. All
seven approval states are published by ServiceNow and are now reachable from the
filter, with the space-vs-underscore punctuation documented. The five legacy
generic tools route through the shared response helpers, and the folder's only
'any' is gone. Block skills now name the semantic operations.
@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Building Building Preview Aug 16, 2026 9:24am

Request Review

@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes touch database SQL guards, large-result truncation, and several integration APIs that affect live workflows; risk is moderate due to breadth and MSSQL/Cloudflare behavior shifts, not auth core.

Overview
Closes functional and documentation gaps found in cold audits of recently merged integrations, with regression tests on the behavioral fixes.

Microsoft SQL Server adds 10k-row / 10MB response ceilings with truncated and truncationReason on query/execute/insert/update/delete routes; expands read-only and WHERE screening for session/key/cursor phrases without blocking ordinary column names like open/close; runs update/delete statement validation before connecting (400 instead of 500); and collapses introspection from per-table round trips to a fixed small batch of schema queries.

Cloudflare stops fabricating DNS analytics min/max and unrequested metric zeros; rejects ambiguous purge (purge_everything plus target lists) and defaults the block toward specific purges; tightens rate-limit characteristics docs (mutually exclusive IP vs visitor); and makes list/settings transforms tolerate non-array API result shapes.

Splunk aligns docs and tool output schemas (drops phantom union fields on run/get results), documents bounded oneshot defaults, types search command epoch bounds as numbers, and adds list paging total/offset plus block skills.

CrowdStrike maps OAuth failures to CrowdStrikeAuthError with real HTTP status (401/502) instead of 500; clarifies IOC action and limit documentation.

Datadog adds a separate Update SLO type control defaulting to “keep current” so updates do not silently force metric.

ServiceNow scopes limit vs attachmentLimit per operation and expands approval state options and skills.

Okta docs clarify sendEmail and polling get_logs cursor/hasMore behavior.

Reviewed by Cursor Bugbot for commit 5395058. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

Comment thread apps/sim/app/api/tools/mssql/utils.ts
@greptile-apps

greptile-apps Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR corrects integration request construction, output metadata, pagination, error reporting, and MSSQL safety limits across eight providers.

  • Prevents invalid or silently altered requests in Datadog, Entra, and ServiceNow.
  • Corrects Okta pagination, Cloudflare telemetry output, and Splunk output declarations.
  • Adds MSSQL query guards and bounded result handling, including a complete fix for the previously reported oversized-first-row bypass.

Confidence Score: 5/5

The PR appears safe to merge because the previously reported MSSQL response-cap bypass is fixed and no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/app/api/tools/mssql/utils.ts The response-cap loop now checks each row before admission, so an oversized first row is dropped and explicitly reported as truncated.
apps/sim/app/api/tools/mssql/utils.test.ts Regression coverage directly verifies the previously reported oversized-first-row failure and the general row and byte ceilings.
apps/sim/tools/mssql/introspect.ts Reworks MSSQL introspection into a fixed-query strategy while preserving bounded output behavior.
apps/sim/blocks/blocks/servicenow.ts Scopes operation-specific limit parameters so attachment and ordinary pagination limits no longer overwrite one another.
apps/sim/tools/okta/get_logs.ts Corrects System Log continuation semantics to avoid treating polling next links as perpetual pagination.
apps/sim/tools/splunk/run_search.ts Adds bounded oneshot search behavior and aligns published output metadata with actual results.

Reviews (2): Last reviewed commit: "fix(integrations): disclose MSSQL trunca..." | Re-trigger Greptile

Comment thread apps/sim/app/api/tools/mssql/utils.ts
Three defects the review round found in the audit fixes themselves.

MSSQL capped a recordset and then reported it as complete: `executeQuery`
computed `truncated`/`truncationReason` but all five statement routes returned
only `message`, `rows`, and `rowCount`, so a caller could not tell paging was
required. A shared `toRowsResponseBody` now folds the reason into `message`
for an agent reading the status line and exposes the two fields for a caller
that branches on them.

The byte ceiling also admitted a single oversized row as a lone exception, so
one `nvarchar(max)` value serialized an unbounded body — the ceiling bounded
everything except the case it exists for. A row is now admitted only when it
still fits, and the drop is disclosed rather than read as an empty table.

Okta's `get_logs` nulled `nextCursor` alongside `hasMore` on an empty polling
page. Terminating the loop is right, but the cursor is the resume handle Okta
tells callers to persist, so a scheduled workflow that hit one quiet interval
restarted from `since` and re-delivered events it had already processed. The
two answer different questions and now diverge.

Cloudflare's purge block no longer lets the invalid combination be built: the
four target fields are hidden once Purge Everything is selected, so the tool's
guard is a backstop rather than a reachable hard error.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Pushed 5395058 addressing this round.

MSSQL — capped results reported as complete (both bots). `executeQuery` computed `truncated`/`truncationReason`; all five statement routes returned only `message`/`rows`/`rowCount`. A shared `toRowsResponseBody` now builds every success body, folding the reason into `message` and exposing both fields (optional on `sqlRowsResponseSchema` and on the five tools' `outputs`).

MSSQL — byte ceiling bypassed by a single oversized row (Greptile). The lone-row exception meant one `nvarchar(max)` value serialized an unbounded body, so the ceiling bounded everything except the case it exists for. A row is admitted only when it still fits; the drop is disclosed rather than read as an empty table.

Two further defects found by a self-review of the same fix pass:

Okta — the poll cursor was nulled along with `hasMore`. Terminating the loop on an empty polling page is right, but `nextCursor` is the resume handle Okta tells callers to persist. A scheduled workflow that hit one quiet interval restarted from `since` and re-delivered events it had already processed. The two answer different questions and now diverge.

Cloudflare — the rejected purge combination was still buildable. The four target fields are now hidden once Purge Everything is selected, so the tool guard is a backstop rather than a reachable hard error.

Regression tests added for the MSSQL byte ceiling, the truncation disclosure, and the Okta cursor; the two behavioral ones were each verified to fail when their fix is reverted. Artifacts regenerated. `tool-metadata:check`, `integration-catalog:check`, `docs:check`, `check:api-validation` all pass; 74 test files / 1379 tests green.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5395058. Configure here.

truncationReason:
rows.length === 0
? `No rows returned: the first row alone exceeds the ${MSSQL_MAX_RESULT_BYTES / (1024 * 1024)} MB response ceiling. Select fewer columns, or slice large values with SUBSTRING.`
: `Result truncated to ${rows.length} row(s): a single statement returns at most ${MSSQL_MAX_RESULT_ROWS} rows or ${MSSQL_MAX_RESULT_BYTES / (1024 * 1024)} MB. Page with OFFSET ... FETCH NEXT to read the rest.`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Empty truncated result misreports rowCount

Medium Severity

When capRecordset drops every row because the first exceeds the byte ceiling, rowCount still falls back to rowsAffected. For a SELECT, that leaves rows empty while rowCount stays non-zero, so the success message claims rows were returned and the new truncation text says none were.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5395058. Configure here.

Okta documents `since` and `after` on the System Log as mutually
exclusive, so `get_logs` lets the cursor win rather than sending both —
the shape a scheduled poll that persists the cursor would otherwise send.

Seven boolean query params reached Okta interpolated raw, so an agent
tool call supplying `yes` was rejected. Each now routes through
`isOktaFlagEnabled`, keeping its existing send-or-omit behavior.

A cleared ServiceNow limit/offset/quantity stayed `''` through the block
mapper and was appended as a valueless `sysparm_limit=`. The mapper now
resolves a blank to undefined, and the tools skip a blank as well.
…t findings

MSSQL read-only screen
- Screen RENAME, documented T-SQL DDL for Azure Synapse dedicated SQL pools and
  Analytics Platform System, which are reachable over TDS with exactly the
  connection fields this block exposes. `SELECT 1 RENAME OBJECT dbo.t TO t2`
  was a schema change passing an operation advertised as read-only.
- Screen the Service Broker family: RECEIVE as a word, and END/MOVE/GET
  CONVERSATION and SEND ON CONVERSATION as two-token phrases, since END closes
  every CASE. RECEIVE is a destructive read and END CONVERSATION WITH CLEANUP
  drops a conversation's messages.

MSSQL routes and block
- Build the insert statement before connecting, matching update and delete, so a
  bad identifier answers 400 instead of burning a TLS+login and returning 500.
- Declare `truncated`/`truncationReason` on the block, which the tools declare
  and the routes emit but the block left unreferenceable.

Microsoft Entra ID
- Pair `$count=true` with `ConsistencyLevel: eventual` conditionally. Graph
  documents `hasMembersWithLicenseErrors`, `isLicenseReconciliationNeeded`, and
  `identities/any(i:i/issuer)` as filterable only *without* advanced query
  parameters, and documents advanced queries as unsupported in Azure AD B2C
  tenants, so the unconditional pair broke filters that previously worked. When
  continuing from a nextLink the pairing is read off the link itself.
- Request `LicenseAssignment.Read.All` instead of `Directory.Read.All`. The
  latter was needed by `GET /subscribedSkus` alone, whose permission table names
  the former as least privileged and does not list the ReadWrite scope we hold.
- Enumerate the block's real output keys instead of a single `response` object
  no tool emits.
… query params

Splunk run_search: revert the `max_count=1000` default added last pass. It was
wrong on both halves. Splunk documents the parameter as "the number of events
that can be accessible in any given status bucket. Also, in transforming mode,
the maximum number of results to store" — so for a non-transforming oneshot it
bounds status buckets, not the response, and for a transforming search (`| stats`,
`| timechart`, which is what the block's own skills generate) it capped results
at 1000 where Splunk would have stored 10000, silently. The block's `maxCount`
placeholder already read `10000`, contradicting the code. Send `max_count` only
when the caller sets it and restate the description in Splunk's own terms,
matching create_search_job. The real guidance — a oneshot buffers the whole
result set, so use Create Search Job + Get Search Results for anything large —
moves into the tool description and the search-splunk-logs skill.

Datadog mute/unmute: send `scope`, `end`, and `all_scopes` as query parameters.
`MuteMonitor` and `UnmuteMonitor` declare no `requestBody` in the authoritative
spec (docs.datadoghq.com/resources/json/full_spec_v1.json — the generated
datadog-api-client-go v1 schema omits both operations and is a subset, not the
authority); all three parameters are `in: query`. Sent as a JSON body they are
dropped, so a scoped, time-boxed mute becomes an indefinite mute across every
scope and unmute's "all scopes" never applies — answered with a 200 and the full
monitor object, so nothing surfaces.

Datadog list_monitors: imply `page=0` when a page size is set without a page.
Datadog "returns all monitors without a `page_size` limit" when `page` is absent,
so Page Size was inert from a control that reads as a bound. `page` is not
defaulted when neither is set — that would silently truncate a caller relying on
the documented return-everything behavior.

Also:

- Note in get_fired_alerts that `name=-` returns every saved search's fired
  alerts and the endpoint documents "Request parameters: None", so there is no
  count/offset to bound it.
- Fix the Splunk block's `messages` output blurb: `[{type, text}]` holds for the
  search and job-control operations, but get_search_job returns an object.
- Generalize the Datadog block's numeric coercion (`datadogPageNumber` →
  `datadogNumber`) over all 32 bare `Number()` mappings, so a typo or unresolved
  reference is omitted rather than sent as `NaN`/`null`, and an explicit `0`
  survives the old truthiness guard.
- Disclose create_event's documented 18-hour `date_happened` ceiling, and that
  send_logs' `ddsource: "custom"` is a Sim default rather than a Datadog one.
Cloudflare: clear purge_cache advanced targets across operations; send
action_parameters/ref/logging on rate-limit rule updates; migrate off the
deprecated batch zone-settings endpoint; correct MX/URI priority wording;
stop coercing blank numerics to 0.

CrowdStrike: seed includeHidden to match Falcon's documented default.

Microsoft Entra ID: resolve a UPN to an object ID for app role assignment;
wrap 21 array outputs in items.properties so nested paths resolve.

Okta: route assign_user_role's notification flag through isOktaFlagEnabled.

ServiceNow: drop the triage skill's claim of a default limit that does not exist.

Splunk: always assign coerced numerics so raw values cannot leak through the
executor's raw-input merge.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant