Skip to main content

permspolicy

Parse, build and check the HTTP Permissions-Policy header, the iframe allow attribute and the legacy Feature-Policy header, with a typed, zero-dependency API.

Install

pip install permspolicy

Usage

Read a header the way a browser does, and ask what it allows:

from permspolicy import parse

policy = parse('geolocation=(self "https://*.maps.example"), camera=(), fullscreen=*')
print(list(policy))
# ['geolocation', 'camera', 'fullscreen']
print(policy["geolocation"].entries())
# ('self', 'https://*.maps.example')
print(policy["camera"].disabled)
# True
print(policy["geolocation"].allows("https://tiles.maps.example"))
# True
print(policy["geolocation"].allows("https://shop.example", self_origin="https://shop.example"))
# True

The field is a structured field Dictionary (RFC 9651). A value that does not parse is ignored as a whole, as browsers ignore it, and entries the spec drops are dropped. strict=True raises instead, with the offset of the problem:

from permspolicy import PermissionsPolicyError, parse

print(parse('geolocation=(self), camera=("https://a.example" , self)'))
# Policy({})
try:
    parse("geolocation=(self), camera=(self", strict=True)
except PermissionsPolicyError as error:
    print(error.offset)
# 32
print(parse("geolocation=(self);report-to=main").report_to)
# {'geolocation': 'main'}

Build one from plain entries ("*", "self" or source expressions) or from Allowlist values. Anything that would not read back the same way raises, so untrusted input cannot add a header line or another member:

from permspolicy import Allowlist, PermissionsPolicyError, Policy, build

print(build({"geolocation": ["self", "https://a.example"], "camera": [], "fullscreen": "*"}))
# geolocation=(self "https://a.example"), camera=(), fullscreen=*
print(build(Policy({"usb": Allowlist.nobody()}, report_to={"usb": "main"})))
# usb=();report-to=main
try:
    build({"camera": ["https://a.example\r\nSet-Cookie: x=1"]})
except PermissionsPolicyError as error:
    print("refused")
# refused

The iframe allow attribute and the legacy Feature-Policy header share an older syntax. Read it, write it, or convert it to the header:

from permspolicy import build, build_allow, parse_allow, parse_feature_policy

frame = parse_allow("camera; geolocation 'self' https://maps.example/embed")
print(frame["camera"].entries(), frame["geolocation"].entries())
# ('src',) ('self', 'https://maps.example')
print(build_allow({"microphone": [], "fullscreen": "*"}))
# microphone 'none'; fullscreen *
print(build(parse_feature_policy("geolocation 'self'; camera 'none'")))
# geolocation=(self), camera=()

Check feature names against the features list kept with the spec:

from permspolicy import RETIRED_FEATURES, parse

policy = parse("camera=(), geolocaton=(), document-domain=()")
print(policy.unknown())
# ('geolocaton', 'document-domain')
print("document-domain" in RETIRED_FEATURES)
# True

Command line

$ permspolicy 'geolocation=(self), camera=()' --origin https://a.example --self-origin https://a.example
{
  "features": {
    "geolocation": {
      "wildcard": false,
      "self": true,
      "src": false,
      "expressions": [],
      "allows_origin": true
    },
    "camera": {
      "wildcard": false,
      "self": false,
      "src": false,
      "expressions": [],
      "allows_origin": false
    }
  },
  "unknown": []
}
$ permspolicy --feature-policy --to-header "geolocation 'self' https://a.example"
geolocation=(self "https://a.example")

The value can also come from standard input. --allow and --feature-policy read the older syntax, --to-header and --to-allow convert, --strict exits 1 on a value the spec would ignore, and --check exits 1 when a feature name is not in the features list.

API

Name What it does
parse(header, *, strict=False) Read a Permissions-Policy (or -Report-Only) value, bytes or field lines into a Policy.
build(policy) Serialize a Policy or a mapping of entries; refuses anything that would not read back.
parse_allow(value, *, strict=False) Read an iframe allow attribute.
build_allow(policy) Write an iframe allow attribute.
parse_feature_policy(value, *, strict=False) Read a legacy Feature-Policy value.
build_feature_policy(policy) Write a legacy Feature-Policy value.
Policy Read-only mapping of feature to Allowlist, with report_to and unknown().
Allowlist Frozen allowlist: wildcard, includes_self, includes_src, expressions, allows().
serialize_origin(url) The origin of a URL (https://a.example:8443), or None when it is opaque.
is_source_expression(text) True for an entry the header accepts (https:, *.a.example, an origin).
KNOWN_FEATURES Every standardized, proposed and experimental feature name.
STANDARDIZED_FEATURES, PROPOSED_FEATURES, EXPERIMENTAL_FEATURES, RETIRED_FEATURES The features list by status.
PermissionsPolicyError A ValueError for invalid policies and strict parse failures, with offset.

Limits

  • Origins are computed for http, https, ws, wss and ftp (and blob: URLs over http or https). Other schemes have opaque origins and are dropped, as browsers drop them. Non-ASCII hosts, percent-encoded hosts, IPv6 literals, short IPv4 forms and xn-- labels that do not decode to letters and digits are refused rather than normalized.
  • A member whose value is neither *, self, a String nor an Inner List is declared with an empty allowlist, as the spec algorithm does, so the feature is disabled. A bare String holding a source expression is read as a one-entry allowlist, as the spec prose allows.
  • Feature names are not filtered when parsing: a browser ignores names it does not know, and which names it knows depends on the browser. Policy.unknown() lists them.
  • Inheritance through frames and the default allowlist of each feature need a browsing context and are out of scope.

Why

Permissions-Policy builders already exist, but they are write-only and tied to a framework, and none reads a header back the way a browser does. permspolicy parses on the structured field grammar, builds only what parses back, answers whether an origin is allowed, and handles the allow attribute and the legacy header too.

Reference files

The tests check the code against the Permissions Policy spec, its features list, CSP3 and the httpwg structured field tests. Those files are not part of the repository; python scripts/fetch_upstream.py fetches them into tests/upstream/ and checks them against tests/upstream/MANIFEST.json. Without them, the tests that read them skip.

License

MIT. See LICENSE.

Metadata

Release files for permspolicy 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for permspolicy 0.2.0
File Size Uploaded Provenance
permspolicy-0.2.0.tar.gz 37.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for permspolicy 0.2.0
File Interpreter ABI Platform
permspolicy-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 62.0 kB

Release files / permspolicy-0.2.0.tar.gz

Download URL permspolicy-0.2.0.tar.gz
Size 37.9 kB
Tags Source
SHA-256 checksum
How to use checksums
d02981e76a605c96c217646ceda6a403b71895cf532ba5bf2fd77e56ad8fcf34
BLAKE2b-256 checksum
How to use checksums
b104dffa213f5d44cf4503723275b3b58a907b6f1e4d5dbd7b7fcd5b8ef50a5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release files / permspolicy-0.2.0-py3-none-any.whl

Download URL permspolicy-0.2.0-py3-none-any.whl
Size 24.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bca7bbfe15bf6851f462178bd5ef6e70ce24525f84e9d22b27346f8705ade610
BLAKE2b-256 checksum
How to use checksums
d44b5afbcefa6b0839aaea8bebb6e6f1e51d4949c6e7bf85b84131055eacb5f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Security for permspolicy 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page