{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-changelog/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Synqly Release Notes 2026-09-28","siteUrl":"https://docs.synqly.com","lang":"en-US","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"release-notes---new-features--bug-fixes","__idx":0},"children":["Release Notes - New Features & Bug Fixes"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"-email-security","__idx":1},"children":["📧 Email Security"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["✨ Enhancement"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Mark the Email Security Connector as generally available. The connector's operations and OCSF objects are now GA, and its API reference no longer carries the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["in-development"]}," label."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"-security-awareness","__idx":2},"children":["🎓 Security Awareness"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["✨ Enhancement"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Add ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["training_campaign_uid"]}," to the Synqly OCSF 1.8.0 ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["enrollment"]}," object, identifying the training campaign an enrollment belongs to, and populate it in the KnowBe4 Security Awareness Training (KSAT) provider. The attribute is set only when the enrollment is not already nested inside a training campaign, such as a user's campaign enrollments."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"-vulnerability-management","__idx":3},"children":["🔍 Vulnerability Management"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["✨ Enhancement"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Add a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finding.title"]}," filter (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["eq"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["in"]},") to CrowdStrike Spotlight Query Findings for looking up findings by CVE ID."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Make Qualys ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["query_assets"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["query_findings"]}," do a bounded amount of work per request and return a resumable cursor. Both operations filter client-side, so on a large tenant with a selective filter a single request could previously outrun the request deadline, lose all progress, and retry indefinitely. Each request now fetches one Qualys page and returns whatever survives filtering — possibly fewer than ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["limit"]},", or none — with a cursor that resumes exactly where it stopped. If the deadline is reached mid-page, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["query_findings"]}," returns the partial results and a resumable cursor rather than an error. Cursors issued before this release continue to work."]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["🐛 Bug Fix"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Fix CrowdStrike Spotlight findings using the CVE as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finding.uid"]},", which gave every host with the same CVE the same finding identity. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finding.uid"]}," is now the Spotlight vulnerability-on-host instance ID. ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["This is a breaking change:"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finding.uid"]}," queries that pass a CVE no longer match — filter on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finding.title"]}," instead. A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finding.uid[eq]"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finding.uid[in]"]}," lookup cannot be combined with other filters, returns no cursor, honors ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["limit"]},", and accepts at most 1000 IDs."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Fix several Qualys pagination defects: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["query_findings"]}," restarting at the first page on every resumed request and returning duplicate or skipped rows, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["query_assets"]}," re-requesting the same page indefinitely when a hostname or MAC filter matched nothing on it, and hostname and MAC filters being silently dropped on resumed pages so unfiltered devices were returned."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Reduce Qualys ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["query_findings"]}," memory use on large tenants by decoding detection pages as they stream in rather than buffering each page in full first."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Set Horizon3 NodeZero ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["vulnerabilities[].is_exploit_available"]}," directly from Horizon3's own ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["has_exploit"]}," flag, including ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["false"]},", rather than inferring it from CISA KEV status. Last week's change populated the field only for KEV-listed findings, so most findings with a known exploit — including every ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["H3-*"]}," advisory — carried no value. KEV status remains available independently on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["xattributes.cisa_kev"]},"."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Restrict Horizon3 NodeZero ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finding.supporting_data[].src_url"]}," to the screenshot image link on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["query_findings"]},". It previously fell back to the URL of the page NodeZero captured, often a customer-internal host, which consumers downloading ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src_url"]}," could not tell apart from an image. The captured page URL now appears on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["target_url"]},"."]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"-identity-management","__idx":4},"children":["👤 Identity Management"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["🐛 Bug Fix"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Fix Okta ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["identity_query_audit_log"]}," scheduled operations failing every run once they had caught up. Without an upper bound, Okta treats a System Log query as an endless polling request whose empty pages always carry a next link, so the run was ended as making no progress. Every System Log request now carries an upper bound — the requested end time, or the current time if none was given —, so a caught-up pull ends normally and the next run resumes from its checkpoint. Okta rate limiting (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["429"]},") is now retried after Okta's rate limit reset, rather than reported as a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["502"]}," and resent immediately."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Fix Microsoft Entra ID ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["identity_query_audit_log"]}," failing with a Graph ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]}," when the requested start time is older than Graph's roughly 30-day audit log retention. A start time past retention is now moved up to the retention boundary, including in cursors persisted before this release, and the adjustment is reported as a problem message on the response. A range that ends before retention returns an empty page."]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"-siem--sink","__idx":5},"children":["📊 SIEM & Sink"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["✨ Enhancement"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Answer explicitly ordered Google SecOps ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["query_events"]}," requests whose time window exceeds Google's 1,000,000-row search limit, instead of returning a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]}," asking the caller to narrow the range. Synqly splits the window into time slices that each fit under the limit and walks them in sort order behind a single cursor. A window that cannot be split still returns a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]},", now with a message saying why. Passthrough queries with an explicit order over the limit now return a partial result with a truncation problem rather than failing, and that problem no longer implies the retained rows are the first N in the requested order."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["🐛 Bug Fix"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Default SIEM ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["query_events"]}," to newest first (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["time"]}," descending) when ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["order"]}," is omitted, and treat a bare ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["order=time"]}," as descending. This matches what every SIEM provider that supports ordering already returned; the API reference previously documented ascending as the default. It also fixes a malformed sort being sent to CrowdStrike Falcon LogScale when no direction was given."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Retry transient ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["5xx"]}," errors from Rapid7 during log search pagination instead of failing the whole query. Any status other than ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["202"]}," previously ended a long pagination run immediately, even when a retry would have succeeded."]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"️-core","__idx":6},"children":["⚙️ Core"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["✨ Enhancement"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Validate AWS STS role configuration when it is declared. A malformed role ARN, an external ID, session name, or duration outside what STS accepts, or an endpoint that is not an absolute http(s) URL now fails with an error naming the field, rather than as a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]}," from AWS on the first sync."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Report the duplicated field value when adding an organization member fails because the username is already in use, instead of only the member ID."]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["🐛 Bug Fix"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Report a refused connection to a provider as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["502"]}," instead of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]},", matching how dropped and reset connections are already reported. The caller's request was not at fault. The problem type ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["integration/connectivity/connection-refused"]}," is unchanged. Because ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["5xx"]}," responses are retryable by convention, SDK clients using default retry behavior will now retry these requests."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Fix scheduled operations getting stuck re-delivering the same records every run. Sub-second cursors were sent to providers truncated to whole seconds, so the newest record was returned again on every run and the cursor never advanced. Separately, when a schedule's cursor field never changes — for example, findings ordered by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["finding.first_seen_time"]}," on a stable fleet — each run re-delivered the whole data set. Cursors now keep their full precision, and a schedule that delivers records without advancing its cursor has its next run pushed out to 12 hours rather than repeating a full pull every interval."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Preserve a provider's original HTTP status and error details when errors are passed up through the engine. Several code paths re-created errors from their message text, which discarded the status and could produce misleading error responses."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Report a provider response that breaks off mid-body as a failure instead of an empty successful result, and strip credentials embedded in request URLs — such as API keys passed as query parameters — from error messages."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Reduce engine memory use by retaining provider response bodies only on requests that use a meta function that reads them, such as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["api/response"]},"."]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["🛡️ Security Improvements"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Restrict credential ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PATCH"]}," requests to a defined set of JSON Patch shapes: the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["add"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["remove"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["replace"]}," operations, targeting ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/name"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/fullname"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/expires"]},", or a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/config"]}," field. The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["copy"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["move"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["test"]}," operations, and any other path, are now rejected with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["400"]},". This closes a path through which an authorized user, using a valid organizational token, could expose stored secret material using the patch operation."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Fix authorization gaps in credential retrieval. The credential lookup endpoint and retrieval of a credential by UUID now check that the calling token is authorized for that credential, rather than accepting any valid organization token."]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Fix token scoping so a token restricted to specific accounts cannot mint a token with broader access. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v1/tokens"]},", token refresh, and token reset now require every requested account ID to be within the caller's own scope, and an account-scoped caller can no longer request an environment or label scope. A caller scoped to several accounts can now mint a token for a subset of them, which was previously rejected."]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"-sdk-releases","__idx":7},"children":["📚 SDK Releases"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Latest Versions"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Released Synqly SDK versions: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2.0.67"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2.0.68"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2.0.69"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["2.0.70"]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"-synqly-embedded","__idx":8},"children":["🚢 Synqly Embedded"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["✨ Enhancement"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Update the billing export guide to lead with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["kubectl port-forward"]}," to the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["embedded"]}," service and the organization token logon, since almost all Embedded customers run on Kubernetes via Helm. Username and password logon moves to its own section, and the guide now states the export window of the 12 most recent completed months."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Latest Release: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["v0.1.159"]}]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Service Image Tag"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["embedded-2026.09.28"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Service Image Tag (NO FIPS)"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["embedded-2026.09.28-no-fips"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Release Date"]},": September 28, 2026"]}]}]},"headings":[{"value":"Release Notes - New Features & Bug Fixes","id":"release-notes---new-features--bug-fixes","depth":1},{"value":"📧 Email Security","id":"-email-security","depth":1},{"value":"🎓 Security Awareness","id":"-security-awareness","depth":1},{"value":"🔍 Vulnerability Management","id":"-vulnerability-management","depth":1},{"value":"👤 Identity Management","id":"-identity-management","depth":1},{"value":"📊 SIEM & Sink","id":"-siem--sink","depth":1},{"value":"⚙️ Core","id":"️-core","depth":1},{"value":"📚 SDK Releases","id":"-sdk-releases","depth":1},{"value":"🚢 Synqly Embedded","id":"-synqly-embedded","depth":1}],"frontmatter":{"seo":{"title":"Synqly Release Notes 2026-09-28"},"slug":"changelog/2026-09-28"},"lastModified":"2026-09-28T20:11:13.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/changelog/2026-09-28","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}