Critical: state-mutating commands wrongly classified as READ_ONLY (ALLOW_WRITES bypass)#1
Draft
Shimagon wants to merge 1 commit into
Draft
Conversation
…LOW_WRITES bypass)
`READ_ONLY_COMMANDS` contained several commands that mutate Pro Tools session
state. Because `isReadOnlyCommand()` short-circuits the permission check
before `COMMAND_PERMISSION_GROUPS` is consulted, these commands bypass the
`ALLOW_WRITES` gate entirely — even when the operator launches the server in
a read-only configuration (e.g. ALLOW_WRITES=memory) intending to protect a
client's mix.
Affected commands removed from READ_ONLY_COMMANDS and routed through the
permission system:
- SetTrackMuteState / SetTrackSoloState -> TRACK_STATE
(mute/solo flags are persisted in the .ptx session file)
- SetPlaybackMode -> SESSION
(Loop / Dynamic Transport setting persists in session prefs)
- Copy -> CLIPBOARD
(mutates the Pro Tools clipboard, observable side effect)
- SelectMemoryLocation -> MEMORY (no longer also listed
in READ_ONLY, which used to short-circuit the gate; it moves the
playhead, so it should require permission like other memory ops)
- Undo / Redo -> new PermissionGroup.UNDO
(mutate session by definition; isolated into its own group so
operators can permit Undo without unlocking SaveSession)
Other minor cleanups in this file:
- GetMemoryLocations was listed twice in READ_ONLY_COMMANDS (deduped).
- READ_ONLY_COMMANDS regrouped with section comments describing why each
remaining entry is safe (read-only query OR truly ephemeral UI state
that produces no .ptx delta: SelectTracksByName, SetEditTool,
SetTimelineSelection, TogglePlayState, PlayHalfSpeed).
Out of scope for this PR (will be sent separately):
- New PTSL command IDs (ImportAudioToClipList, SpotClipsByID, BounceTrack, ...)
- Streaming RPC / event-subscription tooling (SubscribeToEvents, PollEvents,
UnsubscribeFromEvents)
Reproduction (before fix):
1. Start the MCP server with `ALLOW_WRITES=memory` (intended read-only mix).
2. Call set_track_mute on any track.
3. Pro Tools mute toggles. Expected: request rejected by permission gate.
Reproduction (after fix):
Step 2 returns a permission error (TRACK_STATE not granted).
This was referenced May 7, 2026
Owner
|
hey @Shimagon I made you a collaborator on this repo so feel free to make any changes you want, or just fork it and work in your own repo 😄 |
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.
TL;DR
READ_ONLY_COMMANDS(insrc/grpc/commands.ts) contains several commands that actually mutate Pro Tools state — most notablySetTrackMuteState,SetTrackSoloState,SetPlaybackMode,Copy,Undo,Redo, andSelectMemoryLocation.Because
isReadOnlyCommand()short-circuits the permission check beforeCOMMAND_PERMISSION_GROUPSis consulted, these commands completely bypass theALLOW_WRITESgate. An operator who launches the server withALLOW_WRITES=memory(or any restricted set), intending to protect a client mix, can still have mute/solo flipped, the transport mode changed, or the timeline edited via undo/redo.This is the exact situation that motivated the
ALLOW_WRITESmechanism in the first place, so the bypass is high-impact for any read-only deployment.Reproduction
ALLOW_WRITES=memoryin.mcp.json(read-only-ish, intended to protect tracks).set_track_muteon any track.The same applies to
set_track_solo,Copy,Undo,Redo, andSetPlaybackMode.Fix
Move the offending commands out of
READ_ONLY_COMMANDSand intoCOMMAND_PERMISSION_GROUPS:SetTrackMuteState/SetTrackSoloState→TRACK_STATE(mute/solo are persisted in the.ptx).SetPlaybackMode→SESSION(Loop / Dynamic Transport persists in session prefs).Copy→CLIPBOARD(mutates the PT clipboard, observable side effect).SelectMemoryLocation→ already mapped toMEMORY; the duplicateREAD_ONLYlisting that short-circuited the gate is removed (this command moves the playhead and should respect the gate just like other memory ops).Undo/Redo→ newPermissionGroup.UNDO. They are session-mutating by definition, but I split them into a dedicated group so an operator can permit undo/redo without also unlockingSaveSession(SESSIONis heavier than what undo really needs).GetMemoryLocationswas listed twice inREAD_ONLY_COMMANDS; the duplicate is removed.I also regrouped the surviving entries in
READ_ONLY_COMMANDSwith section comments explaining why each one is safe to leave gate-free (read-only query OR truly ephemeral UI state with no.ptxdelta —SelectTracksByName,SetEditTool,SetTimelineSelection,TogglePlayState,PlayHalfSpeed).Verification
npm run build(tsc) passes locally on this branch.npm run lintis broken onmainindependently of this PR (ESLint v9 needseslint.config.js); not addressed here.READ_ONLYbypass fix is intentionally deferred to keep this PR small and easy to merge.ALLOW_WRITES=memory,set_track_muteis now rejected with a permission error. Would appreciate maintainer-side reproduction as well.Out of scope (will be sent in follow-up PRs if welcome)
ImportAudioToClipList,SpotClipsByID,CreateAudioClips,BounceTrack).subscribe_events/poll_events/unsubscribe_eventsMCP tools.session_id/ file paths from surfaced PTSL errors) for NDA-sensitive deployments.command_id→command).Marked draft so the maintainer can chime in on the
UNDOgroup naming and on whether they want the legacygetCommandPermissionLevelmapping touched (currently UNDO falls intoNON_DESTRUCTIVEvia thedefaultarm, which seems right but is implicit).