aegis-devops 0.1.3__tar.gz → 0.1.4__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 (108) hide show
  1. {aegis_devops-0.1.3/src/aegis_devops.egg-info → aegis_devops-0.1.4}/PKG-INFO +46 -22
  2. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/README.md +45 -21
  3. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/__init__.py +1 -1
  4. aegis_devops-0.1.4/src/aegis_core/_examples/__pycache__/__init__.cpython-312.pyc +0 -0
  5. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/hook.py +19 -10
  6. {aegis_devops-0.1.3 → aegis_devops-0.1.4/src/aegis_devops.egg-info}/PKG-INFO +46 -22
  7. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_devops.egg-info/SOURCES.txt +1 -0
  8. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_default_rules.py +16 -0
  9. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_hook.py +9 -0
  10. aegis_devops-0.1.4/tests/test_plugins.py +76 -0
  11. aegis_devops-0.1.3/src/aegis_core/_examples/__pycache__/__init__.cpython-312.pyc +0 -0
  12. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/LICENSE +0 -0
  13. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/MANIFEST.in +0 -0
  14. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/pyproject.toml +0 -0
  15. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/setup.cfg +0 -0
  16. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/__init__.py +0 -0
  17. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/authority.example.yaml +0 -0
  18. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/authority.example.yaml.sig +0 -0
  19. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/constraints.example.yaml +0 -0
  20. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/constraints.example.yaml.sig +0 -0
  21. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/environments.example.yaml +0 -0
  22. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/environments.example.yaml.sig +0 -0
  23. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/example-signing.key +0 -0
  24. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/plan_constraints.example.yaml +0 -0
  25. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/plan_constraints.example.yaml.sig +0 -0
  26. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/AEGIS-MANIFEST.sig +0 -0
  27. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/PRINCIPALS.yaml +0 -0
  28. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-4001.json +0 -0
  29. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-6001.json +0 -0
  30. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-6002.json +0 -0
  31. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-6003.json +0 -0
  32. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-6004.json +0 -0
  33. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-6005.json +0 -0
  34. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-6006.json +0 -0
  35. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-6007.json +0 -0
  36. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-6008.json +0 -0
  37. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-789abc.json +0 -0
  38. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-abc123.json +0 -0
  39. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-bcd234.json +0 -0
  40. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/git-def456.json +0 -0
  41. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-1001.json +0 -0
  42. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-1002.json +0 -0
  43. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-2001.json +0 -0
  44. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-2002.json +0 -0
  45. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-3001.json +0 -0
  46. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-3002.json +0 -0
  47. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-3003.json +0 -0
  48. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-3004.json +0 -0
  49. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-3005.json +0 -0
  50. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-5001.json +0 -0
  51. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-5002.json +0 -0
  52. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-5003.json +0 -0
  53. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-5004.json +0 -0
  54. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/jira-5006.json +0 -0
  55. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/plan-1001.json +0 -0
  56. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/plan-1002.json +0 -0
  57. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/plan-1003.json +0 -0
  58. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/plan-1004.json +0 -0
  59. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/plan-5005.json +0 -0
  60. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/_examples/sources/slack-perma-1.json +0 -0
  61. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/adversarial.py +0 -0
  62. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/authority.py +0 -0
  63. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/baselines/__init__.py +0 -0
  64. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/baselines/base.py +0 -0
  65. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/baselines/external.py +0 -0
  66. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/baselines/llm.py +0 -0
  67. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/baselines/metrics.py +0 -0
  68. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/baselines/opa.py +0 -0
  69. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/cli.py +0 -0
  70. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/config.py +0 -0
  71. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/environments.py +0 -0
  72. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/intent.py +0 -0
  73. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/interceptor.py +0 -0
  74. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/ledger.py +0 -0
  75. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/parser.py +0 -0
  76. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/parsers/__init__.py +0 -0
  77. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/parsers/pulumi.py +0 -0
  78. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/parsers/sql.py +0 -0
  79. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/plan.py +0 -0
  80. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/provenance.py +0 -0
  81. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/shell.py +0 -0
  82. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/signing.py +0 -0
  83. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_core/store.py +0 -0
  84. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_devops.egg-info/dependency_links.txt +0 -0
  85. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_devops.egg-info/entry_points.txt +0 -0
  86. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_devops.egg-info/requires.txt +0 -0
  87. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/src/aegis_devops.egg-info/top_level.txt +0 -0
  88. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_adversarial.py +0 -0
  89. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_authority.py +0 -0
  90. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_baselines.py +0 -0
  91. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_benchmark.py +0 -0
  92. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_cli.py +0 -0
  93. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_config.py +0 -0
  94. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_corpus.py +0 -0
  95. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_docs_consistency.py +0 -0
  96. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_environments.py +0 -0
  97. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_intent.py +0 -0
  98. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_interceptor.py +0 -0
  99. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_ledger.py +0 -0
  100. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_parser.py +0 -0
  101. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_parsers_pulumi.py +0 -0
  102. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_parsers_sql.py +0 -0
  103. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_plan.py +0 -0
  104. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_provenance.py +0 -0
  105. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_shell.py +0 -0
  106. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_signing.py +0 -0
  107. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_store.py +0 -0
  108. {aegis_devops-0.1.3 → aegis_devops-0.1.4}/tests/test_store_match.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aegis-devops
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Provenance-backed, authority-aware policy verifier for agentic DevOps actions.
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/moneytool/aegis-devops
@@ -30,19 +30,34 @@ Provides-Extra: llm
30
30
  Requires-Dist: anthropic; extra == "llm"
31
31
  Dynamic: license-file
32
32
 
33
- # Aegis-DevOps — Policy Verifier for AI DevOps Agents
33
+ # Aegis-DevOps
34
34
 
35
35
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22950337.svg)](https://doi.org/10.5281/zenodo.22950337)
36
36
  [![PyPI](https://img.shields.io/pypi/v/aegis-devops)](https://pypi.org/project/aegis-devops/)
37
37
 
38
- **Provenance-backed, authority-aware guardrails that stop context poisoning and agentic drift
39
- before an AI agent's `kubectl` or `terraform` action reaches your infrastructure.**
38
+ **Stop AI agents from running `kubectl delete`, `terraform destroy` or `DROP TABLE` because a
39
+ ticket told them to.**
40
40
 
41
- **Keywords:** AI agent security · AgentOps · prompt injection · context poisoning · policy
42
- enforcement · policy-as-code · Kubernetes · Terraform · OPA · SRE · LLM guardrails ·
43
- provenance · infrastructure-as-code
41
+ Aegis-DevOps checks every command an agent wants to run before it runs, and blocks it if your
42
+ policy says no. A policy rule only counts if nobody has edited it since it was signed, and if
43
+ its author was allowed to write that kind of rule. So a planted line in a Jira ticket can't
44
+ become policy.
44
45
 
45
- ![Aegis demo](docs/demo.gif)
46
+ ![Aegis-DevOps blocking an injected kubectl delete](docs/where-it-sits.gif)
47
+
48
+ ### Install in Claude Code
49
+
50
+ ```bash
51
+ pip install aegis-devops && aegis init .aegis
52
+ claude plugin marketplace add moneytool/aegis-devops
53
+ claude plugin install aegis-devops@aegis-devops
54
+ ```
55
+
56
+ Codex, GitHub Copilot (CLI and VS Code) and Cursor: `aegis install codex|copilot|vscode|cursor`
57
+ (see [Coding agents](docs/agents.md)). It only acts in projects with a `.aegis/` policy, and
58
+ only blocks what that policy blocks. It also works as a CI step
59
+ (`aegis check terraform plan.json --exit-style ci`) and as a Python library; see
60
+ [Quick start](#quick-start).
46
61
 
47
62
  ## What it does
48
63
 
@@ -92,19 +107,7 @@ a key everyone has. `aegis init` prints the two commands that replace it with yo
92
107
  replace `constraints.example.yaml` with your own `constraints.yaml` (a real file wins over the
93
108
  `.example` one when both exist).
94
109
 
95
- ### In your coding agent
96
-
97
- Aegis can check every shell command Claude Code, Codex, GitHub Copilot (CLI and VS Code) or
98
- Cursor wants to run, before it runs. In Claude Code:
99
-
100
- ```bash
101
- pip install aegis-devops && aegis init .aegis
102
- claude plugin marketplace add moneytool/aegis-devops
103
- claude plugin install aegis-devops@aegis-devops
104
- ```
105
-
106
- For the others, `aegis install codex|copilot|vscode|cursor`. It only acts in projects with a
107
- `.aegis/` policy, and only blocks what that policy blocks. See [Coding agents](docs/agents.md).
110
+ ![Aegis CLI demo](docs/demo.gif)
108
111
 
109
112
  ### From a clone
110
113
 
@@ -213,11 +216,31 @@ OPA/Gatekeeper evaluates **structured API objects** against **hand-authored rule
213
216
  derives **unstructured human constraints** (from Slack, Jira, Git) and applies
214
217
  **authority-driven validation** to the agent's intent *before* it reaches the infrastructure.
215
218
 
219
+ ## How it compares
220
+
221
+ Other tools stop destructive commands in coding agents, and cover more agents and more kinds of
222
+ command than Aegis does today. The difference is where the rules come from: Aegis treats every
223
+ rule as a claim that has to be checked (was it changed since it was signed, does its cited
224
+ source back it, was its author allowed to write that kind of rule), because in an agent's
225
+ context a rule can arrive from a ticket or a chat message as easily as from you.
226
+
227
+ | | Aegis-DevOps | [nah](https://github.com/manuelschipper/nah) | [claude-code-safety-net](https://github.com/kenryu42/claude-code-safety-net) | [destructive_command_guard](https://github.com/Dicklesworthstone/destructive_command_guard) |
228
+ |---|---|---|---|---|
229
+ | Guards | Infra commands and plans: kubectl, terraform/tofu, aws/az/gcloud, helm, argocd, flux, git/gh, SQL, pulumi | Git, filesystem, infra CLIs (Terraform, OpenTofu, Pulumi, kubectl, Docker), secrets, publishing | Destructive git and filesystem commands, secret access; cloud CLIs via optional rulebooks | Git, filesystem, databases, Kubernetes, IaC, clouds, Docker and more (50+ packs) |
230
+ | Agents | Claude Code, Codex, Copilot CLI, VS Code, Cursor | Claude Code, Codex, Cursor, Copilot and 10+ more | Claude Code, Codex, Cursor, Copilot CLI, Gemini CLI and 8+ more | Claude Code, Codex, Copilot, Cursor, Gemini CLI and 9+ more |
231
+ | Rules | Signed rules, each citing a source and an author; checked against an authority map (who may assert what) | Built-in deterministic guards; custom guards can only make it stricter | Built-in AST-based protections, configurable presets, community rulebooks | Built-in regex/AST packs, TOML config, custom YAML packs |
232
+ | Rule provenance and authority | Yes: a tampered, forged or unauthorised rule gets no vote | — | — | — |
233
+ | Terraform/Pulumi plan checks | Yes (plan JSON) | Whole-stack destroy commands | Commands via rulebooks | Destroy commands |
234
+ | CI / non-agent use | `aegis check ... --exit-style ci`, Python library | `nah test` | Node.js library mode | `dcg scan` (SARIF) |
235
+
236
+ "—" means the project's README does not describe it. Checked against each project's README on
237
+ 2026-09-27; corrections welcome.
238
+
216
239
  ## Project status / roadmap
217
240
 
218
241
  The engine (constraint store, interceptor, environment mapping, dry-run handling, rate limits,
219
242
  plan-level constraints, and parsers for every tool in "Supported tools") is complete, and
220
- v0.1.0 is on PyPI as an **alpha**. Real LLM baselines have been run: Claude Sonnet 5 through
243
+ v0.1.3 is on PyPI as an **alpha**. Real LLM baselines have been run: Claude Sonnet 5 through
221
244
  the API (cached in `results/llm-external.md`), Haiku through the Claude Code CLI, `gpt-6-astra`
222
245
  through the Codex CLI, and a local `mistral:latest`.
223
246
 
@@ -234,6 +257,7 @@ of anything you care about.
234
257
 
235
258
  | Page | Covers |
236
259
  | :--- | :--- |
260
+ | [`docs/agents.md`](docs/agents.md) | Claude Code, Codex, Copilot, VS Code and Cursor: install, what gets blocked |
237
261
  | [`docs/cli.md`](docs/cli.md) | Exit codes, store health, the Claude Code hook, argv forms, compound commands, dry runs, library usage |
238
262
  | [`docs/constraints.md`](docs/constraints.md) | Writing constraints, metadata vocabulary, authority policy, environment mapping |
239
263
  | [`docs/configuration.md`](docs/configuration.md) | Configuration/config-dir discovery, signing, source verification, rate limits & ledger |
@@ -1,16 +1,31 @@
1
- # Aegis-DevOps — Policy Verifier for AI DevOps Agents
1
+ # Aegis-DevOps
2
2
 
3
3
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22950337.svg)](https://doi.org/10.5281/zenodo.22950337)
4
4
  [![PyPI](https://img.shields.io/pypi/v/aegis-devops)](https://pypi.org/project/aegis-devops/)
5
5
 
6
- **Provenance-backed, authority-aware guardrails that stop context poisoning and agentic drift
7
- before an AI agent's `kubectl` or `terraform` action reaches your infrastructure.**
6
+ **Stop AI agents from running `kubectl delete`, `terraform destroy` or `DROP TABLE` because a
7
+ ticket told them to.**
8
8
 
9
- **Keywords:** AI agent security · AgentOps · prompt injection · context poisoning · policy
10
- enforcement · policy-as-code · Kubernetes · Terraform · OPA · SRE · LLM guardrails ·
11
- provenance · infrastructure-as-code
9
+ Aegis-DevOps checks every command an agent wants to run before it runs, and blocks it if your
10
+ policy says no. A policy rule only counts if nobody has edited it since it was signed, and if
11
+ its author was allowed to write that kind of rule. So a planted line in a Jira ticket can't
12
+ become policy.
12
13
 
13
- ![Aegis demo](docs/demo.gif)
14
+ ![Aegis-DevOps blocking an injected kubectl delete](docs/where-it-sits.gif)
15
+
16
+ ### Install in Claude Code
17
+
18
+ ```bash
19
+ pip install aegis-devops && aegis init .aegis
20
+ claude plugin marketplace add moneytool/aegis-devops
21
+ claude plugin install aegis-devops@aegis-devops
22
+ ```
23
+
24
+ Codex, GitHub Copilot (CLI and VS Code) and Cursor: `aegis install codex|copilot|vscode|cursor`
25
+ (see [Coding agents](docs/agents.md)). It only acts in projects with a `.aegis/` policy, and
26
+ only blocks what that policy blocks. It also works as a CI step
27
+ (`aegis check terraform plan.json --exit-style ci`) and as a Python library; see
28
+ [Quick start](#quick-start).
14
29
 
15
30
  ## What it does
16
31
 
@@ -60,19 +75,7 @@ a key everyone has. `aegis init` prints the two commands that replace it with yo
60
75
  replace `constraints.example.yaml` with your own `constraints.yaml` (a real file wins over the
61
76
  `.example` one when both exist).
62
77
 
63
- ### In your coding agent
64
-
65
- Aegis can check every shell command Claude Code, Codex, GitHub Copilot (CLI and VS Code) or
66
- Cursor wants to run, before it runs. In Claude Code:
67
-
68
- ```bash
69
- pip install aegis-devops && aegis init .aegis
70
- claude plugin marketplace add moneytool/aegis-devops
71
- claude plugin install aegis-devops@aegis-devops
72
- ```
73
-
74
- For the others, `aegis install codex|copilot|vscode|cursor`. It only acts in projects with a
75
- `.aegis/` policy, and only blocks what that policy blocks. See [Coding agents](docs/agents.md).
78
+ ![Aegis CLI demo](docs/demo.gif)
76
79
 
77
80
  ### From a clone
78
81
 
@@ -181,11 +184,31 @@ OPA/Gatekeeper evaluates **structured API objects** against **hand-authored rule
181
184
  derives **unstructured human constraints** (from Slack, Jira, Git) and applies
182
185
  **authority-driven validation** to the agent's intent *before* it reaches the infrastructure.
183
186
 
187
+ ## How it compares
188
+
189
+ Other tools stop destructive commands in coding agents, and cover more agents and more kinds of
190
+ command than Aegis does today. The difference is where the rules come from: Aegis treats every
191
+ rule as a claim that has to be checked (was it changed since it was signed, does its cited
192
+ source back it, was its author allowed to write that kind of rule), because in an agent's
193
+ context a rule can arrive from a ticket or a chat message as easily as from you.
194
+
195
+ | | Aegis-DevOps | [nah](https://github.com/manuelschipper/nah) | [claude-code-safety-net](https://github.com/kenryu42/claude-code-safety-net) | [destructive_command_guard](https://github.com/Dicklesworthstone/destructive_command_guard) |
196
+ |---|---|---|---|---|
197
+ | Guards | Infra commands and plans: kubectl, terraform/tofu, aws/az/gcloud, helm, argocd, flux, git/gh, SQL, pulumi | Git, filesystem, infra CLIs (Terraform, OpenTofu, Pulumi, kubectl, Docker), secrets, publishing | Destructive git and filesystem commands, secret access; cloud CLIs via optional rulebooks | Git, filesystem, databases, Kubernetes, IaC, clouds, Docker and more (50+ packs) |
198
+ | Agents | Claude Code, Codex, Copilot CLI, VS Code, Cursor | Claude Code, Codex, Cursor, Copilot and 10+ more | Claude Code, Codex, Cursor, Copilot CLI, Gemini CLI and 8+ more | Claude Code, Codex, Copilot, Cursor, Gemini CLI and 9+ more |
199
+ | Rules | Signed rules, each citing a source and an author; checked against an authority map (who may assert what) | Built-in deterministic guards; custom guards can only make it stricter | Built-in AST-based protections, configurable presets, community rulebooks | Built-in regex/AST packs, TOML config, custom YAML packs |
200
+ | Rule provenance and authority | Yes: a tampered, forged or unauthorised rule gets no vote | — | — | — |
201
+ | Terraform/Pulumi plan checks | Yes (plan JSON) | Whole-stack destroy commands | Commands via rulebooks | Destroy commands |
202
+ | CI / non-agent use | `aegis check ... --exit-style ci`, Python library | `nah test` | Node.js library mode | `dcg scan` (SARIF) |
203
+
204
+ "—" means the project's README does not describe it. Checked against each project's README on
205
+ 2026-09-27; corrections welcome.
206
+
184
207
  ## Project status / roadmap
185
208
 
186
209
  The engine (constraint store, interceptor, environment mapping, dry-run handling, rate limits,
187
210
  plan-level constraints, and parsers for every tool in "Supported tools") is complete, and
188
- v0.1.0 is on PyPI as an **alpha**. Real LLM baselines have been run: Claude Sonnet 5 through
211
+ v0.1.3 is on PyPI as an **alpha**. Real LLM baselines have been run: Claude Sonnet 5 through
189
212
  the API (cached in `results/llm-external.md`), Haiku through the Claude Code CLI, `gpt-6-astra`
190
213
  through the Codex CLI, and a local `mistral:latest`.
191
214
 
@@ -202,6 +225,7 @@ of anything you care about.
202
225
 
203
226
  | Page | Covers |
204
227
  | :--- | :--- |
228
+ | [`docs/agents.md`](docs/agents.md) | Claude Code, Codex, Copilot, VS Code and Cursor: install, what gets blocked |
205
229
  | [`docs/cli.md`](docs/cli.md) | Exit codes, store health, the Claude Code hook, argv forms, compound commands, dry runs, library usage |
206
230
  | [`docs/constraints.md`](docs/constraints.md) | Writing constraints, metadata vocabulary, authority policy, environment mapping |
207
231
  | [`docs/configuration.md`](docs/configuration.md) | Configuration/config-dir discovery, signing, source verification, rate limits & ledger |
@@ -4,4 +4,4 @@ infrastructure actions of AI agents.
4
4
  See the README for what this does and, importantly, what it does not yet do.
5
5
  """
6
6
 
7
- __version__ = "0.1.3"
7
+ __version__ = "0.1.4"
@@ -255,6 +255,21 @@ def decide(command: str, config_dir: Path, extra_args: list[str] | None = None)
255
255
  # --- replying --------------------------------------------------------------------
256
256
 
257
257
 
258
+ def _both_shapes(decision: str, reason: str) -> dict:
259
+ """Copilot CLI reads a top-level ``permissionDecision``; VS Code reads
260
+ ``hookSpecificOutput``. Both read the same ``.github/hooks`` files and
261
+ the same Copilot plugins, so their reply carries both."""
262
+ return {
263
+ "permissionDecision": decision,
264
+ "permissionDecisionReason": reason,
265
+ "hookSpecificOutput": {
266
+ "hookEventName": "PreToolUse",
267
+ "permissionDecision": decision,
268
+ "permissionDecisionReason": reason,
269
+ },
270
+ }
271
+
272
+
258
273
  def render(agent: str, verdict: HookVerdict, *, escalate_as: str = "ask") -> tuple[int, str, str]:
259
274
  """``(exit_code, stdout, stderr)`` for this agent. Deny is exit 2 +
260
275
  stderr (Cursor: its JSON on exit 0); ask is each agent's own JSON with
@@ -273,11 +288,8 @@ def render(agent: str, verdict: HookVerdict, *, escalate_as: str = "ask") -> tup
273
288
  "codex": {"hookSpecificOutput": {
274
289
  "hookEventName": "PreToolUse", "permissionDecision": "deny",
275
290
  "permissionDecisionReason": reason}},
276
- "copilot": {"permissionDecision": "deny", "permissionDecisionReason": reason},
277
- "vscode": {"permissionDecision": "deny", "permissionDecisionReason": reason,
278
- "hookSpecificOutput": {"hookEventName": "PreToolUse",
279
- "permissionDecision": "deny",
280
- "permissionDecisionReason": reason}},
291
+ "copilot": _both_shapes("deny", reason),
292
+ "vscode": _both_shapes("deny", reason),
281
293
  "cursor": {"permission": "deny", "user_message": reason, "agent_message": reason},
282
294
  }[agent]
283
295
  if agent == "cursor":
@@ -291,11 +303,8 @@ def render(agent: str, verdict: HookVerdict, *, escalate_as: str = "ask") -> tup
291
303
  "claude": {"hookSpecificOutput": {
292
304
  "hookEventName": "PreToolUse", "permissionDecision": "ask",
293
305
  "permissionDecisionReason": reason}},
294
- "copilot": {"permissionDecision": "ask", "permissionDecisionReason": reason},
295
- "vscode": {"permissionDecision": "ask", "permissionDecisionReason": reason,
296
- "hookSpecificOutput": {"hookEventName": "PreToolUse",
297
- "permissionDecision": "ask",
298
- "permissionDecisionReason": reason}},
306
+ "copilot": _both_shapes("ask", reason),
307
+ "vscode": _both_shapes("ask", reason),
299
308
  "cursor": {"permission": "ask", "user_message": reason, "agent_message": reason},
300
309
  }[agent]
301
310
  return EXIT_ALLOW, json.dumps(body), ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aegis-devops
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Provenance-backed, authority-aware policy verifier for agentic DevOps actions.
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/moneytool/aegis-devops
@@ -30,19 +30,34 @@ Provides-Extra: llm
30
30
  Requires-Dist: anthropic; extra == "llm"
31
31
  Dynamic: license-file
32
32
 
33
- # Aegis-DevOps — Policy Verifier for AI DevOps Agents
33
+ # Aegis-DevOps
34
34
 
35
35
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22950337.svg)](https://doi.org/10.5281/zenodo.22950337)
36
36
  [![PyPI](https://img.shields.io/pypi/v/aegis-devops)](https://pypi.org/project/aegis-devops/)
37
37
 
38
- **Provenance-backed, authority-aware guardrails that stop context poisoning and agentic drift
39
- before an AI agent's `kubectl` or `terraform` action reaches your infrastructure.**
38
+ **Stop AI agents from running `kubectl delete`, `terraform destroy` or `DROP TABLE` because a
39
+ ticket told them to.**
40
40
 
41
- **Keywords:** AI agent security · AgentOps · prompt injection · context poisoning · policy
42
- enforcement · policy-as-code · Kubernetes · Terraform · OPA · SRE · LLM guardrails ·
43
- provenance · infrastructure-as-code
41
+ Aegis-DevOps checks every command an agent wants to run before it runs, and blocks it if your
42
+ policy says no. A policy rule only counts if nobody has edited it since it was signed, and if
43
+ its author was allowed to write that kind of rule. So a planted line in a Jira ticket can't
44
+ become policy.
44
45
 
45
- ![Aegis demo](docs/demo.gif)
46
+ ![Aegis-DevOps blocking an injected kubectl delete](docs/where-it-sits.gif)
47
+
48
+ ### Install in Claude Code
49
+
50
+ ```bash
51
+ pip install aegis-devops && aegis init .aegis
52
+ claude plugin marketplace add moneytool/aegis-devops
53
+ claude plugin install aegis-devops@aegis-devops
54
+ ```
55
+
56
+ Codex, GitHub Copilot (CLI and VS Code) and Cursor: `aegis install codex|copilot|vscode|cursor`
57
+ (see [Coding agents](docs/agents.md)). It only acts in projects with a `.aegis/` policy, and
58
+ only blocks what that policy blocks. It also works as a CI step
59
+ (`aegis check terraform plan.json --exit-style ci`) and as a Python library; see
60
+ [Quick start](#quick-start).
46
61
 
47
62
  ## What it does
48
63
 
@@ -92,19 +107,7 @@ a key everyone has. `aegis init` prints the two commands that replace it with yo
92
107
  replace `constraints.example.yaml` with your own `constraints.yaml` (a real file wins over the
93
108
  `.example` one when both exist).
94
109
 
95
- ### In your coding agent
96
-
97
- Aegis can check every shell command Claude Code, Codex, GitHub Copilot (CLI and VS Code) or
98
- Cursor wants to run, before it runs. In Claude Code:
99
-
100
- ```bash
101
- pip install aegis-devops && aegis init .aegis
102
- claude plugin marketplace add moneytool/aegis-devops
103
- claude plugin install aegis-devops@aegis-devops
104
- ```
105
-
106
- For the others, `aegis install codex|copilot|vscode|cursor`. It only acts in projects with a
107
- `.aegis/` policy, and only blocks what that policy blocks. See [Coding agents](docs/agents.md).
110
+ ![Aegis CLI demo](docs/demo.gif)
108
111
 
109
112
  ### From a clone
110
113
 
@@ -213,11 +216,31 @@ OPA/Gatekeeper evaluates **structured API objects** against **hand-authored rule
213
216
  derives **unstructured human constraints** (from Slack, Jira, Git) and applies
214
217
  **authority-driven validation** to the agent's intent *before* it reaches the infrastructure.
215
218
 
219
+ ## How it compares
220
+
221
+ Other tools stop destructive commands in coding agents, and cover more agents and more kinds of
222
+ command than Aegis does today. The difference is where the rules come from: Aegis treats every
223
+ rule as a claim that has to be checked (was it changed since it was signed, does its cited
224
+ source back it, was its author allowed to write that kind of rule), because in an agent's
225
+ context a rule can arrive from a ticket or a chat message as easily as from you.
226
+
227
+ | | Aegis-DevOps | [nah](https://github.com/manuelschipper/nah) | [claude-code-safety-net](https://github.com/kenryu42/claude-code-safety-net) | [destructive_command_guard](https://github.com/Dicklesworthstone/destructive_command_guard) |
228
+ |---|---|---|---|---|
229
+ | Guards | Infra commands and plans: kubectl, terraform/tofu, aws/az/gcloud, helm, argocd, flux, git/gh, SQL, pulumi | Git, filesystem, infra CLIs (Terraform, OpenTofu, Pulumi, kubectl, Docker), secrets, publishing | Destructive git and filesystem commands, secret access; cloud CLIs via optional rulebooks | Git, filesystem, databases, Kubernetes, IaC, clouds, Docker and more (50+ packs) |
230
+ | Agents | Claude Code, Codex, Copilot CLI, VS Code, Cursor | Claude Code, Codex, Cursor, Copilot and 10+ more | Claude Code, Codex, Cursor, Copilot CLI, Gemini CLI and 8+ more | Claude Code, Codex, Copilot, Cursor, Gemini CLI and 9+ more |
231
+ | Rules | Signed rules, each citing a source and an author; checked against an authority map (who may assert what) | Built-in deterministic guards; custom guards can only make it stricter | Built-in AST-based protections, configurable presets, community rulebooks | Built-in regex/AST packs, TOML config, custom YAML packs |
232
+ | Rule provenance and authority | Yes: a tampered, forged or unauthorised rule gets no vote | — | — | — |
233
+ | Terraform/Pulumi plan checks | Yes (plan JSON) | Whole-stack destroy commands | Commands via rulebooks | Destroy commands |
234
+ | CI / non-agent use | `aegis check ... --exit-style ci`, Python library | `nah test` | Node.js library mode | `dcg scan` (SARIF) |
235
+
236
+ "—" means the project's README does not describe it. Checked against each project's README on
237
+ 2026-09-27; corrections welcome.
238
+
216
239
  ## Project status / roadmap
217
240
 
218
241
  The engine (constraint store, interceptor, environment mapping, dry-run handling, rate limits,
219
242
  plan-level constraints, and parsers for every tool in "Supported tools") is complete, and
220
- v0.1.0 is on PyPI as an **alpha**. Real LLM baselines have been run: Claude Sonnet 5 through
243
+ v0.1.3 is on PyPI as an **alpha**. Real LLM baselines have been run: Claude Sonnet 5 through
221
244
  the API (cached in `results/llm-external.md`), Haiku through the Claude Code CLI, `gpt-6-astra`
222
245
  through the Codex CLI, and a local `mistral:latest`.
223
246
 
@@ -234,6 +257,7 @@ of anything you care about.
234
257
 
235
258
  | Page | Covers |
236
259
  | :--- | :--- |
260
+ | [`docs/agents.md`](docs/agents.md) | Claude Code, Codex, Copilot, VS Code and Cursor: install, what gets blocked |
237
261
  | [`docs/cli.md`](docs/cli.md) | Exit codes, store health, the Claude Code hook, argv forms, compound commands, dry runs, library usage |
238
262
  | [`docs/constraints.md`](docs/constraints.md) | Writing constraints, metadata vocabulary, authority policy, environment mapping |
239
263
  | [`docs/configuration.md`](docs/configuration.md) | Configuration/config-dir discovery, signing, source verification, rate limits & ledger |
@@ -97,6 +97,7 @@ tests/test_parser.py
97
97
  tests/test_parsers_pulumi.py
98
98
  tests/test_parsers_sql.py
99
99
  tests/test_plan.py
100
+ tests/test_plugins.py
100
101
  tests/test_provenance.py
101
102
  tests/test_shell.py
102
103
  tests/test_signing.py
@@ -108,3 +108,19 @@ def test_plan_json_intents_are_not_caught_by_the_workspace_rule(capsys):
108
108
  "name": "b", "change": {"actions": ["delete"]}}]}
109
109
  [intent] = from_terraform_plan(plan)
110
110
  assert not intent.resource.startswith("workspace/")
111
+
112
+
113
+ def test_forged_sources_differ_from_sources_in_one_file_only():
114
+ """data/sources-forged/ is data/sources/ with jira-1001.json edited, for
115
+ the --sources demo. Any other difference (a source missing after new
116
+ rules were added) quarantines more rules and breaks the demo."""
117
+ from pathlib import Path
118
+
119
+ real, forged = Path("data/sources"), Path("data/sources-forged")
120
+ names = {p.name for p in real.iterdir()} | {p.name for p in forged.iterdir()}
121
+ differing = sorted(
122
+ n for n in names - {"AEGIS-MANIFEST.sig"}
123
+ if not (real / n).exists() or not (forged / n).exists()
124
+ or (real / n).read_bytes() != (forged / n).read_bytes()
125
+ )
126
+ assert differing == ["jira-1001.json"]
@@ -625,3 +625,12 @@ def test_remove_deletes_a_file_that_only_held_our_hook(tmp_path, monkeypatch):
625
625
  def test_cursor_allow_is_explicit_when_opted_in(opted_in_project):
626
626
  code, out, _err = run("cursor", payload_for("cursor", "ls -la"))
627
627
  assert (code, json.loads(out)) == (0, {"permission": "allow"})
628
+
629
+
630
+ def test_copilot_reply_carries_the_vscode_shape_too(opted_in_project):
631
+ # Copilot plugins and .github/hooks are read by both Copilot CLI and VS Code
632
+ code, out, _err = run("copilot", payload_for("copilot", BLOCK_COMMAND))
633
+ body = json.loads(out)
634
+ assert code == 2
635
+ assert body["permissionDecision"] == "deny"
636
+ assert body["hookSpecificOutput"]["permissionDecision"] == "deny"
@@ -0,0 +1,76 @@
1
+ """Packaging of the Claude Code and Copilot plugins, and the wrapper script
2
+ they both run when ``aegis`` itself may be missing."""
3
+
4
+ import json
5
+ import os
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ from aegis_core import __version__
12
+
13
+ ROOT = Path(__file__).resolve().parent.parent
14
+ WRAPPER = ROOT / "hooks" / "aegis-hook.sh"
15
+ COPILOT = ROOT / "plugins" / "copilot"
16
+
17
+
18
+ def test_copilot_plugin_manifest():
19
+ manifest = json.loads((COPILOT / "plugin.json").read_text())
20
+ assert manifest["$schema"] == "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"
21
+ assert manifest["name"] == "aegis-devops"
22
+ # awesome-copilot pins a semver version to a release tag
23
+ assert manifest["version"] == __version__
24
+
25
+
26
+ def test_copilot_plugin_hook_runs_the_wrapper():
27
+ hooks = json.loads((COPILOT / "com.github.copilot" / "hooks" / "hooks.json").read_text())
28
+ [entry] = hooks["hooks"]["preToolUse"]
29
+ assert entry["bash"] == '"${PLUGIN_ROOT}"/aegis-hook.sh copilot'
30
+ assert (COPILOT / "aegis-hook.sh").stat().st_mode & 0o111
31
+
32
+
33
+ def test_copilot_wrapper_is_a_copy_of_the_claude_one():
34
+ # a plugin can only ship files inside its own folder
35
+ assert (COPILOT / "aegis-hook.sh").read_bytes() == WRAPPER.read_bytes()
36
+
37
+
38
+ def test_claude_plugin_hook_runs_the_wrapper():
39
+ hooks = json.loads((ROOT / "hooks" / "hooks.json").read_text())
40
+ [entry] = hooks["hooks"]["PreToolUse"]
41
+ assert entry["matcher"] == "Bash"
42
+ assert entry["hooks"][0]["command"] == '"${CLAUDE_PLUGIN_ROOT}"/hooks/aegis-hook.sh claude'
43
+
44
+
45
+ # --- the wrapper with no aegis installed --------------------------------------------
46
+
47
+
48
+ def _run_wrapper(tmp_path, command, *, opted_in):
49
+ home, project = tmp_path / "home", tmp_path / "project"
50
+ home.mkdir()
51
+ project.mkdir()
52
+ if opted_in:
53
+ (project / ".aegis").mkdir()
54
+ env = {"HOME": str(home), "PATH": "/usr/bin:/bin"} # no aegis, no uvx
55
+ payload = json.dumps({"toolName": "bash", "toolArgs": {"command": command}})
56
+ proc = subprocess.run(
57
+ ["bash", str(WRAPPER), "copilot"], input=payload, capture_output=True, text=True,
58
+ cwd=project, env=env, check=False,
59
+ )
60
+ return proc.returncode, proc.stderr
61
+
62
+
63
+ @pytest.mark.skipif(os.name == "nt", reason="bash wrapper")
64
+ @pytest.mark.parametrize("command", ["ls -la", "kubectl delete nodes --all"])
65
+ def test_wrapper_without_aegis_allows_everything_outside_opted_in_projects(tmp_path, command):
66
+ assert _run_wrapper(tmp_path, command, opted_in=False) == (0, "")
67
+
68
+
69
+ @pytest.mark.skipif(os.name == "nt", reason="bash wrapper")
70
+ def test_wrapper_without_aegis_blocks_only_infra_in_opted_in_projects(tmp_path):
71
+ code, err = _run_wrapper(tmp_path, "kubectl get pods", opted_in=True)
72
+ assert code == 2
73
+ assert "pip install aegis-devops" in err
74
+ (tmp_path / "home").rename(tmp_path / "old-home")
75
+ (tmp_path / "project").rename(tmp_path / "old-project")
76
+ assert _run_wrapper(tmp_path, "ls -la", opted_in=True) == (0, "")
File without changes
File without changes
File without changes