Better Stack exposes team members and pending invitations through its
Uptime API. Wire it as an access-review connector so a Better Stack
team can be reviewed in access-review campaigns.
Better Stack has no third-party OAuth app for listing members (its
OAuth is an end-user MCP sign-in), so the connector authenticates with
a Bearer API token plus the team name that scopes the team-members
listing. The driver paginates /api/v2/team-members, maps roles and
invitation records into account records, and the source name is
resolved from the configured team.
This wires the full surface: the provider enum and migration, the
connector settings, the registry registration with the team-name extra
setting, the GraphQL input and resolver marshaling, the frontend field
mapping and connector logo, and cassette-backed driver tests.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add a SigNoz connector so its organization members can be pulled into
access-review campaign snapshots. SigNoz authenticates with a
SIGNOZ-API-KEY admin service-account key and a customer-supplied base
URL (a SigNoz Cloud region/tenant host or a self-hosted instance).
The driver lists users via GET /api/v1/user, which returns the role
(ADMIN/EDITOR/VIEWER) inline so admin detection works in a single call,
and maps the SigNoz user status (active / pending_invite / deleted) to
the account active flag. The name resolver reads the organization
display name from GET /api/v2/orgs/me to title the access source.
Wire the provider through the coredata enum and settings, the
connector-provider registry (driver and name-resolver factories), the
console API-key input schema and validation, the access-review source
label, and the SigNoz brand logo.
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Zendesk is a multi-tenant OAuth connector keyed by the customer
subdomain. The customer enters it at connect time; it rides the signed
state to the callback, is re-validated, and is stored on the connector
settings to build the API host.
List staff (agents and admins) via GET /api/v2/users.json with cursor
pagination, mapping role, active/suspended, and 2FA status; end-users
are excluded. The subdomain is validated as a single DNS label at every
trust boundary to close the SSRF vector, and the data client keeps the
SSRF-protected transport.
Zendesk OAuth across customer subdomains requires a Zendesk-approved
global OAuth client; the connector goes live once those credentials are
supplied via bootstrap.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The driver test cassette was regenerated by running the driver in
record mode against a live Okta org, which validated the field
mapping (id, status, created/activated/lastLogin, profile.*) and the
rel="self" Link header format against the real GET /api/v1/users
response.
The recording is then fully anonymized: the real domain, member
names, emails, user IDs, session cookie, and request-id are replaced
with synthetic values, and a second page with a rel="next" Link
header is added back to keep pagination coverage (the source org had
a single user). TestCassettesUseSyntheticEmails guards the result.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The driver lists GET /api/v1/users (limit=200) on the customer's org
host and follows the RFC 5988 Link header, pinning pagination to the
configured host so a response cannot redirect the crawl off-tenant.
User status maps to the three-valued Active flag (SUSPENDED and
DEPROVISIONED are inactive); ExternalID is the stable Okta user id.
The name resolver reads /api/v1/org and returns ("", nil) on any
non-2xx so a read-only token lacking org-settings read does not loop
the source-name worker.
The org domain is operator-supplied and feeds the URL host, so it is
the one SSRF-sensitive input: NormalizeOktaDomain validates and
strips it on the write path and IsValidOktaDomain re-checks it at
driver construction, on top of the transport's SSRF protection.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
GET /v1/users returns a bare JSON array, not a {"data":[...]} envelope
(total_count is a separate endpoint), so decode directly into a slice.
The previous envelope-first decode errored on the array and never
reached the fallback, so ListAccounts failed against the real API.
Treat deprovisioned users as inactive. Add the missing Clerk
third-party logo and its wiring, and document why the registration
sets no probe URL or name resolver.
Record the driver-test cassette against a live Clerk development
instance, scrubbed of PII and instance identifiers (emails to
example.com, image_url payloads and CF transport headers dropped).
A locked account verifies the inactive path.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Align the Clerk driver tests with the rest of the access-review\ndriver suite by exercising VCR cassette replay instead of a\ncustom transport stub.\n\nThis keeps test coverage consistent with existing providers and\nensures request matching behavior is validated against a checked-in\nfixture.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
The teammate detail endpoint returns a bare object, not a {"result":...}
envelope, so the N+1 scope fetch silently discarded scopes and MFA was
always Unknown for every teammate. Decode the bare object.
Found by re-recording the driver cassette against the live API; the
hand-written cassette had hidden the mismatch. Also from that review:
- Map is_sso/is_partner_sso to AuthMethod (SSO vs Password) instead of
hardcoding Unknown
- Log the swallowed teammate-detail fetch error (wire the driver logger,
matching github.go)
- Add a restricted teammate + a missing-email row to the cassette so the
N+1 decode, the empty-email skip, and the SSO path are load-bearing
- Drop the stale Content-Length response header; document ExternalID
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
- Add SendGrid third-party logo and wire it into ThirdPartyLogo
- Add SendGrid name resolver (account company name, graceful fallback)
- Fix MFA detection: full-access teammates carry both 2fa_exempt and
2fa_required, so report Unknown unless exactly one is present
- Re-record the driver cassette against the live API
- Use a random time suffix for the migration filename
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add a best-effort MFA status check to the SendGrid access-review
fetch pipeline by querying teammate details and inspecting 2FA scopes.
When teammate scopes include 2fa_required or 2fa_exempt, map those
to ENABLED or DISABLED MFA status values; otherwise keep UNKNOWN.
Extend the SendGrid cassette and tests to cover the detail lookups and
MFA scope mapping behavior.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Implement a SendGrid access-review driver that fetches teammates
from the SendGrid API and maps them into AccountRecord values.
Register SendGrid as a connector provider, expose it through the
connector provider enum, and add a migration that extends the
connector_provider type with SENDGRID.
Cover the new driver with a VCR-backed fixture test and helper
tests for role and response-shape handling to keep parsing robust.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Fold POSTHOG_SELF_HOSTED into POSTHOG: one provider now covers Cloud (OAuth + region-pinned API key) and self-hosted (API key + instance URL), since both already share the driver, name resolver, and PostHogConnectorSettings{BaseURL}. The API-key form picks a deployment (Cloud US/EU or self-hosted URL); the resolver requires exactly one of region/instanceUrl.
Drop the POSTHOG_SELF_HOSTED enum value, registration, migration, and logo mapping. Extract the deployment selector into a dedicated PostHogDeploymentField component. Point the driver tests at us.posthog.com instead of the legacy app.posthog.com host.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Replace the PostHog driver unit test's local HTTP server with the same\nrecorder-backed test style used by the other access-review drivers.\n\nAdd a committed PostHog cassette under testdata so replay mode works\nwithout network access while keeping fixture coverage for role, MFA,\nand timestamp mapping expectations.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Align the Metabase driver VCR test with the existing driver fixture
pattern by using a k7 Metabase host in the cassette and default test
instance URL.
This keeps cassette replay deterministic and consistent with the
expected environment naming used by other driver tests.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Refactor the Grafana driver test to use the shared recorder and VCR
helpers used by other access-review drivers.
This aligns the test with the existing cassette workflow and adds a
committed cassette fixture for deterministic replay.
Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
Fetch team members from the Cursor Admin API (GET /teams/members)
and map them to access records. The endpoint is not paginated, so
a single request returns the whole team; removed members are
returned as inactive rather than dropped, and team owners are
flagged as admins.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The driver lists organization members through the Anthropic Admin API
(GET /v1/organizations/users) with cursor pagination, mapping the role
and the RFC 3339 added_at timestamp. The name resolver reads the
organization name from /v1/organizations/me; a non-2xx response (for
example a revoked key) yields no name rather than making the
source-name worker retry forever. Both send the required
anthropic-version header.
Add a VCR test helper that injects the key via x-api-key so the
cassette stays recordable, and strip x-api-key on save. The cassette
holds synthetic members covering the user, developer, and admin roles.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The Sentry and Asana cassettes still had URLs from the old
fmt.Sprintf construction. Update them to match the output of
url.JoinPath / url.Values (no trailing slash, alphabetical
query params, percent-encoded comma).
Signed-off-by: Émile Ré <emile@probo.com>
- Require CLIENT_SECRET for new access-review connectors
- Use Heroku account UUID as ExternalID
- Bump GitHub orgs picker to per_page=100
- Drop Snyk, Ramp, Lever, Deel access-review providers
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add Driver interface and implementations for Google
Workspace, Linear, Slack, 1Password, HubSpot, DocuSign,
Notion, Brex, Tally, Cloudflare, CSV, Probo memberships,
Sentry, OpenAI, Supabase, GitHub, Intercom, and Resend.
Include name resolvers, VCR test infrastructure with
cassettes, and RFC 5988 link header parser.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>