Authorization & Roles¶
This page explains how to decide who can do what in ARC-1.
The goal is simple: an admin should be able to answer these questions without reading code:
- Which env var do I set on the server?
- Which role, scope, or API-key profile does the user need?
- Why was a request blocked?
For a flat list of every flag, use Configuration Reference. For the v0.6 to v0.7 migration table, use Updating.
The model in one picture¶
ARC-1 has three independent gates. A request succeeds only if all relevant gates allow it.
| Gate | Question | Set by | Example |
|---|---|---|---|
| 1. Server ceiling | Is this capability enabled on this ARC-1 instance? | ARC-1 admin, env vars / CLI | SAP_ALLOW_WRITES=true |
| 2. User permission | Is this user allowed to use the capability? | XSUAA role, OIDC scope, or API-key profile | write scope, developer key |
| 3. SAP authorization | Does the SAP user have backend authorization? | SAP Basis / role admin | S_DEVELOP, S_ADT_RES, package auth |
Think of it as AND, never OR:
A user scope can never widen the server. SAP auth can still block a request after ARC-1 allows it.
Defaults¶
With no safety flags set, ARC-1 starts in the safest useful mode:
| Capability | Default |
|---|---|
| Read/search/navigate/lint/diagnose | On, subject to user read scope in HTTP auth mode and SAP auth |
| Object writes / activation / package changes / FLP mutations | Off |
| Named table preview | Off |
| Freestyle SQL | Off |
| Transport writes | Off |
| Git writes | Off |
| Write package allowlist | $TMP if writes are later enabled |
Important details:
- Reads are not package-gated by ARC-1. Use SAP authorization for read-level restrictions.
- Transport and Git read actions are available when the backend feature exists. Transport/Git write actions need extra opt-ins.
SAP_ALLOW_WRITES=falseblocks every mutation, including activation, transport writes, and Git writes.
SAP API Policy: data preview and free SQL are gated for a reason¶
The April 2026 SAP API Policy and the accompanying SAP API Policy FAQ endorse ADT-based developer tooling for "internal development automation such as code checks, build processes, and transport management". The same FAQ excludes "programmatic reading of application tables or export of business data" and "SQL execution against SAP backend systems".
ARC-1 is designed to stay within the ADT development-tooling scope described in SAP's API Policy FAQ v1.1. It uses documented ADT / Eclipse SDK capabilities for internal development-related use cases and does not expose ADT Data Preview, SQL execution, table reads, or business-data extraction.
When ARC-1 is used with AI assistants or MCP clients, customers should apply additional governance for AI-driven or automated access patterns, including real user identity, authorization checks, audit logging, rate limits, conservative tool exposure, and customer-side review against SAP documentation and agreements.
Two ARC-1 server flags map directly onto the excluded capabilities, and both default to off:
| Flag | Default | What it enables | FAQ alignment |
|---|---|---|---|
SAP_ALLOW_DATA_PREVIEW=true |
false (off) |
SAPRead(type=TABLE_CONTENTS) — named table content preview |
Outside the endorsed development tooling scope. |
SAP_ALLOW_FREE_SQL=true |
false (off) |
SAPQuery — freestyle ABAP SQL |
Outside the endorsed development tooling scope. |
With both flags at their defaults, the data/sql rows in the capability matrix below are unreachable, and ARC-1 stays inside the FAQ envelope for endorsed development tooling. Turning either flag on is a customer decision that must be made against the SAP API Policy, the customer's SAP agreement, and the customer's internal data-protection rules — not a default production posture.
The data and sql user scopes (and the viewer-data, viewer-sql, developer-data, developer-sql API-key profiles) only become useful after the matching server flag is on. Granting data / sql to a user does not widen the server ceiling.
Capability requirements¶
Use this table to answer: "what must be true before this action can run?" For HTTP auth, the user needs the listed scope or admin.
| Capability | User needs | Server needs | Notes |
|---|---|---|---|
| Read object source / metadata | read |
Nothing | SAPRead, most SAPContext, metadata reads |
| Search objects | read |
Nothing | SAPSearch |
| Navigate / code intelligence | read |
Nothing | Find definition, references, completion. Class hierarchy is the exception below. |
Class hierarchy (SAPNavigate.hierarchy) |
data or sql plus read |
SAP_ALLOW_DATA_PREVIEW=true or SAP_ALLOW_FREE_SQL=true |
Reads SEOMETAREL via table preview or SQL |
| Lint / local format / diagnostics | read |
Nothing | Unit tests can execute code but do not mutate repository objects |
| Update SAP PrettyPrinter settings | write |
SAP_ALLOW_WRITES=true |
SAPLint.set_formatter_settings mutates global formatter settings |
| Read transport info | read |
Nothing | SAPTransport.list, get, check, history |
| Read Git info | read |
Nothing | SAPGit.list_repos, history, objects, etc. when Git feature exists |
| Preview named table contents | data |
SAP_ALLOW_DATA_PREVIEW=true |
sql implies data |
| Run freestyle SQL | sql |
SAP_ALLOW_FREE_SQL=true |
High risk on productive systems |
| Create / update / delete objects | write |
SAP_ALLOW_WRITES=true |
SAP_ALLOWED_PACKAGES applies; supports exact (ZFOO), prefix (Z*), and DEVCLASS subtree (ZFOO/**) patterns. Subtree resolution is fail-closed on SAP errors. |
| Activate objects | write |
SAP_ALLOW_WRITES=true |
Activation is a mutation |
| Package / FLP mutations | write |
SAP_ALLOW_WRITES=true |
FLP list actions are reads; FLP create/delete actions are writes |
| Create / release / delete transports | write + transports |
SAP_ALLOW_WRITES=true + SAP_ALLOW_TRANSPORT_WRITES=true |
SAP_ALLOWED_TRANSPORTS can further restrict CTS IDs |
| Git clone / pull / push / commit | write + git |
SAP_ALLOW_WRITES=true + SAP_ALLOW_GIT_WRITES=true |
Requires backend gCTS/abapGit feature availability |
Why transport and Git rows list write plus the specialized scope: ARC-1's safety layer turns off all mutations for users without write. The specialized transports / git scopes decide who may use those write families after general write permission exists.
Transport mutation checklist:
- User has
writescope. - User has
transportsscope. - Server has
SAP_ALLOW_WRITES=true. - Server has
SAP_ALLOW_TRANSPORT_WRITES=true. SAP_DENY_ACTIONSdoes not deny the concrete action.- SAP backend authorization allows the SAP user to create, release, delete, or reassign CTS requests.
Tool schemas are pruned to hide actions that cannot pass ARC-1 gates. Treat schema visibility as a helpful signal, not a separate authorization layer.
Where to set things¶
| You want to change... | Change this | Do not change this |
|---|---|---|
| What this ARC-1 instance can ever do | Server env / CLI flags (SAP_ALLOW_*, SAP_ALLOWED_PACKAGES, SAP_DENY_ACTIONS). On BTP, set these with mta-overrides.mtaext, cf set-env, manifest.yml, or MTA properties. |
User JWT scopes |
| What one BTP user can do | XSUAA role collection assignment | Server env vars; they change the whole ARC-1 instance, not one user |
| What a specific API key can do | ARC1_API_KEYS="key:profile" |
Server flags only |
| What an OIDC user can do | scope / scp claim in the JWT |
MCP client JSON |
| What SAP ultimately allows | SAP roles / authorization objects | ARC-1 scopes |
Precedence for server config is:
Why not .env for BTP? .env is mainly the local/dev way to set the same server config. On BTP, use mta-overrides.mtaext (preferred — gitignored per-landscape overrides applied at cf deploy -e ...; copy from the tracked mta-overrides.mtaext.example template), cf set-env, manifest.yml, or MTA properties instead. Those values are still the server ceiling and affect every user of that ARC-1 instance. To change one BTP user's access, change their XSUAA role collection assignment.
Use arc1 config show to see the final resolved server policy and where each field came from.
User scopes¶
Seven scopes exist:
| Scope | Meaning | Implies |
|---|---|---|
read |
Read source, search, navigate, lint, diagnose | - |
write |
Object/package/activation/FLP mutations | read |
data |
Named table preview | - |
sql |
Freestyle SQL | data |
transports |
CTS transport mutations | - |
git |
abapGit/gCTS mutations | - |
admin |
All ARC-1 scopes | all other scopes |
Assigning only transports or only git is not useful for mutations because transport/Git writes also need write. The shipped developer profiles and BTP MCPDeveloper role include write, transports, and git together.
BTP XSUAA role templates¶
Start here for BTP deployments. API-key profiles are only for HTTP deployments without XSUAA/OIDC.
BTP users receive scopes through role collections. The shipped xs-security.json contains these role templates:
| Role template | Scopes |
|---|---|
MCPViewer |
read |
MCPDataViewer |
data |
MCPSqlUser |
data, sql |
MCPDeveloper |
read, write, transports, git |
MCPAdmin |
all 7 |
Common role collections:
| Role collection | Effective scopes |
|---|---|
ARC-1 Viewer |
read |
ARC-1 Data Viewer |
read, data |
ARC-1 Viewer + SQL |
read, data, sql |
ARC-1 Developer |
read, write, transports, git |
ARC-1 Developer + Data |
read, write, data, transports, git |
ARC-1 Developer + SQL |
read, write, data, sql, transports, git |
ARC-1 Admin |
all 7 |
Deployed collection names carry the CF space as a suffix — e.g.
ARC-1 Developer (dev)— becausemta.yamlderives them from the${space}placeholder so the same mtar can run in several spaces of one subaccount. Assign the one matching your space. See XSUAA Setup.
Want a developer who can write code but cannot transport or use Git? Create a custom role template with just read + write, then update the XSUAA service. Or leave the shipped role as-is and turn off SAP_ALLOW_TRANSPORT_WRITES / SAP_ALLOW_GIT_WRITES server-wide.
To grant SQL to one BTP user, assign a role collection that includes MCPSqlUser (for example ARC-1 Viewer + SQL for read-only SQL or ARC-1 Developer + SQL for full developer access) to that user. Do not change server env vars for one user. The ARC-1 instance must already have SAP_ALLOW_FREE_SQL=true; there is no SAP_ALLOW_SQL flag.
See XSUAA Setup for BTP Cockpit assignment steps.
API-key profiles (non-BTP)¶
Use API-key profiles when you run HTTP mode without XSUAA/OIDC:
Each profile grants scopes and, for developer profiles, an additional safety cap. The final result is still intersected with the server ceiling.
Profiles are fixed names built into ARC-1. ARC1_API_KEYS only selects one of the profiles below; it does not let you attach custom scopes or custom package allowlists to one key.
| Profile | Scopes | Extra profile safety |
|---|---|---|
viewer |
read |
No writes, no data preview, no SQL, no transports, no Git |
viewer-data |
read, data |
No writes, no SQL, no transports, no Git |
viewer-sql |
read, data, sql |
No writes, no transports, no Git |
developer |
read, write, transports, git |
Writes capped to $TMP, no data preview, no SQL |
developer-data |
read, write, data, transports, git |
Writes capped to $TMP, no SQL |
developer-sql |
read, write, data, sql, transports, git |
Writes capped to $TMP |
admin |
all 7 scopes | No profile package cap; server ceiling still applies |
Key implications:
- A
developerkey can write only to$TMP, even if the server allowsZ*. - Because API-key profiles are fixed, there is no
developer-zprofile and nokey:developer:Z*syntax. - To give an API key transportable-package write access, use a tightly scoped
adminkey on a server whoseSAP_ALLOWED_PACKAGESis restricted, or use OIDC/XSUAA for real per-user roles. - A profile cannot override the server. If
SAP_ALLOW_WRITES=false, every API key is effectively read-only.
Example: shared sandbox with one viewer and one $TMP developer key:
SAP_TRANSPORT=http-streamable
SAP_ALLOW_WRITES=true
SAP_ALLOW_TRANSPORT_WRITES=true
SAP_ALLOW_GIT_WRITES=false
SAP_ALLOWED_PACKAGES='$TMP,Z*'
ARC1_API_KEYS='viewer-key:viewer,dev-key:developer'
In that example, dev-key:developer can write $TMP only. The server also allows Z*, but the profile narrows the key to $TMP.
Advanced deny actions¶
SAP_DENY_ACTIONS is the fine-grained deny list. It applies after scope and flag checks, and it always wins.
Use it for rules like "developers can write, but cannot delete".
| Form | Meaning | Example |
|---|---|---|
Tool |
Deny every action of this tool | SAPGit |
Tool.action |
Deny exactly this action | SAPWrite.delete |
Tool.glob* |
Glob inside one tool | SAPManage.flp_* |
Cross-tool wildcards like *.delete are rejected at startup.
# Inline CSV
SAP_DENY_ACTIONS='SAPWrite.delete,SAPManage.flp_*'
# Or a JSON file path
SAP_DENY_ACTIONS='./deny-actions.json' # ["SAPWrite.delete", "SAPManage.flp_*"]
ARC-1 fails fast if a deny entry references an unknown tool/action, has invalid grammar, or points to an unreadable file. That is intentional: typoed security config should not silently start.
Recipes¶
1. Read and search only¶
Set nothing. This is the default.
2. Read-only with table preview and SQL¶
Users still need data / sql scopes in HTTP auth mode.
3. Local developer on a sandbox¶
Add only if needed:
SAP_ALLOW_TRANSPORT_WRITES=true
SAP_ALLOW_GIT_WRITES=true
SAP_ALLOW_DATA_PREVIEW=true
SAP_ALLOW_FREE_SQL=true
4. Team server with API keys¶
SAP_TRANSPORT=http-streamable
SAP_ALLOW_WRITES=true
SAP_ALLOWED_PACKAGES='$TMP,Z*'
ARC1_API_KEYS='viewer-key:viewer,dev-key:developer,admin-key:admin'
Use viewer for read-only users, developer for $TMP sandbox writes, and admin only for trusted operators. If admin-key should write only to Z-packages, keep the server ceiling narrow with SAP_ALLOWED_PACKAGES='Z*,$TMP'.
5. BTP/XSUAA with per-user identity¶
SAP_XSUAA_AUTH=true
SAP_PP_ENABLED=true
SAP_ALLOW_WRITES=true
SAP_ALLOW_TRANSPORT_WRITES=true
SAP_ALLOWED_PACKAGES='Z*,$TMP'
Then assign role collections in BTP Cockpit. The server says what the instance can do; XSUAA says which user can do it.
Common misconfigurations¶
| Symptom | Why | Fix |
|---|---|---|
User has write, but writes fail with allowWrites=false |
Server ceiling is still closed | Set SAP_ALLOW_WRITES=true |
User has transports, but transport create fails |
Mutations also need write, and server needs both write flags |
Grant write + transports; set SAP_ALLOW_WRITES=true and SAP_ALLOW_TRANSPORT_WRITES=true |
SAP_ALLOW_TRANSPORT_WRITES=true, but transport create fails |
SAP_ALLOW_WRITES=false still blocks all mutations |
Set both flags |
developer API key cannot write to Z* |
Developer API-key profiles are capped to $TMP |
Use $TMP, use a restricted admin key, or use XSUAA/OIDC |
You want one API key to write Z*, but not be full admin |
API-key profiles are fixed; per-key custom package caps are not supported | Use an admin key on a narrowly configured server, or use XSUAA/OIDC |
SQL still blocked after SAP_ALLOW_FREE_SQL=true |
User lacks sql scope |
Grant sql or use viewer-sql / developer-sql |
Table preview blocked after SAP_ALLOW_DATA_PREVIEW=true |
User lacks data scope |
Grant data; sql also implies data |
| Package allowlist seems ignored for reads | ARC-1 package allowlist is write-only | Enforce read restrictions in SAP roles |
| Action is hidden from tool list | User scope, server flag, backend feature, or SAP_DENY_ACTIONS pruned it |
Run arc1 config show and check startup feature logs |
Troubleshooting: which layer blocked me?¶
| Error fragment | Layer | What to change |
|---|---|---|
Insufficient scope: 'write' required |
User permission | Grant write scope / profile / role collection |
Insufficient scope: 'data' required |
User permission | Grant data scope or viewer-data profile |
Insufficient scope: 'sql' required |
User permission | Grant sql scope or viewer-sql / developer-sql profile |
Insufficient scope: 'transports' required |
User permission | Grant role/profile with transports |
Insufficient scope: 'git' required |
User permission | Grant role/profile with git |
allowWrites=false blocks mutations |
Server ceiling | Set SAP_ALLOW_WRITES=true |
allowTransportWrites=false |
Server ceiling | Set SAP_ALLOW_TRANSPORT_WRITES=true and SAP_ALLOW_WRITES=true |
allowGitWrites=false |
Server ceiling | Set SAP_ALLOW_GIT_WRITES=true and SAP_ALLOW_WRITES=true |
allowDataPreview=false |
Server ceiling | Set SAP_ALLOW_DATA_PREVIEW=true |
allowFreeSQL=false |
Server ceiling | Set SAP_ALLOW_FREE_SQL=true |
Operations on package ... are blocked |
Server/profile safety | Adjust SAP_ALLOWED_PACKAGES or API-key profile choice |
denied by server policy (SAP_DENY_ACTIONS) |
Deny list | Remove or narrow the deny pattern |
No authorization for object ... / SAP 403 |
SAP authorization | Fix SAP user roles / PFCG / package auth |
Legacy authorization config detected |
Migration | Replace old v0.6 env vars per Updating |
Debug commands:
Also read startup logs for:
effective safety: ...- final server ceilingconfig contradiction: ...- flags that cannot take effect, such as transport writes without writesauth: MCP=[...] SAP=[...]- active auth methods
MCP sign-in ends on a blank page / "this site can't be reached" / "Missing required parameters … code, state, nonce"¶
These client-side symptoms almost always share one server-side cause: XSUAA authenticated the user but rejected the authorization with invalid_scope ("this user is not allowed any of the requested scopes"), so no code was issued. The MCP client's loopback callback then receives no code — and its listener may already be closed, which is why the browser shows ERR_CONNECTION_REFUSED.
Confirm the real error in the server log — it lands on ARC-1's callback, not the client:
cf logs arc1-mcp-server --recent | grep '/oauth/callback?error='
# GET /oauth/callback?error=invalid_scope&error_description=[...] is invalid. This user is not allowed any of the requested scopes
Since v0.9.8 ARC-1 renders this reason on its /oauth/callback error page (instead of bouncing to the dead loopback), so the browser shows the cause directly.
invalid_scope means the signed-in identity holds no ARC-1 role collection — usually because the role was assigned under a different IdP origin than the one the login uses. Fix it with the next two entries.
"I changed the user's role but the new scopes don't appear"¶
XSUAA caches the user's authorities in their browser session. When you change role-collection assignments in BTP Cockpit, existing JWTs keep the old scopes until they expire (typically 1 hour) AND the user's SSO session at XSUAA / IAS still references the old authorities.
To force fresh scopes immediately:
- Log out of XSUAA in the same browser the MCP client uses:
https://<your-xsuaa-tenant>.authentication.<region>.hana.ondemand.com/logout.do - Log out of the IAS / business-users IdP if you use one:
https://<your-ias-tenant>.accounts.ondemand.com/logout - In your MCP client (Claude.ai, Cursor, MCP Inspector): disconnect the connector and re-add it - this triggers a fresh DCR + OAuth flow.
- Optional: complete the OAuth login in a fresh browser / private window to guarantee no SSO session is reused.
After that, the new JWT will be issued from a fresh session and carry only the user's currently assigned scopes. You can verify by reading the JWT at jwt.ms - the scope claim should match the role collection's scopes.
"I have two marian@example.com users in BTP and only one shows the role I changed"¶
BTP can hold multiple identities for the same email - one per IdP origin (sap.default, the IAS tenant, custom IdPs). Role assignments are per-identity. The MCP client logs in via one specific IdP, so check that you're updating the role for the same identity that the OAuth flow uses.
In BTP Cockpit → Users you can see all identities for a given email and their Identity-Provider column. Update the role on the identity whose IdP matches the OAuth login.
From the CLI, assign under the right origin — --of-idp is the critical part. On subaccounts with a custom SAP Cloud Identity (IAS) tenant, the application login uses that origin (often sap.custom), not sap.default:
# list the IdP origins (the IAS "business users" trust is the usual app-login IdP)
btp list security/trust --subaccount <subaccount-id>
# assign under the origin the OAuth flow actually uses
btp assign security/role-collection "ARC-1 Admin" --subaccount <subaccount-id> --to-user <email> --of-idp sap.custom
Assigning under only sap.default while logging in via the IAS tenant is the single most common cause of invalid_scope.
References¶
- Configuration Reference - every flag and env var
- API Key Setup - non-BTP role-based API keys
- XSUAA Setup - BTP role collections and OAuth
- OAuth / JWT Setup - external IdP scopes
- Principal Propagation Setup - per-user SAP identity
- Security Guide - production hardening
- Updating - migration from v0.6