@trac3er/oh-my-god 2.0.3 → 2.0.4
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.
- package/.agents/skills/omg/AGENTS.fragment.md +5 -0
- package/.agents/skills/omg/codex-mcp.toml +4 -0
- package/.agents/skills/omg/control-plane/SKILL.md +11 -0
- package/.agents/skills/omg/control-plane/openai.yaml +14 -0
- package/.agents/skills/omg/hook-governor/SKILL.md +11 -0
- package/.agents/skills/omg/hook-governor/openai.yaml +11 -0
- package/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
- package/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
- package/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
- package/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
- package/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
- package/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +20 -4
- package/CHANGELOG.md +10 -0
- package/OMG-setup.sh +9 -3
- package/OMG_COMPAT_CONTRACT.md +92 -0
- package/README.md +24 -4
- package/SECURITY.md +6 -0
- package/commands/OMG:api-twin.md +22 -0
- package/commands/OMG:preflight.md +26 -0
- package/commands/OMG:security-check.md +28 -0
- package/dist/enterprise/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
- package/dist/enterprise/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
- package/dist/enterprise/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
- package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
- package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
- package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
- package/dist/enterprise/bundle/.claude-plugin/marketplace.json +36 -0
- package/dist/enterprise/bundle/.claude-plugin/plugin.json +23 -0
- package/dist/enterprise/bundle/.mcp.json +40 -0
- package/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +92 -0
- package/dist/enterprise/bundle/settings.json +366 -0
- package/dist/enterprise/manifest.json +99 -0
- package/dist/public/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
- package/dist/public/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
- package/dist/public/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
- package/dist/public/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
- package/dist/public/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
- package/dist/public/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
- package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
- package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
- package/dist/public/bundle/.claude-plugin/marketplace.json +36 -0
- package/dist/public/bundle/.claude-plugin/plugin.json +23 -0
- package/dist/public/bundle/.mcp.json +40 -0
- package/dist/public/bundle/OMG_COMPAT_CONTRACT.md +92 -0
- package/dist/public/bundle/settings.json +366 -0
- package/dist/public/manifest.json +99 -0
- package/hooks/policy_engine.py +38 -7
- package/hooks/post-write.py +1 -1
- package/hooks/prompt-enhancer.py +1 -1
- package/hooks/security_validators.py +75 -0
- package/hooks/setup_wizard.py +43 -8
- package/hooks/shadow_manager.py +22 -2
- package/package.json +1 -1
- package/plugins/README.md +3 -1
- package/plugins/advanced/commands/OMG:deep-plan.md +1 -1
- package/plugins/advanced/commands/OMG:security-review.md +10 -113
- package/plugins/advanced/commands/OMG:ship.md +1 -1
- package/plugins/advanced/plugin.json +1 -10
- package/plugins/core/plugin.json +25 -2
- package/pyproject.toml +1 -1
- package/runtime/adoption.py +1 -1
- package/runtime/api_twin.py +130 -0
- package/runtime/compat.py +21 -1
- package/runtime/contract_compiler.py +698 -0
- package/runtime/domain_packs.py +34 -0
- package/runtime/guide_assert.py +45 -0
- package/runtime/mcp_config_writers.py +147 -30
- package/runtime/omg_compat_contract_snapshot.json +8 -7
- package/runtime/omg_contract_snapshot.json +8 -7
- package/runtime/omg_mcp_server.py +205 -0
- package/runtime/preflight.py +52 -0
- package/runtime/providers/codex_provider.py +2 -12
- package/runtime/providers/gemini_provider.py +2 -21
- package/runtime/providers/kimi_provider.py +2 -21
- package/runtime/runtime_profile.py +61 -0
- package/runtime/security_check.py +347 -0
- package/runtime/subagent_dispatcher.py +117 -10
- package/runtime/team_router.py +3 -1
- package/runtime/untrusted_content.py +102 -0
- package/scripts/omg.py +174 -1
- package/settings.json +66 -18
- package/tools/python_repl.py +33 -3
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "OmgCompiledArtifactManifest",
|
|
3
|
+
"channel": "public",
|
|
4
|
+
"contract_version": "2.0.4",
|
|
5
|
+
"artifacts": [
|
|
6
|
+
{
|
|
7
|
+
"path": "bundle/.agents/skills/omg/AGENTS.fragment.md",
|
|
8
|
+
"sha256": "603430cb291632105fda7444ae018da521cc3a57c71c1a1c98179efea42ce0f2"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"path": "bundle/.agents/skills/omg/codex-mcp.toml",
|
|
12
|
+
"sha256": "a56de208a369a2b318d2e66e150eef4cba1fac1ecf32bda6db1a1e4b65db7311"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "bundle/.agents/skills/omg/control-plane/SKILL.md",
|
|
16
|
+
"sha256": "fe281985ffbfb4d324d0ae421653c96d501bfda4ef1462865e2868276c40a3c2"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "bundle/.agents/skills/omg/control-plane/openai.yaml",
|
|
20
|
+
"sha256": "a7ba723d34839c6a444b6ee4416223103cd9b2bbd9caed0d4c0aa1e7d5a523fc"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "bundle/.agents/skills/omg/hook-governor/SKILL.md",
|
|
24
|
+
"sha256": "852df4b5f787f2a885e87cb8ad8dbb8d877e018e14083cfd5e5b98deb9dd19b5"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "bundle/.agents/skills/omg/hook-governor/openai.yaml",
|
|
28
|
+
"sha256": "8b4e219662bd9a12e89cdcd9a1828c1ff14810fa09cbfff9a6d9f932fb42ec85"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "bundle/.agents/skills/omg/lsp-pack/SKILL.md",
|
|
32
|
+
"sha256": "86890c6671441f8687ad7d71dedd7d61574c42aa6e109aa8dd6ceb4b6a139879"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "bundle/.agents/skills/omg/lsp-pack/openai.yaml",
|
|
36
|
+
"sha256": "14000aec9cc9ff630b7354ae101cc43ed90c4e1ecd0fa719013741ca14598142"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "bundle/.agents/skills/omg/mcp-fabric/SKILL.md",
|
|
40
|
+
"sha256": "e741baf4fa5f09957fe37349b79fe6831afe0352eefa945f3e53514ea313bc36"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "bundle/.agents/skills/omg/mcp-fabric/openai.yaml",
|
|
44
|
+
"sha256": "18dc35b4d6fb598b572ec933153e80de5449be9e07ef07178e4f875e39a8915c"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"path": "bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md",
|
|
48
|
+
"sha256": "1eebd557d77cc084161c3ffe11ed3a9ea5d78caa65b1021a992ac8b835fc6a0d"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"path": "bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml",
|
|
52
|
+
"sha256": "1f82a05b2fcad17c126e34541c995dedf3ed242fa499539ab693cc60a0d41c77"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"path": "bundle/.claude-plugin/marketplace.json",
|
|
56
|
+
"sha256": "cf1a17ce1e8db6814209126a79d5a05d057f354ef3ee49a74da4f18f2ec04597"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"path": "bundle/.claude-plugin/plugin.json",
|
|
60
|
+
"sha256": "6ad6db257f9e46528d94760ef895b6ffc87c78a8ec5ca05b430a46b46ee3dc08"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"path": "bundle/.mcp.json",
|
|
64
|
+
"sha256": "d8249fdd26e0df0a9b7cabcb34256d7e33578e1d0e9878cc91d43452a10f7c24"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"path": "bundle/OMG_COMPAT_CONTRACT.md",
|
|
68
|
+
"sha256": "fa91ec0dbff58d543df5a9e3a86a1fe1629104a383d4e1bd440a36cb5522189c"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"path": "bundle/registry/bundles/control-plane.yaml",
|
|
72
|
+
"sha256": "bb58e1d21a7f545548da10e7f9b83898d6ece8010a921b66d0f94a08c8ae0d8e"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"path": "bundle/registry/bundles/hook-governor.yaml",
|
|
76
|
+
"sha256": "93b13ea1e2098328349d33373595144f2f863274548bef47fd266d6cab210260"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"path": "bundle/registry/bundles/lsp-pack.yaml",
|
|
80
|
+
"sha256": "6c2ca235bdca31635d89c428c48a35a5414250e98c7349cd67ba900870203d9a"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"path": "bundle/registry/bundles/mcp-fabric.yaml",
|
|
84
|
+
"sha256": "2cb71fc331820b19c60c3275f521fb49d099ed7c31539fab9959a55d29ae0fe9"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"path": "bundle/registry/bundles/secure-worktree-pipeline.yaml",
|
|
88
|
+
"sha256": "b93e752cef8b5cbc76fb7aa32ebe80d30ce390193183bb0c79dedfa1ce89c4e9"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"path": "bundle/registry/omg-capability.schema.json",
|
|
92
|
+
"sha256": "b5a52c03c6d42c0ce0297a2d8c22f34ed1075062cc5434d3a85b9f4fa6a0f121"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"path": "bundle/settings.json",
|
|
96
|
+
"sha256": "60fb4f9e2bedd4d67af41a2e442ef37c9a89cd2077bf6dfe40761eec01586824"
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
}
|
package/hooks/policy_engine.py
CHANGED
|
@@ -115,6 +115,23 @@ ASK_PATTERNS = [
|
|
|
115
115
|
(r"node\s+-e\s+", "Inline Node execution"),
|
|
116
116
|
]
|
|
117
117
|
|
|
118
|
+
UNTRUSTED_MUTATION_PATTERNS = [
|
|
119
|
+
r"\bgit\s+(commit|push|tag)\b",
|
|
120
|
+
r"\bnpm\s+(install|publish)\b",
|
|
121
|
+
r"\bpython[23]?\s+.*\b(setup\.py|manage\.py)\b",
|
|
122
|
+
r"\b(mv|cp|tee|sed\s+-i|touch|mkdir)\b",
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _is_untrusted_content_mode_active() -> bool:
|
|
127
|
+
try:
|
|
128
|
+
from runtime.untrusted_content import is_untrusted_content_mode_active
|
|
129
|
+
|
|
130
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
|
|
131
|
+
return is_untrusted_content_mode_active(project_dir)
|
|
132
|
+
except Exception:
|
|
133
|
+
return False
|
|
134
|
+
|
|
118
135
|
|
|
119
136
|
def evaluate_bash_command(cmd: str) -> PolicyDecision:
|
|
120
137
|
if not cmd:
|
|
@@ -158,6 +175,15 @@ def evaluate_bash_command(cmd: str) -> PolicyDecision:
|
|
|
158
175
|
if re.search(pat, cmd):
|
|
159
176
|
return ask(f"{label}: {cmd[:120]}", "med", ["human-approval"])
|
|
160
177
|
|
|
178
|
+
if _is_untrusted_content_mode_active():
|
|
179
|
+
for pat in UNTRUSTED_MUTATION_PATTERNS:
|
|
180
|
+
if re.search(pat, cmd):
|
|
181
|
+
return ask(
|
|
182
|
+
"Untrusted external content mode is active. Review before running state-changing commands.",
|
|
183
|
+
"high",
|
|
184
|
+
["manual-approval", "review-provenance"],
|
|
185
|
+
)
|
|
186
|
+
|
|
161
187
|
return allow("command allowed")
|
|
162
188
|
|
|
163
189
|
|
|
@@ -409,8 +435,8 @@ def evaluate_file_access(
|
|
|
409
435
|
) -> PolicyDecision:
|
|
410
436
|
"""Evaluate file access policy.
|
|
411
437
|
|
|
412
|
-
If an allowlist is provided, matching entries override
|
|
413
|
-
for the given path and tool combination.
|
|
438
|
+
If an allowlist is provided, matching entries may override non-secret-file
|
|
439
|
+
deny decisions for the given path and tool combination.
|
|
414
440
|
"""
|
|
415
441
|
if not file_path:
|
|
416
442
|
return allow("no file")
|
|
@@ -424,11 +450,6 @@ def evaluate_file_access(
|
|
|
424
450
|
basename = os.path.basename(normalized).lower()
|
|
425
451
|
lowpath = normalized.lower()
|
|
426
452
|
|
|
427
|
-
# --- Allowlist check (before deny rules) ---
|
|
428
|
-
# Check allowlist early: if path+tool is allowlisted, override deny.
|
|
429
|
-
if allowlist and is_allowlisted(file_path, tool, allowlist):
|
|
430
|
-
return allow(f"Allowlisted: {file_path}")
|
|
431
|
-
|
|
432
453
|
if basename in EXAMPLE_FILES and tool in ("Write", "Edit", "MultiEdit"):
|
|
433
454
|
return deny(
|
|
434
455
|
f"Modifying example env file blocked (Read is allowed): {file_path}",
|
|
@@ -451,6 +472,16 @@ def evaluate_file_access(
|
|
|
451
472
|
if re.search(pat, lowpath):
|
|
452
473
|
return deny(f"Sensitive path blocked: {file_path}", "critical", ["secret-access"])
|
|
453
474
|
|
|
475
|
+
if tool in {"Write", "Edit", "MultiEdit"} and _is_untrusted_content_mode_active():
|
|
476
|
+
return ask(
|
|
477
|
+
"Untrusted external content mode is active. Review before mutating files.",
|
|
478
|
+
"high",
|
|
479
|
+
["manual-approval", "review-provenance"],
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
if allowlist and is_allowlisted(file_path, tool, allowlist):
|
|
483
|
+
return allow(f"Allowlisted: {file_path}")
|
|
484
|
+
|
|
454
485
|
return allow("file allowed")
|
|
455
486
|
|
|
456
487
|
|
package/hooks/post-write.py
CHANGED
|
@@ -213,7 +213,7 @@ for i, line in enumerate(content.split("\n"), 1):
|
|
|
213
213
|
|
|
214
214
|
if sec_warnings:
|
|
215
215
|
msg = f"SECURITY WARNINGS in {file_path}:\n" + "\n".join(sec_warnings[:5])
|
|
216
|
-
msg += "\n\nConsider running /OMG:security-
|
|
216
|
+
msg += "\n\nConsider running /OMG:security-check for the canonical audit pipeline."
|
|
217
217
|
print(msg, file=sys.stderr)
|
|
218
218
|
|
|
219
219
|
sys.exit(0)
|
package/hooks/prompt-enhancer.py
CHANGED
|
@@ -386,7 +386,7 @@ SECURITY_SIGNALS = [
|
|
|
386
386
|
]
|
|
387
387
|
if not route_lock and any(signal_matches_text(sig, prompt) for sig in SECURITY_SIGNALS) and budget_ok():
|
|
388
388
|
if detected_intent in ("fix", "implement", "refactor"):
|
|
389
|
-
add("@security: CRITICAL DOMAIN — No hardcoded secrets. Run /OMG:security-
|
|
389
|
+
add("@security: CRITICAL DOMAIN — No hardcoded secrets. Run /OMG:security-check after.")
|
|
390
390
|
|
|
391
391
|
# ═══════════════════════════════════════════════════════════
|
|
392
392
|
# 5. VISION DETECTION
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Shared validation helpers for security-sensitive filesystem and config writes."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_OPAQUE_IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
10
|
+
_SERVER_NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]*$")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def validate_opaque_identifier(value: str, field_name: str, max_length: int = 64) -> str:
|
|
14
|
+
"""Validate an opaque identifier used in filenames or paths."""
|
|
15
|
+
if not isinstance(value, str):
|
|
16
|
+
raise ValueError(f"Invalid {field_name}: must be a string")
|
|
17
|
+
|
|
18
|
+
normalized = value.strip()
|
|
19
|
+
if not normalized:
|
|
20
|
+
raise ValueError(f"Invalid {field_name}: value is required")
|
|
21
|
+
if len(normalized) > max_length:
|
|
22
|
+
raise ValueError(f"Invalid {field_name}: exceeds {max_length} characters")
|
|
23
|
+
if ".." in normalized or not _OPAQUE_IDENTIFIER_RE.fullmatch(normalized):
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f"Invalid {field_name}: use only ASCII letters, numbers, dot, underscore, and dash"
|
|
26
|
+
)
|
|
27
|
+
return normalized
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def ensure_path_within_dir(base_dir: str | Path, candidate_path: str | Path) -> str:
|
|
31
|
+
"""Return a resolved path and reject traversal outside the intended base directory."""
|
|
32
|
+
base = Path(base_dir).resolve(strict=False)
|
|
33
|
+
candidate = Path(candidate_path).resolve(strict=False)
|
|
34
|
+
try:
|
|
35
|
+
candidate.relative_to(base)
|
|
36
|
+
except ValueError as exc:
|
|
37
|
+
raise ValueError(f"Resolved path escapes base directory: {candidate}") from exc
|
|
38
|
+
return str(candidate)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def validate_server_name(server_name: str, max_length: int = 64) -> str:
|
|
42
|
+
"""Validate an MCP server identifier suitable for JSON keys and TOML table names."""
|
|
43
|
+
if not isinstance(server_name, str):
|
|
44
|
+
raise ValueError("Invalid server_name: must be a string")
|
|
45
|
+
|
|
46
|
+
normalized = server_name.strip()
|
|
47
|
+
if not normalized:
|
|
48
|
+
raise ValueError("Invalid server_name: value is required")
|
|
49
|
+
if len(normalized) > max_length:
|
|
50
|
+
raise ValueError(f"Invalid server_name: exceeds {max_length} characters")
|
|
51
|
+
if not _SERVER_NAME_RE.fullmatch(normalized):
|
|
52
|
+
raise ValueError("Invalid server_name: use only ASCII letters, numbers, underscore, and dash")
|
|
53
|
+
return normalized
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def validate_server_url(server_url: str) -> str:
|
|
57
|
+
"""Validate an MCP server URL and reject newline/control injection."""
|
|
58
|
+
if not isinstance(server_url, str):
|
|
59
|
+
raise ValueError("Invalid server_url: must be a string")
|
|
60
|
+
|
|
61
|
+
normalized = server_url.strip()
|
|
62
|
+
if not normalized:
|
|
63
|
+
raise ValueError("Invalid server_url: value is required")
|
|
64
|
+
if "\n" in normalized or "\r" in normalized:
|
|
65
|
+
raise ValueError("Invalid server_url: newline characters are not allowed")
|
|
66
|
+
|
|
67
|
+
parsed = urlparse(normalized)
|
|
68
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
69
|
+
raise ValueError("Invalid server_url: must be an http or https URL")
|
|
70
|
+
return normalized
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def toml_quote_string(value: str) -> str:
|
|
74
|
+
"""Escape TOML basic string content."""
|
|
75
|
+
return value.replace("\\", "\\\\").replace('"', '\\"')
|
package/hooks/setup_wizard.py
CHANGED
|
@@ -22,9 +22,13 @@ if _PROJECT_ROOT not in sys.path:
|
|
|
22
22
|
from runtime.cli_provider import get_provider, list_available_providers # noqa: E402
|
|
23
23
|
from runtime.mcp_config_writers import ( # noqa: E402
|
|
24
24
|
write_claude_mcp_config,
|
|
25
|
+
write_claude_mcp_stdio_config,
|
|
25
26
|
write_codex_mcp_config,
|
|
27
|
+
write_codex_mcp_stdio_config,
|
|
26
28
|
write_gemini_mcp_config,
|
|
29
|
+
write_gemini_mcp_stdio_config,
|
|
27
30
|
write_kimi_mcp_config,
|
|
31
|
+
write_kimi_mcp_stdio_config,
|
|
28
32
|
)
|
|
29
33
|
|
|
30
34
|
# Trigger provider auto-registration on import
|
|
@@ -41,6 +45,10 @@ from runtime.adoption import ( # noqa: E402
|
|
|
41
45
|
|
|
42
46
|
_logger = logging.getLogger(__name__)
|
|
43
47
|
|
|
48
|
+
OMG_CONTROL_COMMAND = "python3"
|
|
49
|
+
OMG_CONTROL_ARGS = ["-m", "runtime.omg_mcp_server"]
|
|
50
|
+
OMG_CONTROL_SERVER_NAME = "omg-control"
|
|
51
|
+
|
|
44
52
|
_INSTALL_HINTS: dict[str, str] = {
|
|
45
53
|
"codex": "npm install -g @openai/codex",
|
|
46
54
|
"gemini": "npm install -g @google/gemini-cli",
|
|
@@ -109,6 +117,15 @@ MCP_CATALOG: list[dict[str, Any]] = [
|
|
|
109
117
|
"default": True,
|
|
110
118
|
"category": "memory",
|
|
111
119
|
},
|
|
120
|
+
{
|
|
121
|
+
"id": OMG_CONTROL_SERVER_NAME,
|
|
122
|
+
"name": "OMG Control",
|
|
123
|
+
"description": "OMG control plane MCP server via stdio",
|
|
124
|
+
"command": OMG_CONTROL_COMMAND,
|
|
125
|
+
"args": OMG_CONTROL_ARGS,
|
|
126
|
+
"default": True,
|
|
127
|
+
"category": "control",
|
|
128
|
+
},
|
|
112
129
|
{
|
|
113
130
|
"id": "github",
|
|
114
131
|
"name": "GitHub",
|
|
@@ -371,8 +388,11 @@ def configure_mcp(
|
|
|
371
388
|
detected_clis: dict[str, Any],
|
|
372
389
|
server_url: str = "http://127.0.0.1:8765/mcp",
|
|
373
390
|
server_name: str = "omg-memory",
|
|
391
|
+
control_command: str = OMG_CONTROL_COMMAND,
|
|
392
|
+
control_args: list[str] | None = None,
|
|
393
|
+
control_server_name: str = OMG_CONTROL_SERVER_NAME,
|
|
374
394
|
) -> dict[str, Any]:
|
|
375
|
-
"""Configure MCP
|
|
395
|
+
"""Configure OMG MCP servers for authenticated CLIs.
|
|
376
396
|
|
|
377
397
|
For each CLI in detected_clis where detected_clis[cli]["detected"] == True,
|
|
378
398
|
calls the appropriate writer from runtime.mcp_config_writers.
|
|
@@ -382,6 +402,9 @@ def configure_mcp(
|
|
|
382
402
|
detected_clis: Dict of CLI detection results from detect_clis().
|
|
383
403
|
server_url: MCP server URL (default: http://127.0.0.1:8765/mcp).
|
|
384
404
|
server_name: MCP server name (default: omg-memory).
|
|
405
|
+
control_command: stdio command for the OMG control MCP server.
|
|
406
|
+
control_args: stdio args for the OMG control MCP server.
|
|
407
|
+
control_server_name: MCP server name for the OMG control surface.
|
|
385
408
|
|
|
386
409
|
Returns:
|
|
387
410
|
Dict with keys:
|
|
@@ -391,28 +414,40 @@ def configure_mcp(
|
|
|
391
414
|
"""
|
|
392
415
|
configured: list[str] = []
|
|
393
416
|
errors: dict[str, str] = {}
|
|
417
|
+
resolved_control_args = list(control_args or OMG_CONTROL_ARGS)
|
|
394
418
|
|
|
395
|
-
# Always write Claude config
|
|
419
|
+
# Always write Claude project config for both memory and control surfaces.
|
|
396
420
|
try:
|
|
397
421
|
write_claude_mcp_config(project_dir, server_url, server_name)
|
|
422
|
+
write_claude_mcp_stdio_config(
|
|
423
|
+
project_dir,
|
|
424
|
+
command=control_command,
|
|
425
|
+
args=resolved_control_args,
|
|
426
|
+
server_name=control_server_name,
|
|
427
|
+
)
|
|
398
428
|
except Exception as exc:
|
|
399
429
|
_logger.warning("Failed to write Claude MCP config: %s", exc)
|
|
400
430
|
errors["claude"] = str(exc)
|
|
401
431
|
|
|
402
|
-
# Write
|
|
432
|
+
# Write both memory and control surfaces for detected external CLIs.
|
|
403
433
|
cli_writers = {
|
|
404
|
-
"codex": write_codex_mcp_config,
|
|
405
|
-
"gemini": write_gemini_mcp_config,
|
|
406
|
-
"kimi": write_kimi_mcp_config,
|
|
434
|
+
"codex": (write_codex_mcp_config, write_codex_mcp_stdio_config),
|
|
435
|
+
"gemini": (write_gemini_mcp_config, write_gemini_mcp_stdio_config),
|
|
436
|
+
"kimi": (write_kimi_mcp_config, write_kimi_mcp_stdio_config),
|
|
407
437
|
}
|
|
408
438
|
|
|
409
|
-
for cli_name,
|
|
439
|
+
for cli_name, (http_writer, stdio_writer) in cli_writers.items():
|
|
410
440
|
cli_info = detected_clis.get(cli_name, {})
|
|
411
441
|
if not cli_info.get("detected", False):
|
|
412
442
|
continue
|
|
413
443
|
|
|
414
444
|
try:
|
|
415
|
-
|
|
445
|
+
http_writer(server_url, server_name)
|
|
446
|
+
stdio_writer(
|
|
447
|
+
command=control_command,
|
|
448
|
+
args=resolved_control_args,
|
|
449
|
+
server_name=control_server_name,
|
|
450
|
+
)
|
|
416
451
|
configured.append(cli_name)
|
|
417
452
|
except Exception as exc:
|
|
418
453
|
_logger.warning("Failed to write %s MCP config: %s", cli_name, exc)
|
package/hooks/shadow_manager.py
CHANGED
|
@@ -17,6 +17,7 @@ HOOKS_DIR = os.path.dirname(__file__)
|
|
|
17
17
|
if HOOKS_DIR not in sys.path:
|
|
18
18
|
sys.path.insert(0, HOOKS_DIR)
|
|
19
19
|
from _common import _resolve_project_dir
|
|
20
|
+
from security_validators import ensure_path_within_dir, validate_opaque_identifier
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _project_dir() -> str:
|
|
@@ -43,6 +44,10 @@ def _new_run_id() -> str:
|
|
|
43
44
|
return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S%fZ")
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
def _validated_run_id(run_id: str) -> str:
|
|
48
|
+
return validate_opaque_identifier(run_id, "run_id")
|
|
49
|
+
|
|
50
|
+
|
|
46
51
|
def _hash_file(path: str) -> str:
|
|
47
52
|
h = hashlib.sha256()
|
|
48
53
|
with open(path, "rb") as f:
|
|
@@ -61,6 +66,7 @@ def ensure_shadow_dirs(project_dir: str) -> None:
|
|
|
61
66
|
|
|
62
67
|
def set_active_run_id(project_dir: str, run_id: str) -> None:
|
|
63
68
|
ensure_shadow_dirs(project_dir)
|
|
69
|
+
run_id = _validated_run_id(run_id)
|
|
64
70
|
with open(_active_run_path(project_dir), "w", encoding="utf-8") as f:
|
|
65
71
|
f.write(run_id)
|
|
66
72
|
|
|
@@ -81,7 +87,7 @@ def get_active_run_id(project_dir: str) -> str | None:
|
|
|
81
87
|
|
|
82
88
|
|
|
83
89
|
def begin_shadow_run(project_dir: str, metadata: dict[str, Any] | None = None) -> str:
|
|
84
|
-
run_id = get_active_run_id(project_dir) or _new_run_id()
|
|
90
|
+
run_id = _validated_run_id(get_active_run_id(project_dir) or _new_run_id())
|
|
85
91
|
run_dir = os.path.join(_shadow_root(project_dir), run_id)
|
|
86
92
|
os.makedirs(run_dir, exist_ok=True)
|
|
87
93
|
|
|
@@ -124,12 +130,14 @@ def _save_manifest(run_dir: str, manifest: dict[str, Any]) -> None:
|
|
|
124
130
|
|
|
125
131
|
|
|
126
132
|
def map_shadow_path(project_dir: str, run_id: str, file_path: str) -> str:
|
|
133
|
+
run_id = _validated_run_id(run_id)
|
|
127
134
|
rel = os.path.relpath(os.path.abspath(file_path), os.path.abspath(project_dir))
|
|
128
135
|
rel = rel.replace("..", "_up_")
|
|
129
136
|
return os.path.join(_shadow_root(project_dir), run_id, "overlay", rel)
|
|
130
137
|
|
|
131
138
|
|
|
132
139
|
def record_shadow_write(project_dir: str, run_id: str, file_path: str, source: str = "tool") -> dict[str, Any]:
|
|
140
|
+
run_id = _validated_run_id(run_id)
|
|
133
141
|
run_dir = os.path.join(_shadow_root(project_dir), run_id)
|
|
134
142
|
os.makedirs(run_dir, exist_ok=True)
|
|
135
143
|
|
|
@@ -171,8 +179,12 @@ def create_evidence_pack(
|
|
|
171
179
|
diff_summary: dict[str, Any] | None = None,
|
|
172
180
|
reproducibility: dict[str, Any] | None = None,
|
|
173
181
|
unresolved_risks: list[str] | None = None,
|
|
182
|
+
provenance: list[dict[str, Any]] | None = None,
|
|
183
|
+
trust_scores: dict[str, Any] | None = None,
|
|
184
|
+
api_twin: dict[str, Any] | None = None,
|
|
174
185
|
) -> str:
|
|
175
186
|
ensure_shadow_dirs(project_dir)
|
|
187
|
+
run_id = _validated_run_id(run_id)
|
|
176
188
|
evidence = {
|
|
177
189
|
"schema": "EvidencePack",
|
|
178
190
|
"run_id": run_id,
|
|
@@ -182,8 +194,14 @@ def create_evidence_pack(
|
|
|
182
194
|
"diff_summary": diff_summary or {},
|
|
183
195
|
"reproducibility": reproducibility or {},
|
|
184
196
|
"unresolved_risks": unresolved_risks or [],
|
|
197
|
+
"provenance": provenance or [],
|
|
198
|
+
"trust_scores": trust_scores or {},
|
|
199
|
+
"api_twin": api_twin or {},
|
|
185
200
|
}
|
|
186
|
-
evidence_path =
|
|
201
|
+
evidence_path = ensure_path_within_dir(
|
|
202
|
+
_evidence_root(project_dir),
|
|
203
|
+
os.path.join(_evidence_root(project_dir), f"{run_id}.json"),
|
|
204
|
+
)
|
|
187
205
|
with open(evidence_path, "w", encoding="utf-8") as f:
|
|
188
206
|
json.dump(evidence, f, indent=2)
|
|
189
207
|
return evidence_path
|
|
@@ -211,6 +229,7 @@ def has_recent_evidence(project_dir: str, hours: int = 24) -> bool:
|
|
|
211
229
|
|
|
212
230
|
|
|
213
231
|
def apply_shadow(project_dir: str, run_id: str) -> dict[str, Any]:
|
|
232
|
+
run_id = _validated_run_id(run_id)
|
|
214
233
|
run_dir = os.path.join(_shadow_root(project_dir), run_id)
|
|
215
234
|
manifest = _load_manifest(run_dir)
|
|
216
235
|
applied = []
|
|
@@ -236,6 +255,7 @@ def apply_shadow(project_dir: str, run_id: str) -> dict[str, Any]:
|
|
|
236
255
|
|
|
237
256
|
|
|
238
257
|
def drop_shadow(project_dir: str, run_id: str) -> dict[str, Any]:
|
|
258
|
+
run_id = _validated_run_id(run_id)
|
|
239
259
|
run_dir = os.path.join(_shadow_root(project_dir), run_id)
|
|
240
260
|
if os.path.isdir(run_dir):
|
|
241
261
|
print(f"[OMG] Deleting: {run_dir}", file=sys.stderr)
|
package/package.json
CHANGED
package/plugins/README.md
CHANGED
|
@@ -17,6 +17,9 @@ OMG exposes a small native front door and keeps the rest of the surface availabl
|
|
|
17
17
|
| `/OMG:escalate` | Route to Codex, Gemini, or CCG |
|
|
18
18
|
| `/OMG:teams` | Team routing for internal OMG execution |
|
|
19
19
|
| `/OMG:ccg` | Tri-track synthesis |
|
|
20
|
+
| `/OMG:security-check` | Canonical security pipeline |
|
|
21
|
+
| `/OMG:api-twin` | Contract replay and fixture-based API simulation |
|
|
22
|
+
| `/OMG:preflight` | Structured route selection and evidence planning |
|
|
20
23
|
| `/OMG:compat` | Legacy compatibility routing |
|
|
21
24
|
| `/OMG:health-check` | Verify setup and tool integration |
|
|
22
25
|
| `/OMG:mode` | Set cognitive mode for the session |
|
|
@@ -28,7 +31,6 @@ OMG exposes a small native front door and keeps the rest of the surface availabl
|
|
|
28
31
|
| `/OMG:deep-plan` | Planning | Strategic planning with domain awareness |
|
|
29
32
|
| `/OMG:learn` | Knowledge | Convert patterns into OMG-native instincts and skills |
|
|
30
33
|
| `/OMG:code-review` | Quality | Deep review flow |
|
|
31
|
-
| `/OMG:security-review` | Security | Security-focused review |
|
|
32
34
|
| `/OMG:ship` | Delivery | Idea to evidence to release |
|
|
33
35
|
| `/OMG:handoff` | Collaboration | Session transfer and continuity |
|
|
34
36
|
|
|
@@ -87,7 +87,7 @@ Key interfaces: [list the interfaces/types this touches]
|
|
|
87
87
|
|
|
88
88
|
### Phase 3: Integration + Security [N files, ~M lines]
|
|
89
89
|
5. [ ] [specific action]
|
|
90
|
-
6. [ ] Security review: /OMG:security-
|
|
90
|
+
6. [ ] Security review: /OMG:security-check [affected files]
|
|
91
91
|
|
|
92
92
|
### Phase 4: Verification
|
|
93
93
|
7. [ ] Tests: [what to test, how]
|
|
@@ -1,119 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
3
|
-
allowed-tools: Read,
|
|
4
|
-
argument-hint: "[
|
|
2
|
+
description: Deprecated alias to OMG's canonical security-check pipeline.
|
|
3
|
+
allowed-tools: Read, Grep, Glob, Bash(python3:*), Bash(rg:*)
|
|
4
|
+
argument-hint: "[path or '.' for the current project]"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# /OMG:security-review —
|
|
7
|
+
# /OMG:security-review — Deprecated Alias
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`/OMG:security-review` remains available only for compatibility.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- If argument is a file: scan that file deeply
|
|
13
|
-
- If argument is a directory: scan all source files in it
|
|
14
|
-
- If "all" or no argument: scan git-tracked source files
|
|
11
|
+
Use `/OMG:security-check` instead. The canonical pipeline now owns:
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# Payment
|
|
22
|
-
find . -type f -name "*.{ts,js,py,go}" | xargs grep -li "payment\|billing\|stripe\|checkout\|card\|price" 2>/dev/null
|
|
23
|
-
|
|
24
|
-
# Database
|
|
25
|
-
find . -type f -name "*.{ts,js,py,go}" | xargs grep -li "query\|SELECT\|INSERT\|UPDATE\|DELETE\|migration\|schema" 2>/dev/null
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Step 2: Automated Vulnerability Scan
|
|
29
|
-
|
|
30
|
-
For each security-critical file, check LINE-BY-LINE:
|
|
31
|
-
|
|
32
|
-
**2a. Hardcoded Secrets**
|
|
33
|
-
```bash
|
|
34
|
-
grep -rn "AKIA\|sk_live\|sk_test\|ghp_\|github_pat\|xoxb-\|xoxp-\|eyJ.*\.eyJ" [files]
|
|
35
|
-
grep -rn "api[_-]key.*=.*['\"][A-Za-z0-9]" [files]
|
|
36
|
-
grep -rn "password.*=.*['\"]" [files] | grep -v "test\|mock\|example\|placeholder"
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**2b. SQL Injection**
|
|
40
|
-
```bash
|
|
41
|
-
grep -rn "f\".*SELECT\|f\".*INSERT\|f\".*UPDATE\|f\".*DELETE" [files]
|
|
42
|
-
grep -rn "\\.format.*SELECT\|%s.*SELECT\|\\+.*SELECT" [files]
|
|
43
|
-
grep -rn "query.*\\$\\{" [files] # template literal SQL
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**2c. XSS / Injection**
|
|
47
|
-
```bash
|
|
48
|
-
grep -rn "innerHTML\|dangerouslySetInnerHTML\|v-html\|\\|safe\b" [files]
|
|
49
|
-
grep -rn "eval(\|exec(\|subprocess.*shell=True" [files]
|
|
50
|
-
grep -rn "document\\.write\|document\\.location" [files]
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**2d. Auth/Session Issues**
|
|
54
|
-
```bash
|
|
55
|
-
grep -rn "jwt\\.decode.*verify.*false\|verify.*False" [files]
|
|
56
|
-
grep -rn "cors.*\\*\|origin.*\\*" [files]
|
|
57
|
-
grep -rn "httpOnly.*false\|secure.*false\|sameSite.*none" [files]
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
**2e. Path Traversal**
|
|
61
|
-
```bash
|
|
62
|
-
grep -rn "req\\.params\|req\\.query\|req\\.body" [files] | grep -i "path\|file\|dir"
|
|
63
|
-
grep -rn "\\.\\./\|\\.\\.\\\\" [files]
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**2f. Sensitive Data Exposure**
|
|
67
|
-
```bash
|
|
68
|
-
grep -rn "console\\.log.*password\|console\\.log.*token\|console\\.log.*secret" [files]
|
|
69
|
-
grep -rn "log\\.info.*password\|logger.*token\|print.*secret" [files]
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Step 3: Codex Deep Review (for high-risk files)
|
|
73
|
-
|
|
74
|
-
For files with auth, payment, or database logic:
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
/OMG:escalate codex "Security deep review of [file]:
|
|
78
|
-
1. Read every line. Flag any: hardcoded secrets, SQL injection, XSS, CSRF, auth bypass, privilege escalation, insecure deserialization, SSRF.
|
|
79
|
-
2. Check auth flow completeness: does every protected route validate the token? Are permissions checked?
|
|
80
|
-
3. Check payment flow: is card data handled safely? Are amounts validated server-side?
|
|
81
|
-
4. Check database queries: all parameterized? Any raw string concatenation?
|
|
82
|
-
5. Rate this file: SAFE / NEEDS_FIX / CRITICAL with specific line numbers."
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Step 4: Report
|
|
86
|
-
|
|
87
|
-
```
|
|
88
|
-
Security Review — [scope]
|
|
89
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
90
|
-
|
|
91
|
-
Files scanned: [N]
|
|
92
|
-
Security-critical files: [N]
|
|
93
|
-
|
|
94
|
-
CRITICAL [N]:
|
|
95
|
-
[file:line] Hardcoded API key: [type]
|
|
96
|
-
[file:line] SQL injection: unparameterized query
|
|
97
|
-
|
|
98
|
-
HIGH [N]:
|
|
99
|
-
[file:line] Missing auth check on protected route
|
|
100
|
-
[file:line] CORS wildcard in production config
|
|
101
|
-
|
|
102
|
-
MEDIUM [N]:
|
|
103
|
-
[file:line] Sensitive data in console.log
|
|
104
|
-
[file:line] httpOnly not set on session cookie
|
|
105
|
-
|
|
106
|
-
LOW [N]:
|
|
107
|
-
[file:line] Missing rate limiting on login endpoint
|
|
108
|
-
|
|
109
|
-
Codex Deep Review:
|
|
110
|
-
[file]: [SAFE|NEEDS_FIX|CRITICAL] — [summary]
|
|
111
|
-
|
|
112
|
-
Fix priority: [ordered list of what to fix first]
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Anti-patterns
|
|
116
|
-
- Don't just grep and dump raw output — analyze each finding
|
|
117
|
-
- Don't skip test files entirely (they can leak real credentials)
|
|
118
|
-
- Don't claim "looks secure" without running ALL checks above
|
|
119
|
-
- Don't treat this as optional for auth/payment/database changes
|
|
13
|
+
- normalized security findings
|
|
14
|
+
- dependency and AST enrichment
|
|
15
|
+
- evidence-ready provenance and trust scores
|
|
16
|
+
- reuse across CLI, compat routing, control plane, MCP, and ship flows
|
|
@@ -24,7 +24,7 @@ argument-hint: "[goal or optional path to .omg/idea.yml]"
|
|
|
24
24
|
- `tests[]`, `security_scans[]`, `diff_summary`, `reproducibility`, `unresolved_risks[]`.
|
|
25
25
|
|
|
26
26
|
## Step 4: Security and trust checks
|
|
27
|
-
- Run `/OMG:security-
|
|
27
|
+
- Run `/OMG:security-check` for auth/payment/database/sensitive changes.
|
|
28
28
|
- If config/hook/MCP changes are involved, ensure trust manifest is updated at `.omg/trust/manifest.lock.json`.
|
|
29
29
|
|
|
30
30
|
## Step 5: PR-ready output
|