Product documentation
Updated October 4, 2026

API Security Testing: Check That Your API Refuses What It Should

A functional test asks whether your API gives the right answer to a correct request. A security check asks the opposite: when someone sends a request they should not be allowed to send, does your API refuse it?

Applies to: Professional, Custom and Enterprise licences with the Security testing add-on, and Trial (Free shows a locked preview only) · Web app and Desktop app · Viewing needs permission to view security findings; generating and running checks need more (see Editions and permissions)

Overview

A functional test asks whether your API gives the right answer to a correct request. A security check asks the opposite question: when somebody sends a request they should not be allowed to send, does your API refuse it — and when it answers, does it say anything it should not?

Shift-Left Studio answers that with ordinary tests. There is nothing separate to install and no second copy of your endpoints, environments or credentials. Studio generates security checks beside the functional tests you already have, in the same project. They live in your packs, run on your schedules, run from your CI and appear in your reports like any other test.

Three things happen when they run:

  • Checks are sent. A request with no credentials, a token that has expired, a database quote in a text field, a body far larger than the API should accept. Each passes if the API refuses it or treats it as harmless data.
  • Ordinary responses are read. Responses your other tests already received are inspected for weak settings — a missing security header, a session cookie without the usual protections, a version number the API did not need to publish, a card number in a body. No extra request is sent for these.
  • Failures become findings. A failed check becomes one finding per endpoint, check and field, however often it runs, with a severity, the standards it maps to, the evidence, and how to fix it.

Every report begins with what it could not check, and why. That is deliberate: a report that quietly counts an unanswerable probe as a pass is worse than no report.

You find it under Security in the top bar. Everything in it belongs to a project.

Key concepts

TermWhat it means
CheckOne question about safety, such as "a request without credentials is refused". A check becomes tests on your endpoints.
ProbeOne request sent by a check against one endpoint and one field.
PostureWhere the project stands, in words — critical weaknesses open, high-severity open, every check that ran passed, or nothing evaluated yet. There is no single score.
PassiveOnly reads responses your tests already produced. Sends nothing extra.
ActiveSends extra harmless requests of its own: the authentication, input and second-user checks.
IntrusiveSends volume — request bursts, very large or deeply nested bodies. Off until an Administrator allows it on an environment, and never on production.
Data-changingChanges or deletes data to get its answer. Off until an Administrator allows a specific non-production environment.
PrincipalA second test user, set up as an authentication profile with a label, so Studio can ask "is this user refused?".
Access matrixYour answers, per endpoint and principal: Allow, Deny or Own data only. They build the second-user checks.
Object-level authorization (OWASP calls it BOLA)Whether one user can read or change another user's order or profile simply by putting that object's id in the URL. The commonest serious API flaw.
Server-side request forgery (SSRF)Where your API accepts a URL from the caller and fetches it. If it will fetch an internal address, a caller reaches things only your servers should reach.

Before you begin

  • A project with endpoints, imported from a specification or created in Studio.
  • An environment with a base URL and working authentication. Without a profile that can sign in, every check needing a credential reports itself as not evaluated rather than passing.
  • A test environment. Checks are refused on production unless an Administrator has specifically allowed them, and the intrusive and data-changing checks never run against production at all.
  • Worth doing early: a second test user, added as an authentication profile with a principal label. Without one, Studio cannot ask whether one user is refused another user's data — the question that finds the most serious flaws.
  • Permission to generate and run checks. Generating creates and updates tests, so it also needs the test create, edit and delete permissions.

Step 1 — Generate the security checks

  1. Open Security in the top bar and pick your project.
  2. In Security checks on your endpoints, select Generate security checks.
  3. Leave it running. It works through every endpoint as a background job and the panel says when it finishes. Running it again is safe: it refreshes the existing checks rather than duplicating them.

What you get are ordinary tests, marked with a Security check chip in the test table, covering the authentication, input-handling, mass-assignment, privilege, request-forgery, cross-origin and error-detail checks for each endpoint.

Note: A generated security check cannot be edited to accept the behaviour it exists to detect. Fix with AI, bulk repair and the project assistant all refuse, and say why. The fix for a security finding belongs in the API, not in the test.

Tell Studio what it cannot work out

Two inputs make a large difference, both under Security settings:

  • Principals. Add a second authentication profile and give it a Principal label. Mark it Can own data if it owns records such as orders or profiles. If it belongs to a different tenant, give it a Tenant label too, so Studio can ask the cross-tenant question as well as the cross-user one.
  • Access matrix. Per endpoint and principal, answer Deny (must be refused outright), Own data only (may call it, but only on objects it owns) or Allow (a shared or public resource, never probed). Deny builds the privilege checks; Own data only builds the object-level checks.

Where you have not answered, Studio infers that a credential-protected read of a single object by id is probably owner-scoped. Those cells are shown as inferred, and a finding resting on one is marked Suspected with its evidence — confirm the rule to keep the check, or mark the endpoint Allow if the resource really is shared. Studio never infers Deny: a guessed refusal would open a finding nobody could defend.

Step 2 — Run them

ControlWhat it runs
Run security checksThe checks on your endpoints, against an environment you pick. This is the main one.
Scan environment nowThe server-level checks only — TLS and certificate, plain HTTP, management paths, old API versions, exposed specifications and consoles and, where allowed, the request burst. On its own it says nothing about your endpoints.
Any pack or scheduleSecurity checks run inside the packs that carry them, like any other test, including from CI.

When Run security checks finishes, the message underneath is the run's own conclusion — probes judged, passed, failed, not judged, and what changed since last time. Open the full report of this run goes straight to the report.

Important: Sending requests to a system is a deliberate act. Pick an environment you are allowed to test, and read Checks that change data before allowing anything beyond the default.

Step 3 — Read the findings

Where this project stands is the worst open finding, in words. Accepted risks and false positives are shown but never decide it. There is deliberately no single score: one number hides which category is on fire.

Open a finding for What a safe API does, What was seen, How to fix it and the standards it fails. Each carries a CVSS v3.1 base score and vector — calculated from fixed values per check, never from a model — so it can be pasted into a tracker, plus an Owner, a Ticket link and a due date defaulted from the severity (7 days for critical, 30 high, 90 medium, 180 low). The severity word can sit one band from the score, because it also carries Studio's own adjustments: production raises it, an endpoint with no authentication to bypass lowers it. A finding about exposed data says what kind it was — an authentication secret, financial, personal or confidential — read from what the check found, never guessed from a value nobody saw.

Finding statuses

StatusMeaning
OpenThe check failed in the latest run.
Came backIt was fixed, then failed again.
FixedThe check passed after being open.
Accepted riskA person decided to live with it, and said why.
False positiveA person decided it is not a real weakness, and said why.
RetiredThe test that found it no longer exists.

Only a person changes a finding. Accept this risk and Mark as false positive both require a reason, kept with the decision, and a run never overwrites a decision a person made. If a run changes a finding while you are deciding about it, your decision is refused and you are asked to reload, so you never decide about something you did not see.

"Not evaluated" is not a pass

What was not checked is the first thing on every report, because several honest answers are neither pass nor fail:

  • A gateway, firewall, sign-in page or rate limiter answered instead of your API. That says nothing about the API.
  • The object addressed does not exist. A probe on PUT /orders/{id} with an id not on that environment is answered "not found" before the API reads the body. Studio creates a real object first where it can (see How by-id probes find a real object); where it cannot, give the test a real id and run again.
  • The request was refused at sign-in. An input check sent without credentials to an endpoint that needs them never reaches the input handling; add an authentication profile for the environment. (The authentication checks are the exception: they send no credentials on purpose, and a refusal is their pass.)
  • A request-forgery probe was accepted and showed nothing. If the API takes an internal address and returns nothing visible, it may still have fetched it out of sight. Reported as not evaluated, never as a pass, unless out-of-band confirmation is configured.
  • A check needed something only you can supply (see Checks that need something from you).

While anything could not reach what it tests, the posture reads Incomplete: some probes got no answer, with the reasons counted.

Important: "No findings" is true of the checks that ran. It is not a statement about your API. Every report, and the CI gate, says so.

A refusal with a different status than the check expected — a 403 where 401 was written — is the API doing its job, and is not a finding.

What is checked

94 of the 100 checks in the catalogue run today. The six that do not are listed on every report with the reason, and are either intrusive bursts that are off until switched on, or need a collector that only the cloud service hosts — see What it deliberately does not do. Each check maps to the OWASP API Security Top 10 (2023), OWASP ASVS and CWE. That mapping is evidence of testing, not a certification.

AreaWhat it asks
Getting in without a credentialIs a request with no credentials refused? A malformed, empty, unsigned or wrongly signed token? One that has expired, came from another issuer, is not yet valid, or was issued for somebody else? A credential sent in the URL, or over plain HTTP? Is a public operation that writes, or that returns personal data, really meant to be open?
One user reading another's dataIs a second test user refused an object they do not own, or one belonging to another tenant? An id belonging to somebody else, or the ids either side of one you do own?
Fields the caller should not setCan a protected field, a role or an admin flag be set from the request body? Do responses carry secrets — private keys, card numbers, access keys — or fields the specification never documented?
Being overwhelmedIs an oversized body refused without a server error? Is a burst rate-limited, and can the limit be bypassed by spoofing a forwarding header, or exhausted by one caller so others are refused? Is an enormous page size refused?
Operations the caller should not reachDoes an operation refuse a user marked Deny? Are management paths beside the API closed and undocumented methods refused? Is a destructive MCP tool, or a data-changing WebSocket method, refused to a lesser user? Does a SOAP operation's body under another operation's SOAPAction return a Fault?
ConfigurationSecurity headers, cache control, cookie attributes, no version number in server headers, TLS version and certificate, plain HTTP refused or redirected, cross-origin requests not echoing an untrusted origin, framework consoles and raw specifications not served beside the API, every OAuth endpoint on https.
Input treated as dataDatabase quotes, shell characters, path sequences, a query operator where a plain value was documented, directory-service and XPath fragments, a line break that could forge a response header, a template expression, markup — each must be stored or rejected as text, with no database error, command output, file content or stack trace coming back.
Requests your API makesDoes a URL field refuse internal destinations? Several are sent, because each defeats a different defence: the cloud metadata address, that host by name, a loopback address and port, the same address as a decimal number, and a trusted-looking name before an @ with the real target after it. An API refusing only the first has been asked very little.
Old and undocumented versionsAre older or undocumented versions of a path still answering?
What your sign-in server says about itselfRead from the authorization server's own public discovery document: does it advertise the modern proof-key extension, has it dropped the two unsafe grant types, and does it refuse unsigned tokens? Needs the issuer URL on the OAuth profile — asked for rather than guessed, because two tenants on one host publish different documents.
Sign-in strengthAre repeated wrong passwords slowed or locked out? Is a session identifier replaced at sign-in? Does a token stop working after sign-out?

Business rules: you confirm, Studio checks

Some of the worst flaws break no schema. An API that lets the client set the amount charged answers 200 every time. So Studio asks a model to propose invariants from your specification — "the total equals the sum of its lines", "a new order is never already paid" — and asks you to confirm them. Nothing proposed is checked, and nothing the model wrote is executed: an invariant is a comparison in a small fixed grammar, so you read exactly what will be asserted before agreeing. A confirmed one then reads responses your ordinary tests already produced and sends nothing of its own; a response that cannot answer it is reported not evaluated rather than broken.

How by-id probes find a real object

A check that reads or changes an object by its id is worthless against an id that does not exist — the API answers "not found" before it reaches the behaviour being tested. So before a run, Studio copies your project's own passing create test to make a real object, threads its id into the probes that need one, and copies the passing delete test to remove it afterwards. The id is read from the create response that your tests actually received, never guessed from the schema; where a collection has no such create-and-delete pair, the affected probes are honestly reported as not evaluated rather than run against a made-up id.

Checks that need something from you

Some checks are built and runnable, but cannot be honest without something only you can tell Studio. Each names what to add rather than guessing — a guess in either direction is worse than a gap you can see — and reports itself not applicable until you supply it.

CheckWhat it needs, and why
Mutual TLSThe environment marked as requiring a client certificate. From outside, an API that chose not to use client certificates and one that meant to and failed look identical.
Repeated wrong passwords (lockout)An account marked as a throwaway on a "Sign in with steps" profile — because when your API is doing its job, this check locks that account. Your real password is never used: a deliberately wrong one is substituted. A progressive delay counts as protection. If a wrong password is accepted, you are told that instead; it is the worse finding. This check is intrusive and never runs against production.
Session fixation and revocationA "Sign in with steps" profile with its session identifier named, and the call that ends a session named too — a path called /logout may be a page, and the real one may be a delete on a session resource. Only you know. An API issuing bearer tokens has no session identifier to fix, and that is reported as not applicable, not as a pass.
Business rulesYour confirmation of each proposed invariant. Nothing is asserted until you agree to it.
Object-level and privilege checksA second principal, and the access matrix cells that say who should be refused what. Without a second user, the checks that find the most serious flaws cannot run.

Checks that change data

Several checks cannot be done by reading. Changing or deleting another user's object, reusing an idempotency key, and replaying a one-time operation all alter data.

They are built and offered, and off until an Administrator allows a specific non-production environment for data-changing checks, under Security settings. The allowance is per environment, because whether an API may have its data changed is a property of that environment, not of the installation. They are never run against production, whatever is allowed.

The data-changing checks are built from a workflow Studio synthesises from your project's own positive tests — create an object as one user, then try to change or delete it as a second principal, or post the same request twice with one idempotency key. Studio never invents a request, a principal, an id or a body: where it cannot derive a step from a passing test, the preview names what is missing and the check is not built rather than guessed.

Automating this

Security checks run in your packs, so they run wherever your packs run. The headless runner can additionally write a standards file and fail the build:

node backend/scripts/run-pack-cli.js --url https://acme.totalshiftleft.ai --token "$SLSTUDIO_TOKEN" \
  --pack <packId> --project <projectId> --sarif security.sarif --fail-on-severity high --only-new
  • --fail-on-severity (critical, high, medium or low) fails the build while any open or returned finding at or above that severity exists. Accepted risks and false positives never fail a build.
  • --only-new limits the gate to findings first seen in this run, or that came back, so a team can adopt it without first clearing its backlog. A --policy file adds per-severity count limits instead, to tolerate a known backlog while refusing to grow it.
  • The run records the build it judged, read automatically from GitHub, GitLab, Azure, Jenkins and CircleCI, so a new finding is attributable to a build and not only to a moment.

From any run you can download the HTML report (one self-contained page that loads nothing from anywhere — print it to PDF or attach it to a ticket), all results as CSV, findings as CSV, JUnit XML for any CI that reads test results, SARIF for GitHub code scanning or Azure DevOps Advanced Security, and the full JSON. Reports → Security scans lists the runs of every project you can open. Evidence is never part of a SARIF file. Viewing a report needs only permission to view findings; the downloads need permission to export reports.

Ready-made CI templates for GitHub Actions, GitLab, Azure Pipelines, Jenkins and TeamCity are in the product's docs/ci-templates/ folder.

What it deliberately does not do

Studio detects; it does not attack. These are either off by default or not built, and every report lists them with the reason rather than leaving them out:

  • Blind request-forgery and out-of-band confirmation. Confirming that the API fetched an internal address it was given needs a collector listening on the public internet. That is opt-in and hosted only by the cloud service; a stock or desktop install reports an accepted-but-unproven forgery as not evaluated, never as a pass.
  • GraphQL amplification and structurally overloaded bodies. Deeply nested, batched or alias-multiplied queries, and very deeply nested request bodies, cost the server to parse. They are intrusive and off until an Administrator switches them on for an environment.
  • Guessing default passwords, which means attacking a real account.
  • Working exploits. The checks use recognisable but inert markers and look for a leak or a refusal.

A sign-in server that says nothing about the proof-key extension is reported unstated rather than failed, because your server may support it without advertising it.

Editions and permissions

API security testing is a paid add-on to a Professional, Custom or Enterprise licence. No edition includes it on its own, Enterprise included. A Trial includes it so it can be evaluated. A Citizen Developer (Free) licence sees a locked preview with example data and cannot buy the add-on.

If the add-on ends, your findings, runs and reports stay readable; new runs and changes to findings stop until it is renewed, and an Administrator is warned 14 days before. The product picks up a change to your licence at its next check with the licence server, within 4 hours; an Administrator can press Refresh on the License page to check at once. If both add-on screens stay locked straight after a purchase, re-enter the same licence key under Change License — activating saves the new entitlement immediately.

PermissionLets a user
View security findingsSee findings, runs and reports
Run security checksStart a run
Manage security findingsAccept a risk, mark a false positive, or reopen a finding
Manage security settingsConfigure security testing, principals and the access matrix

Contributors can view and run, Reviewers can view and manage findings, Readers can view. Exporting uses the existing Export reports permission. A project you cannot see answers exactly like a project that does not exist.

On the desktop app, a check that is a single request — including a second-user check — runs on the local runner like any test. A check needing the server (several requests in sequence, a request burst, an environment probe) is refused by name rather than quietly run as a different, single request; run those with the server runner.

Which protocols does it cover?

REST, SOAP, GraphQL, JSON-RPC 2.0 / MCP and WebSocket-RPC. The authentication, input and configuration checks apply across all of them; a few are protocol-specific, such as GraphQL introspection, a SOAP operation under the wrong SOAPAction, a destructive MCP tool or a data-changing WebSocket method offered to a lesser user, and a WebSocket type-confusion probe. (JSON-RPC / MCP and WebSocket-RPC testing are themselves Trial and Enterprise capabilities; see the linked articles.)

Best practices

  • Add a second test user early. The object-level and privilege checks find the serious flaws, and cannot run without one.
  • Fill in the access matrix for your important endpoints rather than relying on what Studio infers. Your answer always wins, and an inferred one produces a Suspected finding instead of a confident one.
  • Read "What was not checked" first, every time. A clean posture with twenty unanswerable probes behind it is not a clean API.
  • Give the probes real data. Most unanswerable probes are a missing object id or a missing authentication profile, and both are quick to fix.
  • Adopt the CI gate with --only-new so the build starts failing on new weaknesses without first demanding you clear a backlog.
  • Treat a finding as an API change. If the only way to make a check pass is to change the check, the check is doing its job.

FAQ

Is this a penetration test? No, and it is no substitute for one. Studio sends benign probes and reads the answers. It does not exploit anything, it does not chain weaknesses together, and it has none of the judgment about your business that a person brings. It is a regression net: it catches the classes of weakness it knows about, every time your tests run.

Does "every check that ran passed" mean my API is secure? No. It means the checks that ran passed. The checks that could not be judged, and the ones not available at all, are listed on every report precisely so that sentence is never read as more than it says.

Can an AI agent run security checks? An AI coding agent connected through the MCP server can read your posture, findings and reports, but it cannot start a security run — that stays a person's action, exactly as it is for load tests. Inside the product, the project assistant can explain any finding from its evidence and say what was not checked; if you tell it you accept a risk or that a finding is a false positive, and give your reason, it will offer to record that. It never suggests the decision itself, never writes your reason for you, and never changes a check.

What is stored? Evidence is redacted and capped before it is saved. Credentials, cookies and authorization values are never stored, and neither are response bodies. A leaked card number is reported by its last four digits only, and a private key as "a private key block (not shown)". You can set how many days evidence is kept under Security settings; findings and their history are always kept, because that is how "fixed" and "came back" are known.

Does it map to compliance frameworks? Each check maps to the OWASP API Security Top 10, ASVS and CWE, and a compliance view rolls those up against PCI DSS 4.0, SOC 2, ISO 27001:2022, NIST SSDF, GDPR and HIPAA. It is evidence of testing, not a certification.

For administrators (self-hosted installations)

SettingDefaultWhat switching it does
SECURITY_TESTING_ENABLEDonfalse removes the feature entirely from this installation.
SECURITY_PASSIVE_OBSERVER_ENABLEDonfalse turns off only the checks that read ordinary responses.
SECURITY_INTRUSIVE_TIER_ENABLEDonfalse turns off request bursts and the other intrusive checks on every project, whatever an Administrator has allowed per environment.
SECURITY_AUTHZ_WORKFLOWS_ENABLEDonfalse removes the data-changing checks from the installation. The per-environment allowance is the ordinary control; use this only where no environment should ever be tested that way.
SECURITY_OAST_ENABLEDofftrue (with SECURITY_OAST_BASE_URL) turns on out-of-band confirmation of blind request-forgery. Cloud-only.

The per-environment allowances — active checks on a production environment, intrusive checks, and data-changing checks — are set by an Administrator under Security settings for the project, and recorded with who allowed them and when. Intrusive and data-changing checks are refused on a production environment regardless.

Next steps

Still stuck?

Tell us what you’re trying to accomplish and we’ll point you to the right setup—installation, auth, or CI/CD wiring.