Skip to content

Access Control: Tenant isolation

Identifier: tenant_isolation

Scanner(s) Support

GraphQL Scanner REST Scanner WebApp Scanner

Description

When a system isn't properly isolating tenant data, it can mistakenly flag the same object as belonging to more than one user, breaching strict separation rules. This type of vulnerability is dangerous because it can lead to unauthorized data access where one tenant might see or even change another tenant's data. It often happens due to misconfigured rules or flawed logic in how object instances are tracked and associated with users, making it easier to unintentionally combine or share sensitive information across tenants. Developers should keep a close eye on how their application defines and enforces tenant boundaries to avoid these kinds of security pitfalls.

By default, when no configuration is provided, the check will cover all paths and keys matching. Response similarity will be used to detect isolation violations.

References:

Configuration

Example

Example configuration:

---
security_tests:
  tenant_isolation:
    assets_allowed:
    - REST
    - GRAPHQL
    - WEBAPP
    keys_matching:
    - card_number
    main_user: ''
    natural_language_rule: Ensure that a user's notes cannot be accessed by other
      users.
    other_users:
      detect:
      - if_: request.is_authenticated
        is_: true
        is_not: null
      - if_: helpers.fingerprints.same
        is_: true
        is_not: null
    paths:
    - /users/{id}
    skip: false
    specific_users: {}

Reference

assets_allowed

Type : List[AssetType]*

List of assets that this check will cover.

keys_matching

Type : List[string]*

List of keys in a response body that will be compared between different users, to detect an isolation violation.

If the key values are the exact same between these users, an alert will be raised.

For example if you want to control the key card_number, you can use the following:

---
security_tests:
  tenant_isolation:
    keys_matching:
    - card_number

main_user

Type : string

The main user to use for the check. It will be used as source of truth to detect isolation violations with all other users.

---
security_tests:
  tenant_isolation:
    main_user: user1

natural_language_rule

Type : string

A natural language prompt to describe what should be checked for tenant isolation. This will be used to generate the rules to detect tenant isolation issues when analyzing the responses. You can review the generated rules in the alert details, or the scan logs with the prefix [Agentic - Tenant Isolation]

other_users

Type : TenantIsolationRule*

The conditions to trigger the alert when comparing the original and switched responses. The list of conditions are combined with AND logic by default. By default, the conditions check if the request is authenticated and if the responses of both users have the same fingerprint.

paths

Type : List[string]*

List of paths that this check will cover. Add * to cover all paths.

For example if you want to control the path /users/{id}, you can use the following:

---
security_tests:
  tenant_isolation:
    paths:
    - /users/{id}

To cover all paths, you can use the following:

---
security_tests:
  tenant_isolation:
    paths:
    - '*'

skip

Type : boolean

Skip the test if true.

specific_users

Type : Dict[string, TenantIsolationRule]

The conditions to trigger the alert when analyzing the responses between a given user and specific users. The list of conditions are combined with AND logic by default.

APILogicalAndDetector

and

Type : List[APILogicalAndDetector|APILogicalNotDetector|APILogicalOrDetector|FingerprintCountDetector|FingerprintsSameDetector|HelpersRequestCrudDetector|HelpersResponseIsSuccessfulDetector|JSONMatchesAllDetector|JSONMatchesCountDetector|RegexMatchesAllDetector|RegexMatchesCountDetector|RequestBodyJSONDetector|RequestBodyTextDetector|RequestHeadersDetector|RequestIsAuthenticatedDetector|RequestMethodDetector|RequestObjectDetector|RequestUserDetector|ResponseBodyJSONDetector|ResponseBodyTextDetector|ResponseDurationDetector|ResponseHeadersDetector|ResponseObjectDetector|ResponseStatusCodeDetector|ScanTypeDetector|SchemaNeedAuthenticationDetector|SchemaPathRefDetector|SchemaUrlDetector]*

Logical and on a list of detectors

if

Type : Const[and]*

Use this to apply a logical and on a list of detectors.

Example

detect:
  - if: and
    and:
      - if: helpers.request.crud
        in:
          - CREATE
          - UPDATE
      - if: response.status_code
        is: 200

APILogicalNotDetector

if

Type : Const[not]*

Use this to apply a logical not on a detector.

Example

detect:
  - if: not
    not:
      if: response.status_code
      is: 200
not

Type : APILogicalAndDetector|APILogicalNotDetector|APILogicalOrDetector|FingerprintCountDetector|FingerprintsSameDetector|HelpersRequestCrudDetector|HelpersResponseIsSuccessfulDetector|JSONMatchesAllDetector|JSONMatchesCountDetector|RegexMatchesAllDetector|RegexMatchesCountDetector|RequestBodyJSONDetector|RequestBodyTextDetector|RequestHeadersDetector|RequestIsAuthenticatedDetector|RequestMethodDetector|RequestObjectDetector|RequestUserDetector|ResponseBodyJSONDetector|ResponseBodyTextDetector|ResponseDurationDetector|ResponseHeadersDetector|ResponseObjectDetector|ResponseStatusCodeDetector|ScanTypeDetector|SchemaNeedAuthenticationDetector|SchemaPathRefDetector|SchemaUrlDetector

Logical not of a detector

APILogicalOrDetector

if

Type : Const[or]*

Use this to apply a logical or on a list of detectors.

Example

detect:
  - if: or
    or:
      - if: helpers.request.crud
        in:
          - CREATE
          - UPDATE
      - if: response.status_code
        is: 200
or

Type : List[APILogicalAndDetector|APILogicalNotDetector|APILogicalOrDetector|FingerprintCountDetector|FingerprintsSameDetector|HelpersRequestCrudDetector|HelpersResponseIsSuccessfulDetector|JSONMatchesAllDetector|JSONMatchesCountDetector|RegexMatchesAllDetector|RegexMatchesCountDetector|RequestBodyJSONDetector|RequestBodyTextDetector|RequestHeadersDetector|RequestIsAuthenticatedDetector|RequestMethodDetector|RequestObjectDetector|RequestUserDetector|ResponseBodyJSONDetector|ResponseBodyTextDetector|ResponseDurationDetector|ResponseHeadersDetector|ResponseObjectDetector|ResponseStatusCodeDetector|ScanTypeDetector|SchemaNeedAuthenticationDetector|SchemaPathRefDetector|SchemaUrlDetector]*

Logical or on a list of detectors

FingerprintCountDetector

gt

Type : integer

Condition is greater than this integer

if

Type : Const[helpers.fingerprints.count]*

Use this to select and compare the count of unique fingerprints of the current and original response.

in

Type : List[integer]

Condition is in this list of integers (exact match)

is

Type : integer

Condition is this exact integer

is_not

Type : integer

Condition is not this exact integer

lt

Type : integer

Condition is less than this integer

FingerprintsSameDetector

if

Type : Const[helpers.fingerprints.same]*

Use this to determine whether the current and original responses have the same fingerprint.

is

Type : boolean

Condition is true

is_not

Type : boolean

Condition is false

HelpersRequestCrudDetector

if

Type : Const[helpers.request.crud]*

Use this to select against the detected CRUD operation of the request.

Example

detect:
  - if: helpers.request.crud
    in:
      - CREATE
      - UPDATE
in

Type : List[CustomRuleCrud]

Condition is the request is in this list of CRUD operations (exact match)

is

Type : CustomRuleCrud

Condition is the request is this CRUD operation

is_not

Type : CustomRuleCrud

Condition is the request is not this CRUD operation

HelpersResponseIsSuccessfulDetector

if

Type : Const[helpers.response.is_successful]*

Use this to check whether the response is successful.

Example

detect:
  - if: helpers.response.is_successful
    is: true
is

Type : boolean

Condition is true

is_not

Type : boolean

Condition is false

JSONMatchesAllDetector

if

Type : Const[helpers.json_matches.all]*

Use this to determine whether every the current and original responses contain the same JSON fragment.

is

Type : boolean

Condition is true

is_not

Type : boolean

Condition is false

jq

Type : string

Use this to select the exact JSON you want to compare between the current and original response.

JSONMatchesCountDetector

gt

Type : integer

Condition is greater than this integer

if

Type : Const[helpers.json_matches.count]*

Use this to count the number of times a JSON match is in the current and original response.

in

Type : List[integer]

Condition is in this list of integers (exact match)

is

Type : integer

Condition is this exact integer

is_not

Type : integer

Condition is not this exact integer

jq

Type : string

Use this to select the exact JSON you want to compare between the current and original response.

lt

Type : integer

Condition is less than this integer

ObjectTypeMatcher

in

Type : List[OBJECT_TYPE]

Object type is in the following list

is

Type : OBJECT_TYPE

Object type is exactly this type

is_not

Type : OBJECT_TYPE

Object type is any this type except this one

RegexMatchesAllDetector

if

Type : Const[helpers.regex_matches.all]*

Use this to determine whether every the current and original responses match the same regular expression.

is

Type : boolean

Condition is true

is_not

Type : boolean

Condition is false

regex

Type : string

Condition is matched on this regex with fullmatch

RegexMatchesCountDetector

gt

Type : integer

Condition is greater than this integer

if

Type : Const[helpers.regex_matches.count]*

Use this to count the number of times a regex match is in the current and original response.

in

Type : List[integer]

Condition is in this list of integers (exact match)

is

Type : integer

Condition is this exact integer

is_not

Type : integer

Condition is not this exact integer

lt

Type : integer

Condition is less than this integer

regex

Type : string

Condition is matched on this regex with fullmatch

RequestBodyJSONDetector

if

Type : Const[request.body.json]*

Use this to select and compare the request body when detected as JSON, using jq-like syntax.

Example 1

detect:
  - if: request.body.json
    is:
      id: 42

Example 2

detect:
  - if: request.body.json
    jq: '.role == admin'
in

Type : List[Union[Dict[string, object], List[object]]]

Condition is in this list of JSON

is

Type : Union[Dict[string, object], List[object]]

Condition is this exact JSON

is_not

Type : Union[Dict[string, object], List[object]]

Condition is not this exact JSON

jq

Type : string

JQ query to match and use as boolean

RequestBodyTextDetector

contains

Type : string

Contains this string

if

Type : Const[request.body.text]*

Use this to select and compare the request body as text, using string compare.

Example

detect:
  - if: request.body.text
    contains: 'password='
in

Type : List[string]

Condition is in this list (exact match)

is

Type : string

Condition is this exact string

is_not

Type : string

Condition is not this exact string

regex

Type : string

Condition is matched on this regex with fullmatch

RequestHeadersDetector

if

Type : Const[request.headers]*

Use that to select and compare the request headers in a key value dictionary.

Example

detect:
  - if: request.headers
    key:
      is: 'X-OPERATION'
    value:
      is: 'PAY'
key

Type : StringMatcher

Key to match

value

Type : StringMatcher

Value to match

RequestIsAuthenticatedDetector

if

Type : Const[request.is_authenticated]*

Use this to select whether or not whether the request is authenticated.

Example

detect:
  - if: request.is_authenticated
    is: true
is

Type : boolean

Condition is true

is_not

Type : boolean

Condition is false

RequestMethodDetector

if

Type : Const[request.method]*

Use this to select against the request HTTP Method.

Example

detect:
  - if: request.method
    is: OPTIONS
in

Type : List[HTTPMethod]

Condition is the request is in this list of CRUD operations (exact match)

is

Type : HTTPMethod

Condition is the request is this CRUD operation

is_not

Type : HTTPMethod

Condition is the request is not this CRUD operation

RequestObjectDetector

if

Type : Const[request.object]*

Use this to select and compare the detected object scalars (including custom scalars) in the request, with their kind, name and value.

Example

detect:
  - if: request.object
    type:
      in:
        - email
        - phone
        - street_address
name

Type : StringMatcher

Object scalar name to match

type

Type : ObjectTypeMatcher

Object scalar type to match

value

Type : StringMatcher

Object scalar value to match

RequestUserDetector

contains

Type : string

Contains this string

if

Type : Const[request.user]*

Use this to string compare the configured user for the request.

Example

detect:
  - if: request.user
    is: unprivileged_user
in

Type : List[string]

Condition is in this list (exact match)

is

Type : string

Condition is this exact string

is_not

Type : string

Condition is not this exact string

regex

Type : string

Condition is matched on this regex with fullmatch

ResponseBodyJSONDetector

if

Type : Const[response.body.json]*

Use this to select and compare the response body when detected as JSON, using jq-like syntax.

Example 1

detect:
  - if: response.body.json
    is:
      id: 42

Example 2

detect:
  - if: response.body.json
    jq: '.role == admin'
in

Type : List[Union[Dict[string, object], List[object]]]

Condition is in this list of JSON

is

Type : Union[Dict[string, object], List[object]]

Condition is this exact JSON

is_not

Type : Union[Dict[string, object], List[object]]

Condition is not this exact JSON

jq

Type : string

JQ query to match and use as boolean

ResponseBodyTextDetector

contains

Type : string

Contains this string

if

Type : Const[response.body.text]*

Use this to select and compare the response body as text, using string compare.

Example

detect:
  - if: request.body.text
    is_not: 'unauthorized'
in

Type : List[string]

Condition is in this list (exact match)

is

Type : string

Condition is this exact string

is_not

Type : string

Condition is not this exact string

regex

Type : string

Condition is matched on this regex with fullmatch

ResponseDurationDetector

gt

Type : integer

Condition is greater than this integer

if

Type : Const[response.duration_ms]*

Use this to compare the duration of the request in milliseconds.

Example

detect:
  - if: response.duration_ms
    gt: 200
in

Type : List[integer]

Condition is in this list of integers (exact match)

is

Type : integer

Condition is this exact integer

is_not

Type : integer

Condition is not this exact integer

lt

Type : integer

Condition is less than this integer

ResponseHeadersDetector

if

Type : Const[response.headers]*

Use that to select and compare the response headers in a key value dictionary.

Example

detect:
  - if: response.headers
    key:
      is: 'X-RESULT'
    value:
      is: 'PAID'
key

Type : StringMatcher

Key to match

value

Type : StringMatcher

Value to match

ResponseObjectDetector

if

Type : Const[response.object]*

Use this to select and compare the detected object scalars (including custom scalars) in the response, with their kind, name and value.

Example

detect:
  - if: response.object
    type:
      in:
        - email
        - phone
        - street_address
name

Type : StringMatcher

Object scalar name to match

type

Type : ObjectTypeMatcher

Object scalar type to match

value

Type : StringMatcher

Object scalar value to match

ResponseStatusCodeDetector

gt

Type : integer

Condition is greater than this integer

if

Type : Const[response.status_code]*

Use this to compare the HTTP status code as an integer.

Example

detect:
  - if: response.status_code
    is: 200
in

Type : List[integer]

Condition is in this list of integers (exact match)

is

Type : integer

Condition is this exact integer

is_not

Type : integer

Condition is not this exact integer

lt

Type : integer

Condition is less than this integer

ScanTypeDetector

if

Type : Const[scan.type]*

Use this to select against the type of the scan.

Example

detect:
  - if: scan.type
    is: REST
in

Type : List[CustomRuleScanType]

The scan type is in this list

is

Type : CustomRuleScanType

The scan type is exactly this

is_not

Type : CustomRuleScanType

The scan type is not this type

SchemaNeedAuthenticationDetector

if

Type : Const[schema.need_authentication]*

Use this to select whether or not the schema requires authentication.

Example

detect:
  - if: schema.need_authentication
    is: false
is

Type : boolean

Condition is true

is_not

Type : boolean

Condition is false

SchemaPathRefDetector

contains

Type : string

Contains this string

if

Type : Const[schema.path_ref]*

Use this to string compare the operation name in GraphQL or the path in REST.

Example

detect:
  - if: schema.path_ref
    contains: /admin/secrets
in

Type : List[string]

Condition is in this list (exact match)

is

Type : string

Condition is this exact string

is_not

Type : string

Condition is not this exact string

regex

Type : string

Condition is matched on this regex with fullmatch

SchemaUrlDetector

contains

Type : string

Contains this string

if

Type : Const[schema.url]*

Use this to string compare the URL of the request.

Example

detect:
  - if: schema.url
    regex: .*(internal|private).*
in

Type : List[string]

Condition is in this list (exact match)

is

Type : string

Condition is this exact string

is_not

Type : string

Condition is not this exact string

regex

Type : string

Condition is matched on this regex with fullmatch

StringMatcher

contains

Type : string

Contains this string

in

Type : List[string]

Condition is in this list (exact match)

is

Type : string

Condition is this exact string

is_not

Type : string

Condition is not this exact string

regex

Type : string

Condition is matched on this regex with fullmatch

TenantIsolationRule

detect

Type : List[APILogicalAndDetector|APILogicalNotDetector|APILogicalOrDetector|FingerprintCountDetector|FingerprintsSameDetector|HelpersRequestCrudDetector|HelpersResponseIsSuccessfulDetector|JSONMatchesAllDetector|JSONMatchesCountDetector|RegexMatchesAllDetector|RegexMatchesCountDetector|RequestBodyJSONDetector|RequestBodyTextDetector|RequestHeadersDetector|RequestIsAuthenticatedDetector|RequestMethodDetector|RequestObjectDetector|RequestUserDetector|ResponseBodyJSONDetector|ResponseBodyTextDetector|ResponseDurationDetector|ResponseHeadersDetector|ResponseObjectDetector|ResponseStatusCodeDetector|ScanTypeDetector|SchemaNeedAuthenticationDetector|SchemaPathRefDetector|SchemaUrlDetector]*

The detectors to trigger the alert when analyzing the responses between the main user and other users.

Enums

CustomRuleCrud
Value
CREATE
READ
UPDATE
DELETE
CustomRuleScanType
Value
GRAPHQL
REST
HTTPMethod
Value
CONNECT
DELETE
GET
HEAD
OPTIONS
PATCH
POST
PUT
TRACE
OBJECT_TYPE
Value
adobe_client_id
adobe_client_secret
age_secret_key
algolia_api_key
alibaba_access_key_id
alibaba_secret_key
amount
application
area_code
asana_client_id
asana_client_secret
atlassian_api_token
authentication
author
authorization_code
aws_access_token
aws_mws_id
bank
bank_account
bank_card
base64
bcrypt
beamer_api_token
bearer
bearer_uuid
bitbucket_client_id
bitbucket_client_secret
bitcoin
body_type
boolean
boolean_wannabe
building
card_type
category
city
clojars_api_token
command
commit_hash
confirmation_code
content_type
contentful_delivery_api_token
country
country_code
county
coupon_code
cuid
currency_code
cvv
dash
databricks_api_token
datadog_access_token
date
datetime
delivery_method
device_name
device_type
did
digitalocean_pat
directory
discount
document_type
doppler_api_token
driving_license
dropbox_api_token
dropbox_long_lived_api_token
dropbox_short_lived_api_token
duffel_api_token
duration
dynatrace_api_token
e_commerce_indicator
easypost_api_token
easypost_test_api_token
email
environment
ethereum
etsy_access_token
event_type
facebook
fastly_api_token
fee
file
finicity_api_token
finicity_client_secret
flickr_access_token
float
flutterwave_encryption_key
flutterwave_secret_key
form
frameio_api_token
french_phone
func
gcp_api_key
gender
geocodio
github_app_token
github_fine_grained_pat
github_oauth
github_pat
github_refresh_token
gitlab_pat
gitlab_rrt
grafana_api_key
grafana_service_account_token
graphcms
hash
hashicorp_tf_api_token
health_insurance_number
heroku_api_key
hex_color_code
hexadecimal
host
house_number
hsl
hsla
html_body
http_method
hubspot_api_key
huggingface_token
id
identity_number
injection
instagram_oauth
integer
intercom_api_key
ipc_patent
ipstack_token
ipv4
ipv6
isbn
item
jfrog_api_key
jiratoken
join
json
jwt
language_iso_639_1
language_iso_639_2
latitude
launchdarkly_access_token
legal_name
limit
linear_api_key
linear_client_secret
linkedin_client_secret
llm_input
lob_api_key
locale
location
longitude
mac
mailchimp_api_key
mailgun_private_api_token
mailgun_signing_key
mask
md5
medical_record_number
merchant
messagebird_api_token
microsoft_teams_webhook
monero
mongo_db_object_id
month
navigation
netlify_access_token
new_relic_user_api_id
npm_access_token
offset
okta_access_token
openai_api_key
pagination
pagination_limit
pagination_wannabe
passport
password
paypaloauth
permission
phone
pin_code
plan
planetscale_api_token
planetscale_password
policy
port
postman_api_token
prescription_number
price
private_key
protocol
pubnubpublishkey
pulumi_api_token
pypi_upload_token
reason_code
reference
region
return_type
rgb
rgba
role
rubygems_api_token
search
secret
sendgrid_api_token
sendinblue_api_token
sentry_access_token
serial_number
sha1
sha256
shipping_method
shippo_api_token
shopify_access_token
shopify_custom_access_token
shopify_private_app_access_token
shopify_shared_secret
sidekiq_secret
slack_bot_token
slack_legacy_workspace_token
slack_user_token
slack_webhook_url
slug
social_security_number
software_component
ssh_url
status
status_code
status_message
street_address
string
stripe_access_token
stripe_public_access_token
sumologic_access_token
thinkific
time
timestamp
title
twilio_api_key
twitch_api_token
twitter_api_key
typeform_api_token
unsanitized_payload
upload
uri
url
us_bank_account_number
us_bank_routing_number
us_zip_code
user_agent
username
uuid
vault_service_token
vehicle_type
version
view
year
youtubeapikey
zendesk_secret_key
zip_code