fix(session): pin immutable MCP context; ban cross-host profile substitution (Closes #714) #715
Open
jcwalker3
wants to merge 5 commits from
fix/issue-714-session-context-immutability into master
pull from: fix/issue-714-session-context-immutability
merge into: :master
:master
:feat/issue-687-reconciler-branch-delete
:fix/issue-714-session-context-immutability
:fix/issue-709-decision-lock-cross-profile
:fix/issue-695-native-transport-quarantine
:fix/issue-698-report-validator-schema
:fix/issue-702-stale-binding-lease-recovery
:fix/issue-699-structured-auth-mcp-errors
:fix/issue-695-native-quarantine-v2
:fix/issue-693-review-decision-lock-recovery
:fix/issue-691-obsolete-reviewer-lease-cleanup
:fix/issue-683-workflow-guard-hardening
:chore/issue-681-preserve-review-session-wip
:feat/issue-604-anti-stomp-preflight
:feat/issue-606-sentry-observability
:fix/issue-671-block-stable-branch-push
:fix/issue-675-residual-preflight-remediation
:fix/issue-673-remediate-regressions-part2
:fix/issue-673-remediate-regressions
:feat/issue-603-lifecycle-labels
:fix/issue-627-set-issue-labels-pagination
:feat/issue-601-first-class-leases
:feat/issue-612-incident-bridge
:feat/issue-600-controller-allocator-api
:fix/issue-620-head-scoped-review-locks
:feat/issue-613-allocator-db-substrate
:docs/mcp-stable-control-runtime-policy
:feat/issue-609-prepared-review-verdict-resume
:feat/issue-610-live-remote-parity
:feat/issue-503-reviewer-active-worktree
:feat/issue-470-preflight-contract
:feat/issue-440-lock-recovery
:feat/issue-440-branch-recovery
:feat/issue-458-queue-fail-closed-copy
:feat/issue-400-early-duplicate-work-gate
:feat/issue-308-reconcile-inventory-pagination
:feat/issue-308-reconcile-pagination-proof
:docs/issue-261-agent-temp-artifact-cleanup
:feat/issue-262-map-commit-files
:fix/infra-stop-conflict-marker-false-positive
:feat/issue-139-role-aware-task-routing
:feat/issue-188-continuation-selection-wall
:feat/issue-189-continuation-mode-proofs
:feat/issue-232-refresh-wiki-proof-heads
:feat/issue-210-block-workspace-edits
:feat/issue-204-exact-issue-lock
:docs/issue-80-label-taxonomy
:docs/issue-79-safety-boundary-updates
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dc899d23c8 |
fix(session): require config-backed mutation authority and workspace-bound targets (#714)
Close the #714 remediation gap left at
|
||
|
|
943d40270e |
fix(session): bind workspace-verified repository scope at first bind (#714)
The independent review reproduced a real hole: the production first-bind path never pinned repository/org, so the drift checks it feeds were skipped. Root cause ---------- gitea_whoami, gitea_get_runtime_context, gitea_resolve_task_capability and gitea_activate_profile all seeded the session context without repository or org. First-bind-wins then made the mutation gate's later seed a no-op, and assess_session_context only compares repository/org when the bound fields are non-null. Result, reproduced in production order with the real REMOTES: after whoami: repository=None org=None same-host repo=Other-Tools -> NOT BLOCKED same-host org=Other-Org -> NOT BLOCKED cross-host -> blocked (this part always worked) Binding REMOTES defaults would not fix it: REMOTES['prgs'].repo is 'Timesheet' (a default *target*, not an authorization scope, see #530), so pinning it fails closed on every legitimate Gitea-Tools mutation. There was no trusted source for repository scope, so this adds one. Design ------ - New optional profile field `allowed_repositories`: canonical owner/repository slugs. An authorization boundary, never the binding itself. Config-only — no env var can widen or forge it. - The session repository is derived from the verified, workspace-aligned git remote, never from caller-supplied org/repo, and validated against that allowlist. The session binds immutably to exactly one canonical slug; org is derived from it. A profile authorizing several repositories still binds to the single verified one. - Every first-bind entry point now pins the same complete context. Activation rejects an unauthorized/unverifiable workspace. Mutations fail closed when repository/org is unverified, and a tool-level org/repo override that disagrees with the binding is rejected before the write. - A mutation request can no longer establish, complete, or replace the binding (it previously seeded from `org or REMOTES[...]`, i.e. caller-controlled). - Enforcement is opt-in per profile: a profile without the field keeps prior behaviour, so existing static-profile namespaces stay functional. First-bind-wins, immutability, the RLock, profile isolation, host/identity validation and the private pytest-only reset are unchanged. gitea_auth.get_profile() built an explicit whitelist dict and silently dropped `allowed_repositories`; the new integration tests caught that the scope was never enforced through the real path. Tests ----- New tests/test_issue_714_production_first_bind.py drives the real entry points in production order rather than constructing _SessionContext directly. Covers complete first bind via each entry point, same-host repo/org drift, Timesheet and other-repo/other-org rejection, unverified and unauthorized workspaces, caller values unable to establish the binding, concurrent init selecting one repository, and the PR #715 author path staying functional. Mutation-verified: disabling trusted derivation, override validation, the scope check, or the get_profile passthrough each fails these tests (9/8/4/5 failures). No security assertion was weakened, removed, skipped, or rewritten. Focused: 26 passed. Issue #714 total: 46 passed. Prior failing modules: 240 passed. Config/profile/remote modules: 130 passed. Full suite: 2739 passed, 6 skipped, 1 pre-existing warning, 161 subtests in 25.80s. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |
||
|
|
d936da5c87 |
test(session): cover reverse cross-host denial, concurrent init, repo drift (#714)
The formal REQUEST_CHANGES review on PR #715 was resolved by the test
isolation commit (
|
||
|
|
29ffe1407f | fix: isolate immutable session context tests (#714) | ||
|
|
632c568865 |
fix(session): pin immutable MCP context; ban cross-host profile substitution (Closes #714)
Capability resolution and mutation gates no longer auto-switch profiles. Session profile/remote/host/identity are bound after pin or first seed, and drift or cross-host resolution fails closed before writes. Co-Authored-By: Grok <[email protected]> |