wingfoot 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,25 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - name: Install
21
+ run: pip install -e ".[test]"
22
+ - name: Test
23
+ run: pytest -q
24
+ - name: Demo smoke test
25
+ run: wingfoot demo
@@ -0,0 +1,40 @@
1
+ name: publish
2
+
3
+ # Publishes to PyPI via Trusted Publishing (OIDC, no long-lived tokens).
4
+ # One-time setup on pypi.org: add a "pending publisher" for this repo with
5
+ # workflow file `publish.yml` and environment `pypi`, then publish a GitHub
6
+ # Release (tag `v*`) to trigger it.
7
+
8
+ on:
9
+ release:
10
+ types: [published]
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+ - name: Build sdist and wheel
21
+ run: |
22
+ pip install build
23
+ python -m build
24
+ - uses: actions/upload-artifact@v4
25
+ with:
26
+ name: dist
27
+ path: dist/
28
+
29
+ publish:
30
+ needs: build
31
+ runs-on: ubuntu-latest
32
+ environment: pypi
33
+ permissions:
34
+ id-token: write # required for PyPI Trusted Publishing
35
+ steps:
36
+ - uses: actions/download-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+
14
+ # wingfoot local identity (never commit private keys)
15
+ .wingfoot/
16
+ *.pem
17
+
18
+ # Large video render kept out of git history (ship via a GitHub Release instead).
19
+ # The lightweight README hero (media/*.gif) and poster are committed.
20
+ media/*.mp4
21
+
22
+ # OS / editor
23
+ .DS_Store
24
+ .idea/
25
+ .vscode/
@@ -0,0 +1,35 @@
1
+ # Contributing to wingfoot
2
+
3
+ Thanks for helping. This is a young project and there is plenty of low-hanging fruit.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/AmirF194/wingfoot
9
+ cd wingfoot
10
+ pip install -e ".[test]"
11
+ pytest
12
+ wingfoot demo
13
+ ```
14
+
15
+ ## Ground rules
16
+
17
+ - One issue, one branch, one focused PR. Keep changes small and reviewable.
18
+ - Every behavior change ships with a test that fails without it. Tests in `tests/` are plain
19
+ `pytest` functions.
20
+ - Be careful with the crypto. `rfc9421.py` is intentionally small and literal so it can be read
21
+ and checked. If you change the signature base or parameter handling, add a round-trip test and
22
+ cite the relevant RFC section.
23
+ - Match the existing style: standard library first (the only runtime dependency is
24
+ `cryptography`), type hints, and clear CLI output.
25
+
26
+ ## Good places to start
27
+
28
+ - New `doctor` checks (clock-skew hints, CDN-specific rejection signals).
29
+ - Middleware adapters for `httpx`, `requests`, and `aiohttp`.
30
+ - Interoperability tests against other Web Bot Auth implementations.
31
+
32
+ ## Reporting a problem
33
+
34
+ Open an issue with what you ran, what happened, what you expected, and your OS and Python version.
35
+ For anything large, let's agree on the approach in the issue before you build it.
wingfoot-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AmirF194
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: wingfoot
3
+ Version: 0.1.0
4
+ Summary: Verified-bot identity for AI agents (Web Bot Auth / RFC 9421), with a doctor that reports why a request was blocked.
5
+ Project-URL: Homepage, https://github.com/AmirF194/wingfoot
6
+ Project-URL: Issues, https://github.com/AmirF194/wingfoot/issues
7
+ Author: AmirF194
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai-agents,bot-verification,cloudflare,crawler,http-message-signatures,rfc9421,web-bot-auth
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Internet :: WWW/HTTP
16
+ Classifier: Topic :: Security :: Cryptography
17
+ Requires-Python: >=3.9
18
+ Requires-Dist: cryptography>=41
19
+ Provides-Extra: httpx
20
+ Requires-Dist: httpx>=0.23; extra == 'httpx'
21
+ Provides-Extra: requests
22
+ Requires-Dist: requests>=2; extra == 'requests'
23
+ Provides-Extra: test
24
+ Requires-Dist: httpx>=0.23; extra == 'test'
25
+ Requires-Dist: pytest>=7; extra == 'test'
26
+ Requires-Dist: requests>=2; extra == 'test'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # wingfoot
30
+
31
+ [![CI](https://github.com/AmirF194/wingfoot/actions/workflows/ci.yml/badge.svg)](https://github.com/AmirF194/wingfoot/actions/workflows/ci.yml)
32
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
33
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
34
+
35
+ <p align="center">
36
+ <img src="media/wingfoot-doctor.gif" alt="wingfoot doctor: a blocked 403 request becomes a verified 200 OK" width="760">
37
+ </p>
38
+
39
+ <p align="center"><em><code>wingfoot doctor</code> sends a signed request, reads the response, and tells you why a blocked <code>403</code> becomes a verified <code>200</code>.</em></p>
40
+
41
+ Verified-bot identity for AI agents, using Web Bot Auth (a profile of
42
+ [RFC 9421](https://www.rfc-editor.org/info/rfc9421/) HTTP Message Signatures).
43
+
44
+ Sign your agent's outbound HTTP requests with an Ed25519 key, publish the public key at a
45
+ well-known directory, and check whether a site accepts the signature. Includes a `doctor`
46
+ command that sends a signed request and reports why it was accepted or rejected.
47
+
48
+ Background: Cloudflare, Akamai, and Fastly increasingly block AI agents and crawlers by default.
49
+ [Web Bot Auth](https://developers.cloudflare.com/bots/reference/bot-verification/web-bot-auth/)
50
+ lets a legitimate agent prove its identity instead of being treated as a hostile scraper. Setting
51
+ it up by hand is fiddly, and a failed signature usually just returns `403` with no explanation.
52
+
53
+ ## Install
54
+
55
+ Not on PyPI yet, so install from source:
56
+
57
+ ```bash
58
+ git clone https://github.com/AmirF194/wingfoot
59
+ cd wingfoot
60
+ pip install -e .
61
+ wingfoot demo
62
+ ```
63
+
64
+ Python 3.9+. The only runtime dependency is `cryptography`.
65
+
66
+ ## Demo
67
+
68
+ `wingfoot demo` starts a local reference verifier, sends one unsigned request and one signed
69
+ request, and prints the result. It runs in a single process, so it works offline with no setup.
70
+
71
+ ```console
72
+ $ wingfoot demo
73
+
74
+ 1. Unsigned request
75
+ HTTP 403 Forbidden missing Signature-Input header
76
+
77
+ 2. Signed request
78
+ HTTP 200 OK verified (keyid ig0azrI2bZdo...)
79
+ ```
80
+
81
+ ## Usage
82
+
83
+ ```bash
84
+ # create a keypair and a key directory
85
+ wingfoot init --agent https://your-bot.example
86
+
87
+ # print the JWKS to publish at /.well-known/http-message-signatures-directory
88
+ wingfoot directory
89
+
90
+ # send a signed request
91
+ wingfoot sign https://example.com/
92
+
93
+ # check why a URL accepts or rejects your signed agent
94
+ wingfoot doctor https://example.com/
95
+
96
+ # get a ready-to-paste registration packet for Cloudflare, DataDome, ...
97
+ wingfoot register --email you@example.com
98
+ ```
99
+
100
+ `wingfoot doctor` sends a signed request, reads the response, and prints a checklist:
101
+
102
+ ```console
103
+ $ wingfoot doctor http://localhost:8088/whoami
104
+
105
+ ok Bot identity keyid RtsLV3gfcK73Ql4_...
106
+ ok Signature is well-formed and cryptographically valid
107
+ ok Key directory reachable and publishes this key
108
+ Probing the target
109
+ - Unsigned request HTTP 403
110
+ ok Signed request HTTP 200
111
+
112
+ Verified-bot access is working: signing changed HTTP 403 to 200.
113
+ ```
114
+
115
+ When something is wrong it reports which check failed and how to fix it: expired signature, clock
116
+ skew, unreachable directory, key not listed, missing `tag="web-bot-auth"`, and so on.
117
+
118
+ ## Register with verifiers
119
+
120
+ A valid signature only helps once a verifier (Cloudflare, DataDome, ...) knows your key. None of
121
+ them offer automated registration yet — each runs its own web form with a manual review behind
122
+ it. `wingfoot register` shrinks that step to copy/paste: it first checks your hosted directory
123
+ the way a reviewer would, then prints every answer their forms ask for:
124
+
125
+ ```console
126
+ $ wingfoot register --email you@example.com
127
+
128
+ Preflight — what a reviewer will check
129
+ ok Key directory reachable
130
+ ok Directory publishes this key
131
+ ok Directory response is signed
132
+
133
+ Cloudflare — Verified Bots
134
+ form https://dash.cloudflare.com/?to=/:account/configurations
135
+ Bot / agent name your-bot.example
136
+ User-Agent wingfoot/0.1.0 (+https://github.com/AmirF194/wingfoot)
137
+ Key directory URL https://your-bot.example/.well-known/http-message-signatures-directory
138
+ ...
139
+ ```
140
+
141
+ Add `--open` to launch the forms in your browser, or name one provider: `wingfoot register datadome`.
142
+
143
+ ## Commands
144
+
145
+ | Command | What it does |
146
+ |---------|--------------|
147
+ | `wingfoot demo` | Run the unsigned/signed flow against a local verifier. No setup. |
148
+ | `wingfoot init --agent <url>` | Create a lasting identity (keypair + directory). |
149
+ | `wingfoot directory` | Print the JWKS to host at the well-known path. |
150
+ | `wingfoot serve` | Serve your key directory locally (and verify requests). |
151
+ | `wingfoot sign <url>` | Send a Web-Bot-Auth-signed request. |
152
+ | `wingfoot doctor <url>` | Diagnose why a URL blocks or accepts your signed agent. |
153
+ | `wingfoot register [provider]` | Preflight your setup, then print what to paste into each verifier's registration form. |
154
+ | `wingfoot verifier` | Run a reference verifier for others to test against. |
155
+
156
+ ## Use it in your code
157
+
158
+ Signing is one line — hand a wingfoot auth object to the HTTP client you already use. Every
159
+ outbound request gets a fresh Web Bot Auth signature (re-signed per call, so it never expires
160
+ mid-session). Run `wingfoot init --agent <url>` once, then:
161
+
162
+ ```python
163
+ import requests, wingfoot
164
+ requests.get("https://example.com/", auth=wingfoot.requests_auth())
165
+
166
+ import httpx, wingfoot
167
+ httpx.get("https://example.com/", auth=wingfoot.httpx_auth())
168
+ ```
169
+
170
+ `requests` and `httpx` are optional extras (`pip install "wingfoot[requests]"` /
171
+ `"wingfoot[httpx]"`); wingfoot itself needs only `cryptography`. Driving a different client?
172
+ `wingfoot.signed_headers(url)` returns the headers as a plain dict to merge in yourself.
173
+
174
+ ## How it works
175
+
176
+ Web Bot Auth signs at least the `@authority` derived component and a `Signature-Agent` header,
177
+ with the parameter `tag="web-bot-auth"`. Three request headers carry it:
178
+
179
+ - `Signature-Agent`: the origin that hosts your key directory.
180
+ - `Signature-Input`: the covered components plus `created`, `expires`, `keyid`, `tag`.
181
+ - `Signature`: the Ed25519 signature over the RFC 9421 signature base.
182
+
183
+ A verifier fetches `<Signature-Agent>/.well-known/http-message-signatures-directory` (a JWKS),
184
+ looks up the key by its RFC 7638 thumbprint (`keyid`), and verifies the signature. The
185
+ implementation of that slice lives in [`src/wingfoot/rfc9421.py`](src/wingfoot/rfc9421.py).
186
+
187
+ ## Status
188
+
189
+ Alpha (v0.1). Signing, verification, the directory, and doctor work and are covered by tests
190
+ against a reference verifier. Interoperability with a specific CDN depends on that CDN having your
191
+ registered key. Doctor confirms your side is correct so you can register with confidence.
192
+
193
+ Drop-in signing for `requests` and `httpx` ships now (see
194
+ [Use it in your code](#use-it-in-your-code)).
195
+
196
+ Planned:
197
+
198
+ - Doctor checks for Cloudflare and Akamai rejection signals.
199
+ - Middleware for `aiohttp`, plus an async `httpx` example.
200
+ - A TypeScript/Node port.
201
+
202
+ Issues and PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
203
+
204
+ ## License
205
+
206
+ MIT. See [LICENSE](LICENSE).
207
+
208
+ ## References
209
+
210
+ - [RFC 9421: HTTP Message Signatures](https://www.rfc-editor.org/info/rfc9421/)
211
+ - [Web Bot Auth (Cloudflare docs)](https://developers.cloudflare.com/bots/reference/bot-verification/web-bot-auth/)
212
+ - [cloudflare/web-bot-auth](https://github.com/cloudflare/web-bot-auth)
213
+ - [HTTP Message Signatures Directory draft](https://www.ietf.org/archive/id/draft-meunier-http-message-signatures-directory-01.html)
@@ -0,0 +1,185 @@
1
+ # wingfoot
2
+
3
+ [![CI](https://github.com/AmirF194/wingfoot/actions/workflows/ci.yml/badge.svg)](https://github.com/AmirF194/wingfoot/actions/workflows/ci.yml)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ ![Python](https://img.shields.io/badge/python-3.9%2B-blue)
6
+
7
+ <p align="center">
8
+ <img src="media/wingfoot-doctor.gif" alt="wingfoot doctor: a blocked 403 request becomes a verified 200 OK" width="760">
9
+ </p>
10
+
11
+ <p align="center"><em><code>wingfoot doctor</code> sends a signed request, reads the response, and tells you why a blocked <code>403</code> becomes a verified <code>200</code>.</em></p>
12
+
13
+ Verified-bot identity for AI agents, using Web Bot Auth (a profile of
14
+ [RFC 9421](https://www.rfc-editor.org/info/rfc9421/) HTTP Message Signatures).
15
+
16
+ Sign your agent's outbound HTTP requests with an Ed25519 key, publish the public key at a
17
+ well-known directory, and check whether a site accepts the signature. Includes a `doctor`
18
+ command that sends a signed request and reports why it was accepted or rejected.
19
+
20
+ Background: Cloudflare, Akamai, and Fastly increasingly block AI agents and crawlers by default.
21
+ [Web Bot Auth](https://developers.cloudflare.com/bots/reference/bot-verification/web-bot-auth/)
22
+ lets a legitimate agent prove its identity instead of being treated as a hostile scraper. Setting
23
+ it up by hand is fiddly, and a failed signature usually just returns `403` with no explanation.
24
+
25
+ ## Install
26
+
27
+ Not on PyPI yet, so install from source:
28
+
29
+ ```bash
30
+ git clone https://github.com/AmirF194/wingfoot
31
+ cd wingfoot
32
+ pip install -e .
33
+ wingfoot demo
34
+ ```
35
+
36
+ Python 3.9+. The only runtime dependency is `cryptography`.
37
+
38
+ ## Demo
39
+
40
+ `wingfoot demo` starts a local reference verifier, sends one unsigned request and one signed
41
+ request, and prints the result. It runs in a single process, so it works offline with no setup.
42
+
43
+ ```console
44
+ $ wingfoot demo
45
+
46
+ 1. Unsigned request
47
+ HTTP 403 Forbidden missing Signature-Input header
48
+
49
+ 2. Signed request
50
+ HTTP 200 OK verified (keyid ig0azrI2bZdo...)
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ ```bash
56
+ # create a keypair and a key directory
57
+ wingfoot init --agent https://your-bot.example
58
+
59
+ # print the JWKS to publish at /.well-known/http-message-signatures-directory
60
+ wingfoot directory
61
+
62
+ # send a signed request
63
+ wingfoot sign https://example.com/
64
+
65
+ # check why a URL accepts or rejects your signed agent
66
+ wingfoot doctor https://example.com/
67
+
68
+ # get a ready-to-paste registration packet for Cloudflare, DataDome, ...
69
+ wingfoot register --email you@example.com
70
+ ```
71
+
72
+ `wingfoot doctor` sends a signed request, reads the response, and prints a checklist:
73
+
74
+ ```console
75
+ $ wingfoot doctor http://localhost:8088/whoami
76
+
77
+ ok Bot identity keyid RtsLV3gfcK73Ql4_...
78
+ ok Signature is well-formed and cryptographically valid
79
+ ok Key directory reachable and publishes this key
80
+ Probing the target
81
+ - Unsigned request HTTP 403
82
+ ok Signed request HTTP 200
83
+
84
+ Verified-bot access is working: signing changed HTTP 403 to 200.
85
+ ```
86
+
87
+ When something is wrong it reports which check failed and how to fix it: expired signature, clock
88
+ skew, unreachable directory, key not listed, missing `tag="web-bot-auth"`, and so on.
89
+
90
+ ## Register with verifiers
91
+
92
+ A valid signature only helps once a verifier (Cloudflare, DataDome, ...) knows your key. None of
93
+ them offer automated registration yet — each runs its own web form with a manual review behind
94
+ it. `wingfoot register` shrinks that step to copy/paste: it first checks your hosted directory
95
+ the way a reviewer would, then prints every answer their forms ask for:
96
+
97
+ ```console
98
+ $ wingfoot register --email you@example.com
99
+
100
+ Preflight — what a reviewer will check
101
+ ok Key directory reachable
102
+ ok Directory publishes this key
103
+ ok Directory response is signed
104
+
105
+ Cloudflare — Verified Bots
106
+ form https://dash.cloudflare.com/?to=/:account/configurations
107
+ Bot / agent name your-bot.example
108
+ User-Agent wingfoot/0.1.0 (+https://github.com/AmirF194/wingfoot)
109
+ Key directory URL https://your-bot.example/.well-known/http-message-signatures-directory
110
+ ...
111
+ ```
112
+
113
+ Add `--open` to launch the forms in your browser, or name one provider: `wingfoot register datadome`.
114
+
115
+ ## Commands
116
+
117
+ | Command | What it does |
118
+ |---------|--------------|
119
+ | `wingfoot demo` | Run the unsigned/signed flow against a local verifier. No setup. |
120
+ | `wingfoot init --agent <url>` | Create a lasting identity (keypair + directory). |
121
+ | `wingfoot directory` | Print the JWKS to host at the well-known path. |
122
+ | `wingfoot serve` | Serve your key directory locally (and verify requests). |
123
+ | `wingfoot sign <url>` | Send a Web-Bot-Auth-signed request. |
124
+ | `wingfoot doctor <url>` | Diagnose why a URL blocks or accepts your signed agent. |
125
+ | `wingfoot register [provider]` | Preflight your setup, then print what to paste into each verifier's registration form. |
126
+ | `wingfoot verifier` | Run a reference verifier for others to test against. |
127
+
128
+ ## Use it in your code
129
+
130
+ Signing is one line — hand a wingfoot auth object to the HTTP client you already use. Every
131
+ outbound request gets a fresh Web Bot Auth signature (re-signed per call, so it never expires
132
+ mid-session). Run `wingfoot init --agent <url>` once, then:
133
+
134
+ ```python
135
+ import requests, wingfoot
136
+ requests.get("https://example.com/", auth=wingfoot.requests_auth())
137
+
138
+ import httpx, wingfoot
139
+ httpx.get("https://example.com/", auth=wingfoot.httpx_auth())
140
+ ```
141
+
142
+ `requests` and `httpx` are optional extras (`pip install "wingfoot[requests]"` /
143
+ `"wingfoot[httpx]"`); wingfoot itself needs only `cryptography`. Driving a different client?
144
+ `wingfoot.signed_headers(url)` returns the headers as a plain dict to merge in yourself.
145
+
146
+ ## How it works
147
+
148
+ Web Bot Auth signs at least the `@authority` derived component and a `Signature-Agent` header,
149
+ with the parameter `tag="web-bot-auth"`. Three request headers carry it:
150
+
151
+ - `Signature-Agent`: the origin that hosts your key directory.
152
+ - `Signature-Input`: the covered components plus `created`, `expires`, `keyid`, `tag`.
153
+ - `Signature`: the Ed25519 signature over the RFC 9421 signature base.
154
+
155
+ A verifier fetches `<Signature-Agent>/.well-known/http-message-signatures-directory` (a JWKS),
156
+ looks up the key by its RFC 7638 thumbprint (`keyid`), and verifies the signature. The
157
+ implementation of that slice lives in [`src/wingfoot/rfc9421.py`](src/wingfoot/rfc9421.py).
158
+
159
+ ## Status
160
+
161
+ Alpha (v0.1). Signing, verification, the directory, and doctor work and are covered by tests
162
+ against a reference verifier. Interoperability with a specific CDN depends on that CDN having your
163
+ registered key. Doctor confirms your side is correct so you can register with confidence.
164
+
165
+ Drop-in signing for `requests` and `httpx` ships now (see
166
+ [Use it in your code](#use-it-in-your-code)).
167
+
168
+ Planned:
169
+
170
+ - Doctor checks for Cloudflare and Akamai rejection signals.
171
+ - Middleware for `aiohttp`, plus an async `httpx` example.
172
+ - A TypeScript/Node port.
173
+
174
+ Issues and PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
175
+
176
+ ## License
177
+
178
+ MIT. See [LICENSE](LICENSE).
179
+
180
+ ## References
181
+
182
+ - [RFC 9421: HTTP Message Signatures](https://www.rfc-editor.org/info/rfc9421/)
183
+ - [Web Bot Auth (Cloudflare docs)](https://developers.cloudflare.com/bots/reference/bot-verification/web-bot-auth/)
184
+ - [cloudflare/web-bot-auth](https://github.com/cloudflare/web-bot-auth)
185
+ - [HTTP Message Signatures Directory draft](https://www.ietf.org/archive/id/draft-meunier-http-message-signatures-directory-01.html)
Binary file
Binary file
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "wingfoot"
7
+ version = "0.1.0"
8
+ description = "Verified-bot identity for AI agents (Web Bot Auth / RFC 9421), with a doctor that reports why a request was blocked."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [{ name = "AmirF194" }]
13
+ keywords = ["web-bot-auth", "rfc9421", "http-message-signatures", "ai-agents", "crawler", "bot-verification", "cloudflare"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Internet :: WWW/HTTP",
20
+ "Topic :: Security :: Cryptography",
21
+ ]
22
+ dependencies = ["cryptography>=41"]
23
+
24
+ [project.optional-dependencies]
25
+ requests = ["requests>=2"]
26
+ httpx = ["httpx>=0.23"]
27
+ test = ["pytest>=7", "requests>=2", "httpx>=0.23"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/AmirF194/wingfoot"
31
+ Issues = "https://github.com/AmirF194/wingfoot/issues"
32
+
33
+ [project.scripts]
34
+ wingfoot = "wingfoot.cli:main"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/wingfoot"]
@@ -0,0 +1,22 @@
1
+ """wingfoot: verified-bot identity for AI agents.
2
+
3
+ A toolkit around Web Bot Auth (a profile of RFC 9421 HTTP Message Signatures). Sign your
4
+ agent's outbound requests with an Ed25519 key, publish the public key at a `.well-known`
5
+ directory, and diagnose why a site blocked a signed request.
6
+ """
7
+
8
+ __version__ = "0.1.0"
9
+
10
+ DIRECTORY_PATH = "/.well-known/http-message-signatures-directory"
11
+ WEB_BOT_AUTH_TAG = "web-bot-auth"
12
+ # Tag for the signature ON the key directory response itself (RFC 9421 directory
13
+ # draft §5.2), distinct from the per-request WEB_BOT_AUTH_TAG. Verifiers such as
14
+ # Cloudflare require the directory to carry its own signature so no one can mirror
15
+ # it and register on your behalf.
16
+ DIRECTORY_TAG = "http-message-signatures-directory"
17
+
18
+ # Drop-in signing for requests / httpx. Imported last so the constants above are
19
+ # already defined (integrations -> rfc9421 -> `from . import WEB_BOT_AUTH_TAG`).
20
+ from .integrations import httpx_auth, requests_auth, signed_headers # noqa: E402
21
+
22
+ __all__ = ["httpx_auth", "requests_auth", "signed_headers", "__version__"]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())