Skip to content

feat(auth): org IP allowlisting — member network restrictions#5872

Open
waleedlatif1 wants to merge 9 commits into
stagingfrom
feat/org-ip-allowlist
Open

feat(auth): org IP allowlisting — member network restrictions#5872
waleedlatif1 wants to merge 9 commits into
stagingfrom
feat/org-ip-allowlist

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • New enterprise org setting: IP access (org plane, security group) — an enable toggle + allowlist of IPv4/IPv6 addresses or CIDR ranges (optional # label per entry, up to 200). When enabled, org members can only sign in and use Sim from allowed addresses.
  • Enforced at the getSession chokepoint so every session-authenticated route and layout re-checks the policy (not just hybrid-auth routes) — a denied member resolves as signed out. Also enforced at session establishment (sign-in hook), the API-key auth path (checkHybridAuth), and the realtime Socket.IO handshake.
  • Exempt (intentional, documented): deployed chats, public form shares, webhooks, scheduled executions — outward-facing product surfaces that authenticate by their own means; internal-JWT/webhook-signature paths never hit the check.
  • Save-time lockout guard: enabling a list that excludes the caller's own trusted IP is rejected; enabling with an empty list is rejected (contract superRefine). The settings page shows the caller's current IP.
  • Fail-closed: when a policy is active but no trustworthy client IP can be derived (multi-hop proxy chain without AUTH_TRUSTED_PROXIES), access is denied. DISABLE_ORG_IP_ALLOWLIST is the break-glass (read at call time, no restart needed).
  • Denial audit events (organization.ip_access.denied, throttled per-member) plus the policy-change event, so security teams see who's blocked and from where.
  • Trusted-proxy IP resolution reuses better-auth's hardened resolver (from PR improvement(auth): bump better-auth to 1.6.23 and add trusted-proxy client IP resolution #5857) with AUTH_TRUSTED_PROXIES; shared parseTrustedProxies/buildIpResolutionOptions helpers replace three duplicated parse blocks.
  • Migration 0267: additive organization.network_policy_settings json column. Reuses securityPolicyVersion (from feat(auth): org session policies — lifetime/idle limits, org-wide revocation #5862) so policy changes invalidate cached session cookies org-wide within ~60s.
  • Pure, dependency-free IP/CIDR matcher in @sim/platform-authz/network (43 tests), shared by the app, the realtime server, and the contract validator (client-bundle-safe — no node:net).

Industry validation

Design cross-checked against 12 platforms' live admin docs (GitHub Enterprise, Datadog, Atlassian, Salesforce, Okta, Google Workspace, Notion, Figma, Slack, Workato, Zapier, Retool). Our core (enterprise-gated, IPv4/IPv6/CIDR, continuous enforcement incl. API keys, product-outward exemptions, hard save-time lockout guard, fail-closed + break-glass) matches or exceeds the mature implementations; Zapier and Retool Cloud don't offer this natively at all. The two gaps every mature impl has — per-entry labels and denial audit events — are both implemented here.

Type of Change

  • New feature

Testing

  • 43 matcher tests (IPv4/IPv6/CIDR/labels/malformed/IPv4-mapped/family-scoping), 8 route tests (authz, malformed CIDR, lockout guard, unresolvable-IP fail-closed, disable-skips-guard), audit-mock sync
  • 738 app tests + platform-authz + audit suites green; typecheck, lint, check:api-validation, check:migrations, check:utils all pass
  • /simplify (research + 4 angles) and /cleanup (8 passes) applied — see commits
  • Not yet live-tested — staging verification: enable a policy from an allowed IP → off-network member blocked within ~60s; realtime connect blocked; lockout guard rejects self-exclusion; break-glass env restores access

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@waleedlatif1
waleedlatif1 requested a review from a team as a code owner July 23, 2026 00:19
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 23, 2026 4:13am

Request Review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Touches authentication, session resolution, and API-key paths with fail-closed IP logic—misconfigured proxies or policy mistakes can lock out members org-wide until break-glass or another admin fixes the list.

Overview
Enterprise org IP access lets admins maintain an IPv4/IPv6/CIDR allowlist (stored in organization.network_policy_settings) and restrict where members can use Sim—not deployed chats, webhooks, or similar public surfaces.

Enforcement is wired through getSession / customSession, sign-in session creation, hybrid and v1 API-key auth, socket-token minting, and the realtime handshake via shared @sim/platform-authz/network matching and trusted-proxy IP resolution (AUTH_TRUSTED_PROXIES). Active policies fail closed when client IP cannot be resolved; DISABLE_ORG_IP_ALLOWLIST is break-glass. Policy updates bump securityPolicyVersion, invalidate caches (~60s propagation), and are guarded by a save-time lockout check (caller IP must be in the list).

Admins get a new IP access settings section and GET/PUT /api/organizations/[id]/network-policy (Enterprise + owner/admin), with throttled denial audits and new enterprise docs.

Reviewed by Cursor Bugbot for commit 88c0b15. Configure here.

Comment thread apps/sim/lib/auth/network-policy.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds organization-level IP access restrictions for enterprise members. The main changes are:

  • IPv4, IPv6, CIDR, and labeled allowlist matching.
  • Enforcement for sessions, sign-in, API keys, and realtime connections.
  • Trusted-proxy resolution with lockout protection and break-glass control.
  • Policy settings, persistence, audit events, tests, and documentation.

Confidence Score: 5/5

This looks safe to merge.

  • The realtime resolver no longer falls back to the socket peer.
  • Both realtime denial paths now record throttled audit events.
  • Mapped IPv4 clients and CIDRs are canonicalized consistently across supported forms.
  • No blocking issue remains in the reviewed follow-up changes.

Important Files Changed

Filename Overview
apps/realtime/src/middleware/network-policy.ts Adds trusted-IP resolution, policy caching, allowlist enforcement, and throttled denial auditing for realtime connections.
packages/platform-authz/src/network.ts Adds dependency-free IP and CIDR parsing with numeric canonicalization of mapped IPv4 addresses.
apps/sim/lib/auth/network-policy.ts Centralizes organization policy resolution and enforcement for authenticated application requests.
apps/sim/app/api/organizations/[id]/network-policy/route.ts Adds member-readable policy settings and an enterprise admin update route with a lockout guard.

Reviews (10): Last reviewed commit: "fix(network): resolve IPv6 at /128 for a..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds organization-level IP access restrictions across authenticated product surfaces. The main changes are:

  • IPv4, IPv6, CIDR, and labeled-entry validation and matching.
  • Enforcement for sessions, API keys, and realtime connections.
  • Enterprise settings UI, API contracts, persistence, and cache invalidation.
  • Policy-change and access-denial audit events.
  • Administrator documentation and tests.

Confidence Score: 4/5

The realtime enforcement path and mapped-address matching need fixes before merging.

  • An unresolved forwarded chain can be replaced with the proxy peer address and incorrectly pass the allowlist.
  • Accepted mapped IPv6 CIDRs do not match mapped client addresses.
  • Realtime-only denials are missing from the security audit log.

apps/realtime/src/middleware/network-policy.ts; packages/platform-authz/src/network.ts

Security Review

Realtime IP resolution can fall back from an unresolved forwarded chain to the reverse proxy address, allowing clients when that proxy is covered by the allowlist. Realtime-only denials also bypass the new security audit event.

Important Files Changed

Filename Overview
apps/realtime/src/middleware/network-policy.ts Adds realtime policy enforcement, but unresolved proxy chains can fall back to an allowed proxy address and denials are not audited.
packages/platform-authz/src/network.ts Adds shared IP and CIDR matching, with an inconsistency between accepted mapped IPv6 CIDRs and mapped client-address matching.
apps/sim/lib/auth/network-policy.ts Adds cached app-side policy resolution, trusted-IP checks, entitlement gating, and throttled denial auditing.
apps/sim/app/api/organizations/[id]/network-policy/route.ts Adds member reads and enterprise-admin updates with lockout prevention and policy-version invalidation.
apps/sim/lib/auth/auth.ts Adds policy enforcement during session creation and at the central session-read path.
apps/sim/lib/auth/hybrid.ts Adds network-policy enforcement to successful API-key authentication.
apps/sim/ee/network-policy/components/network-policy-settings.tsx Adds the enterprise allowlist editor with validation and save or discard behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Authenticated request] --> B{Authentication path}
  B -->|Session or API key| C[App policy enforcement]
  B -->|Socket handshake| D[Realtime policy enforcement]
  C --> E[Load member organization policy]
  D --> E
  E --> F[Resolve trusted client IP]
  F --> G{Address matches allowlist?}
  G -->|Yes| H[Allow access]
  G -->|No or unresolved| I[Deny and record audit event]
  F -. unresolved socket chain .-> J[Fallback to proxy peer]
  J -. proxy may be allowed .-> H
Loading

Reviews (1): Last reviewed commit: "polish(network-policy): fix orphaned TSD..." | Re-trigger Greptile

Comment thread apps/realtime/src/middleware/network-policy.ts Outdated
Comment thread packages/platform-authz/src/network.ts Outdated
Comment thread apps/realtime/src/middleware/network-policy.ts
…pped-CIDR matching, realtime fail-open+audit consistency, drop dead exports
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Additional fix in this round from an internal enforcement-coverage audit (not flagged by review, but a real bypass): the public /api/v1/** REST surface authenticated the same personal/workspace API keys via authenticateV1Request but never called enforceOrgNetworkPolicy — a member under an IP restriction could bypass the allowlist entirely by using v1 instead of the legacy path. Now enforced there too, mirroring checkHybridAuth. Also dropped two internal-only exports and bounded the denial-audit throttle map.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/middleware/network-policy.ts Outdated
Comment thread apps/sim/lib/auth/auth.ts Outdated
…(covers client session), match realtime break-glass to isTruthy
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/middleware/network-policy.ts
Comment thread apps/realtime/src/middleware/network-policy.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2ca9a67. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

Comment thread packages/platform-authz/src/network.ts Outdated
…extual forms (compressed/expanded/hex) match uniformly
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/realtime/src/middleware/network-policy.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/auth/network-policy.ts
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit a073439. Configure here.

… to /64); document self-host proxy/break-glass/fail-open
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Final certainty pass (2 adversarial audits on current HEAD + full re-verification). One real bug found and fixed in 88c0b15IPv6 was silently masked to /64 before matching (Better Auth's getIp defaults ipv6Subnet to 64), so exact IPv6 hosts / /128 entries could never match despite the docs/UI advertising them, and the matcher's own unit tests passed only because they bypassed the resolution layer. Fixed by setting ipv6Subnet: 128 in the network-policy buildIpResolutionOptions (isolated from Better Auth's session-IP/rate-limit keying, which keep /64) and resolving the sign-in check the same full-precision way instead of the /64-masked session.ipAddress. Added an integration test through the real getIp (closing the blind spot): a /128 client matches an exact /128 entry, a different host in the same /64 does not, a /64 entry still covers the subnet. Also documented self-host AUTH_TRUSTED_PROXIES (with fail-closed warning + example), NODE_ENV=production, DISABLE_ORG_IP_ALLOWLIST break-glass recovery, and the DB fail-open tradeoff. Everything else audited GO — customSession throw/catch enforcement, error propagation, socket/v1 gating, and mapped-IPv6 math all verified clean against the installed better-auth dist. Multi-org under-enforcement was a non-issue (member.userId has a unique constraint — one org per user).

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 88c0b15. Configure here.

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