@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.
Files changed (95) hide show
  1. package/.agents/skills/omg/AGENTS.fragment.md +5 -0
  2. package/.agents/skills/omg/codex-mcp.toml +4 -0
  3. package/.agents/skills/omg/control-plane/SKILL.md +11 -0
  4. package/.agents/skills/omg/control-plane/openai.yaml +14 -0
  5. package/.agents/skills/omg/hook-governor/SKILL.md +11 -0
  6. package/.agents/skills/omg/hook-governor/openai.yaml +11 -0
  7. package/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
  8. package/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
  9. package/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
  10. package/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
  11. package/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
  12. package/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
  13. package/.claude-plugin/marketplace.json +3 -3
  14. package/.claude-plugin/plugin.json +1 -1
  15. package/.mcp.json +20 -4
  16. package/CHANGELOG.md +10 -0
  17. package/OMG-setup.sh +9 -3
  18. package/OMG_COMPAT_CONTRACT.md +92 -0
  19. package/README.md +24 -4
  20. package/SECURITY.md +6 -0
  21. package/commands/OMG:api-twin.md +22 -0
  22. package/commands/OMG:preflight.md +26 -0
  23. package/commands/OMG:security-check.md +28 -0
  24. package/dist/enterprise/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
  25. package/dist/enterprise/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
  26. package/dist/enterprise/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
  27. package/dist/enterprise/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
  28. package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
  29. package/dist/enterprise/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
  30. package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
  31. package/dist/enterprise/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
  32. package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
  33. package/dist/enterprise/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
  34. package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
  35. package/dist/enterprise/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
  36. package/dist/enterprise/bundle/.claude-plugin/marketplace.json +36 -0
  37. package/dist/enterprise/bundle/.claude-plugin/plugin.json +23 -0
  38. package/dist/enterprise/bundle/.mcp.json +40 -0
  39. package/dist/enterprise/bundle/OMG_COMPAT_CONTRACT.md +92 -0
  40. package/dist/enterprise/bundle/settings.json +366 -0
  41. package/dist/enterprise/manifest.json +99 -0
  42. package/dist/public/bundle/.agents/skills/omg/AGENTS.fragment.md +5 -0
  43. package/dist/public/bundle/.agents/skills/omg/codex-mcp.toml +4 -0
  44. package/dist/public/bundle/.agents/skills/omg/control-plane/SKILL.md +11 -0
  45. package/dist/public/bundle/.agents/skills/omg/control-plane/openai.yaml +14 -0
  46. package/dist/public/bundle/.agents/skills/omg/hook-governor/SKILL.md +11 -0
  47. package/dist/public/bundle/.agents/skills/omg/hook-governor/openai.yaml +11 -0
  48. package/dist/public/bundle/.agents/skills/omg/lsp-pack/SKILL.md +11 -0
  49. package/dist/public/bundle/.agents/skills/omg/lsp-pack/openai.yaml +11 -0
  50. package/dist/public/bundle/.agents/skills/omg/mcp-fabric/SKILL.md +11 -0
  51. package/dist/public/bundle/.agents/skills/omg/mcp-fabric/openai.yaml +13 -0
  52. package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/SKILL.md +11 -0
  53. package/dist/public/bundle/.agents/skills/omg/secure-worktree-pipeline/openai.yaml +12 -0
  54. package/dist/public/bundle/.claude-plugin/marketplace.json +36 -0
  55. package/dist/public/bundle/.claude-plugin/plugin.json +23 -0
  56. package/dist/public/bundle/.mcp.json +40 -0
  57. package/dist/public/bundle/OMG_COMPAT_CONTRACT.md +92 -0
  58. package/dist/public/bundle/settings.json +366 -0
  59. package/dist/public/manifest.json +99 -0
  60. package/hooks/policy_engine.py +38 -7
  61. package/hooks/post-write.py +1 -1
  62. package/hooks/prompt-enhancer.py +1 -1
  63. package/hooks/security_validators.py +75 -0
  64. package/hooks/setup_wizard.py +43 -8
  65. package/hooks/shadow_manager.py +22 -2
  66. package/package.json +1 -1
  67. package/plugins/README.md +3 -1
  68. package/plugins/advanced/commands/OMG:deep-plan.md +1 -1
  69. package/plugins/advanced/commands/OMG:security-review.md +10 -113
  70. package/plugins/advanced/commands/OMG:ship.md +1 -1
  71. package/plugins/advanced/plugin.json +1 -10
  72. package/plugins/core/plugin.json +25 -2
  73. package/pyproject.toml +1 -1
  74. package/runtime/adoption.py +1 -1
  75. package/runtime/api_twin.py +130 -0
  76. package/runtime/compat.py +21 -1
  77. package/runtime/contract_compiler.py +698 -0
  78. package/runtime/domain_packs.py +34 -0
  79. package/runtime/guide_assert.py +45 -0
  80. package/runtime/mcp_config_writers.py +147 -30
  81. package/runtime/omg_compat_contract_snapshot.json +8 -7
  82. package/runtime/omg_contract_snapshot.json +8 -7
  83. package/runtime/omg_mcp_server.py +205 -0
  84. package/runtime/preflight.py +52 -0
  85. package/runtime/providers/codex_provider.py +2 -12
  86. package/runtime/providers/gemini_provider.py +2 -21
  87. package/runtime/providers/kimi_provider.py +2 -21
  88. package/runtime/runtime_profile.py +61 -0
  89. package/runtime/security_check.py +347 -0
  90. package/runtime/subagent_dispatcher.py +117 -10
  91. package/runtime/team_router.py +3 -1
  92. package/runtime/untrusted_content.py +102 -0
  93. package/scripts/omg.py +174 -1
  94. package/settings.json +66 -18
  95. 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
+ }
@@ -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 deny decisions
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
 
@@ -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-review for a full audit."
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)
@@ -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-review after.")
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('"', '\\"')
@@ -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 memory server for authenticated CLIs.
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 configs for detected CLIs
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, writer_func in cli_writers.items():
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
- writer_func(server_url, server_name)
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)
@@ -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 = os.path.join(_evidence_root(project_dir), f"{run_id}.json")
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trac3er/oh-my-god",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "OMG (Oh My God) — Multi-agent orchestration, intelligent model routing, and durable session state for Claude Code",
5
5
  "main": "OMG-setup.sh",
6
6
  "scripts": {
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-review [affected files]
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: Deep security review — scans for vulnerabilities, hardcoded secrets, auth issues, and injection risks. Escalates to Codex for deep line-by-line analysis.
3
- allowed-tools: Read, Bash(grep:*), Bash(find:*), Bash(cat:*), Bash(git:*), Bash(rg:*), Grep, Glob
4
- argument-hint: "[file or directory to review, or 'all' for full scan]"
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 — Vulnerability Scanner + Deep Review
7
+ # /OMG:security-review — Deprecated Alias
8
8
 
9
- ## Step 1: Scope Detection
9
+ `/OMG:security-review` remains available only for compatibility.
10
10
 
11
- Determine what to scan:
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
- Identify security-critical files automatically:
17
- ```bash
18
- # Auth/session
19
- find . -type f \( -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.go" -o -name "*.rs" \) | xargs grep -li "auth\|login\|session\|token\|password\|jwt\|oauth" 2>/dev/null
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-review` for auth/payment/database/sensitive changes.
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