Testing Roles and Permissions Without a Security Background

Written By  Crosscheck Team

Content Team

July 15, 2026 9 minutes

Testing Roles and Permissions Without a Security Background

Testing roles and permissions without a security background

A viewer on a project management tool cannot see the Delete project button. It is not in the menu. It is not in the sidebar. The design team removed it, the developer hid it, and QA signed it off.

Then a viewer pasted a link a colleague sent them, and the project disappeared.

The button was hidden. The action was not. That single gap is the most common permission bug in web apps, and you can find it with nothing but a browser and two test accounts.

Short version

  • Write a permission matrix first. You cannot test what you have not listed.
  • Hidden in the interface does not mean blocked on the server. Test the action, not the button.
  • Change the ID in the URL to another user's record. This finds real bugs in minutes.
  • Log in as the low-privilege account and try the high-privilege thing.
  • A correct block is a clear "not allowed" message, not a crash and not a blank page.

Three words, defined once

  • Authentication: proving who you are. Logging in.
  • Authorization: deciding what you are allowed to do once you are in. Permissions.
  • Role: a named bundle of permissions, such as Admin, Editor, or Viewer.

Almost every bug in this article is an authorization bug. The user logged in correctly. The app just let them do too much.


Step 1: Build the permission matrix

A permission matrix is a table. Roles across the top, actions down the side, and yes or no in each cell. It is the single most useful artifact in this kind of testing, and it usually takes under an hour.

Here is a small one for a project tool with three roles.

ActionViewerEditorAdmin
See project listYesYesYes
Open a projectYesYesYes
Create a taskNoYesYes
Edit any taskNoYesYes
Delete a taskNoOwn onlyYes
Invite a memberNoNoYes
Change member roleNoNoYes
Delete the projectNoNoYes
Export all dataNoNoYes
View billing pageNoNoYes

Build yours from three sources: the product spec, the pricing page, and a conversation with the developer. Where those three disagree, you have already found a bug.

Count the cells. Three roles and ten actions gives thirty checks. That is your test suite. It is boring and it works. Pay special attention to the "Own only" cells, because partial permissions are where the logic gets complicated, and complicated logic is where bugs live.


Step 2: Test the "No" cells, not the "Yes" ones

Most teams test the Yes cells. Admin can delete a project — passed. That proves the feature works. It proves nothing about safety.

The value is in the No cells. For each one, ask: what happens if this user does it anyway?

Set up two Chrome profiles side by side, one logged in as [email protected] and one as [email protected]. Use separate profiles, not two tabs. Two tabs share cookies, and you will keep logging yourself out.


Step 3: URL tampering

This is the simplest technique in the article and it finds the most bugs.

How to run it:

  1. In the admin profile, do the restricted thing. Open the billing page. Copy the full URL: https://staging.example.com/projects/482/settings/billing.
  2. Switch to the viewer profile.
  3. Paste that URL into the address bar and press Enter.

Four things can happen:

  • A clear "You do not have access to this page" screen. Good.
  • A redirect to the dashboard with no explanation. Acceptable, but file a minor usability bug.
  • The billing page loads with real card details. Serious bug.
  • The page shell loads, the fields are empty, and the console fills with 403 Forbidden errors. A bug, and a confusing one.

That last one is worth explaining. The front end let the page open, and only the data calls were blocked. The user sees a broken page titled "Billing" with empty boxes. The data is safe, but it looks like an outage. File it.

Do this for every restricted page in your matrix, and keep a text file of restricted URLs so the next tester does not rebuild the list.


Step 4: ID swapping

URL tampering asks "can I reach this page?". ID swapping asks "can I reach someone else's record?".

Look at any URL with a number or an ID in it, such as https://staging.example.com/invoices/10432.

How to run it:

  1. Log in as [email protected] and open your own invoice. Note the ID: 10432.
  2. Change the number. Try 10431, 10433, and 1.
  3. Reload.

If you can see another customer's invoice, that is a real and serious bug. It has a name in security work: an insecure direct object reference, often shortened to IDOR. You do not need the name to report it. You need a screenshot showing an account name that is not yours.

Try the same idea in other places:

  • Two organisations. Log in as a member of Company A and open a record ID belonging to Company B. Cross-tenant leaks are the worst version of this bug.
  • Editing, not just viewing. Open your own invoice edit page, change the ID, and try to save.
  • IDs in the request body. Some forms send a hidden project_id field. Change it in DevTools before submitting.

If the IDs look like a3f1c8d0-7b21-4e9c-9f3a-2b1c8d0e7f45, you cannot guess the next one. That is not a fix. Get a real ID from another account and try it directly.


Step 5: Hidden but reachable actions

Buttons hidden with CSS are not security. Neither are menu items removed by a front-end check. The question is always what the server does.

You have three ways to reach a hidden action without writing code.

Method 1: Get the link from a higher role. Copy the URL from the admin session, paste it into the viewer session. This is URL tampering, and it covers most cases.

Method 2: Unhide the button. Right-click near where the button should be and choose Inspect. Look for an element with display: none, hidden, or a class like is-disabled. Untick that style in the Styles panel, or delete the disabled attribute. The button appears. Click it.

Method 3: Replay the request. In DevTools, do the action as admin. Open the Network tab, right-click the request, and choose Copy > Copy as fetch. Switch to the viewer profile, open the console, paste, and press Enter. You are sending the admin's request with the viewer's session. It sounds technical, but it is copy and paste. You only need to read the status code that comes back.

What you want is a 403 Forbidden. What you do not want:

  • 200 OK and the action worked.
  • 500 Internal Server Error, which means the server had no rule and simply crashed.
  • 200 OK with an error message in the body, because a badly written client might treat it as success.

Step 6: The role change tests

Roles are not fixed. People get promoted and demoted, and the moment of change is a good place to look.

Run these four:

  1. Demotion mid-session. User is logged in as Editor. An admin changes them to Viewer in another browser. The Editor does not reload, then clicks Save on a task. It should fail.
  2. Promotion mid-session. Viewer is upgraded to Editor. Do they need to log out and back in? If yes, say so in the interface.
  3. Removal from the project. Remove the user while they have the project open. Their next action should end at "You no longer have access".
  4. Last admin. Try to demote the only admin in the workspace. The app should refuse. If it agrees, the workspace is locked with nobody able to manage it.

Number four is not a security bug. It is a support-ticket generator, and it is cheap to catch.


How to write the bug report

Permission bugs get downgraded when the report is vague. Be specific and boring.

Include:

  1. The role that did the thing, with the exact account: [email protected].
  2. The action, in one line: "Viewer deleted a task created by another user".
  3. The request and the status: DELETE /api/tasks/7742 returned 200 OK.
  4. What the matrix says the answer should have been.
  5. Proof of effect. Reload as admin and show the task is gone.
  6. How you reached the action: pasted URL, unhidden button, or replayed request.

Point 6 matters. A developer's first reaction is often "but the button is hidden". Saying exactly how you got past that saves a round trip. Copying the request and status code out of DevTools by hand is fiddly, so some teams use a browser tool such as Crosscheck, which attaches the network requests and console output to the report automatically.

Bad: "Permissions not working for viewer role."

Good: "Viewer ([email protected]) can delete any task by pasting the admin delete URL. DELETE /api/tasks/7742 returns 200. Matrix says Viewer: No. Task is gone when reloaded as admin."


Frequently asked questions

Do I need permission before testing this way?

Yes, and get it in writing. Test on staging, use test accounts, and tell your team lead what you plan to do. Changing IDs on a production system with real customer data is not a test, and it can breach your own company policy.

What if the app has ten roles and eighty actions?

Do not build the full matrix at once. Start with the actions that destroy data, move money, or expose personal information. That is usually under fifteen rows, and it covers most of the risk. Grow the matrix each sprint.

Is this the same as penetration testing?

No. A penetration test is a broader, deeper exercise done by specialists, often with tools and source access. What you are doing is functional testing of permission rules. It overlaps at the edges, and it catches the obvious holes long before a specialist arrives.

The button is hidden. Is that enough for a low-risk action?

Hiding the button is good design, not a control. If the server allows the action, treat it as unprotected. For a low-risk action the fix may be low priority, but it should still be a ticket.

How do I test permissions on an API with no interface?

The same way, with a tool like Postman or curl. Get a token for the low-privilege account, call the restricted endpoint, and read the status code. The matrix stays exactly the same; only the way you press the button changes.

Related Articles

Contact us
to find out how this model can streamline your business!

Trusted by thousands ofengineering teams worldwide.

Add to Chrome
200+ reviews · 100k+ users
Crosscheck browser extension capture controls

Join the Crosscheck Community

Stay in the loop with Crosscheck's newest features and insights.