Skip to content

Fix external access setting persistence#1730

Merged
datlechin merged 11 commits into
TableProApp:mainfrom
xiispace:fix/external-access-persistence
Jun 20, 2026
Merged

Fix external access setting persistence#1730
datlechin merged 11 commits into
TableProApp:mainfrom
xiispace:fix/external-access-persistence

Conversation

@xiispace

Copy link
Copy Markdown
Contributor

Summary

Fixes an issue where the per-connection External Clients access level could revert to read-only after saving and reopening the connection editor.

Users could select Read & Write, save the connection, and then reopen it to find Read Only selected again. This also caused MCP clients to remain read-only, preventing write operations even when the connection was intended to allow them.

Changes

  • Persist the selected external access level through the advanced connection fields when saving a connection.
  • Restore the persisted external access level when loading the advanced pane.
  • Make MCP authorization resolve the persisted external access level instead of falling back to the model default.
  • Add regression tests for writing and loading the persisted external access value.

Notes

The model default remains .readOnly for backward compatibility when no persisted value exists.

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e736b69cb9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread TablePro/Core/MCP/MCPAuthPolicy.swift Outdated
Comment on lines +260 to +263
private static func resolvedExternalAccess(for connection: DatabaseConnection) -> ExternalAccessLevel {
connection.additionalFields[persistedExternalAccessFieldKey]
.flatMap(ExternalAccessLevel.init(rawValue:))
?? connection.externalAccess

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor persisted blocks in MCP filters

When a user saves a connection as blocked and then reloads it from StoredConnection, the new side-channel value remains only in additionalFields; DatabaseConnection.externalAccess is still the default .readOnly. This helper fixes MCPAuthPolicy, but I checked MCPConnectionBridge.listConnections and MCPTabSnapshotProvider.blockedExternalConnectionIds, and they still read connection.externalAccess directly, so blocked connections can still be listed and their tabs/history exposed to external clients after a restart. Please centralize this resolution or hydrate externalAccess on load so every MCP filter sees the persisted value.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. I centralized the fallback in DatabaseConnection.resolvedExternalAccess and updated all MCP paths that were reading connection.externalAccess directly:

  • MCPAuthPolicy now uses conn.resolvedExternalAccess
  • MCPConnectionBridge.listConnections now filters blocked connections using resolvedExternalAccess
  • MCPTabSnapshotProvider.blockedExternalConnectionIds now uses resolvedExternalAccess
  • The advanced pane loads/saves through the same persisted key

I also verified the MCPConnectionBridge.swift diff is now only the intended one-line filter change.

@xiispace

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA.

github-actions Bot added a commit that referenced this pull request Jun 20, 2026

@datlechin datlechin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you.

@datlechin datlechin merged commit d4349f6 into TableProApp:main Jun 20, 2026
2 of 3 checks passed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d50a2c4793

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +281 to +283
externalAccess = try container.decodeIfPresent(
String.self, forKey: .externalAccess
) ?? ExternalAccessLevel.readOnly.rawValue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Migrate side-channel external access before defaulting

Fresh evidence in this revision is that the decoder only reads the new top-level externalAccess key before defaulting. For users who already saved this setting in the previous additionalFields["externalAccess"] side channel, the file has no top-level key, so loading it sets DatabaseConnection.externalAccess to .readOnly while the side-channel remains ignored by MCPConnectionBridge and MCPTabSnapshotProvider; a Blocked connection becomes visible after restart. Please fall back to the additional-fields value, or hydrate it in toConnection, before defaulting.

Useful? React with 👍 / 👎.

try container.encodeIfPresent(groupId, forKey: .groupId)
try container.encodeIfPresent(sshProfileId, forKey: .sshProfileId)
try container.encode(safeModeLevel, forKey: .safeModeLevel)
try container.encode(externalAccess, forKey: .externalAccess)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Persist external access through CloudKit sync

With iCloud sync enabled, this only writes the new setting to the local StoredConnection JSON. ConnectionStorage.add/update marks dirty connections and SyncCoordinator.uploadPendingChanges serializes them through SyncRecordMapper.toCKRecord, but that mapper still writes fields like safeModeLevel and never writes or reads externalAccess, so a connection set to Blocked or Read & Write on one Mac arrives on another as the default Read Only. Please add the field to the CloudKit record mapping alongside this local persistence.

Useful? React with 👍 / 👎.

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.

2 participants