From 0024f3c2df4a8c1b18644804ce1cc6d4fc77c928 Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Mon, 13 Apr 2026 11:25:15 +0200 Subject: [PATCH] fix: remediate code scanning alerts --- .github/workflows/ci.yml | 3 +++ packages/core/__tests__/core/sanitize.test.ts | 16 +++++++++++- .../core/__tests__/repo/ciWorkflow.test.ts | 14 ++++++++++ packages/core/src/identifier.ts | 26 +++++++++++++++---- 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 packages/core/__tests__/repo/ciWorkflow.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00f37ab..5ae50d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest diff --git a/packages/core/__tests__/core/sanitize.test.ts b/packages/core/__tests__/core/sanitize.test.ts index 78590e7..b29db8b 100644 --- a/packages/core/__tests__/core/sanitize.test.ts +++ b/packages/core/__tests__/core/sanitize.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { sanitizeToolName } from "@execbox/core"; +import { sanitizeIdentifier, sanitizeToolName } from "@execbox/core"; describe("sanitizeToolName", () => { it("replaces punctuation and spaces with underscores", () => { @@ -18,3 +18,17 @@ describe("sanitizeToolName", () => { expect(sanitizeToolName("")).toBe("_"); }); }); + +describe("sanitizeIdentifier", () => { + it("removes leading and trailing replacement underscores", () => { + expect(sanitizeIdentifier(" ---tool name--- ")).toBe("tool_name"); + }); + + it("preserves internal underscores while trimming the edges", () => { + expect(sanitizeIdentifier("tool---name")).toBe("tool_name"); + }); + + it("falls back to an underscore when all characters are trimmed away", () => { + expect(sanitizeIdentifier("---")).toBe("_"); + }); +}); diff --git a/packages/core/__tests__/repo/ciWorkflow.test.ts b/packages/core/__tests__/repo/ciWorkflow.test.ts new file mode 100644 index 0000000..e345a9b --- /dev/null +++ b/packages/core/__tests__/repo/ciWorkflow.test.ts @@ -0,0 +1,14 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +describe("ci workflow", () => { + it("declares least-privilege GITHUB_TOKEN permissions", () => { + const workflow = readFileSync( + new URL("../../../../.github/workflows/ci.yml", import.meta.url), + "utf8", + ); + + expect(workflow).toContain("permissions:"); + expect(workflow).toContain("contents: read"); + }); +}); diff --git a/packages/core/src/identifier.ts b/packages/core/src/identifier.ts index e34ae0d..b6cb31a 100644 --- a/packages/core/src/identifier.ts +++ b/packages/core/src/identifier.ts @@ -39,6 +39,23 @@ const RESERVED_WORDS = new Set([ "yield", ]); +const UNDERSCORE_CHAR_CODE = 95; + +function trimEdgeUnderscores(value: string): string { + let start = 0; + let end = value.length; + + while (start < end && value.charCodeAt(start) === UNDERSCORE_CHAR_CODE) { + start += 1; + } + + while (end > start && value.charCodeAt(end - 1) === UNDERSCORE_CHAR_CODE) { + end -= 1; + } + + return value.slice(start, end); +} + /** * Returns whether the value is a valid JavaScript identifier. */ @@ -69,12 +86,11 @@ export function assertValidIdentifier( * Converts a raw identifier-like value into a safe JavaScript identifier. */ export function sanitizeIdentifier(value: string): string { - const sanitized = value - .trim() - .replace(/[^A-Za-z0-9_$]+/g, "_") - .replace(/^_+|_+$/g, ""); + const sanitized = value.trim().replace(/[^A-Za-z0-9_$]+/g, "_"); + + const trimmed = trimEdgeUnderscores(sanitized); - let safeName = sanitized.length > 0 ? sanitized : "_"; + let safeName = trimmed.length > 0 ? trimmed : "_"; if (/^[0-9]/.test(safeName)) { safeName = `_${safeName}`;