Skip to main content

Govern URLs with an allow/block list

Keep a list of URLs that an access and data rule allows or blocks, and let automation add or remove entries as your source of truth changes. There are three ways to model this; the right one depends on how the list grows and how much you want to touch the rule.

Use this when:

  • You maintain an allow list or block list of URLs that changes over time.
  • You want a script (CMDB, risk feed, ticket workflow) to own the membership.

Prerequisites: a Super User service account and the environment variables from Getting started. Read Applications, Application groups, and Delta patch first.


Approaches

ApproachHow it worksBest whenTradeoffs
A. One custom application holds all URLsCreate one custom application, link it to the rule once, then add/remove URLs on the applicationThe list is a flat set of URLs you manage as a wholeUp to 100 URLs per application; individual URLs are not separately named
B. One application per URL, groupedCreate one custom application per URL (named), put them in an application group, link the group to the rule onceYou want each URL individually named/identifiable (reporting, audit)More objects to manage; group caps at 2000 applications
C. URLs directly on the ruleAdd/remove URLs in the rule's application scope via delta patchA small, rule-specific list you do not reuse elsewhereThe rule object churns; not reusable across rules

Recommendation: use A for a simple managed list, B when each URL must be individually named or reused across rules, and C only for small one-off lists. A and B share a key advantage: you link the object to the rule once and never touch the rule again, all churn happens on the application or group.


Approach A: one custom application holds all URLs

curl -sS -X POST "$PB_API_BASE/applications/type/custom" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "type": "custom", "name": "Blocklist - marketing trackers", "urls": [ { "url": "tracker.example.com" } ] }'
{ "id": "0AP01BLOCKLISTXXXXXXXXXXXXXXX" }
export APP_ID='0AP01BLOCKLISTXXXXXXXXXXXXXXX'
export RULE_ID='0RLEXAMPLERULEXXXXXXXXXXXXXX' # an access-and-data rule

Attach the application to the rule's SaaS scope once:

curl -sS -X PATCH "$PB_API_BASE/policy/access-and-data/rules/$RULE_ID" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "applications": { "saas": { "accessMode": "specific", "specific": { "addApplicationIds": ["'"$APP_ID"'"] } } } }'

Response (200):

{ "id": "0RLEXAMPLERULEXXXXXXXXXXXXXX" }

2. From now on, edit the URLs on the application

curl -sS -X PATCH "$PB_API_BASE/applications/type/custom/$APP_ID" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "type": "custom", "urls": { "add": [ { "url": "newtracker.example.com" } ], "remove": [ { "url": "tracker.example.com" } ] } }'

Response (200):

{ "id": "0AP01BLOCKLISTXXXXXXXXXXXXXXX" }

The rule never changes again. Publish (or partial-publish only the application).

note

One application holds up to 100 URLs. If your list can exceed that, split across multiple applications or use approach B.


Approach B: one application per URL, grouped

1. Create one named application per URL

curl -sS -X POST "$PB_API_BASE/applications/type/custom" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "type": "custom", "name": "Allow - Figma", "urls": [ { "url": "figma.com" } ] }'

Response (201):

{ "id": "0AP01FIGMAXXXXXXXXXXXXXXXXXXX" }

Each application gets its own ID and a human-readable name, so it shows up identifiably in reporting.

curl -sS -X POST "$PB_API_BASE/application-groups" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Approved design tools", "applications": [ "0AP01FIGMAXXXXXXXXXXXXXXXXXXX" ] }'
{ "id": "0AG01DESIGNXXXXXXXXXXXXXXXXXX" }
export GROUP_ID='0AG01DESIGNXXXXXXXXXXXXXXXXXX'

Attach the group to the rule once:

curl -sS -X PATCH "$PB_API_BASE/policy/access-and-data/rules/$RULE_ID" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "applications": { "applicationGroups": { "accessMode": "specific", "specific": { "addApplicationGroupIds": ["'"$GROUP_ID"'"] } } } }'

Response (200):

{ "id": "0RLEXAMPLERULEXXXXXXXXXXXXXX" }

3. From now on, add/remove applications on the group

curl -sS -X PATCH "$PB_API_BASE/application-groups/$GROUP_ID" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "applications": { "add": [ "0AP01MIROXXXXXXXXXXXXXXXXXXXX" ], "remove": [ "0AP01FIGMAXXXXXXXXXXXXXXXXXXX" ] } }'

Response (200):

{ "id": "0AG01DESIGNXXXXXXXXXXXXXXXXXX" }
note

A group holds up to 2000 applications. Approach B is the most granular, but it creates the most objects.


Approach C: URLs directly on the rule

For a small list you do not reuse, skip the application entirely and patch URLs onto the rule's SaaS scope:

curl -sS -X PATCH "$PB_API_BASE/policy/access-and-data/rules/$RULE_ID" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "applications": { "saas": { "accessMode": "specific", "specific": { "addUrls": [ { "url": "https://newtool.example.com" } ], "removeUrls": [ { "url": "https://oldtool.example.com" } ] } } } }'

Response (200):

{ "id": "0RLEXAMPLERULEXXXXXXXXXXXXXX" }

This is the simplest to start but churns the rule object on every change and cannot be reused by another rule.


Verify and publish

curl -sS "$PB_API_BASE/policy/access-and-data/rules/$RULE_ID" -H "Authorization: Bearer $PB_TOKEN"

curl -sS -X POST "$PB_API_BASE/configuration-management/draft/publish" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"description": "Update allow/block list"}'

The GET returns the full rule (confirm the applications), and the publish returns 201 when a new active version is created.


Full script (Python, approach A)

import os, requests

base = os.environ["PB_API_BASE"]
headers = {"Authorization": f"Bearer {os.environ['PB_TOKEN']}"}
rule_id = os.environ["RULE_ID"]

# 1. Create the list-holder app (once)
app_id = requests.post(
f"{base}/applications/type/custom", headers=headers,
json={"type": "custom", "name": "Blocklist - marketing trackers", "urls": [{"url": "tracker.example.com"}]},
timeout=30,
).json()["id"]

# 2. Link it to the rule (once)
requests.patch(
f"{base}/policy/access-and-data/rules/{rule_id}", headers=headers,
json={"applications": {"saas": {"accessMode": "specific", "specific": {"addApplicationIds": [app_id]}}}},
timeout=30,
).raise_for_status()

# 3. Ongoing: sync URLs on the app
def sync_urls(to_add, to_remove):
urls = {}
if to_add: urls["add"] = [{"url": u} for u in to_add]
if to_remove: urls["remove"] = [{"url": u} for u in to_remove]
if not urls: return
requests.patch(f"{base}/applications/type/custom/{app_id}", headers=headers,
json={"type": "custom", "urls": urls}, timeout=30).raise_for_status()

sync_urls(["newtracker.example.com"], ["tracker.example.com"])

# 4. Publish just the app
requests.post(f"{base}/configuration-management/draft/partial-publish", headers=headers,
json={"entityIds": [app_id], "description": "Update blocklist"}, timeout=30)