Skip to content

Security hardening across the macOS and iOS apps#1640

Merged
datlechin merged 13 commits into
mainfrom
security/hardening-pass
Jun 10, 2026
Merged

Security hardening across the macOS and iOS apps#1640
datlechin merged 13 commits into
mainfrom
security/hardening-pass

Conversation

@datlechin

@datlechin datlechin commented Jun 10, 2026

Copy link
Copy Markdown
Member

Security audit of the macOS (TablePro/) and iOS (TableProMobile/) apps, with the fixes it surfaced. One concern per commit.

Fixes

Critical / High

  • Deeplink/share import RCE. Imported connections could carry a preConnectScript that runs /bin/bash -c on connect, with the import sheet never showing it. Import now strips behaviour-bearing keys via one denylist, applied at both the deeplink and shared-blob boundaries. (593b093)
  • MCP per-connection access control was dead code. The policy enforcing each connection's external-access level, per-connection AI policy, and a token's connection scope had no call sites. It now runs on every MCP tool call, with the token scope carried on the principal. (a6b05ed)
  • MCP allowed unauthenticated full access. Once the server is enabled, a loopback caller got read/write/admin with no token. It now requires a paired bearer token by default. CORS origins are unchanged, so Claude/Cursor still work with a token. (a6b05ed)
  • MongoDB filter injection. Values in the regex-family operators (Contains, Not Contains, Starts With, Ends With, Regex) were not JSON-escaped, allowing operator and $where injection. All five now route through one escaping helper. (8314069)
  • iOS connections never validated TLS certificates. Every networked iOS driver encrypted but skipped cert/hostname checks, even in a verify SSL mode. The real sslConfiguration mode now flows into MySQL, PostgreSQL, and Redis. (b6b3f65)

Medium / Low

  • External database links now confirm before connecting, and a password in the link is never written to the Keychain. (acd9446)
  • An installed plugin's code signature is re-checked right before its lazy load, closing a swap window after the first check. (0b3622c)
  • Copied database values on iOS stay on the device and clear from the clipboard after a minute. (73065b6)
  • The iOS home screen widget no longer keeps database host and port on disk. (32f953a)
  • The iOS project sources DEVELOPMENT_TEAM from Secrets.xcconfig instead of a hardcoded ID. (c82b81e)
  • download-libs.sh verifies downloaded static libs against the git-committed checksum baseline, not the archive's own copy. (217d885)
  • The plugin build workflow passes dispatch and matrix tags through env: to block shell script injection. (9eb5897)

Validation

  • macOS app: BUILD SUCCEEDED, 0 errors. Test target compiles; new security suites pass.
  • swiftlint --strict: clean on all changed files.
  • iOS: a full build needs the gitignored Secrets.xcconfig and downloaded Libs/ios/*.xcframework, so it was reviewed statically here and builds on a configured machine.

Deferred on purpose

Each of these would break a real path, so they are documented rather than changed:

  • ATS NSAllowsArbitraryLoads: ClickHouse and Etcd connect to user-typed plain-HTTP hosts over URLSession.
  • disable-library-validation: registry plugins are signed by one team while contributors sign with their own.
  • iOS keychain biometric ACL: synchronizable items cannot carry a device-bound ACL without breaking cross-device sync; the app already gates its UI behind a Face ID lock.

Tests

Also fixed 7 ConnectionSharingTests that fail on main. They asserted camelCase serialization tokens (privateKey, verifyCa, red), but the app's shipped wire format (storage, iCloud sync, exports, deeplinks) uses the enum display rawValues (Private Key, Verify CA, Red). The source is the real format and changing it would break decoding of existing saved connections, so the test expectations were corrected to match. ConnectionSharingTests now passes 54/0.

@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: e57c1cbb14

ℹ️ 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 55 to +56
guard let connectionId else {
return decideTokenTier(token: token, tool: tool)
return .allowed

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 Enforce connection scope on connection-less tools

When a bearer token is limited to specific connection IDs, returning .allowed for any tool call without a connection_id lets that token bypass its connectionAccess: list_connections has no arguments and returns all non-blocked saved connections, and search_query_history can omit connection_id to search across all allowed-by-external-access history. In those contexts a scoped token can enumerate other connections and read their query history despite the new per-connection scope check.

Useful? React with 👍 / 👎.

@datlechin datlechin merged commit 7a10b8e into main Jun 10, 2026
6 checks passed
@datlechin datlechin deleted the security/hardening-pass branch June 10, 2026 05:50

@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: 0bd1278cb6

ℹ️ 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 +238 to 241
if MCPToolName.requiresFullAccess.contains(tool) {
if externalAccess != .readWrite {
return String(localized: "Connection is read only for external clients")
}

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 Require full-access scope for destructive confirmations

For confirm_destructive_operation, this branch now returns success as long as the connection's external access is read/write, but it no longer verifies that the principal came from a fullAccess token. Because readWrite tokens map to .toolsWrite and the confirm tool only requires .toolsWrite, any read/write token can execute DROP/TRUNCATE/ALTER...DROP after supplying the phrase; before this change decideTokenTier required .fullAccess for this tool.

Useful? React with 👍 / 👎.

Comment on lines +71 to 72
if !principal.connectionAccess.allows(connectionId) {
return .denied(reason: String(localized: "Token does not have access to this connection"))

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 Enforce connection scope on MCP resources

This connection allow-list is only checked from ToolsCallHandler; resources/read and resources/list never call MCPAuthPolicy before serving tablepro://connections, .../schema, or .../history via ResourcesReadHandler.fetchPayload. A bearer token limited to one connection but granted resources:read can therefore enumerate other saved/connected connections and read their schema or query history by URI, bypassing the new connectionAccess restriction.

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.

1 participant