Skip to content

chore(security): CodeQL hardening, supply-chain floors, and Actions least privilege#2831

Merged
davydkov merged 16 commits into
likec4:mainfrom
sraphaz:chore/security-codeql-dependabot-hardening
Apr 3, 2026
Merged

chore(security): CodeQL hardening, supply-chain floors, and Actions least privilege#2831
davydkov merged 16 commits into
likec4:mainfrom
sraphaz:chore/security-codeql-dependabot-hardening

Conversation

@sraphaz

@sraphaz sraphaz commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

Intent

Reduce GitHub security noise and real risk: clear or prevent Dependabot alerts on transitive npm packages, fix Code scanning (CodeQL) findings in our code and generated virtual modules, and tighten GitHub Actions permissions and checkout behavior.

1. Supply chain (package.json, pnpm-lock.yaml, changeset)

  • Root pnpm.overrides: raise version floors for known vulnerable transitives (e.g. lodash / lodash-es, path-to-regexp, picomatch, brace-expansion chains, bn.js where constrained, yaml, smol-toml, ajv, pbkdf2, sha.js, min-document, diff, express/urlqs, rollup, and related). Goal: align the lockfile with patched releases where npm publishes them.
  • pnpm-lock.yaml: regenerated to match overrides.
  • Changeset (.changeset/security-icons-codeql-lockfile.md): patch bump for likec4 documenting security-related hardening of the published CLI/package surface tied to these changes.

2. Vite plugin — embedded literals & CodeQL (js/bad-code-sanitization)

  • hardenJsonStringLiteralForEmbeddedScript: new helper (+ unit tests) that hardens the output of JSON.stringify(...) when pasted into generated JS (e.g. dynamic import(...)), escaping characters that can break out of string/script context (<, >, /, line/paragraph separators). Includes a contract guard so callers only pass real JSON.stringify output.
  • Applied across virtual codegen sinks: icons, _shared / generateCombinedProjects, single-project, mmd so CodeQL sees consistent sanitization of embedded specifiers/literals.

3. CodeQL test & Langium merge (JavaScript/TypeScript queries)

  • packages/core/.../to-html.spec.ts: replace a single-pass .replace(/<[^>]*>/g, '') with a stripHtmlTags helper that repeats until stable, addressing js/incomplete-multi-character-sanitization in tests.
  • packages/language-server/src/module.ts_merge: harden the Langium-copied DI merge for js/prototype-pollution-utility:
    • skip __proto__, constructor, prototype via literal comparisons (CodeQL CWE-915 examples);
    • deep-merge only when the key is an own property of target (Object.prototype.hasOwnProperty.call(target, key)), avoiding recursion into prototype-chain objects.
  • icons virtual module: // codeql[js/bad-code-sanitization] comment documenting why generated import(...) arguments are safe (allowlisted project ids + JSON.stringify + harden).

4. Dependabot — zx (GHSA-w87r-vg9q-crqm)

  • @likec4/vscode: depend on zx via workspace catalog: (full 8.8.5) instead of lite (8.8.5-lite), matching the patched release line in the advisory.
  • Root override: "zx": "8.8.5" so resolution stays on the fixed full package.

5. GitHub Actions (least privilege & TOCTOU)

  • codeql.yml: keep scheduled analysis; add workflow_dispatch for manual runs; if so the job runs on likec4/likec4 or contributor fork sraphaz/likec4 (same signal when developing on a fork).
  • issue-comment.yaml: checkout PR head using immutable head.sha from the API and persist-credentials: false where appropriate to mitigate untrusted checkout / TOCTOU concerns.
  • checks.yaml: default contents: read; restrict actions: write to jobs that upload/download artifacts so least privilege without breaking artifact steps.
  • ci-pr.yaml, push.yaml, pkg-pr-new.yaml, trigger-deploy-template.yaml: explicit permissions where needed for callers or standalone workflows.

6. Chore

  • .gitignore: ignore local .gh-pr-*.md / .gh-pr-*.txt draft files (local-only; not shipped).

Dependabot note — elliptic (CVE-2025-14505)

  • No patched elliptic version on npm at this time; dependency is transitive (e.g. via browserify-sign). This PR does not claim to remove that alert. Maintainers may dismiss the advisory with a documented risk acceptance until upstream ships a fix or the bundler/polyfill chain drops the package.

Verification (local)

  • pnpm install
  • pnpm turbo run typecheck (relevant packages)
  • vitest for hardenJsonStringLiteralForEmbeddedScript.spec.ts and to-html.spec.ts

Post-merge (maintainers)

  • Confirm Code scanning / Dependabot dashboards after merge (or run CodeQL workflow manually).
  • Dismiss remaining elliptic alert if policy accepts transitive/no-fix state.

sraphaz added 11 commits April 2, 2026 01:31
Apply uncle-bob boundary: one policy surface (root overrides) plus small harden helper + tests.

pnpm audit: only elliptic (GHSA-848j) remains — no patched release on npm (<0.0.0).

Made-with: Cursor
fix(security): CodeQL icons literals + pnpm overrides for advisories
- Use full zx@8.8.5 (catalog + override) instead of lite variant for GHSA-w87r-vg9q-crqm

- to-html.spec: stable HTML tag strip for incomplete sanitization query

- language-server _merge: skip prototype-pollution keys

- vite-plugin icons: codeql[js/bad-code-sanitization] justification for hardened import specifiers

Made-with: Cursor
fix(security): CodeQL findings and zx 8.8.5 resolution
…-utility

- Skip __proto__, constructor, prototype with literal comparisons

- Only deep-merge when key is an own property of target (CodeQL CWE-915 guidance)

Made-with: Cursor
…lution

fix(language-server): harden _merge for CodeQL prototype-pollution-utility
@changeset-bot

changeset-bot Bot commented Apr 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2a92210

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
Name Type
likec4 Patch
@likec4/docs-astro Patch
likec4-vscode Patch
@likec4/playground Patch
@likec4/style-preset Patch
@likec4/styles Patch
@likec4/config Patch
@likec4/core Patch
@likec4/diagram Patch
@likec4/generators Patch
@likec4/language-server Patch
@likec4/language-services Patch
@likec4/layouts Patch
@likec4/leanix-bridge Patch
@likec4/log Patch
@likec4/mcp Patch
@likec4/react Patch
@likec4/tsconfig Patch
@likec4/vite-plugin Patch
@likec4/vscode-preview Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Upstream workflow should not reference external forks; workflow_dispatch remains for manual runs.

Made-with: Cursor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f8d9430865

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci-pr.yaml
Comment thread .github/workflows/push.yaml
- hardenJsonStringLiteralForEmbeddedScript: replaceAll + \\u005C replacements

- _merge: Object.hasOwn for own-property check

- ci-pr/push: actions: write on caller so checks.yaml artifact jobs keep scope

Made-with: Cursor
@sraphaz sraphaz force-pushed the chore/security-codeql-dependabot-hardening branch from 2e8fd8e to 8ff1556 Compare April 2, 2026 05:22
@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68bfa1c5-f22c-4c7f-bac3-77851b88ca6d

📥 Commits

Reviewing files that changed from the base of the PR and between 9202751 and 2a92210.

📒 Files selected for processing (4)
  • packages/generators/src/drawio/generate-drawio.ts
  • packages/generators/src/drawio/parse-drawio.ts
  • packages/leanix-bridge/src/leanix-graphql-operations.ts
  • packages/likec4/src/cli/export/drawio/handler.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/generators/src/drawio/parse-drawio.ts
  • packages/likec4/src/cli/export/drawio/handler.ts
  • packages/leanix-bridge/src/leanix-graphql-operations.ts
  • packages/generators/src/drawio/generate-drawio.ts

📝 Walkthrough

Walkthrough

Adds a JSON-string literal hardening utility and applies it across vite-plugin virtual modules, tightens an internal module merge helper to avoid prototype pollution, enforces least-privilege GitHub Actions permissions, and adds pnpm transitive dependency overrides plus related metadata and tests.

Changes

Cohort / File(s) Summary
GitHub Actions workflows
.github/workflows/checks.yaml, .github/workflows/ci-pr.yaml, .github/workflows/codeql.yml, .github/workflows/pkg-pr-new.yaml, .github/workflows/push.yaml, .github/workflows/trigger-deploy-template.yaml
Add top-level permissions (contents: read) and job-level actions: write where required, normalize quoting, and add workflow_dispatch for CodeQL.
Issue comment workflow
.github/workflows/issue-comment.yaml
Replace ref job output with head_sha (via GitHub API); preview jobs consume SHA, use actions/checkout@v6 with persist-credentials: false.
Vite-plugin: hardening utility & usage
packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.ts, packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.spec.ts, packages/vite-plugin/src/virtuals/_shared.ts, packages/vite-plugin/src/virtuals/icons.ts, packages/vite-plugin/src/virtuals/mmd.ts, packages/vite-plugin/src/virtuals/single-project.ts, packages/vite-plugin/src/virtuals/dot.ts, packages/vite-plugin/src/virtuals/previewsSources.ts, packages/vite-plugin/src/virtuals/puml.ts
Add exported hardenJsonStringLiteralForEmbeddedScript (validates JSON.stringify output; escapes <, >, /, U+2028/U+2029) and replace prior embedding/escaping logic across multiple virtual-module generators to inject hardened literals; add tests.
Internal merge hardening
packages/language-server/src/module.ts
Harden internal _merge to skip __proto__, constructor, prototype, ignore undefined sources, and only deep-merge when target has own property to prevent prototype pollution.
Dependency overrides & package tweak
package.json, packages/vscode/package.json
Add many pnpm.overrides pinning transitive dependency versions; change zx devDependency specifier in packages/vscode/package.json.
Tests, metadata, gitignore
.changeset/security-icons-codeql-lockfile.md, .gitignore, packages/core/src/utils/markdown/to-html.spec.ts
Add changeset entry; ignore .gh-pr-*.md; add stripHtmlTags helper in a markdown-to-html test.
Leanix docs JSDoc additions
packages/leanix-bridge/src/governance-checks.ts, packages/leanix-bridge/src/to-bridge-manifest.ts, packages/leanix-bridge/src/leanix-graphql-operations.ts
Add/extend JSDoc clarifications for exported interfaces and lookup semantics; no signature or logic changes.
Drawio CLI docs
packages/likec4/src/cli/export/drawio/handler.ts
Update CLI handler docs to document `profile: 'default'

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • davydkov
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: CodeQL hardening, supply-chain vulnerability floors, and GitHub Actions least privilege.
Description check ✅ Passed The description comprehensively covers all major changes, provides clear intent, details implementation across six areas (supply chain, vite plugin, CodeQL tests, Dependabot, Actions, chore), includes verification steps, and post-merge guidance.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/vite-plugin/src/virtuals/single-project.ts (1)

5-16: ⚠️ Potential issue | 🟠 Major

Use SAFE_PROJECT_ID_REGEX to validate id before splicing into import specifiers.

Lines 8 and 15 splice id directly into static from specifiers without validation. Unlike icons.ts which validates project IDs with SAFE_PROJECT_ID_REGEX, single-project.ts has no guard. If id contains a quote or special character, the generated module breaks.

The proposed fix above is syntactically invalid—static import specifiers cannot accept expressions (e.g., from ${variable} is not valid JavaScript). Valid solutions:

  1. Apply SAFE_PROJECT_ID_REGEX validation to id before calling code(), matching the pattern used in icons.ts.
  2. Use dynamic imports via import() for the module specifiers (already used in _shared.ts for combined projects).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vite-plugin/src/virtuals/single-project.ts` around lines 5 - 16, The
template builder code(id: string) inserts id directly into static import
specifiers (export ... from 'likec4:icons/${id}' and 'likec4:model/${id}') which
can break if id contains unsafe characters; update code() to validate id against
SAFE_PROJECT_ID_REGEX (same check used in icons.ts) and throw or reject when it
doesn't match before creating projectIdLiteral with
hardenJsonStringLiteralForEmbeddedScript, ensuring only safe IDs are spliced
into the static specifiers; do not attempt to convert the specifiers to
expressions (static imports must remain string literals) — alternatively, if
dynamic loading is desired, switch these to dynamic import() calls elsewhere
rather than using template-spliced static imports.
🧹 Nitpick comments (2)
.github/workflows/codeql.yml (1)

23-24: Consider whether hardcoding a specific contributor fork is the right long-term approach.

The condition now allows sraphaz/likec4 in addition to likec4/likec4. While this is useful for the current contributor to validate CodeQL fixes, hardcoded fork names can become stale if the contributor leaves or renames their fork.

An alternative pattern is to allow any fork when triggered via workflow_dispatch while restricting scheduled runs to the upstream repo:

if: ${{ github.repository == 'likec4/likec4' || github.event_name == 'workflow_dispatch' }}

This is optional—keeping the explicit fork is fine if coordinated with the contributor.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/codeql.yml around lines 23 - 24, The workflow hardcodes a
single contributor fork in the if conditional (the line checking
github.repository), which can become stale; update the conditional in
.github/workflows/codeql.yml (the if that currently checks github.repository) to
stop listing a specific fork and instead allow runs when the repo is the
upstream (github.repository == likec4/likec4) OR when the workflow was manually
triggered (github.event_name == workflow_dispatch) so contributors can use
workflow_dispatch without hardcoding forks.
packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.spec.ts (1)

52-57: Consider adding a test for objects/arrays containing special characters.

The current test verifies that objects/arrays are accepted, but doesn't verify that special characters within them are escaped correctly.

🧪 Optional test case to strengthen coverage
   it('accepts JSON.stringify of object and array roots', () => {
     const obj = JSON.stringify({ a: 1 })
     expect(hardenJsonStringLiteralForEmbeddedScript(obj)).toBe(obj)
     const arr = JSON.stringify([1, 2])
     expect(hardenJsonStringLiteralForEmbeddedScript(arr)).toBe(arr)
   })
+
+  it('escapes special characters inside object values', () => {
+    const obj = JSON.stringify({ url: 'http://example.com/<path>' })
+    const out = hardenJsonStringLiteralForEmbeddedScript(obj)
+    expect(out).toContain('\\u002F')
+    expect(out).toContain('\\u003C')
+    expect(out).toContain('\\u003E')
+    expect(JSON.parse(out)).toEqual({ url: 'http://example.com/<path>' })
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.spec.ts`
around lines 52 - 57, Add unit tests for
hardenJsonStringLiteralForEmbeddedScript that serialize objects and arrays
containing special characters (e.g., HTML-sensitive sequences like "</" and
"<!--", line/paragraph separators \u2028 and \u2029, quotes and backslashes) and
assert the returned string is the hardened/escaped form expected; include both
an object and an array case, and check the specific escapes (e.g., "</" ->
"<\/", \u2028/\u2029 properly escaped) to ensure embedded-script safety for
these characters.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.ts`:
- Around line 24-40: The hardening pass only updated one place; other virtual
modules (puml, dot, previewsSources) still interpolate raw JSON.stringify(...)
output into generated JS, leaving the same sink; locate where those modules
embed JSON.stringify(...) output and wrap the stringified value with
hardenJsonStringLiteralForEmbeddedScript(...) (the function exported as
hardenJsonStringLiteralForEmbeddedScript) and ensure you import that function
into each module, replacing direct .replace hacks or raw interpolation with the
hardened wrapper so all embedded JSON literals are escaped consistently.
- Around line 9-22: The function looksLikeJsonStringifyOutput currently uses
prefix heuristics that let malformed JSON through; replace that logic with a
real JSON parse check: ensure the input is a non-empty string, then attempt
JSON.parse(s) inside a try/catch and return true only if parsing succeeds
(return false on any exception); update the function body for
looksLikeJsonStringifyOutput to use this JSON.parse-based validation so only
valid JSON values (objects, arrays, strings, numbers, true/false/null) pass the
guard.

---

Outside diff comments:
In `@packages/vite-plugin/src/virtuals/single-project.ts`:
- Around line 5-16: The template builder code(id: string) inserts id directly
into static import specifiers (export ... from 'likec4:icons/${id}' and
'likec4:model/${id}') which can break if id contains unsafe characters; update
code() to validate id against SAFE_PROJECT_ID_REGEX (same check used in
icons.ts) and throw or reject when it doesn't match before creating
projectIdLiteral with hardenJsonStringLiteralForEmbeddedScript, ensuring only
safe IDs are spliced into the static specifiers; do not attempt to convert the
specifiers to expressions (static imports must remain string literals) —
alternatively, if dynamic loading is desired, switch these to dynamic import()
calls elsewhere rather than using template-spliced static imports.

---

Nitpick comments:
In @.github/workflows/codeql.yml:
- Around line 23-24: The workflow hardcodes a single contributor fork in the if
conditional (the line checking github.repository), which can become stale;
update the conditional in .github/workflows/codeql.yml (the if that currently
checks github.repository) to stop listing a specific fork and instead allow runs
when the repo is the upstream (github.repository == likec4/likec4) OR when the
workflow was manually triggered (github.event_name == workflow_dispatch) so
contributors can use workflow_dispatch without hardcoding forks.

In
`@packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.spec.ts`:
- Around line 52-57: Add unit tests for hardenJsonStringLiteralForEmbeddedScript
that serialize objects and arrays containing special characters (e.g.,
HTML-sensitive sequences like "</" and "<!--", line/paragraph separators \u2028
and \u2029, quotes and backslashes) and assert the returned string is the
hardened/escaped form expected; include both an object and an array case, and
check the specific escapes (e.g., "</" -> "<\/", \u2028/\u2029 properly escaped)
to ensure embedded-script safety for these characters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab039477-a284-48d8-9f10-a8f63fafcad5

📥 Commits

Reviewing files that changed from the base of the PR and between 0a4af22 and f8d9430.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/security-icons-codeql-lockfile.md
  • .github/workflows/checks.yaml
  • .github/workflows/ci-pr.yaml
  • .github/workflows/codeql.yml
  • .github/workflows/issue-comment.yaml
  • .github/workflows/pkg-pr-new.yaml
  • .github/workflows/push.yaml
  • .github/workflows/trigger-deploy-template.yaml
  • .gitignore
  • package.json
  • packages/core/src/utils/markdown/to-html.spec.ts
  • packages/language-server/src/module.ts
  • packages/vite-plugin/src/virtuals/_shared.ts
  • packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.spec.ts
  • packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.ts
  • packages/vite-plugin/src/virtuals/icons.ts
  • packages/vite-plugin/src/virtuals/mmd.ts
  • packages/vite-plugin/src/virtuals/single-project.ts
  • packages/vscode/package.json

Comment thread packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.ts Outdated
Comment thread packages/vite-plugin/src/virtuals/hardenJsonStringLiteralForEmbeddedScript.ts Outdated
- CodeQL: scheduled runs only on upstream; workflow_dispatch allowed on any fork

- Validate JSON.stringify output with JSON.parse in harden helper

- Harden puml, dot, and previewsSources embedded literals

- Add tests for malformed pseudo-JSON inputs

Made-with: Cursor
@sraphaz

sraphaz commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator Author

@chatgpt-codex-connector Addressed: Both workflows that invoke the reusable checks.yaml declare permissions with contents: read and actions: write, so the GITHUB_TOKEN from the caller does not cap the called workflow below what artifact upload/download steps need.

  • .github/workflows/ci-pr.yamlpermissions block (approx. lines 25–28) plus an inline comment explaining the caller permission ceiling.
  • .github/workflows/push.yaml — same (approx. lines 20–23).

This matches the GitHub rule that a called workflow cannot raise permissions above the caller.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@davydkov

davydkov commented Apr 3, 2026

Copy link
Copy Markdown
Member

Nice one! Thank you!

@davydkov davydkov merged commit b442a71 into likec4:main Apr 3, 2026
15 checks passed
@likec4-ci likec4-ci Bot mentioned this pull request Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants