diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed4758713..51497a877 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -289,7 +289,10 @@ jobs: - name: Build PyPI distribution working-directory: pkg/pypi run: | - python -m pip install --upgrade build twine + # Pin to specific versions (typosquatting / supply-chain mitigation). + # OSSF scorecard prefers --require-hashes, but that needs full + # transitive deps; version pinning is the practical compromise. + python -m pip install --upgrade 'build==1.3.0' 'twine==6.2.0' python -m build - name: Publish to PyPI diff --git a/graph-ui/package-lock.json b/graph-ui/package-lock.json index d75d2ff2c..512eb82f6 100644 --- a/graph-ui/package-lock.json +++ b/graph-ui/package-lock.json @@ -5164,9 +5164,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "funding": [ { "type": "opencollective", diff --git a/src/pipeline/artifact.c b/src/pipeline/artifact.c index 4f343d297..b9ef94e6f 100644 --- a/src/pipeline/artifact.c +++ b/src/pipeline/artifact.c @@ -27,12 +27,15 @@ enum { #include #include +#include +#include #include #include #include #include #include #include +#include /* ── Helpers ──────────────────────────────────────────────────────── */ @@ -228,18 +231,26 @@ static void ensure_gitattributes(const char *repo_path) { char ga_path[CBM_SZ_4K]; artifact_path(ga_path, sizeof(ga_path), repo_path, ".gitattributes"); - struct stat st; - if (stat(ga_path, &st) == 0) { - return; /* already exists */ - } - - FILE *fp = fopen(ga_path, "w"); - if (fp) { - (void)fputs("# Auto-generated by codebase-memory-mcp\n" - "# Prevent merge conflicts on compressed artifact\n" CBM_ARTIFACT_FILENAME - " merge=ours binary\n", - fp); - (void)fclose(fp); + /* Atomic create-only-if-absent: O_EXCL closes the TOCTOU window + * between checking existence and writing. If the file exists, open + * fails with EEXIST and we leave it untouched. */ + int fd = open(ga_path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0) { + if (errno != EEXIST) { + cbm_log_warn("artifact.gitattributes.open path=%s err=%s", ga_path, strerror(errno)); + } + /* fall through to merge driver setup either way */ + } else { + FILE *fp = fdopen(fd, "w"); + if (fp) { + (void)fputs("# Auto-generated by codebase-memory-mcp\n" + "# Prevent merge conflicts on compressed artifact\n" CBM_ARTIFACT_FILENAME + " merge=ours binary\n", + fp); + (void)fclose(fp); + } else { + (void)close(fd); + } } /* Best-effort: configure merge driver */