taskfence 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.
- taskfence-0.1.0/CHANGELOG.md +34 -0
- taskfence-0.1.0/CITATION.cff +26 -0
- taskfence-0.1.0/CONTRIBUTING.md +32 -0
- taskfence-0.1.0/LICENSE +21 -0
- taskfence-0.1.0/MANIFEST.in +5 -0
- taskfence-0.1.0/PKG-INFO +169 -0
- taskfence-0.1.0/README.md +139 -0
- taskfence-0.1.0/SECURITY.md +25 -0
- taskfence-0.1.0/docs/DESIGN.md +293 -0
- taskfence-0.1.0/docs/LIMITATIONS.md +22 -0
- taskfence-0.1.0/docs/THREAT_MODEL.md +65 -0
- taskfence-0.1.0/examples/01_quickstart_sqlite.py +53 -0
- taskfence-0.1.0/examples/03_mcp_server/README.md +57 -0
- taskfence-0.1.0/examples/03_mcp_server/admin.py +74 -0
- taskfence-0.1.0/examples/03_mcp_server/app.py +95 -0
- taskfence-0.1.0/examples/03_mcp_server/demo.py +88 -0
- taskfence-0.1.0/examples/03_mcp_server/server.py +119 -0
- taskfence-0.1.0/pyproject.toml +46 -0
- taskfence-0.1.0/setup.cfg +4 -0
- taskfence-0.1.0/src/taskfence/__init__.py +11 -0
- taskfence-0.1.0/src/taskfence/_hooks.py +32 -0
- taskfence-0.1.0/src/taskfence/fence.py +200 -0
- taskfence-0.1.0/src/taskfence/outcomes.py +93 -0
- taskfence-0.1.0/src/taskfence/stores/__init__.py +3 -0
- taskfence-0.1.0/src/taskfence/stores/postgres.py +328 -0
- taskfence-0.1.0/src/taskfence/stores/sqlite.py +310 -0
- taskfence-0.1.0/src/taskfence/testing/__init__.py +8 -0
- taskfence-0.1.0/src/taskfence/testing/audit.py +81 -0
- taskfence-0.1.0/src/taskfence/testing/latches.py +138 -0
- taskfence-0.1.0/src/taskfence/testing/plugin.py +15 -0
- taskfence-0.1.0/src/taskfence/tokens.py +120 -0
- taskfence-0.1.0/src/taskfence.egg-info/PKG-INFO +169 -0
- taskfence-0.1.0/src/taskfence.egg-info/SOURCES.txt +43 -0
- taskfence-0.1.0/src/taskfence.egg-info/dependency_links.txt +1 -0
- taskfence-0.1.0/src/taskfence.egg-info/entry_points.txt +2 -0
- taskfence-0.1.0/src/taskfence.egg-info/requires.txt +9 -0
- taskfence-0.1.0/src/taskfence.egg-info/top_level.txt +1 -0
- taskfence-0.1.0/tests/conftest.py +120 -0
- taskfence-0.1.0/tests/test_federated_outbox.py +70 -0
- taskfence-0.1.0/tests/test_guard.py +174 -0
- taskfence-0.1.0/tests/test_mcp_example.py +38 -0
- taskfence-0.1.0/tests/test_pg_isolation.py +136 -0
- taskfence-0.1.0/tests/test_races.py +236 -0
- taskfence-0.1.0/tests/test_tokens.py +73 -0
- taskfence-0.1.0/tests/unsafe_baselines.py +38 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow [Semantic Versioning](https://semver.org/). Before 1.0, minor versions may change the API.
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-10-31
|
|
6
|
+
|
|
7
|
+
The first release: a library implementation of the commit-time authorization design from *TaskFence: Commit-Time Authorization and Revocation Safety for Long-Running MCP Agent Workflows* (ICICyTA 2026).
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **The `Fence` API.**
|
|
11
|
+
- `grant`, `revoke`, `regrant` and `issue` (signed task tokens)
|
|
12
|
+
- `guard()`, which authorizes an effect inside the transaction that commits it, plus the `@fenced` decorator
|
|
13
|
+
- `cancel_task` and `cancel_operation` fences
|
|
14
|
+
- idempotent `operation_id`s, with an explicit `Duplicate` outcome
|
|
15
|
+
- an optional application predicate, checked inside the same transaction
|
|
16
|
+
- a transactional outbox: `enqueue` and `drain_outbox`
|
|
17
|
+
- owner-side federated messages: `apply_install` and `apply_fence`, idempotent and with a monotone floor
|
|
18
|
+
- **Tokens.** `tf1` format, HMAC-SHA256, and a `KeyRing` with key rotation. Standard library only; no runtime dependencies.
|
|
19
|
+
- **SQLite store.** WAL mode; the fence check is a conditional UPDATE under the write lock, as evaluated in the paper.
|
|
20
|
+
- **PostgreSQL store** (`taskfence[postgres]`), with two lock modes:
|
|
21
|
+
- `lock="shared"` (default): a fair advisory lock plus `FOR SHARE`
|
|
22
|
+
- `lock="exclusive"`: `FOR UPDATE`
|
|
23
|
+
|
|
24
|
+
It is safe at READ COMMITTED, REPEATABLE READ and SERIALIZABLE, and is tested in CI on Postgres 15, 16 and 17.
|
|
25
|
+
- **`taskfence.testing`.** Latches for deterministic races, an independent SQL auditor (`audit`, `assert_no_violations`), and a `taskfence_latches` pytest fixture.
|
|
26
|
+
- **Examples.** A SQLite quickstart, and an MCP refund-desk server with a demo client (`taskfence[examples]`).
|
|
27
|
+
- **Docs.** Design, threat model and limitations.
|
|
28
|
+
|
|
29
|
+
### Known limitations
|
|
30
|
+
See [docs/LIMITATIONS.md](docs/LIMITATIONS.md). In short:
|
|
31
|
+
- the effect must be in the same database as TaskFence's tables
|
|
32
|
+
- external calls are covered only as far as the outbox
|
|
33
|
+
- there is no async API yet
|
|
34
|
+
- there is no MCP Tasks extension integration yet
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use TaskFence, please cite the paper below."
|
|
3
|
+
title: "TaskFence: revocation-safe side effects for long-running AI agent tasks"
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Mohammad
|
|
7
|
+
given-names: Muzeeb
|
|
8
|
+
license: MIT
|
|
9
|
+
version: 0.1.0
|
|
10
|
+
repository-code: "https://github.com/mohdmuzeeb/taskfence"
|
|
11
|
+
keywords:
|
|
12
|
+
- authorization
|
|
13
|
+
- revocation
|
|
14
|
+
- AI agents
|
|
15
|
+
- Model Context Protocol
|
|
16
|
+
- fencing tokens
|
|
17
|
+
# TODO: add `doi:` for the archived research artifact (Zenodo) once it is reserved.
|
|
18
|
+
preferred-citation:
|
|
19
|
+
type: conference-paper
|
|
20
|
+
title: "TaskFence: Commit-Time Authorization and Revocation Safety for Long-Running MCP Agent Workflows"
|
|
21
|
+
authors:
|
|
22
|
+
- family-names: Mohammad
|
|
23
|
+
given-names: Muzeeb
|
|
24
|
+
collection-title: "International Conference on Intelligent Cybernetics Technology & Applications (ICICyTA 2026)"
|
|
25
|
+
year: 2026
|
|
26
|
+
# TODO: add `doi:` and pages once the proceedings are published.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. Issues, bug reports and pull requests are welcome.
|
|
4
|
+
|
|
5
|
+
## Development setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python3 -m venv .venv && .venv/bin/pip install -e ".[test,postgres,examples]"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
.venv/bin/python -m pytest # SQLite + MCP example
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
PostgreSQL tests need a server with commit timestamps enabled:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
docker run -d --rm --name taskfence-pg -e POSTGRES_PASSWORD=test -p 127.0.0.1:55432:5432 postgres:16 -c track_commit_timestamp=on
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
TASKFENCE_PG_DSN=postgresql://postgres:test@127.0.0.1:55432/postgres .venv/bin/python -m pytest
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Ground rules for changes
|
|
26
|
+
|
|
27
|
+
- **Safety changes come with a race test.** Anything touching `guard()`, a store, or the control plane needs a latch-scheduled test in `tests/test_races.py`, or `tests/test_pg_isolation.py` for PostgreSQL-specific behavior. The test should fail without the change, and `assert_no_violations()` should pass with it.
|
|
28
|
+
- **Every back-end runs the same tests.** New behavior is tested through `conftest.py`'s `backend` fixture, so it runs on SQLite, `pg-shared` and `pg-exclusive`.
|
|
29
|
+
- **The core has no runtime dependencies.** The core uses only the standard library; drivers are optional extras.
|
|
30
|
+
- **Claims match evidence.** If a change affects what TaskFence guarantees, update `docs/THREAT_MODEL.md` and `docs/LIMITATIONS.md` in the same pull request.
|
|
31
|
+
|
|
32
|
+
Report security issues privately; see [SECURITY.md](SECURITY.md).
|
taskfence-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Muzeeb Mohammad
|
|
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.
|
taskfence-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: taskfence
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Revocation-safe side effects for long-running AI agent tasks: commit-time authorization in your own database transaction.
|
|
5
|
+
Author: Muzeeb Mohammad
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mohdmuzeeb/taskfence
|
|
8
|
+
Project-URL: Documentation, https://github.com/mohdmuzeeb/taskfence/tree/main/docs
|
|
9
|
+
Project-URL: Issues, https://github.com/mohdmuzeeb/taskfence/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/mohdmuzeeb/taskfence/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ai-agents,mcp,model-context-protocol,authorization,revocation,fencing-token
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Framework :: Pytest
|
|
18
|
+
Classifier: Topic :: Security
|
|
19
|
+
Classifier: Topic :: Database
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
25
|
+
Provides-Extra: postgres
|
|
26
|
+
Requires-Dist: psycopg[binary]>=3.2; extra == "postgres"
|
|
27
|
+
Provides-Extra: examples
|
|
28
|
+
Requires-Dist: mcp>=2.3; extra == "examples"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# TaskFence
|
|
32
|
+
|
|
33
|
+
[](https://github.com/mohdmuzeeb/taskfence/actions/workflows/ci.yml)
|
|
34
|
+
[](https://pypi.org/project/taskfence/)
|
|
35
|
+
[](https://github.com/mohdmuzeeb/taskfence/blob/main/LICENSE)
|
|
36
|
+
|
|
37
|
+
**Revocation-safe side effects for long-running AI agent tasks.**
|
|
38
|
+
|
|
39
|
+
If a user revokes permission while an agent task is running, TaskFence guarantees the task cannot commit its side effect afterwards. The check runs **inside your own database transaction**, at commit time, not before it.
|
|
40
|
+
|
|
41
|
+
## The problem
|
|
42
|
+
|
|
43
|
+
1. An agent task is admitted: the user has granted `refund.issue`.
|
|
44
|
+
2. The task works for 10 minutes (LLM calls, tool calls, waiting for input).
|
|
45
|
+
3. At minute 4, the user revokes the grant.
|
|
46
|
+
4. At minute 10, the task writes the refund. **Most servers allow this**, because they checked permission when the task started, or just before the write in a separate step.
|
|
47
|
+
|
|
48
|
+
The TaskFence paper measured this window. In one randomized run, designs that check at start or in a separate pre-commit step let 317 provable commits through after revocation. Checking inside the commit transaction let none through.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install taskfence # SQLite; core has no dependencies
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install "taskfence[postgres]" # PostgreSQL via psycopg 3
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Requires Python 3.12+. PostgreSQL 15, 16 and 17 are tested in CI.
|
|
61
|
+
|
|
62
|
+
## Example
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from taskfence import Denied, Fence, KeyRing
|
|
66
|
+
from taskfence.stores.sqlite import connect
|
|
67
|
+
|
|
68
|
+
fence = Fence(KeyRing.from_env("TASKFENCE_KEYS")) # or KeyRing.generate() to try it out
|
|
69
|
+
conn = connect("app.sqlite")
|
|
70
|
+
fence.migrate(conn)
|
|
71
|
+
conn.executescript("CREATE TABLE IF NOT EXISTS orders (id TEXT PRIMARY KEY, refunded INTEGER DEFAULT 0);"
|
|
72
|
+
"INSERT OR IGNORE INTO orders VALUES ('order:9001', 0);")
|
|
73
|
+
|
|
74
|
+
# The user grants the agent permission; a task is admitted with a signed token.
|
|
75
|
+
g = fence.grant(conn, principal="user:42", operation="refund.issue", resources=["order:9001"])
|
|
76
|
+
token = fence.issue(conn, g.grant_id, task_id="task-1")
|
|
77
|
+
|
|
78
|
+
# ... the task works for minutes; meanwhile the user may call fence.revoke(conn, g.grant_id) ...
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
with fence.transaction(conn): # do slow work BEFORE opening the transaction
|
|
82
|
+
fence.guard(conn, token, operation="refund.issue", resource="order:9001",
|
|
83
|
+
operation_id="task-1:refund")
|
|
84
|
+
conn.execute("UPDATE orders SET refunded = 1 WHERE id = 'order:9001'")
|
|
85
|
+
except Denied as e:
|
|
86
|
+
print("blocked:", e.code) # STALE_EPOCH if access was revoked meanwhile
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
[examples/01_quickstart_sqlite.py](https://github.com/mohdmuzeeb/taskfence/blob/main/examples/01_quickstart_sqlite.py) runs this with a revocation in the middle and prints `refund blocked: STALE_EPOCH`.
|
|
90
|
+
|
|
91
|
+
For PostgreSQL:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from taskfence.stores.postgres import PostgresStore, connect
|
|
95
|
+
fence = Fence(KeyRing.from_env(), PostgresStore()) # fair shared locking per grant (default)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How it works
|
|
99
|
+
|
|
100
|
+
- **Grants have epochs.** Revoking a grant raises a fence (`min_epoch`) in your database. Regranting starts a new epoch.
|
|
101
|
+
- **Tasks carry a signed token** naming the epoch they were admitted under.
|
|
102
|
+
- **`guard()` checks the token in the same transaction as the effect.** The commit either sees the revocation, or is strictly ordered before it.
|
|
103
|
+
- **The same check also:**
|
|
104
|
+
- fences cancelled tasks
|
|
105
|
+
- makes each `operation_id` commit at most once
|
|
106
|
+
- enforces resource scope and an optional application predicate
|
|
107
|
+
- can write an outbox entry for external calls
|
|
108
|
+
|
|
109
|
+
Details: [docs/DESIGN.md](https://github.com/mohdmuzeeb/taskfence/blob/main/docs/DESIGN.md) and [docs/THREAT_MODEL.md](https://github.com/mohdmuzeeb/taskfence/blob/main/docs/THREAT_MODEL.md).
|
|
110
|
+
|
|
111
|
+
## What it guarantees, and what it does not
|
|
112
|
+
|
|
113
|
+
- ✅ No effect protected by `guard()` commits after revocation, or after task cancellation, has taken effect.
|
|
114
|
+
- ✅ A revocation waits for at most the guarded transactions already in flight, never for a whole task.
|
|
115
|
+
- ❌ Not retroactive: effects that committed before the revocation stay committed.
|
|
116
|
+
- ❌ External calls (payments, e-mail) are covered only as far as the outbox entry. Your downstream must be idempotent.
|
|
117
|
+
- ❌ The effect must be in the same database as TaskFence's tables. Federated multi-service mode is planned for v0.2.
|
|
118
|
+
|
|
119
|
+
Read [docs/LIMITATIONS.md](https://github.com/mohdmuzeeb/taskfence/blob/main/docs/LIMITATIONS.md) before relying on it.
|
|
120
|
+
|
|
121
|
+
## Testing your own code for revocation races
|
|
122
|
+
|
|
123
|
+
`taskfence.testing` stages races deterministically with latches, and checks the database with an independent auditor:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from taskfence.testing import assert_no_violations, hook, run_in_thread
|
|
127
|
+
|
|
128
|
+
# `taskfence_latches` is a fixture that ships with taskfence; `fence`, `conn` and `grant` are yours.
|
|
129
|
+
def test_revoke_while_refund_runs(taskfence_latches, fence, conn, grant):
|
|
130
|
+
token = fence.issue(conn, grant.grant_id, task_id="t1")
|
|
131
|
+
pause = taskfence_latches.pause_at("app.before_txn") # your code calls hook("app.before_txn")
|
|
132
|
+
worker = run_in_thread(lambda: my_refund(token))
|
|
133
|
+
pause.wait_reached()
|
|
134
|
+
fence.revoke(conn, grant.grant_id) # revoke while the task is mid-flight
|
|
135
|
+
pause.release()
|
|
136
|
+
worker.join()
|
|
137
|
+
assert_no_violations(conn) # no commit after the revocation
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Using with MCP servers
|
|
141
|
+
|
|
142
|
+
[examples/03_mcp_server](https://github.com/mohdmuzeeb/taskfence/tree/main/examples/03_mcp_server) is a refund-desk MCP server whose background refunds are fenced by TaskFence. A demo drives it with the official MCP client and shows four cases:
|
|
143
|
+
- a refund revoked mid-task is blocked
|
|
144
|
+
- a refund cancelled mid-task is blocked
|
|
145
|
+
- a refund on another customer's order is blocked
|
|
146
|
+
- a refund after a fresh grant commits
|
|
147
|
+
|
|
148
|
+
The Python MCP SDK does not include the MCP Tasks extension yet, so the example uses start-then-poll tools.
|
|
149
|
+
|
|
150
|
+
## Roadmap
|
|
151
|
+
|
|
152
|
+
- **v0.2:** a federated grant authority (signed service-to-service RPC), TypeScript middleware for the MCP Tasks extension, and an async API
|
|
153
|
+
- **v0.3:** `taskfence scan`, a revocation-race checker for MCP servers
|
|
154
|
+
|
|
155
|
+
## Research and citation
|
|
156
|
+
|
|
157
|
+
TaskFence is based on peer-reviewed research:
|
|
158
|
+
|
|
159
|
+
> M. Mohammad, "TaskFence: Commit-Time Authorization and Revocation Safety for Long-Running MCP Agent Workflows," *International Conference on Intelligent Cybernetics Technology & Applications (ICICyTA 2026)*.
|
|
160
|
+
|
|
161
|
+
If you use TaskFence, please cite the paper. GitHub's "Cite this repository" button uses [CITATION.cff](https://github.com/mohdmuzeeb/taskfence/blob/main/CITATION.cff). The archived research artifact (code, preregistration, wire traces, raw databases) will be linked here once it has a DOI.
|
|
162
|
+
|
|
163
|
+
## Contributing and security
|
|
164
|
+
|
|
165
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/mohdmuzeeb/taskfence/blob/main/CONTRIBUTING.md). Report vulnerabilities privately as described in [SECURITY.md](https://github.com/mohdmuzeeb/taskfence/blob/main/SECURITY.md).
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# TaskFence
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mohdmuzeeb/taskfence/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/taskfence/)
|
|
5
|
+
[](https://github.com/mohdmuzeeb/taskfence/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
**Revocation-safe side effects for long-running AI agent tasks.**
|
|
8
|
+
|
|
9
|
+
If a user revokes permission while an agent task is running, TaskFence guarantees the task cannot commit its side effect afterwards. The check runs **inside your own database transaction**, at commit time, not before it.
|
|
10
|
+
|
|
11
|
+
## The problem
|
|
12
|
+
|
|
13
|
+
1. An agent task is admitted: the user has granted `refund.issue`.
|
|
14
|
+
2. The task works for 10 minutes (LLM calls, tool calls, waiting for input).
|
|
15
|
+
3. At minute 4, the user revokes the grant.
|
|
16
|
+
4. At minute 10, the task writes the refund. **Most servers allow this**, because they checked permission when the task started, or just before the write in a separate step.
|
|
17
|
+
|
|
18
|
+
The TaskFence paper measured this window. In one randomized run, designs that check at start or in a separate pre-commit step let 317 provable commits through after revocation. Checking inside the commit transaction let none through.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install taskfence # SQLite; core has no dependencies
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install "taskfence[postgres]" # PostgreSQL via psycopg 3
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Requires Python 3.12+. PostgreSQL 15, 16 and 17 are tested in CI.
|
|
31
|
+
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from taskfence import Denied, Fence, KeyRing
|
|
36
|
+
from taskfence.stores.sqlite import connect
|
|
37
|
+
|
|
38
|
+
fence = Fence(KeyRing.from_env("TASKFENCE_KEYS")) # or KeyRing.generate() to try it out
|
|
39
|
+
conn = connect("app.sqlite")
|
|
40
|
+
fence.migrate(conn)
|
|
41
|
+
conn.executescript("CREATE TABLE IF NOT EXISTS orders (id TEXT PRIMARY KEY, refunded INTEGER DEFAULT 0);"
|
|
42
|
+
"INSERT OR IGNORE INTO orders VALUES ('order:9001', 0);")
|
|
43
|
+
|
|
44
|
+
# The user grants the agent permission; a task is admitted with a signed token.
|
|
45
|
+
g = fence.grant(conn, principal="user:42", operation="refund.issue", resources=["order:9001"])
|
|
46
|
+
token = fence.issue(conn, g.grant_id, task_id="task-1")
|
|
47
|
+
|
|
48
|
+
# ... the task works for minutes; meanwhile the user may call fence.revoke(conn, g.grant_id) ...
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
with fence.transaction(conn): # do slow work BEFORE opening the transaction
|
|
52
|
+
fence.guard(conn, token, operation="refund.issue", resource="order:9001",
|
|
53
|
+
operation_id="task-1:refund")
|
|
54
|
+
conn.execute("UPDATE orders SET refunded = 1 WHERE id = 'order:9001'")
|
|
55
|
+
except Denied as e:
|
|
56
|
+
print("blocked:", e.code) # STALE_EPOCH if access was revoked meanwhile
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
[examples/01_quickstart_sqlite.py](https://github.com/mohdmuzeeb/taskfence/blob/main/examples/01_quickstart_sqlite.py) runs this with a revocation in the middle and prints `refund blocked: STALE_EPOCH`.
|
|
60
|
+
|
|
61
|
+
For PostgreSQL:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from taskfence.stores.postgres import PostgresStore, connect
|
|
65
|
+
fence = Fence(KeyRing.from_env(), PostgresStore()) # fair shared locking per grant (default)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## How it works
|
|
69
|
+
|
|
70
|
+
- **Grants have epochs.** Revoking a grant raises a fence (`min_epoch`) in your database. Regranting starts a new epoch.
|
|
71
|
+
- **Tasks carry a signed token** naming the epoch they were admitted under.
|
|
72
|
+
- **`guard()` checks the token in the same transaction as the effect.** The commit either sees the revocation, or is strictly ordered before it.
|
|
73
|
+
- **The same check also:**
|
|
74
|
+
- fences cancelled tasks
|
|
75
|
+
- makes each `operation_id` commit at most once
|
|
76
|
+
- enforces resource scope and an optional application predicate
|
|
77
|
+
- can write an outbox entry for external calls
|
|
78
|
+
|
|
79
|
+
Details: [docs/DESIGN.md](https://github.com/mohdmuzeeb/taskfence/blob/main/docs/DESIGN.md) and [docs/THREAT_MODEL.md](https://github.com/mohdmuzeeb/taskfence/blob/main/docs/THREAT_MODEL.md).
|
|
80
|
+
|
|
81
|
+
## What it guarantees, and what it does not
|
|
82
|
+
|
|
83
|
+
- ✅ No effect protected by `guard()` commits after revocation, or after task cancellation, has taken effect.
|
|
84
|
+
- ✅ A revocation waits for at most the guarded transactions already in flight, never for a whole task.
|
|
85
|
+
- ❌ Not retroactive: effects that committed before the revocation stay committed.
|
|
86
|
+
- ❌ External calls (payments, e-mail) are covered only as far as the outbox entry. Your downstream must be idempotent.
|
|
87
|
+
- ❌ The effect must be in the same database as TaskFence's tables. Federated multi-service mode is planned for v0.2.
|
|
88
|
+
|
|
89
|
+
Read [docs/LIMITATIONS.md](https://github.com/mohdmuzeeb/taskfence/blob/main/docs/LIMITATIONS.md) before relying on it.
|
|
90
|
+
|
|
91
|
+
## Testing your own code for revocation races
|
|
92
|
+
|
|
93
|
+
`taskfence.testing` stages races deterministically with latches, and checks the database with an independent auditor:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from taskfence.testing import assert_no_violations, hook, run_in_thread
|
|
97
|
+
|
|
98
|
+
# `taskfence_latches` is a fixture that ships with taskfence; `fence`, `conn` and `grant` are yours.
|
|
99
|
+
def test_revoke_while_refund_runs(taskfence_latches, fence, conn, grant):
|
|
100
|
+
token = fence.issue(conn, grant.grant_id, task_id="t1")
|
|
101
|
+
pause = taskfence_latches.pause_at("app.before_txn") # your code calls hook("app.before_txn")
|
|
102
|
+
worker = run_in_thread(lambda: my_refund(token))
|
|
103
|
+
pause.wait_reached()
|
|
104
|
+
fence.revoke(conn, grant.grant_id) # revoke while the task is mid-flight
|
|
105
|
+
pause.release()
|
|
106
|
+
worker.join()
|
|
107
|
+
assert_no_violations(conn) # no commit after the revocation
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Using with MCP servers
|
|
111
|
+
|
|
112
|
+
[examples/03_mcp_server](https://github.com/mohdmuzeeb/taskfence/tree/main/examples/03_mcp_server) is a refund-desk MCP server whose background refunds are fenced by TaskFence. A demo drives it with the official MCP client and shows four cases:
|
|
113
|
+
- a refund revoked mid-task is blocked
|
|
114
|
+
- a refund cancelled mid-task is blocked
|
|
115
|
+
- a refund on another customer's order is blocked
|
|
116
|
+
- a refund after a fresh grant commits
|
|
117
|
+
|
|
118
|
+
The Python MCP SDK does not include the MCP Tasks extension yet, so the example uses start-then-poll tools.
|
|
119
|
+
|
|
120
|
+
## Roadmap
|
|
121
|
+
|
|
122
|
+
- **v0.2:** a federated grant authority (signed service-to-service RPC), TypeScript middleware for the MCP Tasks extension, and an async API
|
|
123
|
+
- **v0.3:** `taskfence scan`, a revocation-race checker for MCP servers
|
|
124
|
+
|
|
125
|
+
## Research and citation
|
|
126
|
+
|
|
127
|
+
TaskFence is based on peer-reviewed research:
|
|
128
|
+
|
|
129
|
+
> M. Mohammad, "TaskFence: Commit-Time Authorization and Revocation Safety for Long-Running MCP Agent Workflows," *International Conference on Intelligent Cybernetics Technology & Applications (ICICyTA 2026)*.
|
|
130
|
+
|
|
131
|
+
If you use TaskFence, please cite the paper. GitHub's "Cite this repository" button uses [CITATION.cff](https://github.com/mohdmuzeeb/taskfence/blob/main/CITATION.cff). The archived research artifact (code, preregistration, wire traces, raw databases) will be linked here once it has a DOI.
|
|
132
|
+
|
|
133
|
+
## Contributing and security
|
|
134
|
+
|
|
135
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/mohdmuzeeb/taskfence/blob/main/CONTRIBUTING.md). Report vulnerabilities privately as described in [SECURITY.md](https://github.com/mohdmuzeeb/taskfence/blob/main/SECURITY.md).
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
TaskFence is a security library, so reports are very welcome.
|
|
4
|
+
|
|
5
|
+
## Reporting a vulnerability
|
|
6
|
+
|
|
7
|
+
Please **do not open a public issue**. Report privately through GitHub's private vulnerability reporting: the **Security** tab of this repository, then **Report a vulnerability**.
|
|
8
|
+
|
|
9
|
+
Include:
|
|
10
|
+
- the TaskFence version
|
|
11
|
+
- the store (SQLite or PostgreSQL and its version)
|
|
12
|
+
- the isolation level
|
|
13
|
+
- a minimal reproduction, ideally a test using `taskfence.testing` latches and `audit()`
|
|
14
|
+
|
|
15
|
+
You should get an acknowledgment within 7 days. Fixes for confirmed issues are released with a GitHub security advisory that credits the reporter, unless they prefer otherwise.
|
|
16
|
+
|
|
17
|
+
## What counts
|
|
18
|
+
|
|
19
|
+
Anything that lets an effect commit after its revocation or cancellation is effective, as defined in [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md). Also reportable: token forgery, a scope or principal bypass, or a way to make the auditor miss a violation.
|
|
20
|
+
|
|
21
|
+
The limitations listed in [docs/LIMITATIONS.md](docs/LIMITATIONS.md) are known and documented, not vulnerabilities.
|
|
22
|
+
|
|
23
|
+
## Supported versions
|
|
24
|
+
|
|
25
|
+
Only the latest release receives fixes during the 0.x series.
|