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.
- opsentry/VERSION +1 -0
- opsentry/__init__.py +5 -0
- opsentry/cli.py +295 -0
- opsentry/config/__init__.py +0 -0
- opsentry/config/generate.py +414 -0
- opsentry/config/hook_templates/block-dangerous-commands.sh.j2 +102 -0
- opsentry/config/hook_templates/block-data-exfiltration.sh.j2 +94 -0
- opsentry/config/hook_templates/block-environment-escape.sh.j2 +99 -0
- opsentry/config/hook_templates/block-git-commands.sh.j2 +46 -0
- opsentry/config/hook_templates/block-package-install.sh.j2 +95 -0
- opsentry/config/hook_templates/block-pii-leakage.sh.j2 +100 -0
- opsentry/config/hook_templates/block-scope-escape.sh.j2 +115 -0
- opsentry/config/hook_templates/block-sensitive-files.sh.j2 +97 -0
- opsentry/config/init_wizard.py +201 -0
- opsentry/config/rule_texts/01_sensitive_files.md +15 -0
- opsentry/config/rule_texts/02_client_data.md +10 -0
- opsentry/config/rule_texts/03_credentials.md +17 -0
- opsentry/config/rule_texts/04_sql_safety.md +33 -0
- opsentry/config/rule_texts/05_dangerous_code.md +29 -0
- opsentry/config/rule_texts/06_frontend_xss.md +9 -0
- opsentry/config/rule_texts/07_git_operations_block_all.md +13 -0
- opsentry/config/rule_texts/07_git_operations_read_only.md +18 -0
- opsentry/config/rule_texts/08_destructive_commands.md +12 -0
- opsentry/config/rule_texts/09_database_safety.md +17 -0
- opsentry/config/rule_texts/10_network_requests.md +10 -0
- opsentry/config/rule_texts/11_code_quality.md +13 -0
- opsentry/config/rule_texts/12_data_exfiltration.md +10 -0
- opsentry/config/rule_texts/13_package_install.md +9 -0
- opsentry/config/rule_texts/14_scope_boundaries.md +8 -0
- opsentry/config/rule_texts/15_env_isolation.md +9 -0
- opsentry/config/rule_texts/16_resource_limits.md +8 -0
- opsentry/config/rule_texts/17_compliance_privacy.md +9 -0
- opsentry/config/rule_texts/18_when_in_doubt.md +10 -0
- opsentry/config/schema.py +142 -0
- opsentry/config/templates/generic.yaml +84 -0
- opsentry/deployment/.claude/commands/code-health.md +42 -0
- opsentry/deployment/.claude/commands/governance-check.md +29 -0
- opsentry/deployment/.claude/commands/security-audit.md +41 -0
- opsentry/deployment/baseline.py +440 -0
- opsentry/deployment/blocklog_audit.py +497 -0
- opsentry/deployment/claude/CLAUDE.md +300 -0
- opsentry/deployment/claude/hooks/block-dangerous-commands.sh +106 -0
- opsentry/deployment/claude/hooks/block-data-exfiltration.sh +99 -0
- opsentry/deployment/claude/hooks/block-environment-escape.sh +96 -0
- opsentry/deployment/claude/hooks/block-git-commands.sh +37 -0
- opsentry/deployment/claude/hooks/block-package-install.sh +103 -0
- opsentry/deployment/claude/hooks/block-pii-leakage.sh +86 -0
- opsentry/deployment/claude/hooks/block-scope-escape.sh +210 -0
- opsentry/deployment/claude/hooks/block-sensitive-files.sh +97 -0
- opsentry/deployment/claude/settings.json +72 -0
- opsentry/deployment/install.sh +343 -0
- opsentry/deployment/patrol.sh +266 -0
- opsentry/deployment/test.sh +728 -0
- opsentry/deployment/update.sh +19 -0
- opsentry/deployment/verify.sh +201 -0
- opsentry-1.8.0.dist-info/METADATA +158 -0
- opsentry-1.8.0.dist-info/RECORD +61 -0
- opsentry-1.8.0.dist-info/WHEEL +5 -0
- opsentry-1.8.0.dist-info/entry_points.txt +2 -0
- opsentry-1.8.0.dist-info/licenses/LICENSE +190 -0
- 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
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
|