sovereign-observer 0.1.0__tar.gz

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 (39) hide show
  1. sovereign_observer-0.1.0/.gitignore +67 -0
  2. sovereign_observer-0.1.0/Makefile +17 -0
  3. sovereign_observer-0.1.0/PKG-INFO +215 -0
  4. sovereign_observer-0.1.0/README.md +194 -0
  5. sovereign_observer-0.1.0/pyproject.toml +47 -0
  6. sovereign_observer-0.1.0/scripts/vendor_engine.py +114 -0
  7. sovereign_observer-0.1.0/sovereign_mcp/__init__.py +3 -0
  8. sovereign_observer-0.1.0/sovereign_mcp/_vendor/__init__.py +1 -0
  9. sovereign_observer-0.1.0/sovereign_mcp/_vendor/hcl_fixer.py +524 -0
  10. sovereign_observer-0.1.0/sovereign_mcp/_vendor/policy_engine.py +284 -0
  11. sovereign_observer-0.1.0/sovereign_mcp/_vendor/remediation_registry.py +972 -0
  12. sovereign_observer-0.1.0/sovereign_mcp/_vendor/remediation_service.py +452 -0
  13. sovereign_observer-0.1.0/sovereign_mcp/_vendor/remediation_supplement.py +4946 -0
  14. sovereign_observer-0.1.0/sovereign_mcp/_vendor/scanners/__init__.py +0 -0
  15. sovereign_observer-0.1.0/sovereign_mcp/_vendor/scanners/checkov_scanner.py +829 -0
  16. sovereign_observer-0.1.0/sovereign_mcp/_vendor/scanners/compliance_crosswalk.py +120 -0
  17. sovereign_observer-0.1.0/sovereign_mcp/_vendor/scanners/hcl_locator.py +185 -0
  18. sovereign_observer-0.1.0/sovereign_mcp/_vendor/scanners/hcl_to_plan.py +292 -0
  19. sovereign_observer-0.1.0/sovereign_mcp/_vendor/scanners/regulatory_overlays.py +262 -0
  20. sovereign_observer-0.1.0/sovereign_mcp/_vendor/scanners/terraform_inventory.py +1434 -0
  21. sovereign_observer-0.1.0/sovereign_mcp/engine.py +145 -0
  22. sovereign_observer-0.1.0/sovereign_mcp/org.py +161 -0
  23. sovereign_observer-0.1.0/sovereign_mcp/server.py +243 -0
  24. sovereign_observer-0.1.0/sovereign_mcp/tools/__init__.py +0 -0
  25. sovereign_observer-0.1.0/sovereign_mcp/tools/compliance.py +190 -0
  26. sovereign_observer-0.1.0/sovereign_mcp/tools/explain.py +115 -0
  27. sovereign_observer-0.1.0/sovereign_mcp/tools/fix.py +133 -0
  28. sovereign_observer-0.1.0/sovereign_mcp/tools/org_policy.py +281 -0
  29. sovereign_observer-0.1.0/sovereign_mcp/tools/scan.py +166 -0
  30. sovereign_observer-0.1.0/sovereign_mcp/tools/templates.py +393 -0
  31. sovereign_observer-0.1.0/tests/__init__.py +0 -0
  32. sovereign_observer-0.1.0/tests/conftest.py +7 -0
  33. sovereign_observer-0.1.0/tests/test_compliance.py +99 -0
  34. sovereign_observer-0.1.0/tests/test_fix.py +124 -0
  35. sovereign_observer-0.1.0/tests/test_org_policy.py +308 -0
  36. sovereign_observer-0.1.0/tests/test_parity.py +126 -0
  37. sovereign_observer-0.1.0/tests/test_scan_golden.py +90 -0
  38. sovereign_observer-0.1.0/tests/test_stdio_protocol.py +87 -0
  39. sovereign_observer-0.1.0/tests/test_templates.py +60 -0
@@ -0,0 +1,67 @@
1
+ # ── Secrets — NEVER commit these ───────────────────────────────────────────
2
+ # Losing/leaking ENCRYPTION_KEY (Fernet) makes all stored customer cloud
3
+ # credentials undecryptable; a leaked GCP service account or Firebase key is a
4
+ # full account compromise. Keep this list strict.
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+ *.key
9
+ secret.key
10
+ *service_account*.json
11
+ gcp_service_account.json
12
+ firebase*.json
13
+ serviceAccount*.json
14
+ *.pem
15
+ credentials.json
16
+
17
+ # ── Databases / local state ────────────────────────────────────────────────
18
+ *.db
19
+ *.sqlite
20
+ *.sqlite3
21
+ instance/
22
+ .runtime/
23
+
24
+ # ── Terraform plan artifacts (may embed resource values) ──────────────────
25
+ *.tfplan
26
+ *.tfstate
27
+ *.tfstate.*
28
+ .terraform/
29
+
30
+ # ── Python ─────────────────────────────────────────────────────────────────
31
+ __pycache__/
32
+ *.pyc
33
+ .venv/
34
+ venv/
35
+ .pytest_cache/
36
+
37
+ # ── Node / frontend ──────────────────────────────────────────────────────────
38
+ node_modules/
39
+ frontend/build/
40
+ frontend/build-check*/
41
+ frontend/public/screenshots/
42
+
43
+ # ── Misc ───────────────────────────────────────────────────────────────────
44
+ .DS_Store
45
+
46
+ .vercel
47
+
48
+ # ── Outreach data (scraped contact emails + send history — never public) ───
49
+ marketing/outreach/lead_messages_generated.csv
50
+ marketing/outreach/send_queue.csv
51
+ # The timestamped backups refresh_queue.py writes hold the same scraped
52
+ # addresses as send_queue.csv itself, so they need the same rule — an ignored
53
+ # file whose backups are committed is not ignored.
54
+ marketing/outreach/send_queue.*.csv
55
+ marketing/outreach/sent_log.csv
56
+ # Agent output: proposed actions with lead names, addresses and draft bodies.
57
+ marketing/outreach/approval_queue.json
58
+ marketing/outreach/followup_log.csv
59
+
60
+ # ── MCP server: generated engine copy ──────────────────────────────────────
61
+ # scripts/vendor_engine.py copies the backend's IaC engine modules here at
62
+ # build time. Committing them would put a second copy of the rule logic in git,
63
+ # which is exactly the drift the vendoring design exists to prevent.
64
+ integrations/mcp-server/sovereign_mcp/_vendor/
65
+ integrations/mcp-server/dist/
66
+ integrations/mcp-server/build/
67
+ integrations/mcp-server/*.egg-info/
@@ -0,0 +1,17 @@
1
+ PYTHON ?= python
2
+
3
+ .PHONY: vendor test wheel clean
4
+
5
+ ## Copy the IaC engine in from the backend. Required before building.
6
+ vendor:
7
+ $(PYTHON) scripts/vendor_engine.py
8
+
9
+ test: vendor
10
+ $(PYTHON) -m pytest tests/ -q
11
+
12
+ ## Vendor THEN build — a wheel built without vendoring ships no engine.
13
+ wheel: vendor
14
+ $(PYTHON) -m build --wheel
15
+
16
+ clean:
17
+ rm -rf dist build *.egg-info sovereign_mcp/_vendor
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.5
2
+ Name: sovereign-observer
3
+ Version: 0.1.0
4
+ Summary: Scan AI-generated Terraform for security misconfigurations, in your editor, before it is committed.
5
+ Project-URL: Homepage, https://sovereignobserver.com
6
+ Author: Sovereign Observer
7
+ License: Apache-2.0
8
+ Keywords: checkov,compliance,cspm,iac,mcp,security,terraform
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Security
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: checkov==3.3.11
17
+ Requires-Dist: mcp>=1.2.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=7.4; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Sovereign MCP
23
+
24
+ Your AI assistant writes Terraform. This checks it before you do.
25
+
26
+ An MCP server that scans Terraform for security misconfigurations **while the code is
27
+ being generated**, not after it lands in a pull request. It runs locally, needs no
28
+ account, and your infrastructure code never leaves your machine.
29
+
30
+ ```
31
+ You: "add an RDS instance for the orders service"
32
+ Assistant: [writes HCL] → [scans it] → [fixes 4 findings] → shows you the result
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Why this exists
38
+
39
+ Provider defaults optimise for *it works*, not *it is safe*. Terraform generated from a
40
+ model's memory is routinely unencrypted, publicly reachable, or missing deletion
41
+ protection — and the cost of fixing that rises steeply the further it travels. In the
42
+ editor it is one attribute. In a PR it is a review cycle. In production it is an incident.
43
+
44
+ CI already catches this. CI catches it three days and one argument later.
45
+
46
+ ---
47
+
48
+ ## Install
49
+
50
+ ### Claude Code
51
+
52
+ ```bash
53
+ claude mcp add sovereign -- uvx sovereign-observer
54
+ ```
55
+
56
+ ### Cursor
57
+
58
+ `~/.cursor/mcp.json`:
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "sovereign": {
64
+ "command": "uvx",
65
+ "args": ["sovereign-observer"]
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### VS Code (GitHub Copilot)
72
+
73
+ `.vscode/mcp.json`:
74
+
75
+ ```json
76
+ {
77
+ "servers": {
78
+ "sovereign": {
79
+ "type": "stdio",
80
+ "command": "uvx",
81
+ "args": ["sovereign-observer"]
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Windsurf
88
+
89
+ `~/.codeium/windsurf/mcp_config.json`:
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "sovereign": {
95
+ "command": "uvx",
96
+ "args": ["sovereign-observer"]
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ First run downloads the engine (~100 MB) and takes a moment. After that it is local and
103
+ fast.
104
+
105
+ ---
106
+
107
+ ## Tools
108
+
109
+ | Tool | What it does |
110
+ |---|---|
111
+ | `scan_terraform` | Scan HCL — from disk or an unsaved buffer. Returns findings by severity with file and line. |
112
+ | `explain_finding` | The full remediation for one finding: what is wrong and the exact Terraform to fix it. |
113
+ | `apply_fixes` | Apply the mechanically-safe fixes and return patched HCL. |
114
+ | `secure_template` | A hardened starting point for a resource type, so the insecure version never gets written. |
115
+ | `check_compliance` | Map findings to SOC 2, ISO 27001, NIST 800-53, PCI-DSS, DORA, NIS2, NCA (Saudi), NESA (UAE). |
116
+ | `framework_coverage` | Which articles of a regulation automated scanning can and cannot evidence. |
117
+ | `org_requirements` | Your organization's own rules for a resource type — *before* the code is written. |
118
+ | `org_status` | Whether org policy is in force, or built-in rules only. |
119
+
120
+ You do not call these. The assistant does, on its own, because the server tells it to.
121
+
122
+ ---
123
+
124
+ ## Organization policy
125
+
126
+ Everything above works with no account. Connecting an organization adds **your
127
+ company's own rules** to the same local evaluation:
128
+
129
+ ```bash
130
+ export SOVEREIGN_TOKEN=... # Integrations → GitHub in the dashboard
131
+ ```
132
+
133
+ The difference this makes is in *when* the rule applies. Without it, the assistant writes
134
+ Terraform and then finds out it was wrong. With it:
135
+
136
+ ```
137
+ You: "add an RDS instance for the orders service"
138
+ Assistant: → org_requirements("aws_db_instance")
139
+ ← "backup_retention_period must be at least 365"
140
+ "region must be one of: eu-west-1, eu-central-1"
141
+ [writes Terraform that already satisfies both]
142
+ → scan_terraform → clean
143
+ ```
144
+
145
+ The rule is supplied to the generator, not applied to the output. That is the whole point
146
+ — a violation that never gets written costs nothing to fix.
147
+
148
+ Company rules appear in scans tagged `source: org_policy`, so a developer can always tell
149
+ a company requirement from a built-in one. They are authored in the dashboard as YAML and
150
+ enforced identically in the editor, in CI, and in a cloud scan.
151
+
152
+ **This does not change what leaves your machine.** Rules come down; code never goes up.
153
+ The only request this server makes is a `GET` for your org's rules —
154
+ `tests/test_org_policy.py::test_no_terraform_is_ever_uploaded` asserts that at the
155
+ transport, and asserts an unconnected install opens no socket at all. If the API is
156
+ unreachable or the token is rejected, the built-in rules still run locally and the scan
157
+ still works.
158
+
159
+ ---
160
+
161
+ ## What it does not do
162
+
163
+ Worth stating plainly, because a security tool that overstates its scope is worse than no
164
+ tool:
165
+
166
+ - **It is not a compliance assessment.** `check_compliance` returns control *mappings* —
167
+ evidence that shortens an audit. Every framework it maps also carries governance,
168
+ process and training obligations no configuration scanner can observe. A clean scan is
169
+ not a compliant organisation.
170
+ - **NCA control identifiers are provisional**, pending reconciliation against the
171
+ authority's published catalogue. Cite the subdomain names.
172
+ - **`apply_fixes` is deliberately narrow.** It applies only single-attribute, in-place
173
+ changes from a hand-verified allowlist, and never overwrites a value wired to a variable
174
+ or expression. Everything else stays advisory, because a mechanical fix that is
175
+ syntactically clean can still take a running system down.
176
+ - **It scans Terraform**, not live cloud accounts, container images, or dependencies.
177
+
178
+ For live multi-cloud posture management, attack-path analysis and audit-ready reporting,
179
+ this is the editor-side slice of [Sovereign Observer](https://sovereignobserver.com).
180
+
181
+ ---
182
+
183
+ ## Privacy
184
+
185
+ The scan runs in this process, on your machine. There is no API key, no account, and no
186
+ network call in the default path — the server works with networking disabled. Your
187
+ Terraform is never uploaded.
188
+
189
+ ---
190
+
191
+ ## Development
192
+
193
+ From a checkout of the product repository:
194
+
195
+ ```bash
196
+ pip install -e ".[dev]"
197
+ ```
198
+
199
+ The IaC engine is **copied in from the backend at build time** by
200
+ `scripts/vendor_engine.py`, into a gitignored `sovereign_mcp/_vendor/`. That is
201
+ deliberate: the repository holds exactly one copy of the rule logic, so the editor and CI
202
+ can never disagree about whether a resource is insecure. `sovereign_mcp/engine.py` vendors
203
+ on demand in a source checkout, so there is no build step for day-to-day work.
204
+
205
+ Never edit anything under `_vendor/`. Edit the backend module and re-run the script.
206
+
207
+ ```bash
208
+ python -m pytest tests/ -q
209
+ ```
210
+
211
+ `tests/test_parity.py` is the one that matters — it fails if the vendored copy drifts from
212
+ its source, or if the Checkov pin stops matching the backend's.
213
+
214
+ Licensed Apache-2.0. Built on [Checkov](https://github.com/bridgecrewio/checkov)
215
+ (Apache-2.0).
@@ -0,0 +1,194 @@
1
+ # Sovereign MCP
2
+
3
+ Your AI assistant writes Terraform. This checks it before you do.
4
+
5
+ An MCP server that scans Terraform for security misconfigurations **while the code is
6
+ being generated**, not after it lands in a pull request. It runs locally, needs no
7
+ account, and your infrastructure code never leaves your machine.
8
+
9
+ ```
10
+ You: "add an RDS instance for the orders service"
11
+ Assistant: [writes HCL] → [scans it] → [fixes 4 findings] → shows you the result
12
+ ```
13
+
14
+ ---
15
+
16
+ ## Why this exists
17
+
18
+ Provider defaults optimise for *it works*, not *it is safe*. Terraform generated from a
19
+ model's memory is routinely unencrypted, publicly reachable, or missing deletion
20
+ protection — and the cost of fixing that rises steeply the further it travels. In the
21
+ editor it is one attribute. In a PR it is a review cycle. In production it is an incident.
22
+
23
+ CI already catches this. CI catches it three days and one argument later.
24
+
25
+ ---
26
+
27
+ ## Install
28
+
29
+ ### Claude Code
30
+
31
+ ```bash
32
+ claude mcp add sovereign -- uvx sovereign-observer
33
+ ```
34
+
35
+ ### Cursor
36
+
37
+ `~/.cursor/mcp.json`:
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "sovereign": {
43
+ "command": "uvx",
44
+ "args": ["sovereign-observer"]
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ ### VS Code (GitHub Copilot)
51
+
52
+ `.vscode/mcp.json`:
53
+
54
+ ```json
55
+ {
56
+ "servers": {
57
+ "sovereign": {
58
+ "type": "stdio",
59
+ "command": "uvx",
60
+ "args": ["sovereign-observer"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### Windsurf
67
+
68
+ `~/.codeium/windsurf/mcp_config.json`:
69
+
70
+ ```json
71
+ {
72
+ "mcpServers": {
73
+ "sovereign": {
74
+ "command": "uvx",
75
+ "args": ["sovereign-observer"]
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ First run downloads the engine (~100 MB) and takes a moment. After that it is local and
82
+ fast.
83
+
84
+ ---
85
+
86
+ ## Tools
87
+
88
+ | Tool | What it does |
89
+ |---|---|
90
+ | `scan_terraform` | Scan HCL — from disk or an unsaved buffer. Returns findings by severity with file and line. |
91
+ | `explain_finding` | The full remediation for one finding: what is wrong and the exact Terraform to fix it. |
92
+ | `apply_fixes` | Apply the mechanically-safe fixes and return patched HCL. |
93
+ | `secure_template` | A hardened starting point for a resource type, so the insecure version never gets written. |
94
+ | `check_compliance` | Map findings to SOC 2, ISO 27001, NIST 800-53, PCI-DSS, DORA, NIS2, NCA (Saudi), NESA (UAE). |
95
+ | `framework_coverage` | Which articles of a regulation automated scanning can and cannot evidence. |
96
+ | `org_requirements` | Your organization's own rules for a resource type — *before* the code is written. |
97
+ | `org_status` | Whether org policy is in force, or built-in rules only. |
98
+
99
+ You do not call these. The assistant does, on its own, because the server tells it to.
100
+
101
+ ---
102
+
103
+ ## Organization policy
104
+
105
+ Everything above works with no account. Connecting an organization adds **your
106
+ company's own rules** to the same local evaluation:
107
+
108
+ ```bash
109
+ export SOVEREIGN_TOKEN=... # Integrations → GitHub in the dashboard
110
+ ```
111
+
112
+ The difference this makes is in *when* the rule applies. Without it, the assistant writes
113
+ Terraform and then finds out it was wrong. With it:
114
+
115
+ ```
116
+ You: "add an RDS instance for the orders service"
117
+ Assistant: → org_requirements("aws_db_instance")
118
+ ← "backup_retention_period must be at least 365"
119
+ "region must be one of: eu-west-1, eu-central-1"
120
+ [writes Terraform that already satisfies both]
121
+ → scan_terraform → clean
122
+ ```
123
+
124
+ The rule is supplied to the generator, not applied to the output. That is the whole point
125
+ — a violation that never gets written costs nothing to fix.
126
+
127
+ Company rules appear in scans tagged `source: org_policy`, so a developer can always tell
128
+ a company requirement from a built-in one. They are authored in the dashboard as YAML and
129
+ enforced identically in the editor, in CI, and in a cloud scan.
130
+
131
+ **This does not change what leaves your machine.** Rules come down; code never goes up.
132
+ The only request this server makes is a `GET` for your org's rules —
133
+ `tests/test_org_policy.py::test_no_terraform_is_ever_uploaded` asserts that at the
134
+ transport, and asserts an unconnected install opens no socket at all. If the API is
135
+ unreachable or the token is rejected, the built-in rules still run locally and the scan
136
+ still works.
137
+
138
+ ---
139
+
140
+ ## What it does not do
141
+
142
+ Worth stating plainly, because a security tool that overstates its scope is worse than no
143
+ tool:
144
+
145
+ - **It is not a compliance assessment.** `check_compliance` returns control *mappings* —
146
+ evidence that shortens an audit. Every framework it maps also carries governance,
147
+ process and training obligations no configuration scanner can observe. A clean scan is
148
+ not a compliant organisation.
149
+ - **NCA control identifiers are provisional**, pending reconciliation against the
150
+ authority's published catalogue. Cite the subdomain names.
151
+ - **`apply_fixes` is deliberately narrow.** It applies only single-attribute, in-place
152
+ changes from a hand-verified allowlist, and never overwrites a value wired to a variable
153
+ or expression. Everything else stays advisory, because a mechanical fix that is
154
+ syntactically clean can still take a running system down.
155
+ - **It scans Terraform**, not live cloud accounts, container images, or dependencies.
156
+
157
+ For live multi-cloud posture management, attack-path analysis and audit-ready reporting,
158
+ this is the editor-side slice of [Sovereign Observer](https://sovereignobserver.com).
159
+
160
+ ---
161
+
162
+ ## Privacy
163
+
164
+ The scan runs in this process, on your machine. There is no API key, no account, and no
165
+ network call in the default path — the server works with networking disabled. Your
166
+ Terraform is never uploaded.
167
+
168
+ ---
169
+
170
+ ## Development
171
+
172
+ From a checkout of the product repository:
173
+
174
+ ```bash
175
+ pip install -e ".[dev]"
176
+ ```
177
+
178
+ The IaC engine is **copied in from the backend at build time** by
179
+ `scripts/vendor_engine.py`, into a gitignored `sovereign_mcp/_vendor/`. That is
180
+ deliberate: the repository holds exactly one copy of the rule logic, so the editor and CI
181
+ can never disagree about whether a resource is insecure. `sovereign_mcp/engine.py` vendors
182
+ on demand in a source checkout, so there is no build step for day-to-day work.
183
+
184
+ Never edit anything under `_vendor/`. Edit the backend module and re-run the script.
185
+
186
+ ```bash
187
+ python -m pytest tests/ -q
188
+ ```
189
+
190
+ `tests/test_parity.py` is the one that matters — it fails if the vendored copy drifts from
191
+ its source, or if the Checkov pin stops matching the backend's.
192
+
193
+ Licensed Apache-2.0. Built on [Checkov](https://github.com/bridgecrewio/checkov)
194
+ (Apache-2.0).
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "sovereign-observer"
7
+ version = "0.1.0"
8
+ description = "Scan AI-generated Terraform for security misconfigurations, in your editor, before it is committed."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [{ name = "Sovereign Observer" }]
13
+ keywords = ["mcp", "terraform", "iac", "security", "checkov", "cspm", "compliance"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Topic :: Security",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ ]
22
+
23
+ dependencies = [
24
+ "mcp>=1.2.0",
25
+ # Pinned to the same version the backend runs, so an MCP scan and a CI scan
26
+ # evaluate identical policy. See tests/test_parity.py.
27
+ "checkov==3.3.11",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=7.4"]
32
+
33
+ [project.scripts]
34
+ sovereign-observer = "sovereign_mcp.server:main"
35
+
36
+ [project.urls]
37
+ Homepage = "https://sovereignobserver.com"
38
+
39
+ # `packages` picks up sovereign_mcp/_vendor as well — hatchling does not read
40
+ # .gitignore, so the generated engine copy ships in the wheel even though it is
41
+ # never committed. Run scripts/vendor_engine.py BEFORE building, or the wheel
42
+ # will be missing its engine. `make wheel` does both in order.
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["sovereign_mcp"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env python3
2
+ """Copy the backend's IaC engine modules into ``sovereign_mcp/_vendor``.
3
+
4
+ Why this exists
5
+ ---------------
6
+ The MCP server must run on a developer's laptop with no Flask app and no
7
+ network, but it must evaluate *exactly* the policy the backend evaluates. Two
8
+ hand-maintained copies of the rule logic would be worse than shipping no MCP at
9
+ all — a finding that appears in the editor but not in CI (or the reverse)
10
+ destroys trust in both.
11
+
12
+ So the backend stays the single source of truth in git, and this script
13
+ mechanically copies the modules at build time. ``sovereign_mcp/_vendor`` is
14
+ **generated and gitignored** — never edit it, and never commit it.
15
+
16
+ The copied modules are pure stdlib (verified: no Flask, no SQLAlchemy, no
17
+ ``current_app``). The directory layout below mirrors the backend's
18
+ ``services/`` + ``services/scanners/`` shape on purpose, so the relative import
19
+ ``from ..remediation_service import RemediationService`` inside
20
+ ``checkov_scanner`` keeps resolving without patching a single line.
21
+
22
+ Run directly (``python scripts/vendor_engine.py``) or let ``sovereign_mcp.engine``
23
+ call it automatically on first import in a source checkout.
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import shutil
28
+ import sys
29
+ from pathlib import Path
30
+ from typing import Optional
31
+
32
+ # relpath under the backend -> relpath under _vendor/
33
+ MODULES = {
34
+ "backend/app/services/remediation_service.py": "remediation_service.py",
35
+ "backend/app/services/scanners/checkov_scanner.py": "scanners/checkov_scanner.py",
36
+ "backend/app/services/scanners/hcl_locator.py": "scanners/hcl_locator.py",
37
+ "backend/app/services/scanners/hcl_to_plan.py": "scanners/hcl_to_plan.py",
38
+ "backend/app/services/scanners/regulatory_overlays.py": "scanners/regulatory_overlays.py",
39
+ "backend/app/services/scanners/compliance_crosswalk.py": "scanners/compliance_crosswalk.py",
40
+ # The HCL fix-applier the GitHub Action uses. Shared so a fix applied in the
41
+ # editor is byte-for-byte the fix the Action would have applied in CI.
42
+ "integrations/github-action/scan.py": "hcl_fixer.py",
43
+ # Org custom-policy evaluation. These four run the same YAML rules against
44
+ # the same inventory shape the server does, so an org rule means the same
45
+ # thing in the editor as it does in a scan. They sit at the _vendor root
46
+ # because policy_engine imports remediation_registry by absolute name.
47
+ "backend/app/services/scanners/terraform_inventory.py": "scanners/terraform_inventory.py",
48
+ "policy_engine.py": "policy_engine.py",
49
+ "remediation_registry.py": "remediation_registry.py",
50
+ "remediation_supplement.py": "remediation_supplement.py",
51
+ }
52
+
53
+ _GENERATED_HEADER = (
54
+ "# ⚠ GENERATED FILE — DO NOT EDIT.\n"
55
+ "# Copied verbatim from {source} by scripts/vendor_engine.py.\n"
56
+ "# Edit the backend module instead; this copy is regenerated at build time.\n"
57
+ )
58
+
59
+
60
+ def find_app_root(start: Optional[Path] = None) -> Optional[Path]:
61
+ """Walk up from ``start`` looking for the 'main application' directory.
62
+
63
+ Returns None when running from an installed wheel, where there is no
64
+ backend source tree to copy from (and none is needed — the wheel already
65
+ carries the vendored modules).
66
+ """
67
+ here = (start or Path(__file__).resolve()).resolve()
68
+ for parent in [here, *here.parents]:
69
+ candidate = parent / "backend" / "app" / "services" / "scanners" / "checkov_scanner.py"
70
+ if candidate.is_file():
71
+ return parent
72
+ return None
73
+
74
+
75
+ def vendor(app_root: Optional[Path] = None, dest: Optional[Path] = None) -> Path:
76
+ """Copy the engine modules into ``dest``. Returns the vendor directory."""
77
+ app_root = app_root or find_app_root()
78
+ if app_root is None:
79
+ raise FileNotFoundError(
80
+ "Could not locate the backend source tree. Run this from a checkout "
81
+ "of the Sovereign Observer repository."
82
+ )
83
+
84
+ dest = dest or (Path(__file__).resolve().parent.parent / "sovereign_mcp" / "_vendor")
85
+ if dest.exists():
86
+ shutil.rmtree(dest)
87
+ (dest / "scanners").mkdir(parents=True, exist_ok=True)
88
+
89
+ # Package markers so relative imports inside the copied modules resolve.
90
+ (dest / "__init__.py").write_text(
91
+ '"""Generated vendor tree — see scripts/vendor_engine.py."""\n', encoding="utf-8"
92
+ )
93
+ (dest / "scanners" / "__init__.py").write_text("", encoding="utf-8")
94
+
95
+ for src_rel, dst_rel in MODULES.items():
96
+ src = app_root / src_rel
97
+ if not src.is_file():
98
+ raise FileNotFoundError(f"Expected engine module missing: {src}")
99
+ body = src.read_text(encoding="utf-8")
100
+ (dest / dst_rel).write_text(
101
+ _GENERATED_HEADER.format(source=src_rel) + body, encoding="utf-8"
102
+ )
103
+
104
+ return dest
105
+
106
+
107
+ def main() -> int:
108
+ dest = vendor()
109
+ print(f"vendored {len(MODULES)} engine modules -> {dest}")
110
+ return 0
111
+
112
+
113
+ if __name__ == "__main__":
114
+ sys.exit(main())
@@ -0,0 +1,3 @@
1
+ """Sovereign MCP — Terraform security scanning inside your AI assistant."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ """Generated vendor tree — see scripts/vendor_engine.py."""