Skip to content

Commit cd14174

Browse files
committed
Add 8-layer security test suite + hardening
Code-level defenses: - cbm_validate_shell_arg(): reject shell metacharacters before popen/system - SQLite authorizer: block ATTACH/DETACH at engine level - CORS localhost-only origin reflection (replaces wildcard *) - Path containment: realpath() check in get_code_snippet - process-kill restricted to server-spawned PIDs - SHA256 checksum verification in update command Security audit scripts (8 layers): - L1: Static allow-list for dangerous calls + URLs - L2: Binary string audit (URLs, payloads, credentials) - L3: Network egress monitoring via strace (Linux) - L4: Install output path + content validation - L5: Smoke test hardening (clean shutdown, residual procs) - L6: Graph UI audit (external domains, CORS, binding) - L7: MCP robustness (23 adversarial JSON-RPC payloads) - L8: Vendored integrity (checksums + dangerous call scan) CI: parallel security-static job (no build needed), binary layers in smoke jobs per-platform. Cleanup of test fixture dirs in clean.sh + .gitignore.
1 parent c66d887 commit cd14174

23 files changed

Lines changed: 1769 additions & 82 deletions

.github/workflows/dry-run.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ jobs:
5757
- name: Lint
5858
run: scripts/lint.sh CLANG_FORMAT=clang-format-20
5959

60+
# ── Step 1b: Security audit (source-only, runs parallel with lint+tests) ──
61+
# No build needed — scans source files and vendored deps only.
62+
# Binary-level security (L2/L3/L4/L7) runs in smoke jobs per-platform.
63+
security-static:
64+
if: ${{ !inputs.skip_lint }}
65+
runs-on: ubuntu-latest
66+
steps:
67+
- uses: actions/checkout@v4
68+
69+
- name: "Layer 1: Static allow-list audit"
70+
run: scripts/security-audit.sh
71+
72+
- name: "Layer 6: UI security audit"
73+
run: scripts/security-ui.sh
74+
75+
- name: "Layer 8: Vendored dependency integrity"
76+
run: scripts/security-vendored.sh
77+
6078
# ── Step 2: Unit tests (ASan + UBSan) ───────────────────────
6179
# macOS: use cc (Apple Clang) — GCC on macOS doesn't ship ASan runtime
6280
# Linux: use system gcc — full ASan/UBSan support
@@ -166,6 +184,10 @@ jobs:
166184
- name: Build UI binary
167185
run: scripts/build.sh --with-ui CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
168186

187+
- name: Frontend integrity scan (post-build dist/)
188+
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
189+
run: scripts/security-ui.sh
190+
169191
- name: Archive UI binary
170192
run: |
171193
tar -czf codebase-memory-mcp-ui-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz \
@@ -264,6 +286,22 @@ jobs:
264286
- name: Smoke test (${{ matrix.variant }}, ${{ matrix.goos }}-${{ matrix.goarch }})
265287
run: scripts/smoke-test.sh ./codebase-memory-mcp
266288

289+
- name: Binary string audit (${{ matrix.goos }}-${{ matrix.goarch }})
290+
if: matrix.variant == 'standard'
291+
run: scripts/security-strings.sh ./codebase-memory-mcp
292+
293+
- name: Install output audit (${{ matrix.goos }}-${{ matrix.goarch }})
294+
if: matrix.variant == 'standard'
295+
run: scripts/security-install.sh ./codebase-memory-mcp
296+
297+
- name: Network egress test (${{ matrix.goos }}-${{ matrix.goarch }})
298+
if: matrix.variant == 'standard'
299+
run: scripts/security-network.sh ./codebase-memory-mcp
300+
301+
- name: MCP robustness test
302+
if: matrix.variant == 'standard' && matrix.goos == 'linux' && matrix.goarch == 'amd64'
303+
run: scripts/security-fuzz.sh ./codebase-memory-mcp
304+
267305
smoke-windows:
268306
if: ${{ !inputs.skip_builds }}
269307
needs: [build-windows]
@@ -297,3 +335,13 @@ jobs:
297335
- name: Smoke test (${{ matrix.variant }}, windows-amd64)
298336
shell: msys2 {0}
299337
run: scripts/smoke-test.sh ./codebase-memory-mcp.exe
338+
339+
- name: Binary string audit (windows-amd64)
340+
if: matrix.variant == 'standard'
341+
shell: msys2 {0}
342+
run: scripts/security-strings.sh ./codebase-memory-mcp.exe
343+
344+
- name: Install output audit (windows-amd64)
345+
if: matrix.variant == 'standard'
346+
shell: msys2 {0}
347+
run: scripts/security-install.sh ./codebase-memory-mcp.exe

.github/workflows/release.yml

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ jobs:
5757
- name: Lint
5858
run: scripts/lint.sh CLANG_FORMAT=clang-format-20
5959

60+
# ── Step 1b: Security audit (source-only, runs parallel with lint+tests) ──
61+
# No build needed — scans source files and vendored deps only.
62+
# Binary-level security (L2/L3/L4/L7) runs in smoke jobs per-platform.
63+
security-static:
64+
runs-on: ubuntu-latest
65+
steps:
66+
- uses: actions/checkout@v4
67+
68+
- name: "Layer 1: Static allow-list audit"
69+
run: scripts/security-audit.sh
70+
71+
- name: "Layer 6: UI security audit"
72+
run: scripts/security-ui.sh
73+
74+
- name: "Layer 8: Vendored dependency integrity"
75+
run: scripts/security-vendored.sh
76+
6077
# ── Step 2: Unit tests (ASan + UBSan) ───────────────────────
6178
# macOS: use cc (Apple Clang) — GCC on macOS doesn't ship ASan runtime
6279
# Linux: use system gcc — full ASan/UBSan support
@@ -163,6 +180,10 @@ jobs:
163180
- name: Build UI binary
164181
run: scripts/build.sh --with-ui --version ${{ inputs.version }} CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
165182

183+
- name: Frontend integrity scan (post-build dist/)
184+
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
185+
run: scripts/security-ui.sh
186+
166187
- name: Archive UI binary
167188
run: |
168189
tar -czf codebase-memory-mcp-ui-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz \
@@ -258,6 +279,22 @@ jobs:
258279
- name: Smoke test (${{ matrix.variant }}, ${{ matrix.goos }}-${{ matrix.goarch }})
259280
run: scripts/smoke-test.sh ./codebase-memory-mcp
260281

282+
- name: Binary string audit (${{ matrix.goos }}-${{ matrix.goarch }})
283+
if: matrix.variant == 'standard'
284+
run: scripts/security-strings.sh ./codebase-memory-mcp
285+
286+
- name: Install output audit (${{ matrix.goos }}-${{ matrix.goarch }})
287+
if: matrix.variant == 'standard'
288+
run: scripts/security-install.sh ./codebase-memory-mcp
289+
290+
- name: Network egress test (${{ matrix.goos }}-${{ matrix.goarch }})
291+
if: matrix.variant == 'standard'
292+
run: scripts/security-network.sh ./codebase-memory-mcp
293+
294+
- name: MCP robustness test
295+
if: matrix.variant == 'standard' && matrix.goos == 'linux' && matrix.goarch == 'amd64'
296+
run: scripts/security-fuzz.sh ./codebase-memory-mcp
297+
261298
smoke-windows:
262299
needs: [build-windows]
263300
strategy:
@@ -290,9 +327,19 @@ jobs:
290327
shell: msys2 {0}
291328
run: scripts/smoke-test.sh ./codebase-memory-mcp.exe
292329

330+
- name: Binary string audit (windows-amd64)
331+
if: matrix.variant == 'standard'
332+
shell: msys2 {0}
333+
run: scripts/security-strings.sh ./codebase-memory-mcp.exe
334+
335+
- name: Install output audit (windows-amd64)
336+
if: matrix.variant == 'standard'
337+
shell: msys2 {0}
338+
run: scripts/security-install.sh ./codebase-memory-mcp.exe
339+
293340
# ── Step 5: Create GitHub release ───────────────────────────
294341
release:
295-
needs: [smoke-unix, smoke-windows]
342+
needs: [smoke-unix, smoke-windows, security-static]
296343
runs-on: ubuntu-latest
297344
permissions:
298345
contents: write

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ bin/
1212
*.out
1313
coverage.txt
1414

15+
# Test fixture temp dirs (created by C test suite in CWD instead of /tmp/)
16+
cbm_*/
17+
cli-*/
18+
1519
# IDE
1620
.idea/
1721
.vscode/

Makefile.cbm

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ PP_OBJ_TEST = $(BUILD_DIR)/preprocessor.o
310310

311311
# ── Targets ──────────────────────────────────────────────────────
312312

313-
.PHONY: test test-foundation test-tsan cbm cbm-with-ui frontend embed clean-c lint lint-tidy lint-cppcheck lint-format
313+
.PHONY: test test-foundation test-tsan cbm cbm-with-ui frontend embed clean-c lint lint-tidy lint-cppcheck lint-format security
314314

315315
$(BUILD_DIR):
316316
mkdir -p $(BUILD_DIR)
@@ -519,3 +519,18 @@ lint: lint-tidy lint-cppcheck lint-format
519519
# CI linters (no clang-tidy — platform-dependent, enforced locally via pre-commit)
520520
lint-ci: lint-cppcheck lint-format
521521
@echo "=== CI linters passed ==="
522+
523+
# ── Security audit (6 layers) ────────────────────────────────────
524+
525+
# Run all security checks: static audit, binary strings, UI, install, network
526+
# Requires: production binary already built (make cbm)
527+
security: cbm
528+
@echo "=== Running security audit suite ==="
529+
scripts/security-audit.sh
530+
scripts/security-strings.sh $(BUILD_DIR)/codebase-memory-mcp
531+
scripts/security-ui.sh
532+
scripts/security-install.sh $(BUILD_DIR)/codebase-memory-mcp
533+
scripts/security-network.sh $(BUILD_DIR)/codebase-memory-mcp
534+
scripts/security-fuzz.sh $(BUILD_DIR)/codebase-memory-mcp
535+
scripts/security-vendored.sh
536+
@echo "=== All security checks passed ==="

scripts/clean.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,10 @@ rm -rf "$ROOT/node_modules"
2626
# Generated embedded assets (regenerated by embed-frontend.sh)
2727
rm -f "$ROOT/src/ui/embedded_assets.c"
2828

29+
# Leftover test fixture dirs (C test suite sometimes creates these in CWD)
30+
find "$ROOT" -maxdepth 1 -type d \( -name 'cbm_*' -o -name 'cli-*' \) -exec rm -rf {} + 2>/dev/null || true
31+
32+
# Leftover test fixture dirs in /tmp
33+
find /tmp -maxdepth 1 -type d \( -name 'cbm_*' -o -name 'cli-*' \) -user "$(id -u)" -exec rm -rf {} + 2>/dev/null || true
34+
2935
echo "=== Clean complete ==="

scripts/security-allowlist.txt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Security allow-list for dangerous function calls.
2+
# Format: file:function:justification
3+
# Lines starting with # are comments. Empty lines are ignored.
4+
# Any call to a listed function in a .c file under src/ that is NOT on this
5+
# list causes the security audit (scripts/security-audit.sh) to fail.
6+
7+
# ── Foundation: platform abstraction (defines cbm_popen wrapper) ───────────
8+
src/foundation/compat_fs.c:popen:cbm_popen wrapper definition (POSIX)
9+
src/foundation/compat_fs.c:cbm_popen:cbm_popen function definition
10+
11+
# ── CLI: update command (user-initiated, interactive) ──────────────────────
12+
src/cli/cli.c:system:curl download of release binary (update cmd)
13+
src/cli/cli.c:system:unzip extraction on Windows (update cmd)
14+
src/cli/cli.c:system:version verification after update (update cmd)
15+
src/cli/cli.c:cbm_popen:sha256 checksum verification (update cmd)
16+
src/cli/cli.c:popen:sha256 checksum computation via shasum
17+
18+
# ── Watcher: git status polling (repo paths validated via cbm_validate_shell_arg) ──
19+
src/watcher/watcher.c:system:git repo detection (is_git_repo)
20+
src/watcher/watcher.c:cbm_popen:git HEAD hash (git_head)
21+
src/watcher/watcher.c:cbm_popen:git working tree status (git_is_dirty)
22+
src/watcher/watcher.c:cbm_popen:git file count (git_file_count)
23+
src/watcher/watcher.c:popen:via cbm_popen wrapper calls
24+
25+
# ── MCP server: search and change detection ────────────────────────────────
26+
src/mcp/mcp.c:cbm_popen:search_code via grep (pattern in temp file, path validated)
27+
src/mcp/mcp.c:cbm_popen:detect_changes via git diff (args validated)
28+
src/mcp/mcp.c:cbm_popen:git ls-files count for auto-index (session_root validated)
29+
src/mcp/mcp.c:cbm_popen:update check to api.github.com (hardcoded URL)
30+
src/mcp/mcp.c:popen:via cbm_popen wrapper calls
31+
32+
# ── Pipeline: git history parsing (fallback when libgit2 not available) ────
33+
src/pipeline/pass_githistory.c:cbm_popen:git log for file history (path validated)
34+
src/pipeline/pass_githistory.c:popen:via cbm_popen wrapper call
35+
36+
# ── UI: HTTP server process management ─────────────────────────────────────
37+
src/ui/http_server.c:popen:ps process listing for metrics endpoint
38+
src/ui/http_server.c:fork:spawn indexing subprocess
39+
src/ui/http_server.c:execl:exec indexing binary in child process
40+
41+
# ── Allowed URLs ───────────────────────────────────────────────────────────
42+
# Format: URL:justification
43+
URL:https://api.github.com/repos/DeusData/codebase-memory-mcp/releases/latest:update check
44+
URL:https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/:binary download + checksums
45+
URL:http://127.0.0.1:UI server binding (localhost only)

0 commit comments

Comments
 (0)