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.
Files changed (43) hide show
  1. authdrift_harness-0.1.0/CHANGELOG.md +9 -0
  2. authdrift_harness-0.1.0/CONTRIBUTING.md +17 -0
  3. authdrift_harness-0.1.0/LICENSE +21 -0
  4. authdrift_harness-0.1.0/MANIFEST.in +8 -0
  5. authdrift_harness-0.1.0/PKG-INFO +260 -0
  6. authdrift_harness-0.1.0/README.md +238 -0
  7. authdrift_harness-0.1.0/SECURITY.md +23 -0
  8. authdrift_harness-0.1.0/authdrift/__init__.py +6 -0
  9. authdrift_harness-0.1.0/authdrift/__main__.py +3 -0
  10. authdrift_harness-0.1.0/authdrift/cli/__init__.py +0 -0
  11. authdrift_harness-0.1.0/authdrift/cli/main.py +32 -0
  12. authdrift_harness-0.1.0/authdrift/core/__init__.py +0 -0
  13. authdrift_harness-0.1.0/authdrift/core/events.py +12 -0
  14. authdrift_harness-0.1.0/authdrift/core/injector.py +36 -0
  15. authdrift_harness-0.1.0/authdrift/core/result.py +8 -0
  16. authdrift_harness-0.1.0/authdrift/core/runner.py +125 -0
  17. authdrift_harness-0.1.0/authdrift/core/scenario.py +23 -0
  18. authdrift_harness-0.1.0/authdrift/hooks/__init__.py +0 -0
  19. authdrift_harness-0.1.0/authdrift/hooks/lifecycle.py +14 -0
  20. authdrift_harness-0.1.0/authdrift_harness.egg-info/PKG-INFO +260 -0
  21. authdrift_harness-0.1.0/authdrift_harness.egg-info/SOURCES.txt +41 -0
  22. authdrift_harness-0.1.0/authdrift_harness.egg-info/dependency_links.txt +1 -0
  23. authdrift_harness-0.1.0/authdrift_harness.egg-info/entry_points.txt +2 -0
  24. authdrift_harness-0.1.0/authdrift_harness.egg-info/top_level.txt +1 -0
  25. authdrift_harness-0.1.0/benchmarks/run.py +22 -0
  26. authdrift_harness-0.1.0/examples/delegation/README.md +21 -0
  27. authdrift_harness-0.1.0/examples/delegation/safe.py +7 -0
  28. authdrift_harness-0.1.0/examples/delegation/scenario.py +15 -0
  29. authdrift_harness-0.1.0/examples/refund/README.md +21 -0
  30. authdrift_harness-0.1.0/examples/refund/safe.py +7 -0
  31. authdrift_harness-0.1.0/examples/refund/scenario.py +13 -0
  32. authdrift_harness-0.1.0/examples/session/README.md +21 -0
  33. authdrift_harness-0.1.0/examples/session/safe.py +7 -0
  34. authdrift_harness-0.1.0/examples/session/scenario.py +13 -0
  35. authdrift_harness-0.1.0/pyproject.toml +35 -0
  36. authdrift_harness-0.1.0/release/validation.md +24 -0
  37. authdrift_harness-0.1.0/research/external-validation.md +31 -0
  38. authdrift_harness-0.1.0/research/implementation-status.md +5 -0
  39. authdrift_harness-0.1.0/research/methodology.md +41 -0
  40. authdrift_harness-0.1.0/research/related-work.md +38 -0
  41. authdrift_harness-0.1.0/research/threat-model.md +17 -0
  42. authdrift_harness-0.1.0/setup.cfg +4 -0
  43. 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.
@@ -0,0 +1,6 @@
1
+ """Explicit, synchronous revocation experiments."""
2
+ from .core.scenario import Scenario
3
+ from .core.runner import run
4
+ from .hooks.lifecycle import checkpoint
5
+
6
+ __all__ = ["Scenario", "run", "checkpoint"]
@@ -0,0 +1,3 @@
1
+ from .cli.main import main
2
+
3
+ raise SystemExit(main())
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})