Security & trust
- Why the integration user cannot read records
- The integration user
- The one exception: Custom Metadata Type records
- Creating the permission set
- Verifying the guarantee
- Watching the connection from Salesforce
- Optional metadata categories
Why the integration user cannot read records
MetaSync documents your Salesforce configuration — objects, fields, Flows, permission sets, layouts. It does not read the records stored in that configuration. This section explains the Salesforce mechanism that makes the guarantee hold, so you can check it rather than take it on trust. The integration user gives the exact configuration, and Verifying the guarantee gives the query you can run yourself to confirm it.
The order Salesforce evaluates access in
Salesforce checks record access in a fixed order, and object-level permissions are checked first. Sharing rules, organisation-wide defaults, the role hierarchy, manual shares and territory rules all operate inside the boundary object permissions have already drawn: they decide which of the records a user may see, out of a set that object permissions have already authorised. They cannot grant access to an object the user has no Read permission on.
How a SOQL query against a business object is authorised
1. Object permission (CRUD) Does the user have Read on this object?
NO -> query fails. Nothing below is consulted.
YES -> continue
2. Field-level security Which fields may be returned?
3. Sharing (OWD, role
hierarchy, sharing rules, Which ROWS, of the ones step 1 allowed,
manual + team shares) may be returned?
The integration user’s permission set grants no object permissions at all. Step 1 therefore fails for Account, Contact, Opportunity and every custom object in the org, and steps 2 and 3 are never reached. This is why the guarantee does not depend on how your sharing model is configured: an org with every object set to Public Read/Write returns exactly as much record data to this user as an org set to Private — none.
What would defeat this, and where it stands
Only a handful of Salesforce permissions override object-level checks. All of them are absent from the permission set, and each is worth confirming yourself in Setup:
The permissions that bypass object-level security, and their state on the MetaSync integration user.
| Permission | Effect if granted | State |
|---|---|---|
View All Data (ViewAllData) |
Read access to every record of every object, ignoring object permissions, FLS and sharing entirely. | Not granted |
Modify All Data (ModifyAllData) |
Read, edit and delete every record of every object, ignoring the same checks. | Not granted |
| View All / Modify All on a specific object | The same bypass, scoped to one object. Declared per object under Object Permissions. | Not granted — the permission set has no Object Permissions entries at all |
| Read on a specific object | Passes step 1 for that object, after which sharing decides which rows come back. A user with Read and no sharing still sees zero rows today, but a later sharing change silently opens the data up. | Not granted — see The integration user |
| Apex class access, or Run Flows | Apex and Flows can execute in system context, which bypasses object permissions for whatever they are written to do. This is a different route from the permissions above — it grants access to code, not to an object. | Not granted — the permission set assigns no Apex classes, and Run Flows is off |
Note — What “no record access” does not cover. Two things stay readable however locked down the user is, and an honest reading of this page should name them.
Organization— the org’s name and edition, which MetaSync reads to label the connection. AndUser,UserRoleand group membership — the names, usernames and roles of people in your org, which MetaSync reads to document who holds which permission. Those are employee records, not customer or transactional data, and documenting access is the point of the product. But if your review treats staff directory data as personal data, that is where it lives — not in Accounts, Contacts, Opportunities or any custom object, none of which are reachable at all.
What MetaSync actually queries
The permission model is the control that matters, but the app’s own behaviour corroborates it. Every SOQL query MetaSync issues targets a setup or Tooling object — around forty of them, including EntityDefinition, FieldDefinition, PermissionSet, PermissionSetAssignment, ObjectPermissions, FieldPermissions, SetupEntityAccess, ApexClass, ApexTrigger, Flow, Layout, FlexiPage, ListView, RecordType, ValidationRule, Dashboard, EmailTemplate, NamedCredential, ConnectedApplication, MetadataComponentDependency, User, UserRole, Group, Report, Organization, SetupAuditTrail, UserLicense and InstalledSubscriberPackage — alongside Metadata API and Tooling API reads. No query anywhere in the app selects from Account, Contact, Opportunity, or any custom object’s records. There is one deliberate exception, and because it is the only one, it gets its own section below: Custom Metadata Type records.
The only queries that read a row rather than a definition run against Organization, for the org name and edition shown on the connection card. Nothing in the sync path reads a row of business data, and MetaSync never writes to Salesforce — the only outbound calls that are not reads are the OAuth token requests. The connection is one-directional by construction.
Note — OAuth scope is not the control here. MetaSync requests only the
apiOAuth scope, plusrefresh_token/offline_accesswhen a person signs in. Salesforce has no metadata-only scope —apiis the narrowest scope that lets Metadata and Tooling API calls through, and it inherits whatever the authorising user can see. That is precisely why the permission set carries the guarantee and the scope does not. For the Confluence/Forge side of the picture — every scope the app holds, where data goes, and what happens on uninstall — see Permissions, scopes & data handling.
The integration user
MetaSync acts as a dedicated integration user in your org. That user’s permissions define everything MetaSync can see — so this user, and not the OAuth scope, is where you set the boundary. Create it for MetaSync alone and give it nothing else to do.
There are two ways MetaSync signs in as that user, chosen as Connection method on step 1 of the connect dialog, and the boundary is the same either way. Client Credentials Flow (recommended) sends the app’s key and secret to the org’s My Domain URL and receives a token minted as the Run As user named on the app’s Client Credentials Flow policy: no login page, no person in the loop, and no refresh token. Web Server Flow has someone sign in once on a Salesforce login page as the integration user; MetaSync keeps the refresh token that sign-in produces, and the token belongs to whoever authorised. Whichever you pick, point it at the same locked-down user described below.
The OAuth app: create an External Client App
MetaSync has no app of its own in your org. It signs in through an OAuth app you create and own, which means you set its policies and you can revoke it at any time without involving us. Salesforce’s current app type is the External Client App. New Connected Apps can no longer be created in most orgs — Salesforce disables that by default from Spring ‘26 and the button is greyed out — so create an External Client App. An existing Connected App works identically if you already have one to reuse. The click-by-click steps are in Onboarding: from install to first sync; below is only what a security review will ask about.
The settings on that app that carry security weight, and what MetaSync needs each to be. The Applies to column says which connection method each one governs.
| Setting | Value | Applies to | Why |
|---|---|---|---|
| OAuth scopes | api, plus refresh_token / offline_access for the sign-in method — nothing else |
Both | Salesforce has no metadata-only scope, so api is the narrowest that permits Metadata and Tooling API calls. What it can reach is bounded by the integration user, not by this. |
| Enable Client Credentials Flow | On | Client Credentials Flow | Under the app’s OAuth settings. It is what lets MetaSync exchange the app’s key and secret for an access token, with no login page. Treat the pair as the credential it is: anyone holding the key and secret can mint tokens while the flow is enabled, at the Run As user’s permission level. |
| Policies → Client Credentials Flow → Run As | The integration user | Client Credentials Flow | Every token this flow mints acts as this user, so the permission set on it is the entire boundary. An API-only Integration User licence works here. Note that the Permitted Users policy does not apply to the Run As user — this setting is what bounds the flow instead. |
| Require PKCE | On | Web Server Flow | MetaSync uses the authorization-code flow with PKCE (S256) and a random state value. |
| Require secret for Web Server Flow | On | Web Server Flow | MetaSync holds the consumer secret and sends it. |
| Require secret for Refresh Token Flow | On | Web Server Flow | Same — the refresh exchange is authenticated. |
| Refresh Token Rotation | On or off | Web Server Flow | Either setting works. MetaSync stores each rotated token as Salesforce issues it and re-reads the newest stored token before every refresh, so a rotating org and a non-rotating one both keep working. If your security standard requires rotation, turn it on. |
| Permitted Users | Admin approved users are pre-authorized | Web Server Flow | With All users may self-authorize, anyone in the org holding the Consumer Key and Secret can authorise this app as themselves and receive an api-scoped token at their own permission level — at which point the integration user has stopped being the boundary. Pre-authorize the integration user’s profile or permission set so only that user can mint a token for this app. Connected Apps OAuth Usage shows who has actually authorised it. |
| IP Relaxation | Relax, if you enforce login IP ranges | Both | MetaSync’s calls originate from Atlassian’s cloud, whose addresses vary. See also the identity-verification note below. |
Note — Client Credentials Flow uses your My Domain URL. The token request for the Client Credentials Flow goes to the org’s own address —
https://<name>.my.salesforce.com, copied from Setup → My Domain. Salesforce does not acceptlogin.salesforce.comortest.salesforce.comfor this flow, and MetaSync refuses them on step 1 with the reason under the field. The sign-in method accepts all three.
Note — Revocation is yours. Because the app lives in your org, you end the connection there, and MetaSync cannot re-establish access on its own. Client Credentials Flow: rotate the Consumer Secret, untick Enable Client Credentials Flow, or deactivate the Run As user. Rotating the secret or deactivating the user cuts access at MetaSync’s very next token request. Unticking the flow stops new tokens but does not revoke tokens Salesforce has already issued — MetaSync’s cached access token lives at most 90 minutes, which is the outside window before access stops. Web Server Flow: remove the app’s authorisation for the integration user, deactivate the user, or delete the app; the next sync fails to authenticate and stops. Uninstalling the Confluence app purges the stored credentials from Forge storage either way; see Permissions, scopes & data handling.
Licence and profile
Two pairings work. The first is preferred: Salesforce’s Integration User licence is API-only, so the user cannot log into the UI at all, and it does not consume a full Salesforce licence.
Licence and base profile must belong to the same licence family — the profile list in Setup is filtered by the licence you pick.
| User licence | Base profile | UI login | Notes |
|---|---|---|---|
| Salesforce Integration | Minimum Access - API Only Integrations |
Not possible — API-only by licence | Preferred. You must also assign the Salesforce API Integration permission set licence to the user — see the warning below. It is not applied automatically. |
| Salesforce | Minimum Access - Salesforce |
Possible unless you restrict it separately | Use when no Integration User licence is available. Consumes a full Salesforce licence. |
Warning — Assign the Salesforce API Integration permission set licence first. On the Integration pairing this trips people up. The Salesforce API Integration permission set licence is not applied automatically when you create the user. Until you assign it — Setup → Users → the user → Permission Set License Assignments → Edit — Salesforce refuses to assign the MetaSync permission set at all, with “The user license doesn’t allow the permission: ViewSetup”. Assign the permission set licence first, then the permission set.
Tip — The API-only user can complete the sign-in. On the Web Server Flow method you sign in as the integration user on a Salesforce login page, and an API-only user being unable to reach the Salesforce UI does not prevent that. This was tested end to end on the exact pairing above: the login page accepts the API-only user’s credentials and Salesforce issues a session for the authorization step. Being API-only stops the user landing in the Salesforce application; it does not stop them authorising an app. On Client Credentials Flow the question does not arise — nobody signs in, and an API-only user is the natural Run As choice.
Note — Expect an identity-verification prompt on the first connection. This one is specific to Web Server Flow; Client Credentials Flow never reaches a login page and never triggers it. Because that sign-in comes from Atlassian’s cloud rather than your office, Salesforce treats it as a new device and shows Verify Your Identity, sending a code to the integration user’s email address. Two consequences worth planning for: set that user’s email to a mailbox you can actually read — a shared or distribution address is fine and often better than a personal one — and consider adding Atlassian’s ranges to Trusted IP Ranges, or relaxing IP restrictions on the connected app, so the prompt does not recur. This is standard Salesforce login protection, not something specific to this licence.
Warning — The two profiles are not interchangeable.
Minimum Access - Salesforcebelongs to the Salesforce licence, andMinimum Access - API Only Integrationsbelongs to the Salesforce Integration licence. Salesforce will not offer you the wrong one — if a profile you expect is missing from the picker, check which licence the user is on. Either way the record-access guarantee comes from the permission set below, not from the profile.
The permission set: baseline
Create one dedicated permission set for MetaSync and assign it to the integration user. Leave the base profile untouched. Two user permissions are required:
The baseline permission set. Metadata API names are given so you can diff a deployed permission set against this table.
| Permission | Metadata name | Why MetaSync needs it |
|---|---|---|
| API Enabled | ApiEnabled |
Required for every API call. Without it the connection cannot be established at all. |
| View Setup and Configuration | ViewSetup |
Reads Setup metadata, org limits and the Setup Audit Trail. The Org Licenses & Storage report and the Setup Audit Trail timeline are empty without it. |
| View Roles and Role Hierarchy | ViewRoles |
Not optional — Salesforce requires it alongside ViewSetup and rejects the permission set without it. It also earns its place: the role hierarchy is part of what MetaSync documents. |
None of the three grants access to a single business record. ViewSetup and ViewRoles are read permissions over configuration; neither is ViewAllData, and neither confers anything over record data.
Warning — Absence is the guarantee — not a read-only setting. The permission set must have zero entries under Object Settings (object permissions) and zero field-level security entries. This is the load-bearing detail. There is no “read-only” configuration that produces the guarantee: granting Read on Account passes Salesforce’s object-level check, after which only your sharing model stands between MetaSync and your records — and a sharing change made months later would open them up with no signal. An empty object-permissions list cannot be widened by a sharing change. If a deployed permission set ever shows
objectPermissionsorfieldPermissionsentries, the guarantee described on this page no longer applies to it.
Optional additions
The baseline connects and documents a large part of the org. Three further user permissions extend coverage. None of them grants record access. View All Profiles stops being a choice once your org reaches Winter ‘27 — see the table. The other two stay a policy call you can make one at a time:
Coverage user permissions. All three are configuration permissions; none grants access to record data.
| Permission | Metadata name | What it unlocks | Cost of leaving it off |
|---|---|---|---|
| View All Profiles | ViewAllProfiles |
Lets the access-review and governance reports read every profile and permission set in the org, not only those the integration user holds. Effectively required from Winter ‘27. | Access-review output covers a subset of the org and understates who has what. From Winter ‘27, Salesforce’s Enable Profile Filtering update hides every other user’s profile from the integration user: MetaSync then documents a single profile, prints Not measured in the Profile column, and withholds profile removal detection so the rest are not falsely reported as deleted — which degrades the run until the permission is granted. |
| Modify Metadata Through Metadata API Functions | ModifyMetadata |
Salesforce requires this to read retrieve-based metadata types through the Metadata API — page layouts, Lightning pages, sharing rules, approval processes, workflow rules. | Those types are disclosed on their index page as not captured — no access instead of being documented. |
| Author Apex | AuthorApex |
Salesforce gates reading Apex bodies behind this permission, so it is required to document Apex class and trigger source. Salesforce requires ModifyMetadata alongside it — enabling Author Apex on its own is rejected. |
Apex classes and triggers are listed but their source is not documented. |
Warning — Modify Metadata Through Metadata API Functions is write-capable. Despite being the permission Salesforce requires for reading retrieve-based types,
ModifyMetadataalso permits metadata deployments. MetaSync never writes to Salesforce — the connection is one-directional and the app issues no deploy, insert, update or delete call of any kind — but the permission itself is broader than MetaSync’s use of it. If your review cannot accept a write-capable metadata permission on the integration user, leave it off and accept that layouts and the other retrieve-based types will be reported as not captured. The record-access guarantee is unaffected either way:ModifyMetadataconfers nothing over record data.
Permissions that stay unchecked
Confirm each of these is off. They are listed because a reviewer will look for them specifically, not because MetaSync has any use for them:
Confirm these are unchecked on both the permission set and the base profile.
| Permission | Metadata name | Why it matters |
|---|---|---|
| Modify All Data | ModifyAllData |
Would grant read, edit and delete on every record in the org, bypassing object permissions, FLS and sharing. |
| View All Data | ViewAllData |
Would grant read on every record in the org, bypassing the same checks. |
| Author Apex | AuthorApex |
Grants no record access, but allows writing and compiling Apex. Leave it off unless you have deliberately opted into Apex source documentation above. |
| Manage Users | ManageUsers |
Grants no record access, but allows creating users and changing permissions — including granting the two permissions above. MetaSync has no use for it. |
The one exception: Custom Metadata Type records
Everything else on this page describes an absolute: no records, at all. There is exactly one place where MetaSync reads rows rather than definitions, and a reviewer will find it, so it is stated here rather than left to be discovered. MetaSync reads Custom Metadata Type records and publishes their values into your Confluence space.
Why this is not the same as reading business data
Custom Metadata Types (__mdt) are Salesforce’s mechanism for deployable configuration. They move between orgs in change sets and Metadata API deployments exactly like layouts and validation rules, which ordinary records never do. Admins use them to hold business rules — tax rates, approval thresholds, routing tables, feature switches — and documenting those rules is a large part of documenting the org. That is the reasoning behind the exception. It is a judgement, not a technicality, and you are entitled to disagree with it.
What you should check before your first sync
The risk is not the mechanism, it is what your org happens to keep in these records. Custom Metadata is a common hiding place for API keys, integration endpoints, tokens and other secrets, because it is convenient and deployable. If yours holds anything of that kind, those values will be written onto Confluence pages, where the audience is usually wider than the Salesforce org. Open Setup → Custom Metadata Types, look at the records of each one, and decide before you sync — not after.
Warning — The verification checks cannot catch this one. Custom Metadata Types are not governed by object permissions. Confirmed against a live org:
__mdttypes have zeroObjectPermissionsrows, where an ordinary object has one per profile or permission set that can read it. Three consequences, and they matter. Check 2 returns zero and is still correct — it simply cannot see this. Removing object permissions does not stop it. And there is no permission you can untick on the integration user to prevent it, because there was never one to grant.
How to switch it off
The control is the metadata scope, not the permission set. Custom Metadata Type is its own toggle in the Schema group on the Metadata types tab — switch it off and MetaSync stops reading and publishing these records entirely, while continuing to document everything else. That is the complete mitigation, and it costs you only the documentation of those types. See Metadata Types & scope.
If you leave it on, what lands on the page is bounded: at most 100 types, 50 records per type, 15 custom columns per record, and each cell truncated at 100 characters. Bounded is not redacted — a key shorter than 100 characters arrives complete. Treat the caps as a limit on volume, not as a safety measure.
Note — Custom Settings are not affected. The obvious neighbouring worry is List and Hierarchy Custom Settings, which genuinely do hold data rows. MetaSync reads only their schema — name, type, fields, and whether they are protected. No custom setting values are read or published.
Creating the permission set
There are two ways to create the permission set, and they produce exactly the same thing. Option A is clicks in Setup and needs no tooling at all. Option B deploys a file, and suits orgs that keep their Salesforce metadata in source control. Pick whichever matches how your org works — the guarantee comes from what the permission set does not contain, not from how it was created, and Verifying the guarantee confirms it either way.
Option A — build it in Setup (no tooling required)
Five steps, all clicks. Salesforce moves Setup labels between releases — if a screen is not where this says, search Setup for the setting name; what matters is the end state, not the route to it.
- Setup → Quick Find → “Permission Sets” → New. Set Label to
MetaSync Integration; the API Name fills itself in. Leave License as None — that keeps the permission set assignable to a user on any licence. Save. - Open System Permissions → Edit. Tick API Enabled, View Setup and Configuration, and View Roles and Role Hierarchy. Tick nothing else. Save, and confirm the change when Salesforce asks.
- If Salesforce refuses the save, it will name a dependency — View Setup and Configuration requires View Roles and Role Hierarchy, which is why the third box is on the list. Tick what it names and save again; both are read permissions over configuration and neither grants record access.
- Coverage permissions. Tick View All Profiles — from Winter ‘27 Salesforce hides other users’ profiles from the integration user without it, and MetaSync can then document only one profile. Add Modify Metadata Through Metadata API Functions and Author Apex here too if you decided to in The integration user.
- Assign it to the integration user. From the permission set, click Manage Assignments → Add Assignment, tick the integration user, then Assign.
- Leave Object Settings alone. Do not open it, and do not add an object. An empty Object Settings list is the whole guarantee — see the note below.
Warning — The step to get right is the one you don’t do. Everything that could weaken this permission set lives under Object Settings. If you never add an object there, there is nothing to get wrong — and adding one with only Read ticked is enough to break the guarantee, because Read passes Salesforce’s object-level check and leaves your sharing model as the only thing holding records back. If you are ever unsure whether someone changed it, the Setup check in Verifying the guarantee takes about a minute and needs no tooling.
Option B — deploy the file
If your org keeps Salesforce metadata in source control, deploy it instead of clicking it. The file below is a complete Metadata API permission set. It grants the two baseline user permissions from The integration user and nothing else — no objectPermissions, no fieldPermissions, no classAccesses, no recordTypeVisibilities.
force-app/main/default/permissionsets/MetaSync_Integration.permissionset-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<label>MetaSync Integration</label>
<description>Read-only metadata access for MetaSync for Confluence. Grants no object
permissions and no field-level security, so the assigned user cannot read any
business record regardless of sharing rules or org-wide defaults.</description>
<hasActivationRequired>false</hasActivationRequired>
<userPermissions>
<enabled>true</enabled>
<name>ApiEnabled</name>
</userPermissions>
<userPermissions>
<enabled>true</enabled>
<name>ViewSetup</name>
</userPermissions>
<!-- Salesforce requires ViewRoles alongside ViewSetup. Without it the
permission set is rejected: "Permission ViewSetup depends on
permission(s): ViewRoles". It grants read on the role hierarchy
only, which is configuration, not records. -->
<userPermissions>
<enabled>true</enabled>
<name>ViewRoles</name>
</userPermissions>
</PermissionSet>
There is deliberately no <license> element. A permission set with no licence can be assigned to a user on any user licence, so the same file works for both pairings in The integration user.
- Save the file into your project at
force-app/main/default/permissionsets/MetaSync_Integration.permissionset-meta.xml. - Deploy it with
sf project deploy start --source-dir force-app/main/default/permissionsets/MetaSync_Integration.permissionset-meta.xml --target-org <your-org>. To deploy without a project, place the file in apermissionsets/folder alongside apackage.xmllistingMetaSync_Integrationunder thePermissionSettype, and deploy that directory. - Assign it to the integration user —
sf org assign permset --name MetaSync_Integration --on-behalf-of <integration-user-username> --target-org <your-org>. The--on-behalf-offlag matters: without it the CLI assigns the permission set to you, the authenticated user, and the integration user silently never receivesApiEnabled. Or do it in Setup under Permission Sets → MetaSync Integration → Manage Assignments. - Confirm the result in Setup: open the permission set and check that Object Settings lists no object with any permission, and that System Permissions shows only API Enabled, View Setup and Configuration and View Roles and Role Hierarchy, plus any optional permission you chose.
- Verify it behaves as documented using the checks in Verifying the guarantee.
Adding the optional permissions to the file
To adopt any of the optional permissions from The integration user, add the corresponding block inside <PermissionSet>. Add only the ones you have decided on — each is independent of the others. (In Option A these are ticked in the Setup UI instead, and there is nothing to edit.)
Optional additions — include only the ones you want
<!-- Access-review reports read every profile and permission set -->
<userPermissions>
<enabled>true</enabled>
<name>ViewAllProfiles</name>
</userPermissions>
<!-- Required to READ layouts and other retrieve-based Metadata API types.
Also permits metadata deployments; MetaSync never writes to Salesforce. -->
<userPermissions>
<enabled>true</enabled>
<name>ModifyMetadata</name>
</userPermissions>
<!-- Only if you want Apex class and trigger source documented.
Requires ModifyMetadata above: enabling AuthorApex alone is rejected
with "Permission AuthorApex depends on permission(s): ModifyMetadata". -->
<userPermissions>
<enabled>true</enabled>
<name>AuthorApex</name>
</userPermissions>
Note — A deployed file is also a record of the guarantee. One side benefit of Option B: a later change that adds an
<objectPermissions>or<fieldPermissions>block, orViewAllDataorModifyAllData, appears in the file’s diff. If you took Option A, the Setup check in Verifying the guarantee gives you the same assurance without a repository.
Verifying the guarantee
A configuration you cannot test is an assertion. There are three checks here, in increasing order of both strength and effort. Checks 1 and 2 can be run by any Salesforce administrator — neither needs the command line, and neither needs you to log in as the integration user. Check 3 is the strongest evidence and the most awkward to obtain. Run 1 and 2; treat 3 as confirmation when you can get it.
Check 1 — read it back in Setup (no tooling)
This confirms the permission set says what this page describes. It takes about a minute and needs nothing but Setup access.
- Setup → Quick Find → “Permission Sets” → MetaSync Integration → Object Settings. Every object should show no permissions — no Read, no View All, nothing. This is the single most important thing on the page: an empty list here is the guarantee.
- Back out, then open System Permissions. Only API Enabled, View Setup and Configuration and View Roles and Role Hierarchy should be ticked, plus any optional permission you deliberately chose in The integration user. The third is a Salesforce-imposed dependency of the second, not an extra grant.
- On that same System Permissions list, confirm five are unticked: Modify All Data, View All Data, Manage Users, Run Flows, and Author Apex unless you opted into it.
- Open Apex Class Access on the permission set. It should be empty. Apex runs in system context by default, so granting access to a class is a route to data that object permissions do not govern and that Check 2 cannot see.
- Setup → Quick Find → “Users” → the integration user. Check the User License and Profile match one of the two pairings in The integration user, and that the profile is a minimum-access one rather than System Administrator.
- On the same user page, open Permission Set Assignments. Confirm this is the only permission set assigned. A second permission set — or a permission set group — can grant object access that the MetaSync one does not, and that would not show up anywhere in step 1.
- If your org has it, use the per-user access summary instead of steps 1–5. Recent Salesforce releases add a View Summary button on the user record that aggregates object and field access across the profile and every assigned permission set in one screen. Where it exists it is the better check, because it answers for the user rather than for one permission set. If you cannot find it, search Setup for summary on the user page; if it is not in your release, steps 1–5 and Check 2 below cover the same ground.
Warning — Read-only on an object is not a safe middle ground. If step 1 shows any object with Read ticked, the guarantee on this page does not hold for that object, even if querying it returns nothing today. Read passes Salesforce’s object-level check, which leaves your sharing model as the only thing withholding the records — and a sharing rule or org-wide default changed months later would expose them with no warning and no signal in MetaSync. Untick it.
Check 2 — ask Salesforce what the user can reach, across every object
Check 1 reads back one permission set. This asks a different and much stronger question: across the profile, every assigned permission set and every permission set group, which objects can this user read? It answers for every object in the org at once instead of sampling one, and it names whatever it finds.
Run this in your own administrator session — click the gear icon → Developer Console → Query Editor tab → paste → Execute. Nothing here logs in as the integration user, which is why this check works whichever licence you gave it. Substitute the integration user’s username.
Every object this user can read. Expected: no rows.
SELECT SobjectType, PermissionsRead, PermissionsViewAllRecords,
Parent.Label, Parent.IsOwnedByProfile
FROM ObjectPermissions
WHERE PermissionsRead = true
AND ParentId IN (
SELECT PermissionSetId FROM PermissionSetAssignment
WHERE Assignee.Username = 'integration-user@example.com')
On the Salesforce Integration licence, this returns nothing. That is the result to expect from the configuration this page recommends, and each row returned is an object the integration user can read. Parent.Label tells you which permission set or profile granted it, so you know what to correct.
Note — On the full Salesforce licence, expect a small baseline.
Minimum Access - Salesforcecarries a couple of object grants that Salesforce bakes into the standard profile and an administrator cannot remove — in the org this was written against,QuickText. That is not a hole: what matters is that no object holding business data appears. Read the rows rather than counting them, and satisfy yourself that nothing in the list is an object your organisation keeps records in. The Integration-licence path avoids the question entirely by returning nothing at all.
Tip — Why this covers more than Check 1. A user’s access is the union of their profile and every permission set assigned to them, and Salesforce models the profile itself as a permission set (
IsOwnedByProfile). Because this query resolves assignments rather than one named permission set, a second permission set, an inherited permission set group, or a permissive profile cannot hide from it. That is the failure Check 1 step 5 asks you to look for by eye — here Salesforce answers it directly. View All Data shows up here too, for the reason in the next note, though Check 1 remains where you confirm it directly.
Tip — View All Data cannot be switched on quietly. Salesforce will not let you enable View All Data as a single tick. It refuses unless you also grant a long list of dependencies — including explicit Read on essentially every object in the org. Modify All Data in turn depends on View All Data plus around thirty more, and Manage Users on thirteen administrative permissions. Two things follow. Nobody grants these by accident. And because View All Data drags explicit object Read along with it, the rows it produces appear in the query above — so this check surfaces the org-wide bypass as well as ordinary object grants.
Warning — Do not run the same query against FieldPermissions. It is tempting, and it produces alarming numbers that mean nothing. Every profile accumulates field-level security rows as a by-product of field creation and managed-package installs — the recommended
Minimum Access - API Only Integrationsprofile carried 350 of them in the org this page was written against, while granting read on zero objects. Field-level security only ever narrows access to an object you can already read, so those rows are inert. Object permissions are the check; FLS is not.
Check 3 — prove the runtime behaviour (optional)
Checks 1 and 2 examine the permission model. This one tests what actually happens, end to end, which is the evidence a security reviewer is most likely to ask for. It is optional because it is the only check that must authenticate as the integration user, and that is genuinely awkward on the recommended licence.
Run SELECT Id FROM Account LIMIT 1. A correctly configured integration user cannot complete it. Then repeat it against the object that would hurt most if it leaked — usually a custom object holding customer or financial data, not Account. One object passing proves only that one object; Check 2 is what covers the rest.
Which route is open to you depends on the licence, and this is the one place where the more secure licence is the less convenient one:
Ways to run the query. The Salesforce Integration licence blocks UI login by design, so Developer Console is not available to that user.
| Route | Available when | How |
|---|---|---|
| Developer Console | The integration user is on the Salesforce licence, so it can log into the UI. | Log in as the integration user → gear icon → Developer Console → Query Editor tab → paste the query → Execute. |
| An API client (for example Workbench) | Either licence. An API-only user cannot log into Salesforce’s UI but can still authorise an API client through OAuth. | Sign in as the integration user, then run the query from the tool’s SOQL screen. Check your own policy first — this means authorising a third-party tool against your org. |
| Salesforce CLI | You already have the CLI set up. | sf data query --query "SELECT Id FROM Account LIMIT 1" --target-org <integration-user-connection> |
How to read the result. Two outcomes are a pass; two are a failure that needs the permission set corrected.
| Result | Meaning | Verdict |
|---|---|---|
Error INVALID_TYPE — sObject type ‘Account’ is not supported |
The most likely result. With no Read on the object, Salesforce does not resolve the name for this user at all — the object may as well not exist. Conclusive. | Pass |
Error INSUFFICIENT_ACCESS_OR_READONLY |
Also a refusal. This code is more commonly seen on record-level operations than on a plain query, so do not treat its absence as a problem — INVALID_TYPE above is the usual answer. |
Pass |
| Success, 0 rows, no error | The query was authorised. The user has Read on Account and sharing alone returned nothing — a sharing or OWD change would expose the records. This is the failure mode that looks like success. | Fail |
| Success, rows returned | The user can read business records. | Fail |
Warning — A silent, empty success is a failure. The result to watch for is the third row. An empty result set reads like “no access” and is not — it means object-level access was granted and the sharing model happened to return nothing today. Check for the absence of an error, not for the absence of rows. Repeat the query against a custom object that holds data in your org if you want a second data point.
Repeating these checks after any change to the integration user, its profile, or its permission set assignments is the practical way to keep the guarantee current — permission drift is the realistic risk here, not the initial setup.
Note — If Check 3 is not open to you. Checks 1 and 2 together are a legitimate verification, and both are available to any administrator with no tooling beyond Setup. Between them they confirm the configuration that produces the guarantee and that it holds across every object and field for this user, including access arriving from a profile or another permission set. Run those, note the date, and treat Check 3 as confirmation to obtain whenever someone with an API client is available.
What MetaSync does not check for you
MetaSync does not verify the integration user’s permissions. When you connect an org it confirms the connection works — it signs in and reads the org’s name and edition for the connection card — and that is the whole of it. It never tests whether that user can reach records.
What that means in practice: if someone widens the permission set after setup, MetaSync will carry on syncing and will not warn you. Nothing on this page is enforced by the app. The guarantee is produced by your Salesforce configuration, and confirmed by you.
So treat the checks above as something to repeat, not something to do once. Re-run Check 1 and Check 2 after any change to the integration user, its profile, or its permission set assignments — and alongside whatever access review your organisation already runs. Together they take a couple of minutes and need nothing beyond Setup. For watching what the connection actually does between checks — tokens, logins and API calls — see Watching the connection from Salesforce.
Note — Planned, not built. The intention is for MetaSync to run Check 2 against itself on every sync and stop with a warning if it ever returns anything other than zero. That is not built yet, and this page will keep saying so until it is.
Watching the connection from Salesforce
The checks in Verifying the guarantee confirm what the integration user could reach. This section is the other half a security review asks for: how to watch what the connection actually does. All three instruments below live in your org, not in MetaSync — which is the right place for them, because evidence produced by the platform underneath an integration is worth more than evidence produced by the integration itself.
Connected Apps OAuth Usage — see and revoke the app’s tokens
Setup → Quick Find → “Connected Apps OAuth Usage” lists every OAuth app holding tokens in your org, External Client Apps included. Find the app you created for MetaSync. The User Count column is the number to read: for a correctly set-up connection it is exactly one — the integration user — and clicking it names them. A second name on that row means someone else has authorised the app with its Consumer Key and Secret, which is precisely the exposure the Permitted Users setting in The integration user exists to prevent. Revoke on this page kills the app’s tokens immediately; MetaSync’s next sync fails to authenticate and stops. This is the kill switch — use it first and investigate second.
Login History — every sign-in the connection makes
Setup → Quick Find → “Login History” records each OAuth token exchange as a login row. Filter to the integration user’s username and you should see logins from Atlassian’s cloud addresses at roughly your sync cadence, with Application naming your OAuth app — and nothing else. No browser logins, no other applications, no activity outside the schedule you set. On the Salesforce Integration licence recommended in The integration user the platform refuses interactive login outright, so a UI login row for this user is itself an anomaly worth chasing. The view exports to CSV, which makes it cheap evidence to attach to an access review.
Event Monitoring — per-call detail, if you are licensed for it
The two screens above are free in every org. Per-call detail — which entity a query touched, how many rows came back, from which IP — lives in Event Monitoring (ApiEvent, LoginEvent and the event log files), and honesty requires saying plainly that it is not free: it comes with Salesforce Shield or the standalone Event Monitoring add-on, and without that licence the objects and log files are simply absent. If your org has it, a scheduled query against ApiEvent filtered to the integration user gives a per-request record that the setup and Tooling entities described in Why the integration user cannot read records are the only things being read. If your org does not have it, you are not blind: Login History still shows every session, OAuth Usage still shows every token, and Check 2 in Verifying the guarantee still bounds what any session could have reached.
Note — What a healthy baseline looks like. One token holder on the OAuth Usage row. Logins only from the integration user, only through your OAuth app, at the cadence of your sync schedule — plus a burst when you connect or reconnect. Anything outside that pattern is worth investigating, and revoking the token while you do costs you nothing but a paused sync.
These Setup labels are Salesforce’s at the time of writing and move between releases — if a screen is not where this says, search Setup for its name. The three compose with the repeat advice in Verifying the guarantee: the configuration checks bound what is reachable, and these confirm that nothing else happened.
Optional metadata categories
This section is a scoping note rather than a security control, and is kept separate for that reason. Nothing below affects the record-access guarantee: every category named here is configuration metadata, never record data, so including or excluding it changes what your Confluence space documents and not what MetaSync can reach in Salesforce.
Permission Sets, Profiles and Sharing Rules are three separate toggles in the Security & Access group on the Metadata types tab. Each is switched independently — there is no combined security switch, and turning one off leaves the other two running.
All three are on by default, as is every other metadata type: a fresh installation with no saved selection documents everything MetaSync supports. Switch off whichever you do not want documented and run a sync; the categories you disabled stop being published, and pages already published for them are handled as described in Removed & out-of-scope pages.
Note — Why you might switch these off. Usually noise or audience, not exposure. Profiles and permission sets generate a page per component and dominate a space in a large org; a team that only wants schema and automation documented turns them off to keep the space readable. If your reason is that the content is sensitive, note that it is your access configuration being documented — who can see what — and the Confluence space it lands in can be restricted like any other.
The full list of togglable types, what each publishes, and how a narrowed selection interacts with an existing space is in Metadata Types & scope.