Skip to content

Commit 4fdcdd4

Browse files
committed
fix(security): close open scanner alerts
Dependabot/CodeQL #33 — postcss XSS GHSA-qx2v-qp2m-jg93: npm audit fix in graph-ui (8.5.8 -> 8.5.14, above the <8.5.10 vuln range) CodeQL #39 — TOCTOU race in artifact.c ensure_gitattributes(): Replace stat() + fopen() with open(O_WRONLY|O_CREAT|O_EXCL). Atomic create-only-if-absent closes the check-vs-write window. Falls through to merge driver setup if file already exists. CodeQL #55 — pip install not pinned in release.yml: Pin build==1.3.0 and twine==6.2.0. Comment explains why --require-hashes is not used (transitive-deps overhead). Dismissed (won't-fix): - #56 contents: write — required for 'gh release edit --draft=false'; no narrower permission exists. - #54-51 Crystal grammar warnings — vendored upstream code. - #50-40 Agda grammar warnings — vendored upstream code.
1 parent 562c3d9 commit 4fdcdd4

3 files changed

Lines changed: 30 additions & 16 deletions

File tree

.github/workflows/release.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,10 @@ jobs:
289289
- name: Build PyPI distribution
290290
working-directory: pkg/pypi
291291
run: |
292-
python -m pip install --upgrade build twine
292+
# Pin to specific versions (typosquatting / supply-chain mitigation).
293+
# OSSF scorecard prefers --require-hashes, but that needs full
294+
# transitive deps; version pinning is the practical compromise.
295+
python -m pip install --upgrade 'build==1.3.0' 'twine==6.2.0'
293296
python -m build
294297
295298
- name: Publish to PyPI

graph-ui/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/pipeline/artifact.c

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,15 @@ enum {
2727
#include <sqlite3.h>
2828
#include <yyjson/yyjson.h>
2929

30+
#include <errno.h>
31+
#include <fcntl.h>
3032
#include <stdint.h>
3133
#include <stdio.h>
3234
#include <stdlib.h>
3335
#include <string.h>
3436
#include <sys/stat.h>
3537
#include <time.h>
38+
#include <unistd.h>
3639

3740
/* ── Helpers ──────────────────────────────────────────────────────── */
3841

@@ -228,18 +231,26 @@ static void ensure_gitattributes(const char *repo_path) {
228231
char ga_path[CBM_SZ_4K];
229232
artifact_path(ga_path, sizeof(ga_path), repo_path, ".gitattributes");
230233

231-
struct stat st;
232-
if (stat(ga_path, &st) == 0) {
233-
return; /* already exists */
234-
}
235-
236-
FILE *fp = fopen(ga_path, "w");
237-
if (fp) {
238-
(void)fputs("# Auto-generated by codebase-memory-mcp\n"
239-
"# Prevent merge conflicts on compressed artifact\n" CBM_ARTIFACT_FILENAME
240-
" merge=ours binary\n",
241-
fp);
242-
(void)fclose(fp);
234+
/* Atomic create-only-if-absent: O_EXCL closes the TOCTOU window
235+
* between checking existence and writing. If the file exists, open
236+
* fails with EEXIST and we leave it untouched. */
237+
int fd = open(ga_path, O_WRONLY | O_CREAT | O_EXCL, 0644);
238+
if (fd < 0) {
239+
if (errno != EEXIST) {
240+
cbm_log_warn("artifact.gitattributes.open path=%s err=%s", ga_path, strerror(errno));
241+
}
242+
/* fall through to merge driver setup either way */
243+
} else {
244+
FILE *fp = fdopen(fd, "w");
245+
if (fp) {
246+
(void)fputs("# Auto-generated by codebase-memory-mcp\n"
247+
"# Prevent merge conflicts on compressed artifact\n" CBM_ARTIFACT_FILENAME
248+
" merge=ours binary\n",
249+
fp);
250+
(void)fclose(fp);
251+
} else {
252+
(void)close(fd);
253+
}
243254
}
244255

245256
/* Best-effort: configure merge driver */

0 commit comments

Comments
 (0)