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 andxn--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.
Links
- Website: https://lianes8studio.com
- GitHub: https://github.com/JaviLianes8
- LinkedIn: https://www.linkedin.com/in/jlianes/
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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| permspolicy-0.2.0.tar.gz | 37.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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.