Manage allowed browser extensions
Maintain the list of browser extensions a security rule allows, adding or removing extension IDs from automation. Extensions are configured inside the rule's allowedOrBlockedExtensions control, so the workflow is different from list fields elsewhere in the API: there is no per-element delta, you read the control, change the array, and write it back whole.
Use this when:
- You curate a list of approved Chrome extensions and want a script to keep it current.
Prerequisites: a Super User service account and the environment variables from Getting started. Read Policy overview first.
export RULE_ID='0RLEXAMPLEEXTENSIONSXXXXXXXX' # a security rule
Pair an allow list with a block-all baseline. An allow list only matters if everything else is blocked. Make sure a baseline rule (or any lower-priority rule that matches the same users) sets the extensions policy to block all, and let this higher-priority rule carry the allow list. Without a block-all baseline, extensions not on your list are still allowed by default.
Gotcha: controls have no delta. Unlike a rule's scope or an access-and-data application list, controls are replaced whole. There is no addExtension / removeExtension. To change the list you must GET the rule, edit the extensions array yourself, and PATCH the entire control back. Read-modify-write is required here, so guard against concurrent editors (read immediately before you write).
1. Read the current control
curl -sS "$PB_API_BASE/policy/security/rules/$RULE_ID" -H "Authorization: Bearer $PB_TOKEN"
The relevant slice of the response:
{
"controls": {
"allowedOrBlockedExtensions": {
"mode": "allowByList",
"extensions": [
{ "id": "aapbdbdomjkkjkaonfhkkikfgjllcleb" }
]
}
}
}
| Field | Notes |
|---|---|
mode | allowAll, blockAll, allowByList, or blockByListOrRisk. Use allowByList to allow only the listed extensions. |
extensions | Array of { id }. Each id is a 32-character Chrome extension ID (a-p only). Up to 1000. |
2. Compute the new array
In your code, append the extension IDs to add and drop the ones to remove. The result is the complete new list.
3. PATCH the whole control
Send the full control body with the updated extensions array. Include mode so the control stays in allow-by-list mode:
curl -sS -X PATCH "$PB_API_BASE/policy/security/rules/$RULE_ID" \
-H "Authorization: Bearer $PB_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"controls": {
"allowedOrBlockedExtensions": {
"mode": "allowByList",
"extensions": [
{ "id": "aapbdbdomjkkjkaonfhkkikfgjllcleb" },
{ "id": "bfnaelmomeimhlpmgjnjophhpkkoljpa" }
]
}
}
}'
Response (200):
{ "id": "0RLEXAMPLEEXTENSIONSXXXXXXXX" }
Other controls on the rule are untouched. In a PATCH, controls you do not mention are preserved; only allowedOrBlockedExtensions is replaced because that is the only key you sent.
Verify and publish
curl -sS "$PB_API_BASE/policy/security/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 allowed extensions"}'
The GET returns the full rule (confirm the extensions array), and the publish returns 201 when a new active version is created.
Full script (Python)
import os, requests
base = os.environ["PB_API_BASE"]
headers = {"Authorization": f"Bearer {os.environ['PB_TOKEN']}"}
rule_id = os.environ["RULE_ID"]
def sync_extensions(to_add, to_remove):
# 1. Read immediately before writing (controls have no delta)
rule = requests.get(f"{base}/policy/security/rules/{rule_id}", headers=headers, timeout=30).json()
control = rule.get("controls", {}).get("allowedOrBlockedExtensions", {"mode": "allowByList", "extensions": []})
# 2. Compute the new array
current = {e["id"] for e in control.get("extensions", [])}
current |= set(to_add)
current -= set(to_remove)
# 3. PATCH the whole control back
body = {"controls": {"allowedOrBlockedExtensions": {
"mode": control.get("mode", "allowByList"),
"extensions": [{"id": i} for i in sorted(current)],
}}}
resp = requests.patch(f"{base}/policy/security/rules/{rule_id}", headers=headers, json=body, timeout=30)
resp.raise_for_status()
return resp.json()["id"]
sync_extensions(
to_add=["bfnaelmomeimhlpmgjnjophhpkkoljpa"],
to_remove=[],
)
# Publish (full, or partial-publish is not yet available for policy objects)
requests.post(f"{base}/configuration-management/draft/publish", headers=headers,
json={"description": "Update allowed extensions"}, timeout=30)
partial publish does not yet support policy objects, so publish the whole draft (or stage extension changes in their own publish cycle).
Related
- Policy: Policy overview, Security rules
- Concepts: Delta patch (why controls differ from list fields)
