@trac3er/oh-my-god 1.0.2
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/.claude-plugin/marketplace.json +36 -0
- package/.claude-plugin/plugin.json +23 -0
- package/.claude-plugin/scripts/install.sh +49 -0
- package/.claude-plugin/scripts/uninstall.sh +80 -0
- package/.claude-plugin/scripts/update.sh +84 -0
- package/.mcp.json +20 -0
- package/LICENSE +21 -0
- package/OMG-setup.sh +1093 -0
- package/README.md +335 -0
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/UPSTREAM_DIFF.md +20 -0
- package/agents/__init__.py +1 -0
- package/agents/_model_roles.yaml +26 -0
- package/agents/designer.md +67 -0
- package/agents/explore.md +60 -0
- package/agents/model_roles.py +196 -0
- package/agents/omg-api-builder.md +23 -0
- package/agents/omg-architect-mode.md +43 -0
- package/agents/omg-architect.md +13 -0
- package/agents/omg-backend-engineer.md +43 -0
- package/agents/omg-critic.md +16 -0
- package/agents/omg-database-engineer.md +43 -0
- package/agents/omg-escalation-router.md +17 -0
- package/agents/omg-executor.md +12 -0
- package/agents/omg-frontend-designer.md +42 -0
- package/agents/omg-implement-mode.md +50 -0
- package/agents/omg-infra-engineer.md +43 -0
- package/agents/omg-qa-tester.md +16 -0
- package/agents/omg-research-mode.md +43 -0
- package/agents/omg-security-auditor.md +43 -0
- package/agents/omg-testing-engineer.md +43 -0
- package/agents/plan.md +80 -0
- package/agents/quick_task.md +64 -0
- package/agents/reviewer.md +83 -0
- package/agents/task.md +71 -0
- package/commands/OMG:ccg.md +22 -0
- package/commands/OMG:compat.md +57 -0
- package/commands/OMG:crazy.md +125 -0
- package/commands/OMG:domain-init.md +11 -0
- package/commands/OMG:escalate.md +52 -0
- package/commands/OMG:health-check.md +45 -0
- package/commands/OMG:init.md +134 -0
- package/commands/OMG:mode.md +44 -0
- package/commands/OMG:project-init.md +11 -0
- package/commands/OMG:ralph-start.md +43 -0
- package/commands/OMG:ralph-stop.md +23 -0
- package/commands/OMG:teams.md +39 -0
- package/commands/ai-commit.md +113 -0
- package/commands/ccg.md +9 -0
- package/commands/create-agent.md +183 -0
- package/commands/omc-teams.md +9 -0
- package/commands/session-branch.md +85 -0
- package/commands/session-fork.md +53 -0
- package/commands/session-merge.md +134 -0
- package/commands/theme.md +44 -0
- package/config/lsp_languages.yaml +324 -0
- package/config/themes/catppuccin-frappe.yaml +14 -0
- package/config/themes/catppuccin-latte.yaml +14 -0
- package/config/themes/catppuccin-macchiato.yaml +14 -0
- package/config/themes/catppuccin-mocha.yaml +14 -0
- package/config/themes/dracula.yaml +14 -0
- package/config/themes/gruvbox-dark.yaml +14 -0
- package/config/themes/nord.yaml +14 -0
- package/config/themes/one-dark.yaml +14 -0
- package/config/themes/solarized-dark.yaml +14 -0
- package/config/themes/tokyo-night.yaml +14 -0
- package/control_plane/__init__.py +2 -0
- package/control_plane/openapi.yaml +109 -0
- package/control_plane/server.py +107 -0
- package/control_plane/service.py +148 -0
- package/crates/omg-natives/Cargo.toml +17 -0
- package/crates/omg-natives/src/clipboard.rs +5 -0
- package/crates/omg-natives/src/glob.rs +15 -0
- package/crates/omg-natives/src/grep.rs +15 -0
- package/crates/omg-natives/src/highlight.rs +15 -0
- package/crates/omg-natives/src/html.rs +14 -0
- package/crates/omg-natives/src/image.rs +5 -0
- package/crates/omg-natives/src/keys.rs +5 -0
- package/crates/omg-natives/src/lib.rs +36 -0
- package/crates/omg-natives/src/prof.rs +5 -0
- package/crates/omg-natives/src/ps.rs +5 -0
- package/crates/omg-natives/src/shell.rs +5 -0
- package/crates/omg-natives/src/task.rs +5 -0
- package/crates/omg-natives/src/text.rs +14 -0
- package/hooks/_agent_registry.py +421 -0
- package/hooks/_budget.py +31 -0
- package/hooks/_common.py +476 -0
- package/hooks/_learnings.py +126 -0
- package/hooks/_memory.py +103 -0
- package/hooks/circuit-breaker.py +270 -0
- package/hooks/config-guard.py +163 -0
- package/hooks/context_pressure.py +53 -0
- package/hooks/credential_store.py +801 -0
- package/hooks/fetch-rate-limits.py +212 -0
- package/hooks/firewall.py +48 -0
- package/hooks/hashline-formatter-bridge.py +224 -0
- package/hooks/hashline-injector.py +273 -0
- package/hooks/hashline-validator.py +216 -0
- package/hooks/idle-detector.py +95 -0
- package/hooks/intentgate-keyword-detector.py +188 -0
- package/hooks/magic-keyword-router.py +195 -0
- package/hooks/policy_engine.py +310 -0
- package/hooks/post-tool-failure.py +19 -0
- package/hooks/post-write.py +199 -0
- package/hooks/pre-compact.py +204 -0
- package/hooks/pre-tool-inject.py +98 -0
- package/hooks/prompt-enhancer.py +672 -0
- package/hooks/quality-runner.py +191 -0
- package/hooks/secret-guard.py +47 -0
- package/hooks/session-end-capture.py +137 -0
- package/hooks/session-start.py +275 -0
- package/hooks/shadow_manager.py +297 -0
- package/hooks/state_migration.py +209 -0
- package/hooks/stop-gate.py +7 -0
- package/hooks/stop_dispatcher.py +929 -0
- package/hooks/test-validator.py +138 -0
- package/hooks/todo-state-tracker.py +114 -0
- package/hooks/tool-ledger.py +126 -0
- package/hooks/trust_review.py +524 -0
- package/install.sh +9 -0
- package/omg_natives/__init__.py +186 -0
- package/omg_natives/_bindings.py +165 -0
- package/omg_natives/clipboard.py +36 -0
- package/omg_natives/glob.py +42 -0
- package/omg_natives/grep.py +61 -0
- package/omg_natives/highlight.py +54 -0
- package/omg_natives/html.py +157 -0
- package/omg_natives/image.py +51 -0
- package/omg_natives/keys.py +46 -0
- package/omg_natives/prof.py +39 -0
- package/omg_natives/ps.py +93 -0
- package/omg_natives/shell.py +58 -0
- package/omg_natives/task.py +41 -0
- package/omg_natives/text.py +50 -0
- package/package.json +26 -0
- package/plugins/README.md +82 -0
- package/plugins/advanced/commands/OMG:code-review.md +114 -0
- package/plugins/advanced/commands/OMG:deep-plan.md +221 -0
- package/plugins/advanced/commands/OMG:handoff.md +115 -0
- package/plugins/advanced/commands/OMG:learn.md +110 -0
- package/plugins/advanced/commands/OMG:maintainer.md +31 -0
- package/plugins/advanced/commands/OMG:ralph-start.md +43 -0
- package/plugins/advanced/commands/OMG:ralph-stop.md +23 -0
- package/plugins/advanced/commands/OMG:security-review.md +119 -0
- package/plugins/advanced/commands/OMG:sequential-thinking.md +20 -0
- package/plugins/advanced/commands/OMG:ship.md +46 -0
- package/plugins/advanced/plugin.json +96 -0
- package/plugins/core/plugin.json +82 -0
- package/pytest.ini +5 -0
- package/registry/__init__.py +1 -0
- package/registry/verify_artifact.py +90 -0
- package/rules/contextual/architect-mode.md +9 -0
- package/rules/contextual/big-picture.md +20 -0
- package/rules/contextual/code-hygiene.md +26 -0
- package/rules/contextual/context-management.md +19 -0
- package/rules/contextual/context-minimization.md +32 -0
- package/rules/contextual/ddd-sdd.md +28 -0
- package/rules/contextual/dependency-safety.md +16 -0
- package/rules/contextual/doc-check.md +13 -0
- package/rules/contextual/implement-mode.md +9 -0
- package/rules/contextual/infra-safety.md +14 -0
- package/rules/contextual/outside-in.md +13 -0
- package/rules/contextual/persistent-mode.md +24 -0
- package/rules/contextual/research-mode.md +9 -0
- package/rules/contextual/security-domains.md +25 -0
- package/rules/contextual/vision-detection.md +27 -0
- package/rules/contextual/web-search.md +25 -0
- package/rules/contextual/write-verify.md +23 -0
- package/rules/core/00-truth.md +20 -0
- package/rules/core/01-surgical.md +19 -0
- package/rules/core/02-circuit-breaker.md +22 -0
- package/rules/core/03-ensemble.md +28 -0
- package/rules/core/04-testing.md +30 -0
- package/runtime/__init__.py +32 -0
- package/runtime/adapters/__init__.py +13 -0
- package/runtime/adapters/claude.py +60 -0
- package/runtime/adapters/gpt.py +53 -0
- package/runtime/adapters/local.py +53 -0
- package/runtime/business_workflow.py +220 -0
- package/runtime/compat.py +1299 -0
- package/runtime/custom_agent_loader.py +366 -0
- package/runtime/dispatcher.py +47 -0
- package/runtime/ecosystem.py +371 -0
- package/runtime/legacy_compat.py +7 -0
- package/runtime/omc_compat.py +7 -0
- package/runtime/omc_contract_snapshot.json +916 -0
- package/runtime/omg_compat_contract_snapshot.json +916 -0
- package/runtime/subagent_dispatcher.py +362 -0
- package/runtime/team_router.py +838 -0
- package/scripts/check-omc-contract-snapshot.py +12 -0
- package/scripts/check-omg-compat-contract-snapshot.py +137 -0
- package/scripts/check-omg-standalone-clean.py +102 -0
- package/scripts/legacy_to_omg_migrate.py +29 -0
- package/scripts/migrate-omc.py +464 -0
- package/scripts/omc_to_omg_migrate.py +12 -0
- package/scripts/omg.py +493 -0
- package/scripts/settings-merge.py +224 -0
- package/scripts/verify-no-omc.sh +5 -0
- package/scripts/verify-standalone.sh +21 -0
- package/templates/idea.yml +30 -0
- package/templates/policy.yaml +15 -0
- package/templates/profile.yaml +25 -0
- package/templates/runtime.yaml +12 -0
- package/templates/working-memory.md +17 -0
- package/tools/__init__.py +2 -0
- package/tools/browser_consent.py +289 -0
- package/tools/browser_stealth.py +481 -0
- package/tools/browser_tool.py +448 -0
- package/tools/changelog_generator.py +268 -0
- package/tools/commit_splitter.py +361 -0
- package/tools/config_discovery.py +151 -0
- package/tools/config_merger.py +449 -0
- package/tools/git_inspector.py +298 -0
- package/tools/lsp_client.py +275 -0
- package/tools/lsp_discovery.py +231 -0
- package/tools/lsp_operations.py +392 -0
- package/tools/python_repl.py +656 -0
- package/tools/python_sandbox.py +609 -0
- package/tools/search_providers/__init__.py +77 -0
- package/tools/search_providers/brave.py +115 -0
- package/tools/search_providers/exa.py +116 -0
- package/tools/search_providers/jina.py +104 -0
- package/tools/search_providers/perplexity.py +139 -0
- package/tools/search_providers/synthetic.py +74 -0
- package/tools/session_snapshot.py +736 -0
- package/tools/ssh_manager.py +912 -0
- package/tools/theme_engine.py +294 -0
- package/tools/theme_selector.py +137 -0
- package/tools/web_search.py +622 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Browser Stealth Plugins for OMG
|
|
4
|
+
|
|
5
|
+
Provides 14 stealth plugin definitions that can be applied to a browser
|
|
6
|
+
session to evade bot detection. Each plugin is a dict with `name`,
|
|
7
|
+
`description`, and `js_snippet` fields.
|
|
8
|
+
|
|
9
|
+
Feature flag: OMG_BROWSER_STEALTH_ENABLED (default: False)
|
|
10
|
+
Requires SEPARATE consent: .omg/state/browser_consent.json -> {"consented": true}
|
|
11
|
+
|
|
12
|
+
IMPORTANT: Stealth plugins are opt-in only. They require:
|
|
13
|
+
1. OMG_BROWSER_ENABLED=true (base browser feature)
|
|
14
|
+
2. OMG_BROWSER_STEALTH_ENABLED=true (stealth feature flag)
|
|
15
|
+
3. Explicit user consent in .omg/state/browser_consent.json
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from typing import Any, Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# --- Lazy imports for hooks/_common.py ---
|
|
25
|
+
|
|
26
|
+
_get_feature_flag = None
|
|
27
|
+
_atomic_json_write = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _ensure_imports():
|
|
31
|
+
"""Lazy import utilities from hooks/_common.py."""
|
|
32
|
+
global _get_feature_flag, _atomic_json_write
|
|
33
|
+
if _get_feature_flag is not None:
|
|
34
|
+
return
|
|
35
|
+
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
36
|
+
if repo_root not in sys.path:
|
|
37
|
+
sys.path.insert(0, repo_root)
|
|
38
|
+
try:
|
|
39
|
+
from hooks._common import get_feature_flag as _gff
|
|
40
|
+
from hooks._common import atomic_json_write as _ajw
|
|
41
|
+
_get_feature_flag = _gff
|
|
42
|
+
_atomic_json_write = _ajw
|
|
43
|
+
except ImportError:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# --- Feature flag ---
|
|
48
|
+
|
|
49
|
+
def _is_stealth_enabled() -> bool:
|
|
50
|
+
"""Check if Browser Stealth feature is enabled.
|
|
51
|
+
|
|
52
|
+
Requires BOTH OMG_BROWSER_ENABLED and OMG_BROWSER_STEALTH_ENABLED.
|
|
53
|
+
"""
|
|
54
|
+
# Check browser base flag first
|
|
55
|
+
browser_env = os.environ.get("OMG_BROWSER_ENABLED", "").lower()
|
|
56
|
+
if browser_env in ("0", "false", "no"):
|
|
57
|
+
return False
|
|
58
|
+
browser_on = browser_env in ("1", "true", "yes")
|
|
59
|
+
if not browser_on:
|
|
60
|
+
_ensure_imports()
|
|
61
|
+
if _get_feature_flag is not None:
|
|
62
|
+
browser_on = _get_feature_flag("BROWSER", default=False)
|
|
63
|
+
if not browser_on:
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
# Check stealth flag
|
|
67
|
+
stealth_env = os.environ.get("OMG_BROWSER_STEALTH_ENABLED", "").lower()
|
|
68
|
+
if stealth_env in ("0", "false", "no"):
|
|
69
|
+
return False
|
|
70
|
+
if stealth_env in ("1", "true", "yes"):
|
|
71
|
+
return True
|
|
72
|
+
_ensure_imports()
|
|
73
|
+
if _get_feature_flag is not None:
|
|
74
|
+
return _get_feature_flag("BROWSER_STEALTH", default=False)
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# --- Response helpers ---
|
|
79
|
+
|
|
80
|
+
def _error_response(error: str, requires_consent: bool = False) -> Dict[str, Any]:
|
|
81
|
+
"""Create an error response dict."""
|
|
82
|
+
return {
|
|
83
|
+
"success": False,
|
|
84
|
+
"applied": [],
|
|
85
|
+
"error": error,
|
|
86
|
+
"requires_consent": requires_consent,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _disabled_response() -> Dict[str, Any]:
|
|
91
|
+
"""Response when stealth feature flag is disabled."""
|
|
92
|
+
return _error_response(
|
|
93
|
+
"Browser stealth feature is disabled "
|
|
94
|
+
"(requires OMG_BROWSER_ENABLED=true and OMG_BROWSER_STEALTH_ENABLED=true)"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# =============================================================================
|
|
99
|
+
# Stealth Plugin Definitions — 14 plugins
|
|
100
|
+
# =============================================================================
|
|
101
|
+
|
|
102
|
+
STEALTH_PLUGINS: List[Dict[str, str]] = [
|
|
103
|
+
{
|
|
104
|
+
"name": "toString_tampering",
|
|
105
|
+
"description": "Override Function.prototype.toString to hide native code modifications",
|
|
106
|
+
"js_snippet": (
|
|
107
|
+
"const _origToString = Function.prototype.toString;"
|
|
108
|
+
"Function.prototype.toString = function() {"
|
|
109
|
+
" if (this === Function.prototype.toString) return 'function toString() { [native code] }';"
|
|
110
|
+
" return _origToString.call(this);"
|
|
111
|
+
"};"
|
|
112
|
+
),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "webgl_fingerprint",
|
|
116
|
+
"description": "Spoof WebGL vendor and renderer strings to generic values",
|
|
117
|
+
"js_snippet": (
|
|
118
|
+
"const _getParam = WebGLRenderingContext.prototype.getParameter;"
|
|
119
|
+
"WebGLRenderingContext.prototype.getParameter = function(p) {"
|
|
120
|
+
" if (p === 0x9245) return 'Intel Inc.';"
|
|
121
|
+
" if (p === 0x9246) return 'Intel Iris OpenGL Engine';"
|
|
122
|
+
" return _getParam.call(this, p);"
|
|
123
|
+
"};"
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "audio_context",
|
|
128
|
+
"description": "Spoof AudioContext fingerprint by adding noise to getFloatFrequencyData",
|
|
129
|
+
"js_snippet": (
|
|
130
|
+
"const _getFloat = AnalyserNode.prototype.getFloatFrequencyData;"
|
|
131
|
+
"AnalyserNode.prototype.getFloatFrequencyData = function(arr) {"
|
|
132
|
+
" _getFloat.call(this, arr);"
|
|
133
|
+
" for (let i = 0; i < arr.length; i++) arr[i] += Math.random() * 0.0001;"
|
|
134
|
+
"};"
|
|
135
|
+
),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "screen_dimensions",
|
|
139
|
+
"description": "Spoof screen width/height to common desktop resolution",
|
|
140
|
+
"js_snippet": (
|
|
141
|
+
"Object.defineProperty(screen, 'width', {get: () => 1920});"
|
|
142
|
+
"Object.defineProperty(screen, 'height', {get: () => 1080});"
|
|
143
|
+
"Object.defineProperty(screen, 'availWidth', {get: () => 1920});"
|
|
144
|
+
"Object.defineProperty(screen, 'availHeight', {get: () => 1040});"
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "font_enumeration",
|
|
149
|
+
"description": "Mock document.fonts.check to report a standard font list",
|
|
150
|
+
"js_snippet": (
|
|
151
|
+
"if (document.fonts && document.fonts.check) {"
|
|
152
|
+
" const _stdFonts = ['Arial','Helvetica','Times New Roman','Courier New','Verdana'];"
|
|
153
|
+
" document.fonts.check = function(font) {"
|
|
154
|
+
" return _stdFonts.some(f => font.includes(f));"
|
|
155
|
+
" };"
|
|
156
|
+
"}"
|
|
157
|
+
),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "plugin_mime_types",
|
|
161
|
+
"description": "Mock navigator.plugins and navigator.mimeTypes to appear as a real browser",
|
|
162
|
+
"js_snippet": (
|
|
163
|
+
"Object.defineProperty(navigator, 'plugins', {"
|
|
164
|
+
" get: () => [{"
|
|
165
|
+
" name: 'Chrome PDF Plugin',"
|
|
166
|
+
" description: 'Portable Document Format',"
|
|
167
|
+
" filename: 'internal-pdf-viewer',"
|
|
168
|
+
" length: 1"
|
|
169
|
+
" }]"
|
|
170
|
+
"});"
|
|
171
|
+
"Object.defineProperty(navigator, 'mimeTypes', {"
|
|
172
|
+
" get: () => [{type: 'application/pdf', suffixes: 'pdf', description: 'PDF'}]"
|
|
173
|
+
"});"
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "hardware_concurrency",
|
|
178
|
+
"description": "Spoof navigator.hardwareConcurrency to a common value",
|
|
179
|
+
"js_snippet": (
|
|
180
|
+
"Object.defineProperty(navigator, 'hardwareConcurrency', {get: () => 8});"
|
|
181
|
+
),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "codec_availability",
|
|
185
|
+
"description": "Mask supported codec list by overriding MediaSource.isTypeSupported",
|
|
186
|
+
"js_snippet": (
|
|
187
|
+
"if (typeof MediaSource !== 'undefined') {"
|
|
188
|
+
" const _isType = MediaSource.isTypeSupported;"
|
|
189
|
+
" MediaSource.isTypeSupported = function(mimeType) {"
|
|
190
|
+
" if (mimeType.includes('webm')) return true;"
|
|
191
|
+
" if (mimeType.includes('mp4')) return true;"
|
|
192
|
+
" return _isType.call(this, mimeType);"
|
|
193
|
+
" };"
|
|
194
|
+
"}"
|
|
195
|
+
),
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"name": "iframe_detection",
|
|
199
|
+
"description": "Evade iframe-based bot detection by spoofing window.parent and top",
|
|
200
|
+
"js_snippet": (
|
|
201
|
+
"try {"
|
|
202
|
+
" Object.defineProperty(window, 'parent', {get: () => window});"
|
|
203
|
+
" Object.defineProperty(window, 'top', {get: () => window});"
|
|
204
|
+
"} catch(e) {}"
|
|
205
|
+
),
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "locale_spoofing",
|
|
209
|
+
"description": "Spoof navigator.language and navigator.languages to en-US",
|
|
210
|
+
"js_snippet": (
|
|
211
|
+
"Object.defineProperty(navigator, 'language', {get: () => 'en-US'});"
|
|
212
|
+
"Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});"
|
|
213
|
+
),
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "worker_detection",
|
|
217
|
+
"description": "Evade web worker fingerprinting by wrapping Worker constructor",
|
|
218
|
+
"js_snippet": (
|
|
219
|
+
"const _OrigWorker = window.Worker;"
|
|
220
|
+
"window.Worker = function(url, opts) {"
|
|
221
|
+
" return new _OrigWorker(url, opts);"
|
|
222
|
+
"};"
|
|
223
|
+
"window.Worker.prototype = _OrigWorker.prototype;"
|
|
224
|
+
"window.Worker.toString = () => 'function Worker() { [native code] }';"
|
|
225
|
+
),
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"name": "canvas_fingerprint",
|
|
229
|
+
"description": "Spoof canvas toDataURL by injecting imperceptible noise",
|
|
230
|
+
"js_snippet": (
|
|
231
|
+
"const _toDataURL = HTMLCanvasElement.prototype.toDataURL;"
|
|
232
|
+
"HTMLCanvasElement.prototype.toDataURL = function(type) {"
|
|
233
|
+
" const ctx = this.getContext('2d');"
|
|
234
|
+
" if (ctx) {"
|
|
235
|
+
" const imgData = ctx.getImageData(0, 0, this.width, this.height);"
|
|
236
|
+
" for (let i = 0; i < imgData.data.length; i += 4) {"
|
|
237
|
+
" imgData.data[i] ^= 1;"
|
|
238
|
+
" }"
|
|
239
|
+
" ctx.putImageData(imgData, 0, 0);"
|
|
240
|
+
" }"
|
|
241
|
+
" return _toDataURL.call(this, type);"
|
|
242
|
+
"};"
|
|
243
|
+
),
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"name": "battery_status",
|
|
247
|
+
"description": "Spoof navigator.getBattery to return a plausible battery status",
|
|
248
|
+
"js_snippet": (
|
|
249
|
+
"navigator.getBattery = async function() {"
|
|
250
|
+
" return {"
|
|
251
|
+
" charging: true,"
|
|
252
|
+
" chargingTime: 0,"
|
|
253
|
+
" dischargingTime: Infinity,"
|
|
254
|
+
" level: 0.87,"
|
|
255
|
+
" addEventListener: function() {},"
|
|
256
|
+
" removeEventListener: function() {}"
|
|
257
|
+
" };"
|
|
258
|
+
"};"
|
|
259
|
+
),
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"name": "media_devices",
|
|
263
|
+
"description": "Spoof mediaDevices.enumerateDevices to return standard device list",
|
|
264
|
+
"js_snippet": (
|
|
265
|
+
"if (navigator.mediaDevices) {"
|
|
266
|
+
" navigator.mediaDevices.enumerateDevices = async function() {"
|
|
267
|
+
" return ["
|
|
268
|
+
" {deviceId: 'default', kind: 'audioinput', label: '', groupId: 'g1'},"
|
|
269
|
+
" {deviceId: 'default', kind: 'videoinput', label: '', groupId: 'g2'},"
|
|
270
|
+
" {deviceId: 'default', kind: 'audiooutput', label: '', groupId: 'g3'}"
|
|
271
|
+
" ];"
|
|
272
|
+
" };"
|
|
273
|
+
"}"
|
|
274
|
+
),
|
|
275
|
+
},
|
|
276
|
+
]
|
|
277
|
+
|
|
278
|
+
# Build name-to-plugin index for O(1) lookup
|
|
279
|
+
_PLUGIN_INDEX: Dict[str, Dict[str, str]] = {p["name"]: p for p in STEALTH_PLUGINS}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# =============================================================================
|
|
283
|
+
# StealthManager — manages stealth plugin lifecycle
|
|
284
|
+
# =============================================================================
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class StealthManager:
|
|
288
|
+
"""Manages browser stealth plugin definitions and application.
|
|
289
|
+
|
|
290
|
+
All operations check the feature flag gate. Plugin application also
|
|
291
|
+
requires explicit user consent via .omg/state/browser_consent.json.
|
|
292
|
+
|
|
293
|
+
Attributes:
|
|
294
|
+
project_dir: Root directory containing the .omg/ state folder.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
def __init__(self, project_dir: Optional[str] = None):
|
|
298
|
+
self.project_dir = project_dir or os.environ.get(
|
|
299
|
+
"CLAUDE_PROJECT_DIR", os.getcwd()
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def get_plugins(self) -> List[Dict[str, str]]:
|
|
303
|
+
"""Return all 14 stealth plugin definitions.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List of plugin dicts, or empty list if feature is disabled.
|
|
307
|
+
"""
|
|
308
|
+
if not _is_stealth_enabled():
|
|
309
|
+
return []
|
|
310
|
+
return list(STEALTH_PLUGINS)
|
|
311
|
+
|
|
312
|
+
def get_plugin(self, name: str) -> Optional[Dict[str, str]]:
|
|
313
|
+
"""Return a single plugin definition by name.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
name: The plugin name (e.g. 'canvas_fingerprint').
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Plugin dict if found and feature enabled, None otherwise.
|
|
320
|
+
"""
|
|
321
|
+
if not _is_stealth_enabled():
|
|
322
|
+
return None
|
|
323
|
+
return _PLUGIN_INDEX.get(name)
|
|
324
|
+
|
|
325
|
+
def is_consented(self) -> bool:
|
|
326
|
+
"""Check if user has given explicit consent for stealth plugins.
|
|
327
|
+
|
|
328
|
+
Delegates to ConsentManager from browser_consent module.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
True if consent file exists and consented is True, False otherwise.
|
|
332
|
+
"""
|
|
333
|
+
try:
|
|
334
|
+
from browser_consent import ConsentManager as _CM
|
|
335
|
+
return _CM(project_dir=self.project_dir).is_consented()
|
|
336
|
+
except ImportError:
|
|
337
|
+
# Fallback: inline check if browser_consent not available
|
|
338
|
+
consent_path = os.path.join(
|
|
339
|
+
self.project_dir, ".omg", "state", "browser_consent.json"
|
|
340
|
+
)
|
|
341
|
+
try:
|
|
342
|
+
if not os.path.exists(consent_path):
|
|
343
|
+
return False
|
|
344
|
+
with open(consent_path, "r", encoding="utf-8") as f:
|
|
345
|
+
data = json.load(f)
|
|
346
|
+
return data.get("consented", False) is True
|
|
347
|
+
except (json.JSONDecodeError, OSError, TypeError):
|
|
348
|
+
return False
|
|
349
|
+
def apply_plugins(
|
|
350
|
+
self,
|
|
351
|
+
session: Any,
|
|
352
|
+
plugin_names: Optional[List[str]] = None,
|
|
353
|
+
) -> Dict[str, Any]:
|
|
354
|
+
"""Apply stealth plugins to a browser session spec.
|
|
355
|
+
|
|
356
|
+
Generates a list of JavaScript snippets to inject. Does NOT make
|
|
357
|
+
actual browser calls — returns a spec dict for the caller to execute.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
session: A BrowserSession instance (used for context/validation).
|
|
361
|
+
plugin_names: Optional list of plugin names to apply.
|
|
362
|
+
If None, applies all 14 plugins.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Dict with keys:
|
|
366
|
+
- success: bool
|
|
367
|
+
- applied: list[str] — names of applied plugins
|
|
368
|
+
- error: str|None
|
|
369
|
+
- requires_consent: bool
|
|
370
|
+
"""
|
|
371
|
+
# Gate 1: Feature flag
|
|
372
|
+
if not _is_stealth_enabled():
|
|
373
|
+
return _disabled_response()
|
|
374
|
+
|
|
375
|
+
# Gate 2: Consent check
|
|
376
|
+
if not self.is_consented():
|
|
377
|
+
return _error_response(
|
|
378
|
+
"Browser stealth requires explicit consent. "
|
|
379
|
+
"Write {\"consented\": true} to .omg/state/browser_consent.json",
|
|
380
|
+
requires_consent=True,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Determine which plugins to apply
|
|
384
|
+
if plugin_names is None:
|
|
385
|
+
plugins_to_apply = list(STEALTH_PLUGINS)
|
|
386
|
+
else:
|
|
387
|
+
plugins_to_apply = []
|
|
388
|
+
for name in plugin_names:
|
|
389
|
+
plugin = _PLUGIN_INDEX.get(name)
|
|
390
|
+
if plugin is None:
|
|
391
|
+
return _error_response(f"Unknown plugin: {name}")
|
|
392
|
+
plugins_to_apply.append(plugin)
|
|
393
|
+
|
|
394
|
+
# Build injection specs
|
|
395
|
+
applied_names = [p["name"] for p in plugins_to_apply]
|
|
396
|
+
snippets = [p["js_snippet"] for p in plugins_to_apply]
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
"success": True,
|
|
400
|
+
"applied": applied_names,
|
|
401
|
+
"error": None,
|
|
402
|
+
"requires_consent": False,
|
|
403
|
+
"snippets": snippets,
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
# =============================================================================
|
|
408
|
+
# CLI Interface
|
|
409
|
+
# =============================================================================
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _cli_main():
|
|
413
|
+
"""CLI entry point for browser_stealth.py."""
|
|
414
|
+
import argparse
|
|
415
|
+
|
|
416
|
+
parser = argparse.ArgumentParser(
|
|
417
|
+
description="OMG Browser Stealth — stealth plugin manager for bot detection evasion",
|
|
418
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
419
|
+
)
|
|
420
|
+
parser.add_argument(
|
|
421
|
+
"--list", action="store_true", dest="list_plugins",
|
|
422
|
+
help="List all stealth plugin definitions",
|
|
423
|
+
)
|
|
424
|
+
parser.add_argument(
|
|
425
|
+
"--get", dest="get_name",
|
|
426
|
+
help="Get a single plugin by name",
|
|
427
|
+
)
|
|
428
|
+
parser.add_argument(
|
|
429
|
+
"--check-consent", action="store_true",
|
|
430
|
+
help="Check if stealth consent has been granted",
|
|
431
|
+
)
|
|
432
|
+
parser.add_argument(
|
|
433
|
+
"--status", action="store_true",
|
|
434
|
+
help="Show stealth feature status",
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
args = parser.parse_args()
|
|
438
|
+
manager = StealthManager()
|
|
439
|
+
|
|
440
|
+
if args.status:
|
|
441
|
+
print(json.dumps({
|
|
442
|
+
"stealth_enabled": _is_stealth_enabled(),
|
|
443
|
+
"consented": manager.is_consented(),
|
|
444
|
+
"plugin_count": len(STEALTH_PLUGINS),
|
|
445
|
+
}, indent=2))
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
if args.check_consent:
|
|
449
|
+
consented = manager.is_consented()
|
|
450
|
+
print(json.dumps({"consented": consented}, indent=2))
|
|
451
|
+
return
|
|
452
|
+
|
|
453
|
+
if args.list_plugins:
|
|
454
|
+
plugins = manager.get_plugins()
|
|
455
|
+
if not plugins:
|
|
456
|
+
print(json.dumps({
|
|
457
|
+
"error": "Stealth feature is disabled",
|
|
458
|
+
"plugins": [],
|
|
459
|
+
}, indent=2))
|
|
460
|
+
else:
|
|
461
|
+
print(json.dumps({
|
|
462
|
+
"count": len(plugins),
|
|
463
|
+
"plugins": [{"name": p["name"], "description": p["description"]} for p in plugins],
|
|
464
|
+
}, indent=2))
|
|
465
|
+
return
|
|
466
|
+
|
|
467
|
+
if args.get_name:
|
|
468
|
+
plugin = manager.get_plugin(args.get_name)
|
|
469
|
+
if plugin is None:
|
|
470
|
+
print(json.dumps({
|
|
471
|
+
"error": f"Plugin not found or feature disabled: {args.get_name}",
|
|
472
|
+
}, indent=2))
|
|
473
|
+
else:
|
|
474
|
+
print(json.dumps(plugin, indent=2))
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
parser.print_help()
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
if __name__ == "__main__":
|
|
481
|
+
_cli_main()
|