trunk-merge/pr-89909/f5757c3c-8331-4c1b-9208-c1d14326f6d7 - #89965
Closed
trunk-io[bot] wants to merge 60 commits into
Closed
trunk-merge/pr-89909/f5757c3c-8331-4c1b-9208-c1d14326f6d7#89965trunk-io[bot] wants to merge 60 commits into
trunk-io[bot] wants to merge 60 commits into
Conversation
Adds AccessCeiling, an org-scoped per-channel cap (channel, optional resource, max_level), with channel_ceiling()/classify_channel() in the access_control facade. APIScopePermission denies write-scoped actions for MCP-channel requests (classified by the MCP server user agent on token auth) when the org caps the channel at viewer, before the wildcard-scope early return so *-scoped tokens are clamped too. Gated on the organization security settings feature. No settings UI yet; rows are the API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
APIScopePermission now only translates DRF vocabulary and applies the verdict; classification, the entitlement gate, row lookup and the denial copy live in ceiling_denial_for_request. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…n class ChannelCeilingPermission joins the mixin stack (including the dangerously_get_permissions branch, like domain enforcement) instead of living inside APIScopePermission. DRF evaluates permission classes with AND semantics, so the wildcard-scope early return can no longer matter, and session-authenticated channels become cappable later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
AccessCeiling becomes SurfaceAccessLimit, channel becomes surface, and the permission class becomes WithinSurfaceLimits, so the vocabulary reads without a glossary: a surface is how the request arrived (MCP now; personal API keys, share links, impersonation later) and a limit is the max level the org allows through it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…lish One statement per sentence, no semicolons, explicit subjects, hedges kept. Also removes the test class docstring per the no-doc-comments-in-tests house rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…elds Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
resource="*" replaces a nullable resource: a null one column away from max_level="none" read as two different nones. Also drops the nulls_distinct special case from the unique constraint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…de modules The surface_limits module docstring kept its pre-rewrite wording. Function docstrings now start with a verb, and the user-agent comment loses the mint-time jargon. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…mitPermission The repo's stack convention is the Permission suffix, and the exact model-name prefix makes one grep find the storage, the policy and the enforcement together. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…IDOR rules The coverage check requires every org-scoped model in the semgrep taint rules, so lookups without an organization filter get flagged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
The column is not nullable since the wildcard change; the row now takes the "*" default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
… signatures The writes early-return ran before the row lookup, so a disabled surface still served reads, against the model's documented semantics. Adds the regression test and types the permission and policy entry points with concrete request and view classes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
Expose list, create, and update operations for error tracking severity rules through the PostHog MCP server. Generated-By: PostHog Desktop Task-Id: 12089906-88c9-4363-aa68-4705d0f4c5f4
…tion column Replaces the SurfaceAccessLimit table with Organization.mcp_access_read_only, matching the sibling security settings, per review. The enforcement layer keeps its shape: the policy lives in facade/mcp_access.py and MCPAccessPermission enforces it in every viewset stack. Drops the per-resource wildcard and the none level; read-only is the only cap. The hot-table migration is acknowledged and needs a coordinated deploy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
A non-member probing another project with an MCP-marked write got the policy message before the membership check ran, disclosing that organization's security setting. The cap now runs after TeamMemberAccessPermission and domain enforcement, so non-members get the generic denial. Enforcement is unchanged: DRF combines permission classes with AND semantics, so order only decides which denial message wins. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…ilings # Conflicts: # posthog/migrations/max_migration.txt
…wledgment Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…annotation Master added an import-linter contract while this branch was in flight: facade modules must not import DRF. The permission class moves to presentation/ (exposed via tach), and the facade policy types its request parameter with a structural protocol instead of the DRF Request. Also drops a wrong HttpResponse annotation mypy caught. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…rwards A write forwarded through a PostHog connection reached the target with the connection token and no MCP user agent, so a target organization with MCP read-only could still be written to on behalf of an MCP client in the source organization. The forwarder now marks outbound requests with the MCP user agent when the originating request is MCP, and the target classifies them like any other MCP traffic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
Validate that each affected tool remains reachable through the compact domain index instead of requiring one exact domain spelling. Generated-By: PostHog Desktop Task-Id: bc15d8bc-972e-4aea-8ad5-bebb38b8b6b1
…ents One statement per sentence, no compound colon clauses, plain words, and the stale facade path in the policy docstring corrected to presentation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…he ack header Review findings on the draft: the acknowledgment file header got alphabetized by the entry-sorting edit, and permission chains from dangerously_get_permissions can omit APIScopePermission, so an unclassified action there collapsed to writes=False and passed the cap. Unclassified actions now fall back to the HTTP method. Adds a test that the organization viewset (a dangerously-defined chain) is capped, and corrects the RequestLike justification to cite the import-linter contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…ly property HttpRequest.headers is a cached_property, and mypy rejects a read-only descriptor where a protocol declares a settable attribute. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…nd enforcement is_mcp_request and the user-agent marker move to posthog/auth.py, next to the authenticator classes they inspect. The facade keeps only the decision: mcp_access_denial(organization, is_mcp, writes), with no request type and no RequestLike protocol. MCPAccessPermission moves to posthog/permissions.py, next to its base class and the sibling stack permissions, and the tach carve-out for presentation.permissions is reverted. posthog_connection now imports the classifier from posthog.auth, which removes that posthog-to-products import. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…and docstrings The user-agent constant cannot be shared across the two runtimes, so both sides now carry a plain mirror note. The moved class and facade docstrings get one statement per sentence and explicit subjects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…ent org
On root viewsets (/api/environments/{id}/, /api/projects/{id}/) the target is the
detail pk, so get_organization_from_view fell back to the caller current organization,
a UI preference. A member of two organizations could then have the cap evaluated
against the wrong one, failing open when the current org is uncapped. MCPAccessPermission
now uses the same guard as VerifiedDomainEnforcementPermission: defer to
has_object_permission when the target is not URL-derivable, and resolve the org from
the fetched object there.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…ilings # Conflicts: # posthog/migrations/max_migration.txt # posthog/permissions.py
Regenerate the MCP tool-schema snapshots after the generated error tracking API gained behavioral filters. Generated-By: PostHog Desktop Task-Id: 7c8792a2-2d15-4047-9067-ca7fb2c72e8f
Rename Organization.mcp_access_read_only to read_only_mcp_access per review, including the migration module. Cite the OrganizationMemberPermissions delegate-to-object split in the MCP guard comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…ilings # Conflicts: # posthog/api/test/dashboards/__snapshots__/test_dashboard.ambr # posthog/migrations/max_migration.txt
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LEer2y9uGMkADPjMfWG8WK
…ands migrations:down, migrations:up and migrations:sync each carried their own copy of orphan classification, cache recovery, confirmation, rollback, record removal and pending apply. migrations_sync scored 27 on Ruff's cyclomatic complexity check and migrations_down scored 17, against a repository limit of 10. Extract the shared steps into helpers and leave each Click command as a thin sequence of calls. All three commands now score under the limit. Two output strings are unified in the process: the --force warning and the uncached record removal line now read the same in both commands. Add command-level tests for dry run, cancellation, --force, a failed rollback, duplicate schema recovery and an unrecoverable apply error. Generated-By: PostHog Desktop Task-Id: 78eac211-3c34-4783-b4a6-e0fb10dba557
`_classify_orphans` built a frozen `OrphanRollbackPlan` and then appended to its lists, and `_fetch_uncached_from_git` appended recovered migrations into the caller's `cached` list, so `replace()` returned a plan aliasing the mutated list. Both now build their lists first and construct the plan once. Add two cases: a git-recovered orphan gets a real schema rollback rather than a bare record delete, and `--force --dry-run` passes `dry_run=True` down to the record-removal path. Generated-By: PostHog Desktop Task-Id: 78eac211-3c34-4783-b4a6-e0fb10dba557
Generated-By: PostHog Desktop Task-Id: 78eac211-3c34-4783-b4a6-e0fb10dba557
The help said --force removes DB records without a schema rollback. It does that only for uncached migrations. Cached orphans are rolled back for real, with DROP COLUMN and DROP TABLE against the local database, whether or not --force is set. What --force actually does is let the run continue past the abort that uncached migrations would otherwise raise. The warning shown at the confirmation prompt already says this. The option help now agrees with it. Generated-By: PostHog Desktop Task-Id: 78eac211-3c34-4783-b4a6-e0fb10dba557
The hint shown when uncached migrations block a rollback repeated the claim that --force skips the schema rollback. Cached orphans in the same run are still rolled back for real. The hint now says what --force does, which matches the option help and the confirmation warning. Generated-By: PostHog Desktop Task-Id: 78eac211-3c34-4783-b4a6-e0fb10dba557
_fetch_uncached_from_git returned tuple[list[MigrationInfo], list[MigrationInfo]], whose two same-typed halves can be swapped silently at the call site. Return a frozen GitRecoveryResult with newly_cached and still_uncached fields instead. This clears the blocking semgrep devex tuple-return-prefer-dataclass finding. Generated-By: PostHog Desktop Task-Id: 78eac211-3c34-4783-b4a6-e0fb10dba557
Generated-By: PostHog Desktop Task-Id: 2adfe509-5e60-4089-b1d8-0d6ab686c67c
…ilings # Conflicts: # posthog/migrations/max_migration.txt
…g into alex/feat/access-ceilings
Metabase credential validation refuses to follow redirects as an SSRF guard. When the Instance URL responds with a 3xx, validation returned the raw "Metabase host is not allowed", which tells the user nothing they can act on. Return a message that names the redirect and points at the fix: enter the direct instance URL, then reconnect. Generated-By: PostHog Desktop Task-Id: ae9fe31b-7111-4d9b-893c-3d3ffe64ce00
A user who pastes an HTTP method in front of the URL (for example a "POST https://..." copied from API docs) into the custom source base URL failed validation with "is missing a hostname", even though the URL plainly has one. urlparse reads no host once a method prefix is present. Detect a leading HTTP verb and return a message that names the fix: remove the method and enter just the address. Generated-By: PostHog Desktop Task-Id: ae9fe31b-7111-4d9b-893c-3d3ffe64ce00
Author
Contributor
🤖 CI report
|
trunk-io
Bot
deleted the
trunk-merge/pr-89909/f5757c3c-8331-4c1b-9208-c1d14326f6d7
branch
August 27, 2026 10:10
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.
This pull request was created and is being managed by Trunk Merge.
This pull request is based on the master branch at SHA d788f6229b9f0f51530ec0e6c7f5a46158df17f5.
See more details about each PR in the batch here:
When CI completes, this pull request will be closed automatically.
Pull Requests Being Tested
This pull request is testing a batch with the changes from pull requests 89909 and 89911 - batching documentation.
Dependencies
This pull request depends on the changes from pull requests 89686, 87881, 89715, and 86299.