Skip to content

Commit 296a97a

Browse files
committed
Merge branch 'main' into fix/libgit2-diff-options-init
2 parents feeb0b1 + fad0ff7 commit 296a97a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+3708
-863
lines changed

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"

.github/workflows/codeql.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CodeQL SAST
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
permissions:
8+
security-events: write
9+
contents: read
10+
11+
jobs:
12+
analyze:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
16+
17+
- name: Install build dependencies
18+
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
19+
20+
- name: Initialize CodeQL
21+
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4
22+
with:
23+
languages: c-cpp
24+
build-mode: manual
25+
26+
- name: Build for CodeQL analysis
27+
run: scripts/build.sh
28+
29+
- name: Perform CodeQL Analysis
30+
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4
31+
with:
32+
category: "/language:c-cpp"

.github/workflows/dry-run.yml

Lines changed: 142 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
if: ${{ !inputs.skip_lint }}
2626
runs-on: ubuntu-latest
2727
steps:
28-
- uses: actions/checkout@v4
28+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2929

3030
- name: Install build deps
3131
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev cmake
@@ -37,7 +37,7 @@ jobs:
3737
sudo apt-get update
3838
sudo apt-get install -y clang-format-20
3939
40-
- uses: actions/cache@v4
40+
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
4141
id: cppcheck-cache
4242
with:
4343
path: /opt/cppcheck
@@ -64,7 +64,7 @@ jobs:
6464
if: ${{ !inputs.skip_lint }}
6565
runs-on: ubuntu-latest
6666
steps:
67-
- uses: actions/checkout@v4
67+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
6868

6969
- name: "Layer 1: Static allow-list audit"
7070
run: scripts/security-audit.sh
@@ -75,6 +75,79 @@ jobs:
7575
- name: "Layer 8: Vendored dependency integrity"
7676
run: scripts/security-vendored.sh
7777

78+
# ── Step 1c: CodeQL SAST gate ────────────────────────────────
79+
codeql-gate:
80+
if: ${{ !inputs.skip_lint }}
81+
runs-on: ubuntu-latest
82+
steps:
83+
- name: Wait for CodeQL on current commit (max 45 min)
84+
env:
85+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
86+
run: |
87+
CURRENT_SHA="${{ github.sha }}"
88+
echo "Current commit: $CURRENT_SHA"
89+
echo "Waiting for CodeQL to complete on this commit..."
90+
91+
for attempt in $(seq 1 90); do
92+
LATEST=$(gh api repos/${{ github.repository }}/actions/workflows/codeql.yml/runs?per_page=5 \
93+
--jq '.workflow_runs[] | select(.head_sha == "'"$CURRENT_SHA"'") | "\(.conclusion) \(.status)"' 2>/dev/null | head -1 || echo "")
94+
95+
if [ -z "$LATEST" ]; then
96+
echo " Attempt $attempt/90: No CodeQL run found for $CURRENT_SHA yet..."
97+
sleep 30
98+
continue
99+
fi
100+
101+
CONCLUSION=$(echo "$LATEST" | cut -d' ' -f1)
102+
STATUS=$(echo "$LATEST" | cut -d' ' -f2)
103+
104+
if [ "$STATUS" = "completed" ] && [ "$CONCLUSION" = "success" ]; then
105+
echo "=== CodeQL completed successfully on current commit ==="
106+
exit 0
107+
elif [ "$STATUS" = "completed" ]; then
108+
echo "BLOCKED: CodeQL completed with conclusion: $CONCLUSION"
109+
exit 1
110+
fi
111+
112+
echo " Attempt $attempt/90: CodeQL status=$STATUS (waiting 30s)..."
113+
sleep 30
114+
done
115+
116+
echo "BLOCKED: CodeQL did not complete within 45 minutes"
117+
exit 1
118+
119+
- name: Check for open code scanning alerts
120+
env:
121+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
122+
run: |
123+
# Wait for GitHub to finish processing alert state changes.
124+
# There is a race between CodeQL marking the workflow as "completed"
125+
# and the alerts API reflecting new/closed alerts from that scan.
126+
echo "Waiting 60s for alert API to settle after CodeQL completion..."
127+
sleep 60
128+
129+
# Poll alerts twice with a gap to confirm the count is stable
130+
ALERTS1=$(gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' --jq 'length' 2>/dev/null || echo "0")
131+
echo "Open alerts (check 1): $ALERTS1"
132+
sleep 15
133+
ALERTS2=$(gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' --jq 'length' 2>/dev/null || echo "0")
134+
echo "Open alerts (check 2): $ALERTS2"
135+
136+
# Use the higher count (conservative — if either check sees alerts, block)
137+
ALERTS=$ALERTS2
138+
if [ "$ALERTS1" -gt "$ALERTS2" ]; then
139+
ALERTS=$ALERTS1
140+
fi
141+
142+
if [ "$ALERTS" -gt 0 ]; then
143+
echo "BLOCKED: $ALERTS open code scanning alert(s) found."
144+
gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' \
145+
--jq '.[] | " #\(.number) [\(.rule.security_severity_level // .rule.severity)] \(.rule.id) — \(.most_recent_instance.location.path):\(.most_recent_instance.location.start_line)"' 2>/dev/null || true
146+
echo "Fix them: https://github.com/${{ github.repository }}/security/code-scanning"
147+
exit 1
148+
fi
149+
echo "=== CodeQL gate passed (0 open alerts) ==="
150+
78151
# ── Step 2: Unit tests (ASan + UBSan) ───────────────────────
79152
# macOS: use cc (Apple Clang) — GCC on macOS doesn't ship ASan runtime
80153
# Linux: use system gcc — full ASan/UBSan support
@@ -104,7 +177,7 @@ jobs:
104177
cxx: c++
105178
runs-on: ${{ matrix.os }}
106179
steps:
107-
- uses: actions/checkout@v4
180+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
108181

109182
- name: Install deps (Ubuntu)
110183
if: startsWith(matrix.os, 'ubuntu')
@@ -118,9 +191,9 @@ jobs:
118191
needs: [lint]
119192
runs-on: windows-latest
120193
steps:
121-
- uses: actions/checkout@v4
194+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
122195

123-
- uses: msys2/setup-msys2@v2
196+
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2
124197
with:
125198
msystem: CLANG64
126199
path-type: inherit
@@ -163,13 +236,13 @@ jobs:
163236
cxx: c++
164237
runs-on: ${{ matrix.os }}
165238
steps:
166-
- uses: actions/checkout@v4
239+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
167240

168241
- name: Install deps (Ubuntu)
169242
if: startsWith(matrix.os, 'ubuntu')
170243
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
171244

172-
- uses: actions/setup-node@v4
245+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
173246
with:
174247
node-version: "22"
175248

@@ -178,8 +251,9 @@ jobs:
178251

179252
- name: Archive standard binary
180253
run: |
254+
cp LICENSE build/c/
181255
tar -czf codebase-memory-mcp-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz \
182-
-C build/c codebase-memory-mcp
256+
-C build/c codebase-memory-mcp LICENSE
183257
184258
- name: Build UI binary
185259
run: scripts/build.sh --with-ui CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
@@ -190,10 +264,11 @@ jobs:
190264

191265
- name: Archive UI binary
192266
run: |
267+
cp LICENSE build/c/
193268
tar -czf codebase-memory-mcp-ui-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz \
194-
-C build/c codebase-memory-mcp
269+
-C build/c codebase-memory-mcp LICENSE
195270
196-
- uses: actions/upload-artifact@v4
271+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
197272
with:
198273
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
199274
path: "*.tar.gz"
@@ -203,9 +278,9 @@ jobs:
203278
needs: [test-unix, test-windows]
204279
runs-on: windows-latest
205280
steps:
206-
- uses: actions/checkout@v4
281+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
207282

208-
- uses: msys2/setup-msys2@v2
283+
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2
209284
with:
210285
msystem: CLANG64
211286
path-type: inherit
@@ -215,7 +290,7 @@ jobs:
215290
make
216291
zip
217292
218-
- uses: actions/setup-node@v4
293+
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
219294
with:
220295
node-version: "22"
221296

@@ -229,7 +304,7 @@ jobs:
229304
BIN=build/c/codebase-memory-mcp
230305
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
231306
cp "$BIN" codebase-memory-mcp.exe
232-
zip codebase-memory-mcp-windows-amd64.zip codebase-memory-mcp.exe
307+
zip codebase-memory-mcp-windows-amd64.zip codebase-memory-mcp.exe LICENSE
233308
234309
- name: Build UI binary
235310
shell: msys2 {0}
@@ -241,9 +316,9 @@ jobs:
241316
BIN=build/c/codebase-memory-mcp
242317
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
243318
cp "$BIN" codebase-memory-mcp-ui.exe
244-
zip codebase-memory-mcp-ui-windows-amd64.zip codebase-memory-mcp-ui.exe
319+
zip codebase-memory-mcp-ui-windows-amd64.zip codebase-memory-mcp-ui.exe LICENSE
245320
246-
- uses: actions/upload-artifact@v4
321+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
247322
with:
248323
name: binaries-windows-amd64
249324
path: "*.zip"
@@ -271,9 +346,9 @@ jobs:
271346
variant: [standard, ui]
272347
runs-on: ${{ matrix.os }}
273348
steps:
274-
- uses: actions/checkout@v4
349+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
275350

276-
- uses: actions/download-artifact@v4
351+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
277352
with:
278353
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
279354

@@ -302,6 +377,37 @@ jobs:
302377
if: matrix.variant == 'standard' && matrix.goos == 'linux' && matrix.goarch == 'amd64'
303378
run: scripts/security-fuzz.sh ./codebase-memory-mcp
304379

380+
- name: Fuzz testing (60s random input)
381+
if: matrix.variant == 'standard' && matrix.goos == 'linux' && matrix.goarch == 'amd64'
382+
run: scripts/security-fuzz-random.sh ./codebase-memory-mcp 60
383+
384+
- name: ClamAV scan (Linux)
385+
if: matrix.variant == 'standard' && startsWith(matrix.os, 'ubuntu')
386+
run: |
387+
sudo apt-get update -qq && sudo apt-get install -y -qq clamav > /dev/null 2>&1
388+
sudo sed -i 's/^Example/#Example/' /etc/clamav/freshclam.conf 2>/dev/null || true
389+
grep -q "DatabaseMirror" /etc/clamav/freshclam.conf 2>/dev/null || \
390+
echo "DatabaseMirror database.clamav.net" | sudo tee -a /etc/clamav/freshclam.conf > /dev/null
391+
sudo freshclam --quiet
392+
echo "=== ClamAV scan ==="
393+
clamscan --no-summary ./codebase-memory-mcp
394+
echo "=== ClamAV: clean ==="
395+
396+
- name: ClamAV scan (macOS)
397+
if: matrix.variant == 'standard' && startsWith(matrix.os, 'macos')
398+
run: |
399+
brew install clamav > /dev/null 2>&1
400+
CLAMAV_ETC=$(brew --prefix)/etc/clamav
401+
if [ ! -f "$CLAMAV_ETC/freshclam.conf" ]; then
402+
cp "$CLAMAV_ETC/freshclam.conf.sample" "$CLAMAV_ETC/freshclam.conf" 2>/dev/null || true
403+
sed -i '' 's/^Example/#Example/' "$CLAMAV_ETC/freshclam.conf" 2>/dev/null || true
404+
echo "DatabaseMirror database.clamav.net" >> "$CLAMAV_ETC/freshclam.conf"
405+
fi
406+
freshclam --quiet --no-warnings 2>/dev/null || freshclam --quiet 2>/dev/null || echo "WARNING: freshclam update failed, using bundled signatures"
407+
echo "=== ClamAV scan (macOS) ==="
408+
clamscan --no-summary ./codebase-memory-mcp
409+
echo "=== ClamAV: clean ==="
410+
305411
smoke-windows:
306412
if: ${{ !inputs.skip_builds }}
307413
needs: [build-windows]
@@ -311,17 +417,17 @@ jobs:
311417
variant: [standard, ui]
312418
runs-on: windows-latest
313419
steps:
314-
- uses: actions/checkout@v4
420+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
315421

316-
- uses: msys2/setup-msys2@v2
422+
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2
317423
with:
318424
msystem: CLANG64
319425
path-type: inherit
320426
install: >-
321427
mingw-w64-clang-x86_64-python3
322428
unzip
323429
324-
- uses: actions/download-artifact@v4
430+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
325431
with:
326432
name: binaries-windows-amd64
327433

@@ -345,3 +451,17 @@ jobs:
345451
if: matrix.variant == 'standard'
346452
shell: msys2 {0}
347453
run: scripts/security-install.sh ./codebase-memory-mcp.exe
454+
455+
- name: Windows Defender scan
456+
if: matrix.variant == 'standard'
457+
shell: pwsh
458+
run: |
459+
Write-Host "=== Windows Defender scan (with ML heuristics) ==="
460+
& "C:\Program Files\Windows Defender\MpCmdRun.exe" -SignatureUpdate 2>$null
461+
$result = & "C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File "$PWD\codebase-memory-mcp.exe" -DisableRemediation
462+
Write-Host $result
463+
if ($LASTEXITCODE -ne 0) {
464+
Write-Host "BLOCKED: Windows Defender flagged the binary!"
465+
exit 1
466+
}
467+
Write-Host "=== Windows Defender: clean ==="

0 commit comments

Comments
 (0)