Rate Limiting Scans That Run Through a Private Location¶
Private Locations often target internal infrastructure that's sensitive to bursts of scanner traffic: corporate monitoring agents (Datadog or New Relic), SIEMs, WAFs, or test databases. Set a rate limit that your targets and monitoring systems can handle.
Use the recipes below to apply a consistent profile rate limit across a Private Location's scans. Browser crawling uses separate controls, described in Scope and Traffic Controls.
Set the Rate on Each Profile
Control the scan rate with network.requests_per_second on each profile using the Private Location. Apply it via PUT /v3/profiles/{profileId}/configuration, and reapply it whenever new profiles are created. See Recipe 1 below.
Profile-Based Rate Limiting¶
Set Escape's scan rate in the profile configuration with network.requests_per_second. A Private Location is a transport: it gives the scanner access to your network. The profile rate limit applies to API DAST requests, ASM HTTP requests, and port scanner probes, whether the profile uses an Escape-managed location or a Private Location. Configure browser crawling with its separate controls in Scope and Traffic Controls.
This keeps behavior explicit and reproducible:
- Profiles can use the same rate limit across different locations.
- A scan's configured rate is visible in the profile configuration and stays the same across locations.
- Per-profile rate limits can diverge (slow scans for internal monitoring-sensitive assets, fast scans for public staging).
To apply the same rate to all profiles using a Private Location, use the recipes below to update those profiles together.
Prerequisites¶
-
An API key with profile write access. Generate one from your user profile.
-
The ID of the Private Location you want to throttle, either from
escape-cli locations listor from the Private Locations page in the UI. -
A way to identify the profiles bound to that location. Select them by tag, name, or domain on
GET /v3/profiles. Its response doesn't includeproxyId, so choose a convention before starting:- Tag-based (recommended): tag every profile you create against the Private Location with a stable tag such as
location:site-internal. This makes the set explicit, searchable in the UI, and filterable via the API. - Name-based: prefix the profile name (for example
[Internal]or[MySite]). Filter with thesearchquery parameter onGET /v3/profiles. - Domain-based: filter with the
domainsquery parameter when all internal assets live under a distinct domain (for example*.internal.site.com). - Exhaustive: apply the rate limit to every profile in the organization. Appropriate when the Private Location is the dominant scan path (for example, the whole organization scans only internal assets).
- Tag-based (recommended): tag every profile you create against the Private Location with a stable tag such as
Recipe 1: One-Shot Rate Limit Across Profiles¶
Applies network.requests_per_second to every profile matching the selection convention you chose. Run it once after the initial rollout.
The script below assumes tag-based selection: replace TAG_ID and RATE to fit your environment. Both filtering variants (by search or domains) work with the same pattern; only the query string changes.
#!/usr/bin/env bash
set -euo pipefail
API_KEY="${ESCAPE_API_KEY:?export ESCAPE_API_KEY first}"
API="https://public.escape.tech/v3"
TAG_ID="00000000-0000-0000-0000-000000000000" # tag marking "internal" profiles
RATE=10 # requests_per_second to enforce
# 1. List every profile carrying the tag, walking the cursor.
cursor=""
while :; do
page=$(curl -fsS -G "$API/profiles" \
-H "X-ESCAPE-API-KEY: $API_KEY" \
--data-urlencode "tagIds=$TAG_ID" \
--data-urlencode "size=100" \
--data-urlencode "cursor=$cursor")
echo "$page" | jq -r '.data[].id' | while read -r profile_id; do
# 2. Read the current configuration, override requests_per_second, write it back.
current=$(curl -fsS "$API/profiles/$profile_id" \
-H "X-ESCAPE-API-KEY: $API_KEY" | jq '.configuration')
updated=$(jq --argjson rate "$RATE" \
'.network = (.network // {}) | .network.requests_per_second = $rate' \
<<< "$current")
curl -fsS -X PUT "$API/profiles/$profile_id/configuration" \
-H "X-ESCAPE-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --argjson cfg "$updated" '{configuration: $cfg}')" \
> /dev/null
echo "Updated $profile_id -> requests_per_second=$RATE"
done
cursor=$(echo "$page" | jq -r '.nextCursor // empty')
[[ -z "$cursor" ]] && break
done
What the script does:
- Pages through
GET /v3/profilesfiltered by a tag that identifies profiles bound to the Private Location. - For each profile, fetches the full configuration with
GET /v3/profiles/{profileId}. - Merges
network.requests_per_secondinto the existing configuration (preserving other settings, such as custom headers, authentication, and scope). - Writes the new configuration back with
PUT /v3/profiles/{profileId}/configuration.
Merge, Don't Overwrite
PUT /v3/profiles/{profileId}/configuration replaces the entire configuration body with what you send. Always read the current configuration first, patch only the fields you want to change, and write the merged object back. The jq step in the script above is the minimal safe pattern.
Valid Range for requests_per_second
The scanner configuration schema defaults to 100 and allows integers from 1 to 1000. New DAST profiles created in the UI start at 500; the UI rate slider allows 10 to 500. Set the rate explicitly for internal targets, then check the effect on your target and monitoring systems before raising it.
Recipe 2: Keep It Enforced for Newly Created Profiles¶
Recipe 1 updates existing profiles. New profiles need an explicit rate limit too: defaults differ between the scanner schema and the UI. Two approaches keep the rate limit in place as the environment evolves.
Option A: Enforce at Creation Time¶
Set the field inline when the profile is created, either via the DAST creation UI (Fine-Tune (Optional) → Duration → Rate limit) or via the API:
API_KEY="${ESCAPE_API_KEY:?export ESCAPE_API_KEY first}"
API="https://public.escape.tech/v3"
curl -X POST "$API/profiles/rest" \
-H "X-ESCAPE-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"assetId": "<ASSET_ID>",
"extraAssetIds": ["<SCHEMA_ID>"],
"name": "[Internal] Customer Service API",
"proxyId": "<PRIVATE_LOCATION_ID>",
"tagsIds": ["<INTERNAL_TAG_ID>"],
"mode": "read_only",
"configurationObject": {
"network": {
"requests_per_second": 10
}
}
}'
Any team that creates profiles against the Private Location should use this shape. If profile creation is scripted or templated, update the template once.
Option B: Periodic Re-Enforcement Job¶
To apply the rate to new profiles that follow your tag or name convention, wrap Recipe 1 in a scheduled job (GitHub Actions, GitLab CI schedules, or a Kubernetes CronJob) that runs nightly or hourly. It's idempotent: profiles already at the target rate are PUT with the same value and keep their configuration.
# .github/workflows/enforce-private-location-rate-limit.yml
name: Enforce private-location rate limit
on:
schedule:
- cron: "0 2 * * *" # every night at 02:00 UTC
workflow_dispatch:
jobs:
enforce:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Apply rate limit
env:
ESCAPE_API_KEY: ${{ secrets.ESCAPE_API_KEY }}
run: ./scripts/enforce-rate-limit.sh # the script from Recipe 1
Scope and Traffic Controls¶
- Combined traffic across scans. Control the combined rate through each profile's
network.requests_per_secondand the number of concurrent scans. Two concurrent scans each configured for 100 requests/second have a combined configured rate of 200 requests/second; actual throughput depends on connections and the target. These are per-profile settings, with no separate per-location requests-per-second ceiling. Concurrent connection limits close excess connections independently of the rate settings. See Resource Management. - Sensitive endpoints and domains.
network.requests_per_secondapplies across API DAST requests, ASM HTTP requests, and port scanner probes. The rate is configured per profile, rather than separately per endpoint or domain. Use the API Testing blocklist to exclude specific sensitive paths from API testing. - Browser crawling. Control crawling traffic (the browser fetching pages) with
frontend_dast.parallel_workersand related settings. Crawling uses these controls separately fromnetwork.requests_per_second. See WebApp Testing: Performance Tuning for the full model.
Related Documentation¶
- Public API: authentication, base URL, and other recipes
- Profiles Management: creating and listing profiles via the CLI
- API DAST: Rate Limiting: scanner-side rate limiting reference
- WebApp DAST: Performance Tuning: WebApp-specific throttling
- Private Location Logging & Monitoring: observing Private Location behavior in production