authdrift-harness 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.
- authdrift_harness-0.1.0/CHANGELOG.md +9 -0
- authdrift_harness-0.1.0/CONTRIBUTING.md +17 -0
- authdrift_harness-0.1.0/LICENSE +21 -0
- authdrift_harness-0.1.0/MANIFEST.in +8 -0
- authdrift_harness-0.1.0/PKG-INFO +260 -0
- authdrift_harness-0.1.0/README.md +238 -0
- authdrift_harness-0.1.0/SECURITY.md +23 -0
- authdrift_harness-0.1.0/authdrift/__init__.py +6 -0
- authdrift_harness-0.1.0/authdrift/__main__.py +3 -0
- authdrift_harness-0.1.0/authdrift/cli/__init__.py +0 -0
- authdrift_harness-0.1.0/authdrift/cli/main.py +32 -0
- authdrift_harness-0.1.0/authdrift/core/__init__.py +0 -0
- authdrift_harness-0.1.0/authdrift/core/events.py +12 -0
- authdrift_harness-0.1.0/authdrift/core/injector.py +36 -0
- authdrift_harness-0.1.0/authdrift/core/result.py +8 -0
- authdrift_harness-0.1.0/authdrift/core/runner.py +125 -0
- authdrift_harness-0.1.0/authdrift/core/scenario.py +23 -0
- authdrift_harness-0.1.0/authdrift/hooks/__init__.py +0 -0
- authdrift_harness-0.1.0/authdrift/hooks/lifecycle.py +14 -0
- authdrift_harness-0.1.0/authdrift_harness.egg-info/PKG-INFO +260 -0
- authdrift_harness-0.1.0/authdrift_harness.egg-info/SOURCES.txt +41 -0
- authdrift_harness-0.1.0/authdrift_harness.egg-info/dependency_links.txt +1 -0
- authdrift_harness-0.1.0/authdrift_harness.egg-info/entry_points.txt +2 -0
- authdrift_harness-0.1.0/authdrift_harness.egg-info/top_level.txt +1 -0
- authdrift_harness-0.1.0/benchmarks/run.py +22 -0
- authdrift_harness-0.1.0/examples/delegation/README.md +21 -0
- authdrift_harness-0.1.0/examples/delegation/safe.py +7 -0
- authdrift_harness-0.1.0/examples/delegation/scenario.py +15 -0
- authdrift_harness-0.1.0/examples/refund/README.md +21 -0
- authdrift_harness-0.1.0/examples/refund/safe.py +7 -0
- authdrift_harness-0.1.0/examples/refund/scenario.py +13 -0
- authdrift_harness-0.1.0/examples/session/README.md +21 -0
- authdrift_harness-0.1.0/examples/session/safe.py +7 -0
- authdrift_harness-0.1.0/examples/session/scenario.py +13 -0
- authdrift_harness-0.1.0/pyproject.toml +35 -0
- authdrift_harness-0.1.0/release/validation.md +24 -0
- authdrift_harness-0.1.0/research/external-validation.md +31 -0
- authdrift_harness-0.1.0/research/implementation-status.md +5 -0
- authdrift_harness-0.1.0/research/methodology.md +41 -0
- authdrift_harness-0.1.0/research/related-work.md +38 -0
- authdrift_harness-0.1.0/research/threat-model.md +17 -0
- authdrift_harness-0.1.0/setup.cfg +4 -0
- authdrift_harness-0.1.0/tests/test_runner.py +162 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-09-17
|
|
4
|
+
|
|
5
|
+
- Synchronous Scenario/factory/reset API and named checkpoint injection.
|
|
6
|
+
- Positive, negative, and mid-flight controls with state confirmation.
|
|
7
|
+
- Structured evidence, CLI, three controlled fixture families and corrected variants.
|
|
8
|
+
- Documented authority-contract requirement and interpretation limitations.
|
|
9
|
+
- Blocked external validation provenance, with no external security outcome.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Use Python 3.10+ and `python -m pip install -e .` from a checkout.
|
|
4
|
+
Run `python -m unittest discover -s tests -v` and `python benchmarks/run.py`.
|
|
5
|
+
Tests use the standard library; no extra test dependencies are required.
|
|
6
|
+
|
|
7
|
+
Keep changes small; include regression tests for correctness changes. Scenarios
|
|
8
|
+
must specify the authority contract, independent confirmation, sink, checkpoint
|
|
9
|
+
ordering, isolation, and positive/negative/mid-flight controls. Distinguish
|
|
10
|
+
harness-supplied authority from upstream authorization semantics.
|
|
11
|
+
|
|
12
|
+
Independent specimens must pin source and preserve its license and behavior.
|
|
13
|
+
Record actual prerequisites and outcomes. Never commit credentials or real user
|
|
14
|
+
data. Review deliberately retained release evidence before adding it.
|
|
15
|
+
|
|
16
|
+
Read the [trusted-code and report-privacy boundaries](SECURITY.md#trusted-code-and-report-privacy).
|
|
17
|
+
Keep raw validation and external-specimen evidence private.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AuthDrift contributors
|
|
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,8 @@
|
|
|
1
|
+
include LICENSE README.md CONTRIBUTING.md SECURITY.md CHANGELOG.md
|
|
2
|
+
recursive-include examples *.py *.md
|
|
3
|
+
recursive-include tests *.py
|
|
4
|
+
include benchmarks/run.py
|
|
5
|
+
include research/methodology.md research/threat-model.md research/related-work.md
|
|
6
|
+
include research/external-validation.md research/implementation-status.md
|
|
7
|
+
include release/validation.md
|
|
8
|
+
prune external-specimens
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: authdrift-harness
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A developer testing harness for confirmed authority changes in agent workflows
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/cheerstopriya/authdrift
|
|
7
|
+
Project-URL: Issues, https://github.com/cheerstopriya/authdrift/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/cheerstopriya/authdrift#readme
|
|
9
|
+
Project-URL: Security, https://github.com/cheerstopriya/authdrift/security/advisories/new
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# AuthDrift
|
|
24
|
+
|
|
25
|
+
**Can your AI agent still act after you revoke its authority?**
|
|
26
|
+
|
|
27
|
+
AuthDrift is a developer testing harness for injecting confirmed authority changes
|
|
28
|
+
into running agent trajectories and observing whether consequential effects still
|
|
29
|
+
commit. It reproduces the gap between an earlier approval and a later action.
|
|
30
|
+
It does not enforce policy or protect a running agent.
|
|
31
|
+
|
|
32
|
+
## A 30-second example
|
|
33
|
+
|
|
34
|
+
An approval is checked, then withdrawn while the workflow is paused. Does the
|
|
35
|
+
refund still happen?
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# Naive: reuse the earlier decision.
|
|
39
|
+
approved = approval.valid()
|
|
40
|
+
authdrift.checkpoint("authority_observed")
|
|
41
|
+
prepare_refund()
|
|
42
|
+
if approved:
|
|
43
|
+
issue_refund()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Corrected for this synchronous fixture: consult current authority.
|
|
48
|
+
approved = approval.valid()
|
|
49
|
+
authdrift.checkpoint("authority_observed")
|
|
50
|
+
prepare_refund()
|
|
51
|
+
if approval.valid():
|
|
52
|
+
issue_refund()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
These illustrative snippets assume revocation invalidates outstanding effects.
|
|
56
|
+
The runnable fixtures below explicitly define that contract. In concurrent
|
|
57
|
+
systems the final check and mutation must be atomic with respect to revocation;
|
|
58
|
+
a separate recheck alone does not guarantee that.
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
Python **3.10+**, **no runtime dependencies**. From a source checkout:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
git clone https://github.com/cheerstopriya/authdrift.git
|
|
66
|
+
cd authdrift
|
|
67
|
+
python -m venv .venv
|
|
68
|
+
# Windows PowerShell: .venv\Scripts\Activate.ps1
|
|
69
|
+
# macOS/Linux: source .venv/bin/activate
|
|
70
|
+
python -m pip install -e .
|
|
71
|
+
authdrift --help
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Version 0.1.0 is prepared for release. PyPI publication is pending;
|
|
75
|
+
use the source installation above until publication is confirmed.
|
|
76
|
+
The distribution name is `authdrift-harness`; the Python import and CLI remain
|
|
77
|
+
`authdrift`. The existing PyPI package named `authdrift` is a different project.
|
|
78
|
+
Source builds require setuptools >= 77, normally supplied by pip's isolated
|
|
79
|
+
build environment. Examples are included in the repository/source distribution.
|
|
80
|
+
The connected editable-install workflow passed on Windows and Ubuntu with Python
|
|
81
|
+
3.10, 3.12, and 3.13; see [GitHub validation](release/validation.md).
|
|
82
|
+
|
|
83
|
+
## Quick start
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
authdrift run examples/refund/scenario.py --repeats 20 --json vulnerable.json
|
|
87
|
+
authdrift run examples/refund/safe.py --repeats 20 --json safe.json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The controlled vulnerable fixture reports `REVOCATION_ESCAPE` (exit **1**);
|
|
91
|
+
the corrected fixture reports `CLOSED` (exit **0**).
|
|
92
|
+
|
|
93
|
+
In AuthDrift's deliberately vulnerable controlled fixtures, all 20/20 runs
|
|
94
|
+
produced `REVOCATION_ESCAPE`; the corresponding corrected fixtures produced
|
|
95
|
+
20/20 `CLOSED`. These counts are fixture runs, not vulnerabilities discovered in
|
|
96
|
+
independent agents. The scenario's declared authority contract gives the security
|
|
97
|
+
classification its meaning; AuthDrift cannot infer that contract.
|
|
98
|
+
|
|
99
|
+
Measured final summary lines for the refund commands:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
vulnerable: Escapes: 20/20 valid trials (100.0%); total trials: 20
|
|
103
|
+
corrected: Escapes: 0/20 valid trials (0.0%); total trials: 20
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Run all three families and the tests:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
python benchmarks/run.py
|
|
110
|
+
python -m unittest discover -s tests -v
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
See [release validation](release/validation.md) for measured results and
|
|
114
|
+
exact commands. The fixtures use in-memory effect records, not real payments,
|
|
115
|
+
LLM calls, or durable external services.
|
|
116
|
+
|
|
117
|
+
## How AuthDrift works
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
Workflow observes authority
|
|
121
|
+
|
|
|
122
|
+
Checkpoint pauses continuation
|
|
123
|
+
|
|
|
124
|
+
Request authority change -> independently confirm state
|
|
125
|
+
|
|
|
126
|
+
Verify effect is still absent
|
|
127
|
+
|
|
|
128
|
+
Resume the same workflow -> inspect authoritative sink state
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Each repetition uses fresh state for positive, pre-revoked negative, and
|
|
132
|
+
mid-flight controls. Failed controls prevent that mid-flight trial from running.
|
|
133
|
+
Confirmation reads the supplied authority predicate, not the revocation callback's
|
|
134
|
+
return value or model narration.
|
|
135
|
+
|
|
136
|
+
## Outcomes
|
|
137
|
+
|
|
138
|
+
**Scenario contract required:** confirmed revocation must invalidate outstanding,
|
|
139
|
+
uncommitted effects. A policy/configuration change alone does not establish this.
|
|
140
|
+
The v0.1 API assumes this contract; it cannot discover or verify it automatically.
|
|
141
|
+
Do not use its security classifications for unspecified upstream semantics.
|
|
142
|
+
|
|
143
|
+
| Outcome | Meaning under the scenario contract | CLI exit |
|
|
144
|
+
| --- | --- | --- |
|
|
145
|
+
| `REVOCATION_ESCAPE` | Effect absent after confirmation, present after continuation | 1 |
|
|
146
|
+
| `CLOSED` | Authority remains revoked; no effect observed after completion, scoped to this trial | 0 |
|
|
147
|
+
| `BASELINE_FAILURE` | Pre-revoked control commits; temporal interpretation stops | 2 |
|
|
148
|
+
| `INVALID_EXPERIMENT` | Failed positive control, bad predicates, missing checkpoint, callback error, or ambiguous ordering | 3 |
|
|
149
|
+
|
|
150
|
+
JSON schema 1.0 contains controls, per-experiment IDs, ordered monotonic events,
|
|
151
|
+
reasons, counts, and escape rate over valid mid-flight trials only (null if none).
|
|
152
|
+
Mixed reports prioritize invalid experiments, baseline failures, escapes, then
|
|
153
|
+
closed outcomes. CLI errors also exit 3. JSON does not yet separate observation
|
|
154
|
+
from security interpretation; see [methodology](research/methodology.md).
|
|
155
|
+
|
|
156
|
+
## Included scenarios
|
|
157
|
+
|
|
158
|
+
| Family | Authority | Controlled effect | Implementations |
|
|
159
|
+
| --- | --- | --- | --- |
|
|
160
|
+
| Refund | Approval | Refund record | [vulnerable](examples/refund/scenario.py), [corrected](examples/refund/safe.py) |
|
|
161
|
+
| Delegation | Worker eligibility | Worker result | [vulnerable](examples/delegation/scenario.py), [corrected](examples/delegation/safe.py) |
|
|
162
|
+
| Session | Session authorization | Protected-action record | [vulnerable](examples/session/scenario.py), [corrected](examples/session/safe.py) |
|
|
163
|
+
|
|
164
|
+
Each directory documents its authority contract. Delegation is a synchronous
|
|
165
|
+
worker fixture, not a distributed-agent integration.
|
|
166
|
+
|
|
167
|
+
## Writing your own scenario
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
import authdrift
|
|
171
|
+
|
|
172
|
+
def build_scenario():
|
|
173
|
+
# Contract: revoked approval invalidates every outstanding refund.
|
|
174
|
+
state = {"approved": True, "refunds": []}
|
|
175
|
+
|
|
176
|
+
def workflow():
|
|
177
|
+
approved = state["approved"]
|
|
178
|
+
authdrift.checkpoint("authority_observed")
|
|
179
|
+
if approved:
|
|
180
|
+
state["refunds"].append("refund")
|
|
181
|
+
|
|
182
|
+
return authdrift.Scenario(
|
|
183
|
+
name="refund-revocation",
|
|
184
|
+
run=workflow,
|
|
185
|
+
revoke=lambda: state.update(approved=False),
|
|
186
|
+
revoked=lambda: not state["approved"],
|
|
187
|
+
committed=lambda: bool(state["refunds"]),
|
|
188
|
+
trigger="authority_observed",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
authdrift.run(build_scenario)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
CLI files export `build_scenario()` or `scenario`. Prefer a factory creating
|
|
196
|
+
fresh authority and sink state for each experiment. A `Scenario` instance requires
|
|
197
|
+
`reset=` restoring authorization and clearing the sink. Callbacks are synchronous;
|
|
198
|
+
predicates return actual booleans. Optional `confirmation_timeout` and
|
|
199
|
+
`poll_interval` default to 1 second and 0.001 seconds.
|
|
200
|
+
|
|
201
|
+
## Methodology and validity
|
|
202
|
+
|
|
203
|
+
Place the checkpoint after authority observation and before any effect. With
|
|
204
|
+
truthful monotonic sink state, no independent writers, and synchronous continuation,
|
|
205
|
+
absence after confirmation followed by presence establishes causal ordering:
|
|
206
|
+
observation < confirmed revocation < commit. Exact commit time is not measured.
|
|
207
|
+
`effect_observed` is an observation timestamp; **Post-Revocation Commit Delay
|
|
208
|
+
(PRCD)** is not calculated from it.
|
|
209
|
+
|
|
210
|
+
The workflow must join all consequential work before returning. Revocation must
|
|
211
|
+
persist without regrant; effects must not be erased. Predicates independently
|
|
212
|
+
read actual authority and sink state without side effects. Factory isolation is
|
|
213
|
+
a caller obligation, not a sandbox. Read [methodology](research/methodology.md)
|
|
214
|
+
and [threat model](research/threat-model.md) before interpreting results.
|
|
215
|
+
|
|
216
|
+
## What AuthDrift does not do
|
|
217
|
+
|
|
218
|
+
No enforcement, automatic interception, framework adapters, universal compatibility,
|
|
219
|
+
async execution, or production validation. Threads/processes do not automatically
|
|
220
|
+
inherit the checkpoint hook. Confirmation polling has a timeout, but the harness
|
|
221
|
+
cannot safely preempt blocked callbacks. Scenario files execute Python: load only
|
|
222
|
+
trusted code. CLOSED is not proof of general revocation closure.
|
|
223
|
+
|
|
224
|
+
## External validation status
|
|
225
|
+
|
|
226
|
+
| Specimen | Execution status | Security outcome |
|
|
227
|
+
| --- | --- | --- |
|
|
228
|
+
| LangGraph airline | `BLOCKED_NOT_EXECUTED` | None |
|
|
229
|
+
| Goose | `BLOCKED_NOT_EXECUTED`; `SEMANTICS_UNSPECIFIED` | None |
|
|
230
|
+
|
|
231
|
+
These are validation-attempt provenance, not successful external validation or
|
|
232
|
+
AuthDrift failures. No LangGraph or Goose vulnerability is claimed. Details and
|
|
233
|
+
the failed Ollama memory preflight: [external validation](research/external-validation.md).
|
|
234
|
+
|
|
235
|
+
## Related research
|
|
236
|
+
|
|
237
|
+
The intended contribution is developer-facing testing, injection, and reproduction
|
|
238
|
+
tooling. AuthDrift does not claim to invent stale authority, TOCTOU, commit-time
|
|
239
|
+
authorization, or revocation closure. See [related work](research/related-work.md).
|
|
240
|
+
|
|
241
|
+
## Contributing
|
|
242
|
+
|
|
243
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Independent specimens are welcome with
|
|
244
|
+
honest execution provenance and explicit authority contracts. For suspected
|
|
245
|
+
harness vulnerabilities, see [SECURITY.md](SECURITY.md).
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
[MIT](LICENSE). Retained upstream specimen material keeps its own license.
|
|
250
|
+
|
|
251
|
+
## Trusted code and report privacy
|
|
252
|
+
|
|
253
|
+
AuthDrift executes user-supplied Python scenarios. Running a scenario is
|
|
254
|
+
equivalent to executing trusted Python code. AuthDrift is not a sandbox.
|
|
255
|
+
Only execute scenarios you trust.
|
|
256
|
+
|
|
257
|
+
Exceptions and error messages may be included in generated result artifacts
|
|
258
|
+
and console output. Users should review and sanitize reports before sharing
|
|
259
|
+
or publishing them. AuthDrift does not automatically redact secrets or personal
|
|
260
|
+
data. Keep raw reports private by default.
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# AuthDrift
|
|
2
|
+
|
|
3
|
+
**Can your AI agent still act after you revoke its authority?**
|
|
4
|
+
|
|
5
|
+
AuthDrift is a developer testing harness for injecting confirmed authority changes
|
|
6
|
+
into running agent trajectories and observing whether consequential effects still
|
|
7
|
+
commit. It reproduces the gap between an earlier approval and a later action.
|
|
8
|
+
It does not enforce policy or protect a running agent.
|
|
9
|
+
|
|
10
|
+
## A 30-second example
|
|
11
|
+
|
|
12
|
+
An approval is checked, then withdrawn while the workflow is paused. Does the
|
|
13
|
+
refund still happen?
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
# Naive: reuse the earlier decision.
|
|
17
|
+
approved = approval.valid()
|
|
18
|
+
authdrift.checkpoint("authority_observed")
|
|
19
|
+
prepare_refund()
|
|
20
|
+
if approved:
|
|
21
|
+
issue_refund()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
# Corrected for this synchronous fixture: consult current authority.
|
|
26
|
+
approved = approval.valid()
|
|
27
|
+
authdrift.checkpoint("authority_observed")
|
|
28
|
+
prepare_refund()
|
|
29
|
+
if approval.valid():
|
|
30
|
+
issue_refund()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
These illustrative snippets assume revocation invalidates outstanding effects.
|
|
34
|
+
The runnable fixtures below explicitly define that contract. In concurrent
|
|
35
|
+
systems the final check and mutation must be atomic with respect to revocation;
|
|
36
|
+
a separate recheck alone does not guarantee that.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Python **3.10+**, **no runtime dependencies**. From a source checkout:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
git clone https://github.com/cheerstopriya/authdrift.git
|
|
44
|
+
cd authdrift
|
|
45
|
+
python -m venv .venv
|
|
46
|
+
# Windows PowerShell: .venv\Scripts\Activate.ps1
|
|
47
|
+
# macOS/Linux: source .venv/bin/activate
|
|
48
|
+
python -m pip install -e .
|
|
49
|
+
authdrift --help
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Version 0.1.0 is prepared for release. PyPI publication is pending;
|
|
53
|
+
use the source installation above until publication is confirmed.
|
|
54
|
+
The distribution name is `authdrift-harness`; the Python import and CLI remain
|
|
55
|
+
`authdrift`. The existing PyPI package named `authdrift` is a different project.
|
|
56
|
+
Source builds require setuptools >= 77, normally supplied by pip's isolated
|
|
57
|
+
build environment. Examples are included in the repository/source distribution.
|
|
58
|
+
The connected editable-install workflow passed on Windows and Ubuntu with Python
|
|
59
|
+
3.10, 3.12, and 3.13; see [GitHub validation](release/validation.md).
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
authdrift run examples/refund/scenario.py --repeats 20 --json vulnerable.json
|
|
65
|
+
authdrift run examples/refund/safe.py --repeats 20 --json safe.json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The controlled vulnerable fixture reports `REVOCATION_ESCAPE` (exit **1**);
|
|
69
|
+
the corrected fixture reports `CLOSED` (exit **0**).
|
|
70
|
+
|
|
71
|
+
In AuthDrift's deliberately vulnerable controlled fixtures, all 20/20 runs
|
|
72
|
+
produced `REVOCATION_ESCAPE`; the corresponding corrected fixtures produced
|
|
73
|
+
20/20 `CLOSED`. These counts are fixture runs, not vulnerabilities discovered in
|
|
74
|
+
independent agents. The scenario's declared authority contract gives the security
|
|
75
|
+
classification its meaning; AuthDrift cannot infer that contract.
|
|
76
|
+
|
|
77
|
+
Measured final summary lines for the refund commands:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
vulnerable: Escapes: 20/20 valid trials (100.0%); total trials: 20
|
|
81
|
+
corrected: Escapes: 0/20 valid trials (0.0%); total trials: 20
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Run all three families and the tests:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
python benchmarks/run.py
|
|
88
|
+
python -m unittest discover -s tests -v
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
See [release validation](release/validation.md) for measured results and
|
|
92
|
+
exact commands. The fixtures use in-memory effect records, not real payments,
|
|
93
|
+
LLM calls, or durable external services.
|
|
94
|
+
|
|
95
|
+
## How AuthDrift works
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
Workflow observes authority
|
|
99
|
+
|
|
|
100
|
+
Checkpoint pauses continuation
|
|
101
|
+
|
|
|
102
|
+
Request authority change -> independently confirm state
|
|
103
|
+
|
|
|
104
|
+
Verify effect is still absent
|
|
105
|
+
|
|
|
106
|
+
Resume the same workflow -> inspect authoritative sink state
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Each repetition uses fresh state for positive, pre-revoked negative, and
|
|
110
|
+
mid-flight controls. Failed controls prevent that mid-flight trial from running.
|
|
111
|
+
Confirmation reads the supplied authority predicate, not the revocation callback's
|
|
112
|
+
return value or model narration.
|
|
113
|
+
|
|
114
|
+
## Outcomes
|
|
115
|
+
|
|
116
|
+
**Scenario contract required:** confirmed revocation must invalidate outstanding,
|
|
117
|
+
uncommitted effects. A policy/configuration change alone does not establish this.
|
|
118
|
+
The v0.1 API assumes this contract; it cannot discover or verify it automatically.
|
|
119
|
+
Do not use its security classifications for unspecified upstream semantics.
|
|
120
|
+
|
|
121
|
+
| Outcome | Meaning under the scenario contract | CLI exit |
|
|
122
|
+
| --- | --- | --- |
|
|
123
|
+
| `REVOCATION_ESCAPE` | Effect absent after confirmation, present after continuation | 1 |
|
|
124
|
+
| `CLOSED` | Authority remains revoked; no effect observed after completion, scoped to this trial | 0 |
|
|
125
|
+
| `BASELINE_FAILURE` | Pre-revoked control commits; temporal interpretation stops | 2 |
|
|
126
|
+
| `INVALID_EXPERIMENT` | Failed positive control, bad predicates, missing checkpoint, callback error, or ambiguous ordering | 3 |
|
|
127
|
+
|
|
128
|
+
JSON schema 1.0 contains controls, per-experiment IDs, ordered monotonic events,
|
|
129
|
+
reasons, counts, and escape rate over valid mid-flight trials only (null if none).
|
|
130
|
+
Mixed reports prioritize invalid experiments, baseline failures, escapes, then
|
|
131
|
+
closed outcomes. CLI errors also exit 3. JSON does not yet separate observation
|
|
132
|
+
from security interpretation; see [methodology](research/methodology.md).
|
|
133
|
+
|
|
134
|
+
## Included scenarios
|
|
135
|
+
|
|
136
|
+
| Family | Authority | Controlled effect | Implementations |
|
|
137
|
+
| --- | --- | --- | --- |
|
|
138
|
+
| Refund | Approval | Refund record | [vulnerable](examples/refund/scenario.py), [corrected](examples/refund/safe.py) |
|
|
139
|
+
| Delegation | Worker eligibility | Worker result | [vulnerable](examples/delegation/scenario.py), [corrected](examples/delegation/safe.py) |
|
|
140
|
+
| Session | Session authorization | Protected-action record | [vulnerable](examples/session/scenario.py), [corrected](examples/session/safe.py) |
|
|
141
|
+
|
|
142
|
+
Each directory documents its authority contract. Delegation is a synchronous
|
|
143
|
+
worker fixture, not a distributed-agent integration.
|
|
144
|
+
|
|
145
|
+
## Writing your own scenario
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
import authdrift
|
|
149
|
+
|
|
150
|
+
def build_scenario():
|
|
151
|
+
# Contract: revoked approval invalidates every outstanding refund.
|
|
152
|
+
state = {"approved": True, "refunds": []}
|
|
153
|
+
|
|
154
|
+
def workflow():
|
|
155
|
+
approved = state["approved"]
|
|
156
|
+
authdrift.checkpoint("authority_observed")
|
|
157
|
+
if approved:
|
|
158
|
+
state["refunds"].append("refund")
|
|
159
|
+
|
|
160
|
+
return authdrift.Scenario(
|
|
161
|
+
name="refund-revocation",
|
|
162
|
+
run=workflow,
|
|
163
|
+
revoke=lambda: state.update(approved=False),
|
|
164
|
+
revoked=lambda: not state["approved"],
|
|
165
|
+
committed=lambda: bool(state["refunds"]),
|
|
166
|
+
trigger="authority_observed",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
authdrift.run(build_scenario)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
CLI files export `build_scenario()` or `scenario`. Prefer a factory creating
|
|
174
|
+
fresh authority and sink state for each experiment. A `Scenario` instance requires
|
|
175
|
+
`reset=` restoring authorization and clearing the sink. Callbacks are synchronous;
|
|
176
|
+
predicates return actual booleans. Optional `confirmation_timeout` and
|
|
177
|
+
`poll_interval` default to 1 second and 0.001 seconds.
|
|
178
|
+
|
|
179
|
+
## Methodology and validity
|
|
180
|
+
|
|
181
|
+
Place the checkpoint after authority observation and before any effect. With
|
|
182
|
+
truthful monotonic sink state, no independent writers, and synchronous continuation,
|
|
183
|
+
absence after confirmation followed by presence establishes causal ordering:
|
|
184
|
+
observation < confirmed revocation < commit. Exact commit time is not measured.
|
|
185
|
+
`effect_observed` is an observation timestamp; **Post-Revocation Commit Delay
|
|
186
|
+
(PRCD)** is not calculated from it.
|
|
187
|
+
|
|
188
|
+
The workflow must join all consequential work before returning. Revocation must
|
|
189
|
+
persist without regrant; effects must not be erased. Predicates independently
|
|
190
|
+
read actual authority and sink state without side effects. Factory isolation is
|
|
191
|
+
a caller obligation, not a sandbox. Read [methodology](research/methodology.md)
|
|
192
|
+
and [threat model](research/threat-model.md) before interpreting results.
|
|
193
|
+
|
|
194
|
+
## What AuthDrift does not do
|
|
195
|
+
|
|
196
|
+
No enforcement, automatic interception, framework adapters, universal compatibility,
|
|
197
|
+
async execution, or production validation. Threads/processes do not automatically
|
|
198
|
+
inherit the checkpoint hook. Confirmation polling has a timeout, but the harness
|
|
199
|
+
cannot safely preempt blocked callbacks. Scenario files execute Python: load only
|
|
200
|
+
trusted code. CLOSED is not proof of general revocation closure.
|
|
201
|
+
|
|
202
|
+
## External validation status
|
|
203
|
+
|
|
204
|
+
| Specimen | Execution status | Security outcome |
|
|
205
|
+
| --- | --- | --- |
|
|
206
|
+
| LangGraph airline | `BLOCKED_NOT_EXECUTED` | None |
|
|
207
|
+
| Goose | `BLOCKED_NOT_EXECUTED`; `SEMANTICS_UNSPECIFIED` | None |
|
|
208
|
+
|
|
209
|
+
These are validation-attempt provenance, not successful external validation or
|
|
210
|
+
AuthDrift failures. No LangGraph or Goose vulnerability is claimed. Details and
|
|
211
|
+
the failed Ollama memory preflight: [external validation](research/external-validation.md).
|
|
212
|
+
|
|
213
|
+
## Related research
|
|
214
|
+
|
|
215
|
+
The intended contribution is developer-facing testing, injection, and reproduction
|
|
216
|
+
tooling. AuthDrift does not claim to invent stale authority, TOCTOU, commit-time
|
|
217
|
+
authorization, or revocation closure. See [related work](research/related-work.md).
|
|
218
|
+
|
|
219
|
+
## Contributing
|
|
220
|
+
|
|
221
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Independent specimens are welcome with
|
|
222
|
+
honest execution provenance and explicit authority contracts. For suspected
|
|
223
|
+
harness vulnerabilities, see [SECURITY.md](SECURITY.md).
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
[MIT](LICENSE). Retained upstream specimen material keeps its own license.
|
|
228
|
+
|
|
229
|
+
## Trusted code and report privacy
|
|
230
|
+
|
|
231
|
+
AuthDrift executes user-supplied Python scenarios. Running a scenario is
|
|
232
|
+
equivalent to executing trusted Python code. AuthDrift is not a sandbox.
|
|
233
|
+
Only execute scenarios you trust.
|
|
234
|
+
|
|
235
|
+
Exceptions and error messages may be included in generated result artifacts
|
|
236
|
+
and console output. Users should review and sanitize reports before sharing
|
|
237
|
+
or publishing them. AuthDrift does not automatically redact secrets or personal
|
|
238
|
+
data. Keep raw reports private by default.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Security reporting
|
|
2
|
+
|
|
3
|
+
AuthDrift executes trusted scenario Python; it is not a sandbox or enforcement
|
|
4
|
+
system. An intentionally vulnerable fixture is not a harness vulnerability.
|
|
5
|
+
|
|
6
|
+
GitHub Private Vulnerability Reporting is enabled for
|
|
7
|
+
[cheerstopriya/authdrift](https://github.com/cheerstopriya/authdrift).
|
|
8
|
+
Use [Report a vulnerability](https://github.com/cheerstopriya/authdrift/security/advisories/new)
|
|
9
|
+
to privately submit a suspected harness vulnerability. Include the version,
|
|
10
|
+
impact, minimal reproduction, and sanitized evidence. Do not include credentials
|
|
11
|
+
or open a public issue containing an unpatched exploit. No response-time or
|
|
12
|
+
security-support guarantee is offered.
|
|
13
|
+
|
|
14
|
+
## Trusted code and report privacy
|
|
15
|
+
|
|
16
|
+
AuthDrift executes user-supplied Python scenarios. Running a scenario is
|
|
17
|
+
equivalent to executing trusted Python code. AuthDrift is not a sandbox.
|
|
18
|
+
Only execute scenarios you trust.
|
|
19
|
+
|
|
20
|
+
Exceptions and error messages may be included in generated result artifacts
|
|
21
|
+
and console output. Users should review and sanitize reports before sharing
|
|
22
|
+
or publishing them. AuthDrift does not automatically redact secrets or personal
|
|
23
|
+
data. Keep raw reports private by default.
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import runpy
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from authdrift import run
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main(argv=None):
|
|
10
|
+
parser = argparse.ArgumentParser(prog="authdrift")
|
|
11
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
12
|
+
command = commands.add_parser("run", help="execute a trusted local scenario file")
|
|
13
|
+
command.add_argument("scenario", type=Path)
|
|
14
|
+
command.add_argument("--repeats", type=int, default=1)
|
|
15
|
+
command.add_argument("--json", type=Path, default=Path("authdrift-results.json"))
|
|
16
|
+
args = parser.parse_args(argv)
|
|
17
|
+
try:
|
|
18
|
+
namespace = runpy.run_path(str(args.scenario.resolve()))
|
|
19
|
+
source = namespace.get("build_scenario", namespace.get("scenario"))
|
|
20
|
+
if source is None:
|
|
21
|
+
raise ValueError("scenario file must export build_scenario() or scenario")
|
|
22
|
+
report = run(source, repeats=args.repeats, json_path=args.json)
|
|
23
|
+
print(f"Evidence: {args.json}")
|
|
24
|
+
return {"CLOSED": 0, "REVOCATION_ESCAPE": 1,
|
|
25
|
+
"BASELINE_FAILURE": 2, "INVALID_EXPERIMENT": 3}[report["result"]]
|
|
26
|
+
except Exception as exc:
|
|
27
|
+
print(f"authdrift: {exc}", file=sys.stderr)
|
|
28
|
+
return 3
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
raise SystemExit(main())
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from time import monotonic_ns
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Events:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.start = monotonic_ns()
|
|
7
|
+
self.items = []
|
|
8
|
+
|
|
9
|
+
def add(self, event, **details):
|
|
10
|
+
self.items.append({"sequence": len(self.items), "event": event,
|
|
11
|
+
"t_ms": (monotonic_ns() - self.start) / 1_000_000,
|
|
12
|
+
**details})
|