Repository navigation
Conversation
Also treat an empty parseResponse array as an empty page instead of falling back to default parsing, and send buildRequest query params when the hook returns only a query.
The scheduler slept for the smallest interval across all sources and then polled every source, so a 3600s source ran every 60s next to a 60s one. Track a next-due time per source and run only due sources. Each tick now runs on a config snapshot instead of holding the config read lock, so reload no longer waits for an in-flight poll.
Both strategies stored a watermark but never sent it, so every poll restarted from the first page and re-emitted the same events. Send the watermark, incremental_from, or from value, plus query_params, on every page.
The HTTP sink queued lines and dropped a batch after a few retries or on any non-5xx response, while the poller had already saved the cursor, so a short collector outage lost events for good. Retry transient failures until delivery, make every later flush fail once a batch is dropped, and flush the output before each checkpoint. Watermark and incremental_from write errors are now propagated, and the per-page Link next_url write honors on_state_write_error.
Refuse to follow a next URL on another origin, since every request carries the source's auth. Parse Link headers by <uri> segments so commas inside URIs and multi-value rel attributes work.
DPoP proofs used source_id plus the iat second as jti, and the resource-request variant appended Instant::now().elapsed(), which is always about zero, so proofs within one second reused the same jti and servers that track jti reject the replay. A huge expires_in from the token server overflowed Instant and panicked.
…, and backoff math The circuit failure rate divided consecutive failures by every request since startup, so it almost never opened on mixed failures; evaluate it over windows of minimum_requests. A rate limit of 0.5 rps was rounded up to 1 rps. Retry backoff panicked on a negative or NaN multiplier, and jitter could exceed max_backoff_secs.
Truncation cut the serialized line and appended "...", producing invalid JSON, and counted characters instead of bytes, so multibyte events still exceeded the limit. Replace event with the longest prefix of its JSON text that fits and mark meta.truncated.
Dedupe recorded each ID before the event was delivered. When the poll then failed (output flush, write error, timeout) the next tick refetched the same events from the unchanged checkpoint and dedupe dropped them as duplicates. IDs added during a poll are now kept only if it succeeds.
The Postgres state store connected with NoTls, so sslmode=require URLs (managed Postgres) could not connect and prefer sent credentials in plaintext. Connect through rustls with the platform verifier, matching the HTTP client. A dropped connection also failed every later state call until restart; reconnect when the client reports it closed.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes correctness and security issues found in an audit of the poller, output, and state paths. Each commit is one fix with its own test.
Data loss and duplication:
max_pagestimes. ThebuildRequestbody always won over thegetNextPagebody, so each poll re-emitted the first page 100 times andcommitStateadvancedskipby 5000, skipping real events.getNextPagenow wins on later pages. An emptyparseResponsearray was treated as "no hook" and the whole GraphQL response was emitted as one event. A query-onlybuildRequestresult was ignored.page_offsetandoffsetpagination resume from state. Both stored a watermark but never sent it, so every poll re-emitted from page 1. The watermark,incremental_from, orfromvalue andquery_paramsare now sent on every page.incremental_fromwrite errors were discarded, and the per-page Linknext_urlwrite bypassedon_state_write_error.Security:
GET /api/v1/configand/api/v1/sources/:id/configreturned expanded secrets (for exampleoutput.http.headers.Authorizationand the Postgres password instate.url). Header, query param, and body values, plus URL passwords and query values, are now redacted.NoTls, sosslmode=require(managed Postgres) failed andprefersent credentials in plaintext. TLS now goes through rustls with the platform verifier, like the HTTP client. A dropped connection is re-established instead of failing every state call until restart.jtiis random. It wassource_idplus theiatsecond, and the resource-request variant appendedInstant::now().elapsed()(always about zero), so proofs within one second reused the samejti.Scheduling and resilience:
interval_secs. The scheduler slept for the smallest interval and then polled every source, so a 3600s source ran every 60s next to a 60s one. Ticks now run on a config snapshot instead of holding the config read lock.minimum_requests.max_backoff_secs. A hugeexpires_infrom a token server overflowedInstantand panicked.max_line_bytes_behavior: truncateproduced invalid JSON and counted characters rather than bytes. The line now stays valid NDJSON, witheventreplaced by a prefix of its JSON text andmeta.truncated: true.relnow parse.Behavior changes to note on upgrade:
sslmode=prefer. Add the CA to the trust store or setsslmode=disable.New dependencies:
tokio-postgres-rustls0.14,rustls-platform-verifier0.7, andrustls0.23 (the last two were already in the tree through reqwest).cargo auditreports nothing new.Test plan
cargo fmt -- --checkcargo clippy --all-targets --all-features -- -D warningsand without featurescargo doc --all-features --no-depscargo test --all-features(273 tests) andcargo testwith default featurescargo test --features testcontainers --test integration_testcontainersmainand pass here: hook pagination, page/offset resume, HTTP output failure keeps the watermark, cross-origin Link refusal/api/v1/configand the source config endpoint return no secrets withHTTP_SINK_TOKENand a source key setssl=onandsslmode=requireperforms the TLS handshake and rejects the self-signed certificate; withsslmode=disable, polling continues acrossdocker restartof Postgres after one reconnect