opsentry 1.8.0__py3-none-any.whl

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 (61) hide show
  1. opsentry/VERSION +1 -0
  2. opsentry/__init__.py +5 -0
  3. opsentry/cli.py +295 -0
  4. opsentry/config/__init__.py +0 -0
  5. opsentry/config/generate.py +414 -0
  6. opsentry/config/hook_templates/block-dangerous-commands.sh.j2 +102 -0
  7. opsentry/config/hook_templates/block-data-exfiltration.sh.j2 +94 -0
  8. opsentry/config/hook_templates/block-environment-escape.sh.j2 +99 -0
  9. opsentry/config/hook_templates/block-git-commands.sh.j2 +46 -0
  10. opsentry/config/hook_templates/block-package-install.sh.j2 +95 -0
  11. opsentry/config/hook_templates/block-pii-leakage.sh.j2 +100 -0
  12. opsentry/config/hook_templates/block-scope-escape.sh.j2 +115 -0
  13. opsentry/config/hook_templates/block-sensitive-files.sh.j2 +97 -0
  14. opsentry/config/init_wizard.py +201 -0
  15. opsentry/config/rule_texts/01_sensitive_files.md +15 -0
  16. opsentry/config/rule_texts/02_client_data.md +10 -0
  17. opsentry/config/rule_texts/03_credentials.md +17 -0
  18. opsentry/config/rule_texts/04_sql_safety.md +33 -0
  19. opsentry/config/rule_texts/05_dangerous_code.md +29 -0
  20. opsentry/config/rule_texts/06_frontend_xss.md +9 -0
  21. opsentry/config/rule_texts/07_git_operations_block_all.md +13 -0
  22. opsentry/config/rule_texts/07_git_operations_read_only.md +18 -0
  23. opsentry/config/rule_texts/08_destructive_commands.md +12 -0
  24. opsentry/config/rule_texts/09_database_safety.md +17 -0
  25. opsentry/config/rule_texts/10_network_requests.md +10 -0
  26. opsentry/config/rule_texts/11_code_quality.md +13 -0
  27. opsentry/config/rule_texts/12_data_exfiltration.md +10 -0
  28. opsentry/config/rule_texts/13_package_install.md +9 -0
  29. opsentry/config/rule_texts/14_scope_boundaries.md +8 -0
  30. opsentry/config/rule_texts/15_env_isolation.md +9 -0
  31. opsentry/config/rule_texts/16_resource_limits.md +8 -0
  32. opsentry/config/rule_texts/17_compliance_privacy.md +9 -0
  33. opsentry/config/rule_texts/18_when_in_doubt.md +10 -0
  34. opsentry/config/schema.py +142 -0
  35. opsentry/config/templates/generic.yaml +84 -0
  36. opsentry/deployment/.claude/commands/code-health.md +42 -0
  37. opsentry/deployment/.claude/commands/governance-check.md +29 -0
  38. opsentry/deployment/.claude/commands/security-audit.md +41 -0
  39. opsentry/deployment/baseline.py +440 -0
  40. opsentry/deployment/blocklog_audit.py +497 -0
  41. opsentry/deployment/claude/CLAUDE.md +300 -0
  42. opsentry/deployment/claude/hooks/block-dangerous-commands.sh +106 -0
  43. opsentry/deployment/claude/hooks/block-data-exfiltration.sh +99 -0
  44. opsentry/deployment/claude/hooks/block-environment-escape.sh +96 -0
  45. opsentry/deployment/claude/hooks/block-git-commands.sh +37 -0
  46. opsentry/deployment/claude/hooks/block-package-install.sh +103 -0
  47. opsentry/deployment/claude/hooks/block-pii-leakage.sh +86 -0
  48. opsentry/deployment/claude/hooks/block-scope-escape.sh +210 -0
  49. opsentry/deployment/claude/hooks/block-sensitive-files.sh +97 -0
  50. opsentry/deployment/claude/settings.json +72 -0
  51. opsentry/deployment/install.sh +343 -0
  52. opsentry/deployment/patrol.sh +266 -0
  53. opsentry/deployment/test.sh +728 -0
  54. opsentry/deployment/update.sh +19 -0
  55. opsentry/deployment/verify.sh +201 -0
  56. opsentry-1.8.0.dist-info/METADATA +158 -0
  57. opsentry-1.8.0.dist-info/RECORD +61 -0
  58. opsentry-1.8.0.dist-info/WHEEL +5 -0
  59. opsentry-1.8.0.dist-info/entry_points.txt +2 -0
  60. opsentry-1.8.0.dist-info/licenses/LICENSE +190 -0
  61. opsentry-1.8.0.dist-info/top_level.txt +1 -0
opsentry/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.8.0
opsentry/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """OpSentry — AI agent security guardrails for engineering teams."""
2
+
3
+ from pathlib import Path
4
+
5
+ __version__ = (Path(__file__).parent / "VERSION").read_text().strip()
opsentry/cli.py ADDED
@@ -0,0 +1,295 @@
1
+ """OpSentry CLI — AI agent security guardrails for engineering teams.
2
+
3
+ Entry point for the pip-installed opsentry command.
4
+ Resolves data files from the installed package, not a git checkout.
5
+ """
6
+
7
+ import argparse
8
+ import json
9
+ import os
10
+ import shutil
11
+ import subprocess
12
+ import sys
13
+ import urllib.request
14
+ from pathlib import Path
15
+
16
+ # Package root — where data files live alongside this module
17
+ PKG_ROOT = Path(__file__).resolve().parent
18
+ VERSION_FILE = PKG_ROOT / "VERSION"
19
+ CONFIG_DIR = PKG_ROOT / "config"
20
+ DEPLOYMENT_DIR = PKG_ROOT / "deployment"
21
+ INSTALL_SCRIPT = DEPLOYMENT_DIR / "install.sh"
22
+ VERIFY_SCRIPT = DEPLOYMENT_DIR / "verify.sh"
23
+ PATROL_SCRIPT = DEPLOYMENT_DIR / "patrol.sh"
24
+ CLAUDE_HOME = Path.home() / ".claude"
25
+
26
+ GITHUB_REPO = "opsight-intelligence/opsentry"
27
+ RELEASES_API = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"
28
+
29
+
30
+ def get_local_version() -> str:
31
+ if VERSION_FILE.exists():
32
+ return VERSION_FILE.read_text().strip()
33
+ return "unknown"
34
+
35
+
36
+ def get_installed_version() -> str:
37
+ installed = CLAUDE_HOME / "VERSION"
38
+ if installed.exists():
39
+ return installed.read_text().strip()
40
+ return "not installed"
41
+
42
+
43
+ def check_latest_version() -> str | None:
44
+ """Check GitHub for the latest release tag. Returns None on failure."""
45
+ try:
46
+ req = urllib.request.Request(
47
+ RELEASES_API, headers={"Accept": "application/vnd.github.v3+json"}
48
+ )
49
+ with urllib.request.urlopen(req, timeout=5) as resp:
50
+ data = json.loads(resp.read())
51
+ return data.get("tag_name", "").lstrip("v")
52
+ except Exception:
53
+ return None
54
+
55
+
56
+ def print_version_status():
57
+ local = get_local_version()
58
+ installed = get_installed_version()
59
+ print(f" Repo version: {local}")
60
+ print(f" Installed version: {installed}")
61
+
62
+ latest = check_latest_version()
63
+ if latest:
64
+ print(f" Latest release: {latest}")
65
+ if local != latest:
66
+ print(f"\n Update available: {local} -> {latest}")
67
+ print(" Run: pip install --upgrade opsentry && opsentry install")
68
+ else:
69
+ print(" Latest release: (could not check)")
70
+
71
+
72
+ def run_script(script: Path, label: str, cwd: Path | None = None) -> int:
73
+ if not script.exists():
74
+ print(f"ERROR: {label} not found at {script}", file=sys.stderr)
75
+ return 1
76
+ result = subprocess.run(
77
+ ["bash", str(script)],
78
+ cwd=str(cwd) if cwd else None,
79
+ env={**os.environ, "OPSENTRY_PKG_ROOT": str(PKG_ROOT)},
80
+ )
81
+ return result.returncode
82
+
83
+
84
+ def check_deps():
85
+ """Check required dependencies are available."""
86
+ missing = []
87
+ for cmd in ("jq", "bash"):
88
+ if shutil.which(cmd) is None:
89
+ missing.append(cmd)
90
+ if missing:
91
+ print(f"Missing dependencies: {', '.join(missing)}", file=sys.stderr)
92
+ print("Install with:", file=sys.stderr)
93
+ print(" macOS: brew install " + " ".join(missing), file=sys.stderr)
94
+ print(" Ubuntu: sudo apt install " + " ".join(missing), file=sys.stderr)
95
+ return False
96
+ return True
97
+
98
+
99
+ # -- Subcommands ---------------------------------------------------------------
100
+
101
+
102
+ def cmd_init(args):
103
+ """Run the interactive setup wizard."""
104
+ sys.path.insert(0, str(CONFIG_DIR))
105
+ from init_wizard import main as wizard_main
106
+
107
+ sys.argv = ["opsentry init"] + args.extra
108
+ wizard_main()
109
+
110
+
111
+ def cmd_generate(args):
112
+ """Generate enforcement files from guardrails.yaml."""
113
+ guardrails_yaml = Path.cwd() / "guardrails.yaml"
114
+ if not guardrails_yaml.exists():
115
+ print(f"ERROR: guardrails.yaml not found in {Path.cwd()}", file=sys.stderr)
116
+ print("Run 'opsentry init' first to create it.", file=sys.stderr)
117
+ return 1
118
+
119
+ sys.path.insert(0, str(CONFIG_DIR))
120
+ from generate import load_config, write_outputs
121
+
122
+ config = load_config(str(guardrails_yaml))
123
+ output_dir = str(DEPLOYMENT_DIR / "claude")
124
+ print(f"Generating from {guardrails_yaml}...")
125
+ write_outputs(config, output_dir)
126
+ print("Done.")
127
+ return 0
128
+
129
+
130
+ def cmd_install(args):
131
+ """Install guardrails to ~/.claude/."""
132
+ if not check_deps():
133
+ return 1
134
+
135
+ print("=" * 50)
136
+ print(" OpSentry Install")
137
+ print("=" * 50)
138
+ print()
139
+
140
+ # Generate if guardrails.yaml exists in cwd
141
+ guardrails_yaml = Path.cwd() / "guardrails.yaml"
142
+ if guardrails_yaml.exists():
143
+ print("Found guardrails.yaml — generating config...")
144
+ ret = cmd_generate(args)
145
+ if ret != 0:
146
+ return ret
147
+ print()
148
+
149
+ ret = run_script(INSTALL_SCRIPT, "install.sh", cwd=DEPLOYMENT_DIR.parent)
150
+ if ret == 0:
151
+ print()
152
+ print_version_status()
153
+ return ret
154
+
155
+
156
+ def cmd_update(args):
157
+ """Update opsentry to the latest version."""
158
+ print("Updating opsentry...")
159
+ result = subprocess.run(
160
+ [sys.executable, "-m", "pip", "install", "--upgrade", "opsentry"],
161
+ capture_output=True,
162
+ text=True,
163
+ )
164
+ if result.returncode != 0:
165
+ print(f"Upgrade failed: {result.stderr}", file=sys.stderr)
166
+ return 1
167
+ print(result.stdout.strip())
168
+ print()
169
+ return cmd_install(args)
170
+
171
+
172
+ def cmd_verify(args):
173
+ """Check installed guardrails are intact."""
174
+ ret = run_script(VERIFY_SCRIPT, "verify.sh", cwd=DEPLOYMENT_DIR.parent)
175
+ if ret == 0:
176
+ print()
177
+ print_version_status()
178
+ return ret
179
+
180
+
181
+ def cmd_patrol(args):
182
+ """Run compliance patrol (extended audit)."""
183
+ return run_script(PATROL_SCRIPT, "patrol.sh", cwd=DEPLOYMENT_DIR.parent)
184
+
185
+
186
+ def cmd_status(args):
187
+ """Show installation status and version info."""
188
+ print("=" * 50)
189
+ print(" OpSentry Status")
190
+ print("=" * 50)
191
+ print()
192
+
193
+ print_version_status()
194
+ print()
195
+
196
+ checks = [
197
+ (CLAUDE_HOME / "CLAUDE.md", "CLAUDE.md"),
198
+ (CLAUDE_HOME / "settings.json", "settings.json"),
199
+ (CLAUDE_HOME / "hooks", "hooks/"),
200
+ ]
201
+ all_ok = True
202
+ for path, label in checks:
203
+ if path.exists():
204
+ print(f" + {label}")
205
+ else:
206
+ print(f" x {label} — MISSING")
207
+ all_ok = False
208
+
209
+ hook_names = [
210
+ "block-sensitive-files.sh",
211
+ "block-dangerous-commands.sh",
212
+ "block-git-commands.sh",
213
+ "block-data-exfiltration.sh",
214
+ "block-package-install.sh",
215
+ "block-scope-escape.sh",
216
+ "block-environment-escape.sh",
217
+ "block-pii-leakage.sh",
218
+ ]
219
+ hooks_dir = CLAUDE_HOME / "hooks"
220
+ if hooks_dir.exists():
221
+ for h in hook_names:
222
+ hp = hooks_dir / h
223
+ if hp.exists() and os.access(hp, os.X_OK):
224
+ print(f" + hooks/{h}")
225
+ else:
226
+ status = "not executable" if hp.exists() else "MISSING"
227
+ print(f" x hooks/{h} — {status}")
228
+ all_ok = False
229
+
230
+ print()
231
+ guardrails_yaml = Path.cwd() / "guardrails.yaml"
232
+ if guardrails_yaml.exists():
233
+ print(f" Config: {guardrails_yaml}")
234
+ else:
235
+ print(" Config: none (using static files)")
236
+
237
+ log = CLAUDE_HOME / "guardrail-blocks.log"
238
+ if log.exists():
239
+ line_count = sum(1 for _ in open(log))
240
+ print(f" Block log: {line_count} entries ({log})")
241
+ else:
242
+ print(" Block log: empty (no blocks recorded yet)")
243
+
244
+ print()
245
+ if all_ok:
246
+ print(" Status: All guardrails installed and intact.")
247
+ else:
248
+ print(" Status: INCOMPLETE — run 'opsentry install' to fix.")
249
+
250
+ return 0 if all_ok else 1
251
+
252
+
253
+ # -- Main ----------------------------------------------------------------------
254
+
255
+
256
+ def main():
257
+ parser = argparse.ArgumentParser(
258
+ prog="opsentry",
259
+ description="OpSentry — AI agent security guardrails for engineering teams",
260
+ )
261
+ parser.add_argument(
262
+ "--version", action="version", version=f"opsentry {get_local_version()}"
263
+ )
264
+ sub = parser.add_subparsers(dest="command")
265
+
266
+ p_init = sub.add_parser("init", help="Interactive setup wizard")
267
+ p_init.add_argument("extra", nargs="*", help="Extra args passed to init wizard")
268
+
269
+ sub.add_parser("generate", help="Generate enforcement files from guardrails.yaml")
270
+ sub.add_parser("install", help="Generate + install guardrails to ~/.claude/")
271
+ sub.add_parser("update", help="Update to latest version + reinstall")
272
+ sub.add_parser("verify", help="Check installed guardrails are intact")
273
+ sub.add_parser("status", help="Show installation status and version info")
274
+ sub.add_parser("patrol", help="Run compliance patrol (extended audit)")
275
+
276
+ args = parser.parse_args()
277
+
278
+ if not args.command:
279
+ parser.print_help()
280
+ return 0
281
+
282
+ commands = {
283
+ "init": cmd_init,
284
+ "generate": cmd_generate,
285
+ "install": cmd_install,
286
+ "update": cmd_update,
287
+ "verify": cmd_verify,
288
+ "status": cmd_status,
289
+ "patrol": cmd_patrol,
290
+ }
291
+ return commands[args.command](args)
292
+
293
+
294
+ if __name__ == "__main__":
295
+ sys.exit(main() or 0)
File without changes