feat: per-project origin allowlisting for event ingestion - #2
Merged
Conversation
Registering a project's allowed origins (upsertProject) makes /capture, /batch, /replay, and the capture GraphQL mutation reject requests whose Origin/Referer doesn't match one of them (403). Unregistered projects, or ones with no origins configured, stay open — this doesn't break existing deployments. Mirrors the "site verification" model Plausible/PostHog use: it stops a page on another site from silently posting events under a different project_id in a visitor's browser, though it's not a substitute for a secret since a non-browser client can still set Origin/Referer to whatever it wants. Bumps iris-server's axum dependency from 0.7 to 0.8 to match what async-graphql-axum already pulls in, avoiding two copies of axum in the dependency tree (needed so the capture mutation's resolver can read request headers via GraphQL context data).
Athenox14
force-pushed
the
claude/iris-claude-mentions-uncny6
branch
from
July 13, 2026 19:11
d6c2f0f to
cb22f83
Compare
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
project_idis a free-form string: any site that knows it can post events under it (confirmed: no auth,CorsLayer::very_permissive(), no api key anywhere in the codebase). This adds an opt-in "site verification" layer like Plausible/PostHog use, without touching the existing (fully open) GraphQL admin surface.upsertProject(id, name, allowedOrigins)GraphQL mutation registers the browser origins a project is actually embedded on, and aproject(id)query to read it back./capture,/batch,/replay, and thecaptureGraphQL mutation reject requests (403) whoseOriginheader (falling back toReferer) doesn't match one of them.allowedOrigins, stays fully open — no breaking change for existing deployments.Origin/Refererare just headers, so a non-browser client (curl, a script) that already knows theproject_idand expected origin can still spoof them. It raises the bar against browser-context abuse (a page on site B silently posting events under site A'sproject_idin a visitor's browser), same limitation as the equivalent feature in Plausible/PostHog.Changes
crates/iris-core/src/origin.rs(new):is_origin_allowed, the core matching logic (Origin header, or origin parsed from Referer, against a project's allowlist), with unit tests.crates/iris-core/src/models.rs: newProject { id, name, allowed_origins }.crates/iris-server/src/storage.rs: newprojectstable +upsert_project/get_project/allowed_origins.crates/iris-server/src/capture.rs: origin check on/capture,/batch(checked per uniqueproject_idin the batch),/replay.crates/iris-server/src/schema.rs:projectquery,upsertProjectmutation, origin check on thecapturemutation (readsOrigin/Refererfrom request headers via GraphQL context data).crates/iris-server/src/main.rs: switches/graphqlfrompost_service(GraphQL::new(schema))to a small handler that injects request headers into the GraphQL request's context data, so thecaptureresolver can read them.crates/iris-server/Cargo.toml: bumpsaxumfrom0.7to0.8to match whatasync-graphql-axumalready pulls in transitively — needed to avoid two incompatible copies of axum'sHandler/IntoResponsetraits in the dependency tree once the GraphQL route uses axum extractors.README.md/DOC.md: document the new (optional) origin allowlisting and its limitations.Test plan
cargo build --workspace/cargo test --workspace— clean build, all tests pass (including neworigin::tests)./captureaccepted regardless ofOrigin(back-compat).allowedOrigins: ["https://oxalisheberg.fr"]:/capturereturns403for a mismatchedOrigin,200for the matching one,403whenOrigin/Refererare both absent, and200when only a matchingRefereris present (fallback path).captureGraphQL mutation: same403/success behavior based onOrigin.project(id)query returns the registeredallowedOrigins.mainafter PR feat: add GeoIP session location (country/city/coordinates from IP) #1 (GeoIP) merged — verified both features coexist (origin check +sessionGeoquery both work on the same request/project).