Skip to content

Commit 8443c01

Browse files
authored
Security hardening: 7 fixes from security evaluation + integration tests (#24)
1 parent ab019a0 commit 8443c01

10 files changed

Lines changed: 382 additions & 16 deletions

File tree

plugins/cortex-code/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,29 @@ Edit the config to change approval mode, allowed envelopes, audit settings, and
9292

9393
Skill discovery runs automatically on session start. To force a re-discovery, start a new Claude Code session.
9494

95+
## Testing
96+
97+
Tests live in `tests/run-tests.sh` at the repo root. Two tiers:
98+
99+
```bash
100+
# Structural + unit tests (no network, runs in CI)
101+
bash tests/run-tests.sh
102+
103+
# Include integration tests (requires cortex CLI + Snowflake connection)
104+
bash tests/run-tests.sh --integration
105+
```
106+
107+
**Structural tests** (always run): file existence checks, config validation, Python syntax, and unit tests for `envelope_policy.py`, `prompt_filter.py`, and plugin hooks.
108+
109+
**Integration tests** (`--integration` flag): spawn real Cortex CLI sessions against a live Snowflake connection. Located at `scripts/router/test_integration.py`. Verifies:
110+
111+
- Credential path blocking (prompts referencing `.ssh/`, `.env`, etc. are rejected pre-flight)
112+
- End-to-end query flow (RO envelope, permission protocol, result event)
113+
- Envelope enforcement (RO blocks DDL — via hard gate denial or LLM self-policing)
114+
- Process cleanup (no orphaned `cortex` processes after execution)
115+
116+
Set `CORTEX_TEST_CONNECTION` env var to test against a specific Snowflake connection (defaults to your CLI default).
117+
95118
## License
96119

97120
Copyright (c) Snowflake Inc. All rights reserved.

plugins/cortex-code/scripts/router/config.yaml.example

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ security:
3838
- "**/.npmrc"
3939
- "**/.pypirc"
4040

41-
# Which envelopes are allowed (RO, RW, RESEARCH, DEPLOY)
41+
# Which envelopes are allowed (RO, RW, RESEARCH)
42+
# Note: DEPLOY grants full access — only enable if you understand the blast radius.
4243
allowed_envelopes:
4344
- "RO"
4445
- "RW"
4546
- "RESEARCH"
46-
- "DEPLOY"
47+
# - "DEPLOY" # Uncomment to enable full-access mode
4748

4849
# --- Deployment Profiles (uncomment one) ---
4950

plugins/cortex-code/scripts/router/discover_cortex.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121

2222

2323
def run_command(cmd):
24-
"""Run shell command and return output."""
24+
"""Run command and return output."""
2525
try:
2626
result = subprocess.run(
27-
cmd,
28-
shell=True,
27+
cmd.split(),
28+
shell=False,
2929
capture_output=True,
3030
text=True,
3131
timeout=10

plugins/cortex-code/scripts/router/execute_cortex.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,15 @@ def execute_cortex_streaming(prompt: str, connection: Optional[str] = None,
323323
return results
324324

325325
except Exception as e:
326+
# Prevent orphaned cortex processes on unexpected exceptions
327+
try:
328+
process.terminate()
329+
process.wait(timeout=2)
330+
except Exception:
331+
try:
332+
process.kill()
333+
except Exception:
334+
pass
326335
return {
327336
"session_id": None,
328337
"events": [],

plugins/cortex-code/scripts/router/predict_tools.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,18 @@
3939

4040

4141
def load_capabilities():
42-
"""Load cached Cortex capabilities."""
43-
cache_path = Path("/tmp/cortex-capabilities.json")
44-
45-
if not cache_path.exists():
42+
"""Load cached Cortex capabilities via CacheManager."""
43+
try:
44+
sys.path.insert(0, str(Path(__file__).parent.parent))
45+
from security.config_manager import ConfigManager
46+
from security.cache_manager import CacheManager
47+
config_manager = ConfigManager()
48+
cache_dir = Path(config_manager.get("security.cache_dir")).expanduser()
49+
cache_manager = CacheManager(cache_dir)
50+
return cache_manager.read("cortex-capabilities") or {}
51+
except Exception:
4652
return {}
4753

48-
with open(cache_path, 'r') as f:
49-
return json.load(f)
50-
5154

5255
def predict_tools(prompt, envelope=None):
5356
"""

plugins/cortex-code/scripts/router/read_cortex_sessions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ def parse_session_file(session_path, sanitize=True):
4343
Dictionary with session data, or None on error
4444
"""
4545
try:
46+
# Guard against pathologically large session files (10MB limit)
47+
file_size = session_path.stat().st_size
48+
if file_size > 10 * 1024 * 1024:
49+
print(f"Skipping oversized session file ({file_size} bytes): {session_path}", file=sys.stderr)
50+
return None
51+
4652
with open(session_path, 'r') as f:
4753
lines = f.readlines()
4854

plugins/cortex-code/scripts/router/session_state.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def save_active_session(session_id: str) -> None:
5757
with os.fdopen(fd, "w") as f:
5858
json.dump(payload, f)
5959
os.replace(tmp_name, path)
60+
os.chmod(path, 0o600)
6061
except Exception:
6162
try:
6263
os.unlink(tmp_name)

0 commit comments

Comments
 (0)