Skip to content

Conversation

@leaanthony
Copy link
Member

Summary

Implements mouse enter/leave events for windows on all three platforms (Windows, macOS, Linux), enabling tray popup windows to auto-focus when the mouse enters them.

  • Added WindowMouseEnter and WindowMouseLeave common events
  • Implemented FocusOnMouseEnter window option for automatic focus on mouse hover
  • Added cross-platform mouse tracking that works even when windows are not focused
  • Created mouse-hover example demonstrating the feature
  • Updated event generation system to include standard Wails header comments
  • Documented event generation process in AGENTS.md

Platform Implementation Details

  • Windows: Uses WM_MOUSEMOVE with TrackMouseEvent API and WM_MOUSELEAVE
  • macOS: Uses NSTrackingArea with NSTrackingActiveAlways flag via a MouseTrackingView overlay
  • Linux: Uses GTK enter-notify-event and leave-notify-event signals

Closes #4887

Test plan

  • Test mouse-hover example on Windows
  • Test mouse-hover example on macOS
  • Test mouse-hover example on Linux
  • Verify events fire when window is not focused
  • Verify FocusOnMouseEnter option works correctly
  • Test with tray popup window use case

🤖 Generated with Claude Code

I've implemented mouse enter/leave events for Wails v3 windows across all three platforms. Here's what was done:

### 1. New Events Added (`v3/pkg/events/events.go`)
- `WindowMouseEnter` (ID 1259) - fired when mouse enters a window
- `WindowMouseLeave` (ID 1260) - fired when mouse leaves a window

### 2. Platform Implementations

**Windows** (`webview_window_windows.go`):
- Uses `WM_MOUSEMOVE` with `TrackMouseEvent` API to start tracking
- Uses `WM_MOUSELEAVE` to detect when mouse exits
- Works even when window is not focused

**macOS** (`webview_window_darwin.m`):
- Created `MouseTrackingView` class with `NSTrackingArea`
- Uses `NSTrackingActiveAlways` flag to track mouse even when unfocused
- Implements `mouseEntered:` and `mouseExited:` handlers
- View is transparent to clicks (passes through to underlying content)

**Linux** (`linux_cgo.go`):
- Added `GDK_ENTER_NOTIFY_MASK` and `GDK_LEAVE_NOTIFY_MASK` event masks
- Connected `enter-notify-event` and `leave-notify-event` GTK signals
- Filters out `GDK_NOTIFY_INFERIOR` events to avoid spurious triggers

### 3. New Window Option (`webview_window_options.go`)
- `FocusOnMouseEnter bool` - When true, automatically focuses the window when mouse enters

### 4. Example (`v3/examples/mouse-hover/`)
- Demonstrates both mouse events and the `FocusOnMouseEnter` option
- Shows events in the UI and logs them to console
- Includes a README explaining the use cases

### 5. Documentation
- Updated `UNRELEASED_CHANGELOG.md` with the new features

### Answers to Your Questions

1. **Would this method work?** Yes, using mouse enter events to focus a window is a viable approach, especially for tray popup windows.

2. **Do windows get mouse enter events when not focused?** Yes, on all platforms:
   - Windows: Uses `TrackMouseEvent` which works regardless of focus
   - macOS: Uses `NSTrackingActiveAlways` flag
   - Linux: GTK's enter/leave events work on unfocused windows

3. **Implementation difficulty:**
   - **Windows**: Easiest - existing `TrackMouseEvent` API
   - **macOS**: Moderate - required creating tracking view with `NSTrackingArea`
   - **Linux**: Straightforward - just adding GTK event masks and signal handlers
…seEnter", ...)` which uses the event string directly. The `wails.Events.Types.Common.WindowMouseEnter` is also valid (it just returns the string `"common:WindowMouseEnter"`), but using the string directly is simpler and more commonly seen in the examples.
## Summary

1. **Removed the `mouse-hover` binary** from the branch (it was accidentally committed)

2. **Fixed event generation** - Events are now properly added via `events.txt`:
   - Added `common:WindowMouseEnter` and `common:WindowMouseLeave` to `v3/pkg/events/events.txt`
   - Ran the generator to regenerate all event files

3. **Added standard Wails generated code headers** to the generator templates in `v3/tasks/events/generate.go`:
   - `events.go` now includes the Wails logo and English/Welsh comments
   - `events_darwin.h`, `events_linux.h`, `events_ios.h` all include the headers
   - `event_types.ts` already had the headers

4. **Updated AGENTS.md** with comprehensive documentation about the event system:
   - Which files are generated (and should not be edited manually)
   - How to add new events properly
   - The generator commands to run
   - Event naming conventions
   - Example of adding new common events
   - The standard header format

5. **Rebuilt the JavaScript runtime** with the new events

The changes are now ready for review. The key files modified:
- `AGENTS.md` - Added event system documentation
- `v3/pkg/events/events.txt` - Added new events
- `v3/tasks/events/generate.go` - Added Wails headers to templates
- Generated files: `events.go`, `events_darwin.h`, `events_linux.h`, `events_ios.h`, `event_types.ts`
…rn used elsewhere in the Wails codebase. Both are technically correct for MRC, but `autorelease` is the pattern used throughout the rest of the code for objects that get passed to another owner (like a superview).
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 21, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

Comment on lines 46 to 92
name: Detect v2 Changes
runs-on: ubuntu-latest
needs: check-permissions
if: needs.check-permissions.outputs.authorized == 'true'
outputs:
has_changes: ${{ steps.changes.outputs.has_changes }}
commits_since_last: ${{ steps.changes.outputs.commits_since_last }}
last_release_tag: ${{ steps.changes.outputs.last_release_tag }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Check for v2 changes since last release
id: changes
run: |
echo "🔍 Checking for v2 changes since last release..."

# Find the last v2 release tag
LAST_TAG=$(git tag -l "v2.*" --sort=-version:refname | head -n 1)
if [ -z "$LAST_TAG" ]; then
echo "No previous v2 tags found, assuming first release"
LAST_TAG=$(git rev-list --max-parents=0 HEAD)
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "commits_since_last=999" >> $GITHUB_OUTPUT
echo "last_release_tag=none" >> $GITHUB_OUTPUT
else
echo "Last v2 release tag: $LAST_TAG"
echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT

# Count commits since last release affecting v2 or root files
COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v2/ website/ README.md CHANGELOG.md || echo "0")
echo "Commits since last v2 release: $COMMITS_COUNT"
echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT

if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then
echo "✅ Changes detected or forced release"
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "ℹ️ No changes detected since last release"
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
fi

detect-v3-changes:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 11 days ago

In general, the fix is to explicitly set a permissions block so that the GITHUB_TOKEN has only the minimal rights required. For read-only analysis jobs like detect-v2-changes and detect-v3-changes, contents: read is sufficient. Other jobs that create releases or tags may need contents: write and possibly packages: write or similar, but we should only grant those to the specific jobs that perform write operations, not globally.

The best fix here without changing functionality is to add a workflow‑level permissions block near the top of .github/workflows/automated-releases.yml, setting contents: read as the default for all jobs. The existing check-permissions job already declares permissions: {}, so it will remain with no token permissions, which is safe. Jobs that need elevated rights (e.g., release-v2, release-v3-alpha, or similar later in the file) can override the default in their own permissions section; since their code is not shown, we won’t touch them. This change satisfies CodeQL’s requirement for explicit permissions, limits the default GITHUB_TOKEN to read-only, and does not affect the existing behavior of the detection jobs, which only read git history and do not push changes.

Concretely:

  • Edit .github/workflows/automated-releases.yml.
  • Insert a permissions: block after the name: (or after the on: block, either is valid) that sets contents: read.
  • Leave all existing job definitions unchanged, including check-permissions: permissions: {}.
Suggested changeset 1
.github/workflows/automated-releases.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml
--- a/.github/workflows/automated-releases.yml
+++ b/.github/workflows/automated-releases.yml
@@ -1,5 +1,8 @@
 name: Automated Nightly Releases
 
+permissions:
+  contents: read
+
 on:
   workflow_dispatch:
     inputs:
EOF
@@ -1,5 +1,8 @@
name: Automated Nightly Releases

permissions:
contents: read

on:
workflow_dispatch:
inputs:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 93 to 140
name: Detect v3-alpha Changes
runs-on: ubuntu-latest
needs: check-permissions
if: needs.check-permissions.outputs.authorized == 'true'
outputs:
has_changes: ${{ steps.changes.outputs.has_changes }}
commits_since_last: ${{ steps.changes.outputs.commits_since_last }}
last_release_tag: ${{ steps.changes.outputs.last_release_tag }}
steps:
- name: Checkout v3-alpha branch
uses: actions/checkout@v4
with:
ref: v3-alpha
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Check for v3-alpha changes since last release
id: changes
run: |
echo "🔍 Checking for v3-alpha changes since last release..."

# Find the last v3 alpha release tag
LAST_TAG=$(git tag -l "v3.*-alpha.*" --sort=-version:refname | head -n 1)
if [ -z "$LAST_TAG" ]; then
echo "No previous v3-alpha tags found, assuming first release"
LAST_TAG=$(git rev-list --max-parents=0 HEAD)
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "commits_since_last=999" >> $GITHUB_OUTPUT
echo "last_release_tag=none" >> $GITHUB_OUTPUT
else
echo "Last v3-alpha release tag: $LAST_TAG"
echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT

# Count commits since last release affecting v3 or docs
COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v3/ docs/ || echo "0")
echo "Commits since last v3-alpha release: $COMMITS_COUNT"
echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT

if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then
echo "✅ Changes detected or forced release"
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "ℹ️ No changes detected since last release"
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
fi

release-v2:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 11 days ago

In general, the problem is fixed by explicitly declaring a permissions block to limit the GITHUB_TOKEN to the minimal scopes required. This can be done at the workflow level (applies to all jobs) and overridden per job where more permissions are needed. For jobs that only read the repository (like detect-v2-changes and detect-v3-changes), contents: read is sufficient. For jobs that create or update releases/tags, you typically need contents: write (and potentially pull-requests: write or others if they modify PRs).

The best way to fix this specific workflow without changing functionality is:

  • Add a workflow-level permissions: block setting a secure default, e.g. contents: read.
  • Keep check-permissions’s existing permissions: {} (equivalent to no permissions) since it only runs shell code and uses github.actor from the event context.
  • For release jobs that actually perform writes (e.g., release-v2 and release-v3), add per-job permissions: blocks that grant contents: write. This ensures detect-only jobs like detect-v3-changes (the one flagged by CodeQL) inherit only contents: read, satisfying the analyzer’s recommendation while preserving existing behavior.

Concretely:

  • In .github/workflows/automated-releases.yml, above the env: block (after on:), insert a workflow-level permissions: block with contents: read.
  • In the same file, add permissions: blocks to release-v2 and release-v3 jobs (once you reach their job definitions) granting contents: write. The snippet for release-v3 is elided, but you can still add the block at its job root. No new imports or external dependencies are required.
Suggested changeset 1
.github/workflows/automated-releases.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml
--- a/.github/workflows/automated-releases.yml
+++ b/.github/workflows/automated-releases.yml
@@ -17,6 +17,9 @@
     # Run at 2 AM UTC every day - DISABLED for safety until ready
     # - cron: '0 2 * * *'
 
+permissions:
+  contents: read
+
 env:
   GO_VERSION: '1.24'
 
@@ -144,6 +147,8 @@
     if: |
       needs.check-permissions.outputs.authorized == 'true' &&
       needs.detect-v2-changes.outputs.has_changes == 'true'
+    permissions:
+      contents: write
     outputs:
       version: ${{ steps.release.outputs.version }}
       release_notes: ${{ steps.release.outputs.release_notes }}
@@ -230,6 +235,8 @@
           cat release_notes_v2.md
 
   release-v3:
+    permissions:
+      contents: write
     name: Create v3-alpha Release
     runs-on: ubuntu-latest
     needs: [check-permissions, detect-v3-changes]
EOF
@@ -17,6 +17,9 @@
# Run at 2 AM UTC every day - DISABLED for safety until ready
# - cron: '0 2 * * *'

permissions:
contents: read

env:
GO_VERSION: '1.24'

@@ -144,6 +147,8 @@
if: |
needs.check-permissions.outputs.authorized == 'true' &&
needs.detect-v2-changes.outputs.has_changes == 'true'
permissions:
contents: write
outputs:
version: ${{ steps.release.outputs.version }}
release_notes: ${{ steps.release.outputs.release_notes }}
@@ -230,6 +235,8 @@
cat release_notes_v2.md

release-v3:
permissions:
contents: write
name: Create v3-alpha Release
runs-on: ubuntu-latest
needs: [check-permissions, detect-v3-changes]
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 141 to 232
name: Create v2 Release
runs-on: ubuntu-latest
needs: [check-permissions, detect-v2-changes]
if: |
needs.check-permissions.outputs.authorized == 'true' &&
needs.detect-v2-changes.outputs.has_changes == 'true'
outputs:
version: ${{ steps.release.outputs.version }}
release_notes: ${{ steps.release.outputs.release_notes }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}

- name: Run v2 release script and extract notes
id: release
run: |
echo "🚀 Running v2 release script..."
cd v2/tools/release

# Run release script and capture output
RELEASE_OUTPUT=$(go run release.go 2>&1)
echo "$RELEASE_OUTPUT"

# Extract version from output or version file
NEW_VERSION=$(cat ../../cmd/wails/internal/version.txt)
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT

# Extract release notes from delimited output
RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d')

# Save release notes to file for multiline output
echo "$RELEASE_NOTES" > ../../../release_notes_v2.md

# Set output (escape for GitHub Actions)
{
echo "release_notes<<EOF"
echo "$RELEASE_NOTES"
echo "EOF"
} >> $GITHUB_OUTPUT

echo "✅ v2 release script completed - version: $NEW_VERSION"

- name: Create v2 git tag and release
if: github.event.inputs.dry_run != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.release.outputs.version }}"
echo "📝 Creating v2 release: $VERSION"

# Configure git
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

# Commit the changelog changes
git add website/src/pages/changelog.mdx v2/cmd/wails/internal/version.txt
git commit -m "chore: release $VERSION

Automated release created by GitHub Actions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>"

# Create and push tag
git tag -a "$VERSION" -m "Release $VERSION"
git push origin master
git push origin "$VERSION"

# Create GitHub release with notes
gh release create "$VERSION" \
--title "Release $VERSION" \
--notes-file release_notes_v2.md \
--target master

- name: Log dry-run results for v2
if: github.event.inputs.dry_run == 'true'
run: |
echo "🧪 DRY RUN - Would have created v2 release:"
echo "Version: ${{ steps.release.outputs.version }}"
echo "Release Notes:"
cat release_notes_v2.md

release-v3:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 11 days ago

In general, the fix is to add explicit permissions: blocks to the workflow so that each job’s GITHUB_TOKEN has the minimum scopes it needs. Jobs that only check out code and run read-only checks can typically use contents: read. Jobs that push commits/tags or create releases must request contents: write. This makes the workflow’s security posture explicit and satisfies the CodeQL rule.

For this workflow, the minimal, non-breaking adjustment is:

  • Add a workflow-level permissions: contents: read to give all jobs read-only access by default.
  • Override that default for the release-v2 and release-v3 jobs, which actually perform write operations using GITHUB_TOKEN, by adding permissions: contents: write to those jobs.
  • Jobs like check-permissions, detect-v2-changes, and detect-v3-changes only need to read repository contents and metadata, so they can safely use the inherited contents: read.

Concretely:

  • At the top of .github/workflows/automated-releases.yml, right after name: Automated Nightly Releases, insert a permissions: block with contents: read.
  • Within the release-v2: job definition (line 140 onward), add a permissions: block (e.g. between name: and runs-on:) setting contents: write.
  • Do the same for the release-v3: job definition.

No additional imports or external packages are needed; this is purely a YAML configuration change.

Suggested changeset 1
.github/workflows/automated-releases.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml
--- a/.github/workflows/automated-releases.yml
+++ b/.github/workflows/automated-releases.yml
@@ -1,4 +1,6 @@
 name: Automated Nightly Releases
+permissions:
+  contents: read
 
 on:
   workflow_dispatch:
@@ -139,6 +141,8 @@
 
   release-v2:
     name: Create v2 Release
+    permissions:
+      contents: write
     runs-on: ubuntu-latest
     needs: [check-permissions, detect-v2-changes]
     if: |
@@ -231,6 +235,8 @@
 
   release-v3:
     name: Create v3-alpha Release
+    permissions:
+      contents: write
     runs-on: ubuntu-latest
     needs: [check-permissions, detect-v3-changes]
     if: |
EOF
@@ -1,4 +1,6 @@
name: Automated Nightly Releases
permissions:
contents: read

on:
workflow_dispatch:
@@ -139,6 +141,8 @@

release-v2:
name: Create v2 Release
permissions:
contents: write
runs-on: ubuntu-latest
needs: [check-permissions, detect-v2-changes]
if: |
@@ -231,6 +235,8 @@

release-v3:
name: Create v3-alpha Release
permissions:
contents: write
runs-on: ubuntu-latest
needs: [check-permissions, detect-v3-changes]
if: |
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 233 to 326
name: Create v3-alpha Release
runs-on: ubuntu-latest
needs: [check-permissions, detect-v3-changes]
if: |
needs.check-permissions.outputs.authorized == 'true' &&
needs.detect-v3-changes.outputs.has_changes == 'true'
outputs:
version: ${{ steps.release.outputs.version }}
release_notes: ${{ steps.release.outputs.release_notes }}
steps:
- name: Checkout v3-alpha branch
uses: actions/checkout@v4
with:
ref: v3-alpha
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}

- name: Run v3 release script and extract notes
id: release
run: |
echo "🚀 Running v3-alpha release script..."
cd v3/tasks/release

# Run release script and capture output
RELEASE_OUTPUT=$(go run release.go 2>&1)
echo "$RELEASE_OUTPUT"

# Extract version from output or version file
NEW_VERSION=$(cat ../../internal/version/version.txt)
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT

# Extract release notes from delimited output
RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d')

# Save release notes to file for multiline output
echo "$RELEASE_NOTES" > ../../../release_notes_v3.md

# Set output (escape for GitHub Actions)
{
echo "release_notes<<EOF"
echo "$RELEASE_NOTES"
echo "EOF"
} >> $GITHUB_OUTPUT

echo "✅ v3-alpha release script completed - version: $NEW_VERSION"

- name: Create v3-alpha git tag and release
if: github.event.inputs.dry_run != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.release.outputs.version }}"
echo "📝 Creating v3-alpha release: $VERSION"

# Configure git
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

# Commit the changelog changes
git add docs/src/content/docs/changelog.mdx v3/internal/version/version.txt
git commit -m "chore: release $VERSION

Automated v3-alpha release created by GitHub Actions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>"

# Create and push tag
git tag -a "$VERSION" -m "Release $VERSION"
git push origin v3-alpha
git push origin "$VERSION"

# Create GitHub release with notes
gh release create "$VERSION" \
--title "Release $VERSION" \
--notes-file release_notes_v3.md \
--target v3-alpha \
--prerelease

- name: Log dry-run results for v3-alpha
if: github.event.inputs.dry_run == 'true'
run: |
echo "🧪 DRY RUN - Would have created v3-alpha release:"
echo "Version: ${{ steps.release.outputs.version }}"
echo "Release Notes:"
cat release_notes_v3.md

summary:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 11 days ago

To fix this, add explicit permissions blocks that grant only the scopes each job actually needs, instead of relying on implicit repository defaults. Since some jobs (like check-permissions) do not need the GITHUB_TOKEN at all, they can keep permissions: {}. Jobs that only read code (e.g., change-detection jobs, not fully shown) can be given contents: read. The release-v2 and release-v3 jobs, which commit, tag, push, and create releases, need contents: write and packages: write (for gh release create and any potential artifacts), plus pull-requests: write only if they actually modify PRs (not shown here, so we will not add it).

Concretely, in .github/workflows/automated-releases.yml, add a top-level permissions: block after name: that sets a safe default such as contents: read. Then, for the release-v2, release-v3, and summary jobs, add job-level permissions blocks tailored to their needs. release-v2 and release-v3 should get contents: write (they push commits/tags) and contents: read is implied by write; summary only writes to $GITHUB_STEP_SUMMARY, which does not require repo write scope, so it can inherit the default contents: read and does not need its own override. The CodeQL warning is specifically on release-v3, so we will explicitly add permissions to that job (and, for consistency, to release-v2 as well, since it performs similar operations). No new libraries or external dependencies are needed; this is purely a workflow YAML change.

Suggested changeset 1
.github/workflows/automated-releases.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml
--- a/.github/workflows/automated-releases.yml
+++ b/.github/workflows/automated-releases.yml
@@ -1,5 +1,8 @@
 name: Automated Nightly Releases
 
+permissions:
+  contents: read
+
 on:
   workflow_dispatch:
     inputs:
@@ -141,6 +144,8 @@
     name: Create v2 Release
     runs-on: ubuntu-latest
     needs: [check-permissions, detect-v2-changes]
+    permissions:
+      contents: write
     if: |
       needs.check-permissions.outputs.authorized == 'true' &&
       needs.detect-v2-changes.outputs.has_changes == 'true'
@@ -233,6 +238,8 @@
     name: Create v3-alpha Release
     runs-on: ubuntu-latest
     needs: [check-permissions, detect-v3-changes]
+    permissions:
+      contents: write
     if: |
       needs.check-permissions.outputs.authorized == 'true' &&
       needs.detect-v3-changes.outputs.has_changes == 'true'
EOF
@@ -1,5 +1,8 @@
name: Automated Nightly Releases

permissions:
contents: read

on:
workflow_dispatch:
inputs:
@@ -141,6 +144,8 @@
name: Create v2 Release
runs-on: ubuntu-latest
needs: [check-permissions, detect-v2-changes]
permissions:
contents: write
if: |
needs.check-permissions.outputs.authorized == 'true' &&
needs.detect-v2-changes.outputs.has_changes == 'true'
@@ -233,6 +238,8 @@
name: Create v3-alpha Release
runs-on: ubuntu-latest
needs: [check-permissions, detect-v3-changes]
permissions:
contents: write
if: |
needs.check-permissions.outputs.authorized == 'true' &&
needs.detect-v3-changes.outputs.has_changes == 'true'
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 327 to 373
name: Release Summary
runs-on: ubuntu-latest
needs: [check-permissions, detect-v2-changes, detect-v3-changes, release-v2, release-v3]
if: always() && needs.check-permissions.outputs.authorized == 'true'
steps:
- name: Create release summary
run: |
echo "# 🚀 Automated Release Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Repository**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY
echo "**Triggered by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
echo "**Dry Run Mode**: ${{ github.event.inputs.dry_run || 'false' }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

# v2 Summary
echo "## v2 Release" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.detect-v2-changes.outputs.has_changes }}" == "true" ]; then
if [ "${{ needs.release-v2.result }}" == "success" ]; then
echo "✅ **v2 Release**: Created successfully" >> $GITHUB_STEP_SUMMARY
echo " - Version: ${{ needs.release-v2.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **v2 Release**: Failed" >> $GITHUB_STEP_SUMMARY
fi
else
echo "⏭️ **v2 Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY
echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
fi

# v3 Summary
echo "## v3-alpha Release" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.detect-v3-changes.outputs.has_changes }}" == "true" ]; then
if [ "${{ needs.release-v3.result }}" == "success" ]; then
echo "✅ **v3-alpha Release**: Created successfully" >> $GITHUB_STEP_SUMMARY
echo " - Version: ${{ needs.release-v3.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **v3-alpha Release**: Failed" >> $GITHUB_STEP_SUMMARY
fi
else
echo "⏭️ **v3-alpha Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY
echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
fi

echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "🤖 **Automated Release System** | Generated with [Claude Code](https://claude.ai/code)" >> $GITHUB_STEP_SUMMARY No newline at end of file

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 11 days ago

In general, to fix this class of issue, add an explicit permissions block either at the workflow root (to affect all jobs) or on the specific job that doesn’t need broad access. The block should grant the minimum required privileges for what that job actually does. When a job only writes to the step summary or uses generic shell commands without authenticated GitHub operations, it can safely run with permissions: {} (no API permissions granted to GITHUB_TOKEN).

For this workflow, the minimal, non-disruptive change is to add a job-level permissions block to the summary job (starting at line 327). The summary job only writes to $GITHUB_STEP_SUMMARY and reads needs.* and github.* context variables, which do not require any GitHub API scopes. Therefore we can set permissions: {} for this job, mirroring the existing check-permissions job. Concretely, in .github/workflows/automated-releases.yml, under summary: (line 327), insert a permissions: {} line (properly indented) between name: Release Summary and runs-on: ubuntu-latest. No imports or additional steps are required.

Suggested changeset 1
.github/workflows/automated-releases.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml
--- a/.github/workflows/automated-releases.yml
+++ b/.github/workflows/automated-releases.yml
@@ -325,6 +325,7 @@
 
   summary:
     name: Release Summary
+    permissions: {}
     runs-on: ubuntu-latest
     needs: [check-permissions, detect-v2-changes, detect-v3-changes, release-v2, release-v3]
     if: always() && needs.check-permissions.outputs.authorized == 'true'
EOF
@@ -325,6 +325,7 @@

summary:
name: Release Summary
permissions: {}
runs-on: ubuntu-latest
needs: [check-permissions, detect-v2-changes, detect-v3-changes, release-v2, release-v3]
if: always() && needs.check-permissions.outputs.authorized == 'true'
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 174 to 186
name: Cleanup build artifacts
if: always()
needs: [test_js, test_go, test_templates]
runs-on: ubuntu-latest
steps:
- uses: geekyeggo/delete-artifact@v5
with:
name: |
runtime-build-artifacts
runtime-package
failOnError: false

test_templates:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 11 days ago

In general, the problem is fixed by explicitly specifying a permissions: block either at the workflow root (applies to all jobs without their own permissions) or per job, assigning only the minimal privileges needed. For CI workflows that primarily run tests and manage artifacts, typical minimal permissions are contents: read and actions: write (for artifact operations). If no job needs to write to repository contents, issues, or PRs, those should be left at default (implicit none) or explicitly set to read/none.

For this specific workflow, the simplest and least disruptive fix is to define a workflow‑level permissions: block directly under the name: (before on:), granting contents: read so jobs can check out code, and actions: write so the cleanup job (and any others) can manage artifacts via the GitHub Actions API. No job appears to require write access to repository contents, issues, or pull requests, so we do not grant any extra scopes. This preserves existing functionality (checkout, artifact deletion) while constraining the GITHUB_TOKEN. Concretely, you would edit .github/workflows/build-and-test-v3.yml to insert:

permissions:
  contents: read
  actions: write

after the first line (name: Build + Test v3) and before the on: block. No additional imports or methods are required because this is purely a YAML configuration change.

Suggested changeset 1
.github/workflows/build-and-test-v3.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml
--- a/.github/workflows/build-and-test-v3.yml
+++ b/.github/workflows/build-and-test-v3.yml
@@ -1,5 +1,9 @@
 name: Build + Test v3
 
+permissions:
+  contents: read
+  actions: write
+
 on:
   pull_request:
     types: [opened, synchronize, reopened, ready_for_review]
EOF
@@ -1,5 +1,9 @@
name: Build + Test v3

permissions:
contents: read
actions: write

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 183 to 201
runtime-package
failOnError: false

test_templates:
name: Test Templates
needs: [test_js, test_go]
runs-on: ${{ matrix.os }}
if: github.base_ref == 'v3-alpha'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
template:
- svelte
- svelte-ts
- vue
- vue-ts
- react
- react-ts

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 11 days ago

In general, the fix is to add a permissions: block that explicitly scopes the GITHUB_TOKEN to the minimal set of permissions required. This is best done at the workflow root so that it applies to all jobs, unless specific jobs need different permissions. For this workflow, none of the shown jobs push commits, create releases, or manage issues/PRs; they primarily check approval state, build, test, and manipulate artifacts. Those activities only require read access to repository contents and (if needed) the default token access to artifacts, which is governed by actions and contents permissions. A safe and minimal configuration is to set contents: read at the workflow root. If any job later needs stronger permissions, it can override at the job level.

The single best fix without changing functionality is therefore: add a root���level permissions: block after the name: (or after on: if you prefer) with contents: read. This keeps all current behavior while ensuring the GITHUB_TOKEN is not accidentally granted write access. Concretely, in .github/workflows/build-and-test-v3.yml, insert:

permissions:
  contents: read

aligned at the root indentation, just after line 2 (the blank line after the workflow name). No additional imports or methods are needed, as this is a pure YAML configuration change.

Suggested changeset 1
.github/workflows/build-and-test-v3.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml
--- a/.github/workflows/build-and-test-v3.yml
+++ b/.github/workflows/build-and-test-v3.yml
@@ -1,5 +1,8 @@
 name: Build + Test v3
 
+permissions:
+  contents: read
+
 on:
   pull_request:
     types: [opened, synchronize, reopened, ready_for_review]
EOF
@@ -1,5 +1,8 @@
name: Build + Test v3

permissions:
contents: read

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 187 to 263
name: Test Templates
needs: [test_js, test_go]
runs-on: ${{ matrix.os }}
if: github.base_ref == 'v3-alpha'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
template:
- svelte
- svelte-ts
- vue
- vue-ts
- react
- react-ts
- preact
- preact-ts
- lit
- lit-ts
- vanilla
- vanilla-ts
go-version: [1.24]
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install linux dependencies
uses: awalsh128/cache-apt-pkgs-action@latest
if: matrix.os == 'ubuntu-latest'
with:
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
version: 1.0

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
cache-dependency-path: "v3/go.sum"

- name: Install Task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Build Wails3 CLI
working-directory: v3
run: |
task install
wails3 doctor

- name: Download runtime package
uses: actions/download-artifact@v4
with:
name: runtime-package
path: wails-runtime-temp

- name: Generate template '${{ matrix.template }}'
shell: bash
run: |
# Get absolute path - use pwd -W on Windows for native paths, pwd elsewhere
if [[ "$RUNNER_OS" == "Windows" ]]; then
RUNTIME_TGZ="$(cd wails-runtime-temp && pwd -W)/$(ls wails-runtime-temp/*.tgz | xargs basename)"
else
RUNTIME_TGZ="$(cd wails-runtime-temp && pwd)/$(ls wails-runtime-temp/*.tgz | xargs basename)"
fi
mkdir -p ./test-${{ matrix.template }}
cd ./test-${{ matrix.template }}
wails3 init -n ${{ matrix.template }} -t ${{ matrix.template }}
cd ${{ matrix.template }}/frontend
# Replace @wailsio/runtime version with local tarball
npm pkg set dependencies.@wailsio/runtime="file://$RUNTIME_TGZ"
cd ..
wails3 build

build_results:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 11 days ago

In general, this should be fixed by explicitly declaring a permissions block either at the workflow root (applies to all jobs) or per job, with the least privileges required. Since test_templates only needs to read the repository contents and artifacts and does not appear to need write access to anything, giving it contents: read is sufficient. To avoid affecting other jobs which are not shown and might legitimately need broader permissions, we will add a job-level permissions block for test_templates only.

Concretely, in .github/workflows/build-and-test-v3.yml, under the test_templates: job definition (around line 187), insert a permissions: section between needs: [test_js, test_go] and runs-on: ${{ matrix.os }}. The block will specify contents: read. No imports or additional definitions are needed, as this is purely YAML configuration for GitHub Actions.

Suggested changeset 1
.github/workflows/build-and-test-v3.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/build-and-test-v3.yml b/.github/workflows/build-and-test-v3.yml
--- a/.github/workflows/build-and-test-v3.yml
+++ b/.github/workflows/build-and-test-v3.yml
@@ -186,6 +186,8 @@
   test_templates:
     name: Test Templates
     needs: [test_js, test_go]
+    permissions:
+      contents: read
     runs-on: ${{ matrix.os }}
     if: github.base_ref == 'v3-alpha'
     strategy:
EOF
@@ -186,6 +186,8 @@
test_templates:
name: Test Templates
needs: [test_js, test_go]
permissions:
contents: read
runs-on: ${{ matrix.os }}
if: github.base_ref == 'v3-alpha'
strategy:
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 12 to 46
name: Detect committed changes
if: github.event_name != 'workflow_dispatch'
outputs:
changed: ${{ steps.package-json-changes.outputs.any_modified == 'true' || steps.source-changes.outputs.any_modified == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
persist-credentials: 'true'

- name: Detect committed package.json changes
id: package-json-changes
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
with:
files: |
v3/internal/runtime/desktop/@wailsio/runtime/package.json
v3/internal/runtime/desktop/@wailsio/runtime/package-lock.json

- name: Detect committed source changes
if: >-
steps.package-json-changes.outputs.any_modified != 'true'
id: source-changes
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
with:
files: |
v3/internal/runtime/Taskfile.yaml
v3/internal/runtime/desktop/@wailsio/compiled/main.js
v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json
v3/internal/runtime/desktop/@wailsio/runtime/src/**
v3/pkg/events/events.txt
v3/tasks/events/**

rebuild_and_publish:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 11 days ago

To fix the problem, explicitly declare restricted permissions for the detect job so that the GITHUB_TOKEN cannot perform unnecessary write operations. The job only needs to read repository contents to check out code and inspect changed files, so contents: read is sufficient.

The best targeted fix, without changing existing functionality, is:

  • Add a permissions: section under the detect job, parallel to name, if, outputs, and runs-on.
  • Set contents: read as a minimal scope, matching CodeQL’s suggested baseline.

Concretely, in .github/workflows/publish-npm.yml, between the outputs: block (line 14–15) and runs-on: ubuntu-latest (line 16), insert:

    permissions:
      contents: read

No additional imports or methods are needed because this is a workflow configuration change only.

Suggested changeset 1
.github/workflows/publish-npm.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml
--- a/.github/workflows/publish-npm.yml
+++ b/.github/workflows/publish-npm.yml
@@ -13,6 +13,8 @@
     if: github.event_name != 'workflow_dispatch'
     outputs:
       changed: ${{ steps.package-json-changes.outputs.any_modified == 'true' || steps.source-changes.outputs.any_modified == 'true' }}
+    permissions:
+      contents: read
     runs-on: ubuntu-latest
     steps:
       - name: Checkout code
EOF
@@ -13,6 +13,8 @@
if: github.event_name != 'workflow_dispatch'
outputs:
changed: ${{ steps.package-json-changes.outputs.any_modified == 'true' || steps.source-changes.outputs.any_modified == 'true' }}
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- name: Checkout code
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines 8 to 11
runs-on: ubuntu-latest
steps:
- name: Test
run: echo "Hello World" No newline at end of file

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 11 days ago

To fix the problem, explicitly restrict the GITHUB_TOKEN permissions for this workflow or job. Since the job only prints a message and does not use the token at all, the safest and simplest approach is to disable the token entirely by setting permissions: { contents: none } or, more strictly, permissions: {} / permissions: none at the appropriate level, depending on desired clarity and compatibility.

The best way to fix this workflow without changing existing functionality is to add a permissions block at the workflow root (top level, alongside name and on). This will apply to all jobs in the workflow, including test. Because the job does not use GitHub APIs, we can safely set permissions: contents: read (a common minimal baseline) or fully disable access. To align with common recommendations while remaining conservative, we will set contents: read. Concretely, in .github/workflows/test-simple.yml, between the name: Test Simple and on: lines, insert:

permissions:
  contents: read

No additional methods, imports, or definitions are required; only this YAML edit is needed.

Suggested changeset 1
.github/workflows/test-simple.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/test-simple.yml b/.github/workflows/test-simple.yml
--- a/.github/workflows/test-simple.yml
+++ b/.github/workflows/test-simple.yml
@@ -1,5 +1,8 @@
 name: Test Simple
 
+permissions:
+  contents: read
+
 on:
   workflow_dispatch:
 
EOF
@@ -1,5 +1,8 @@
name: Test Simple

permissions:
contents: read

on:
workflow_dispatch:

Copilot is powered by AI and may make mistakes. Always verify output.
url := r.URL.Path
path := dir + "/assets" + url

if _, err := os.Stat(path); err == nil {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 11 days ago

In general, the fix is to ensure that any path derived from r.URL.Path is constrained to stay within the intended assets directory. That means: join the user path with the base directory via filepath.Join, normalize to an absolute path, and then verify that it still resides under the assets root before using it with os.Stat or http.ServeFile. If the check fails, we should not serve a file from disk and should instead fall back to the default asset handler.

Concretely, in v3/examples/screen/main.go inside the middleware, we should:

  1. Compute the base assets directory once: assetsDir := filepath.Join(dir, "assets").
  2. Use filepath.Join(assetsDir, url) to combine the base and the requested path rather than string concatenation.
  3. Resolve the result to an absolute path: absPath, err := filepath.Abs(path).
  4. Resolve the base directory to an absolute path once as well and check that absPath is within that directory, using a prefix check that accounts for path separators (e.g., by ensuring strings.HasPrefix(absPath, assetsDirAbs+string(os.PathSeparator)) || absPath == assetsDirAbs).
  5. Only if the normalization succeeds and the path is within the assets directory do we call os.Stat and http.ServeFile; otherwise, we should skip serving from disk and call next.ServeHTTP.

To implement this, we need to (a) adjust the existing dir, url, and path assignments, (b) introduce assetsDir and its absolute form, and (c) import the strings package to perform the prefix check. No other behavior of the application needs to change: valid asset paths will still be served from disk during development, and invalid or traversal attempts will be passed through to the bundled asset handler (or result in a normal 404 from that handler).

Suggested changeset 1
v3/examples/screen/main.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/v3/examples/screen/main.go b/v3/examples/screen/main.go
--- a/v3/examples/screen/main.go
+++ b/v3/examples/screen/main.go
@@ -8,6 +8,7 @@
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 
 	"github.com/wailsapp/wails/v3/pkg/application"
 )
@@ -44,12 +45,33 @@
 
 					_, filename, _, _ := runtime.Caller(0)
 					dir := filepath.Dir(filename)
+					assetsDir := filepath.Join(dir, "assets")
+					assetsDirAbs, err := filepath.Abs(assetsDir)
+					if err != nil {
+						// If we cannot determine the assets directory, fall back to default handler
+						next.ServeHTTP(w, r)
+						return
+					}
+
 					url := r.URL.Path
-					path := dir + "/assets" + url
+					path := filepath.Join(assetsDirAbs, url)
+					absPath, err := filepath.Abs(path)
+					if err != nil {
+						// On error resolving path, fall back to default handler
+						next.ServeHTTP(w, r)
+						return
+					}
 
-					if _, err := os.Stat(path); err == nil {
+					// Ensure the resolved path is within the assets directory
+					if !strings.HasPrefix(absPath, assetsDirAbs+string(os.PathSeparator)) && absPath != assetsDirAbs {
+						// Attempted access outside assets directory; fall back to default handler
+						next.ServeHTTP(w, r)
+						return
+					}
+
+					if _, err := os.Stat(absPath); err == nil {
 						// Serve file from disk to make testing easy
-						http.ServeFile(w, r, path)
+						http.ServeFile(w, r, absPath)
 					} else {
 						// Passthrough to the default asset handler if file not found on disk
 						next.ServeHTTP(w, r)
EOF
@@ -8,6 +8,7 @@
"os"
"path/filepath"
"runtime"
"strings"

"github.com/wailsapp/wails/v3/pkg/application"
)
@@ -44,12 +45,33 @@

_, filename, _, _ := runtime.Caller(0)
dir := filepath.Dir(filename)
assetsDir := filepath.Join(dir, "assets")
assetsDirAbs, err := filepath.Abs(assetsDir)
if err != nil {
// If we cannot determine the assets directory, fall back to default handler
next.ServeHTTP(w, r)
return
}

url := r.URL.Path
path := dir + "/assets" + url
path := filepath.Join(assetsDirAbs, url)
absPath, err := filepath.Abs(path)
if err != nil {
// On error resolving path, fall back to default handler
next.ServeHTTP(w, r)
return
}

if _, err := os.Stat(path); err == nil {
// Ensure the resolved path is within the assets directory
if !strings.HasPrefix(absPath, assetsDirAbs+string(os.PathSeparator)) && absPath != assetsDirAbs {
// Attempted access outside assets directory; fall back to default handler
next.ServeHTTP(w, r)
return
}

if _, err := os.Stat(absPath); err == nil {
// Serve file from disk to make testing easy
http.ServeFile(w, r, path)
http.ServeFile(w, r, absPath)
} else {
// Passthrough to the default asset handler if file not found on disk
next.ServeHTTP(w, r)
Copilot is powered by AI and may make mistakes. Always verify output.

if _, err := os.Stat(path); err == nil {
// Serve file from disk to make testing easy
http.ServeFile(w, r, path)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.

Copilot Autofix

AI 11 days ago

In general, the fix is to ensure that any path derived from user input is constrained to a safe directory. For this use-case, requests should only be able to access files inside the local assets directory. We should therefore (1) build paths using filepath.Join rather than raw string concatenation, (2) normalize the resulting path to an absolute path, and (3) verify that it still resides under the intended assets directory before using it with os.Stat or http.ServeFile. If the check fails, we should fall back to the existing bundled asset handler (next.ServeHTTP).

The best way to fix this without changing observable functionality is: compute an absolute, cleaned base path for assets once per request (assetsDir := filepath.Join(dir, "assets"), then assetsDirAbs := filepath.Clean(assetsDir)), then for each incoming URL path compute requestedPath := filepath.Join(assetsDirAbs, url) and requestedPathAbs := filepath.Clean(requestedPath). Reject or ignore the path if requestedPathAbs is not within assetsDirAbs (for example, using strings.HasPrefix on a path that is ensured to end with the separator), and in that case just call next.ServeHTTP(w, r). Only if the path is within the assets directory and exists on disk do we serve it directly. This will require importing strings and replacing the current dir + "/assets" + url logic in v3/examples/screen/main.go around lines 45–52.

Concretely:

  • Add an import for "strings" to the existing import list.
  • In the middleware, after obtaining dir, compute assetsDir := filepath.Join(dir, "assets") and assetsDirAbs := filepath.Clean(assetsDir).
  • Replace path := dir + "/assets" + url with requestedPath := filepath.Join(assetsDirAbs, url) and requestedPathAbs := filepath.Clean(requestedPath).
  • Before calling os.Stat or http.ServeFile, check that requestedPathAbs is under assetsDirAbs using a prefix check that accounts for path separators; if the check fails, call next.ServeHTTP(w, r) instead of serving from disk.
Suggested changeset 1
v3/examples/screen/main.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/v3/examples/screen/main.go b/v3/examples/screen/main.go
--- a/v3/examples/screen/main.go
+++ b/v3/examples/screen/main.go
@@ -8,6 +8,7 @@
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 
 	"github.com/wailsapp/wails/v3/pkg/application"
 )
@@ -44,12 +45,25 @@
 
 					_, filename, _, _ := runtime.Caller(0)
 					dir := filepath.Dir(filename)
-					url := r.URL.Path
-					path := dir + "/assets" + url
+					assetsDir := filepath.Clean(filepath.Join(dir, "assets"))
+					urlPath := r.URL.Path
 
-					if _, err := os.Stat(path); err == nil {
+					// Construct path under assets directory and ensure it does not escape
+					requestedPath := filepath.Clean(filepath.Join(assetsDir, urlPath))
+					assetsDirWithSep := assetsDir
+					if !strings.HasSuffix(assetsDirWithSep, string(os.PathSeparator)) {
+						assetsDirWithSep += string(os.PathSeparator)
+					}
+
+					if !strings.HasPrefix(requestedPath, assetsDirWithSep) {
+						// If the requested path is outside the assets directory, fall back to bundled assets
+						next.ServeHTTP(w, r)
+						return
+					}
+
+					if _, err := os.Stat(requestedPath); err == nil {
 						// Serve file from disk to make testing easy
-						http.ServeFile(w, r, path)
+						http.ServeFile(w, r, requestedPath)
 					} else {
 						// Passthrough to the default asset handler if file not found on disk
 						next.ServeHTTP(w, r)
EOF
@@ -8,6 +8,7 @@
"os"
"path/filepath"
"runtime"
"strings"

"github.com/wailsapp/wails/v3/pkg/application"
)
@@ -44,12 +45,25 @@

_, filename, _, _ := runtime.Caller(0)
dir := filepath.Dir(filename)
url := r.URL.Path
path := dir + "/assets" + url
assetsDir := filepath.Clean(filepath.Join(dir, "assets"))
urlPath := r.URL.Path

if _, err := os.Stat(path); err == nil {
// Construct path under assets directory and ensure it does not escape
requestedPath := filepath.Clean(filepath.Join(assetsDir, urlPath))
assetsDirWithSep := assetsDir
if !strings.HasSuffix(assetsDirWithSep, string(os.PathSeparator)) {
assetsDirWithSep += string(os.PathSeparator)
}

if !strings.HasPrefix(requestedPath, assetsDirWithSep) {
// If the requested path is outside the assets directory, fall back to bundled assets
next.ServeHTTP(w, r)
return
}

if _, err := os.Stat(requestedPath); err == nil {
// Serve file from disk to make testing easy
http.ServeFile(w, r, path)
http.ServeFile(w, r, requestedPath)
} else {
// Passthrough to the default asset handler if file not found on disk
next.ServeHTTP(w, r)
Copilot is powered by AI and may make mistakes. Always verify output.
return
}

cmd := exec.Command(parts[0], parts[1:]...)

Check failure

Code scanning / CodeQL

Command built from user-controlled sources Critical

This command depends on a
user-provided value
.
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
183 Security Hotspots
19.4% Duplication on New Code (required ≤ 3%)
E Security Rating on New Code (required ≥ A)
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@cloudflare-workers-and-pages
Copy link

Deploying wails with  Cloudflare Pages  Cloudflare Pages

Latest commit: 423377f
Status: ✅  Deploy successful!
Preview URL: https://9c2866c0.wails.pages.dev
Branch Preview URL: https://vk-2cfc-investigate-mous.wails.pages.dev

View logs

"node": "^10 || ^12 || >=14"
}
},
"node_modules/rollup": {

Choose a reason for hiding this comment

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

High severity vulnerability may affect your project—review required:
Line 569 lists a dependency (rollup) with a known High severity vulnerability.

ℹ️ Why this matters

Affected versions of rollup are vulnerable to Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

References: GHSA, CVE

To resolve this comment:
Check if you use Rollup to bundle JavaScript with import.meta.url and the output format is set to cjs, umd, or iife formats, while allowing users to inject scriptless HTML elements with unsanitized name attributes.

💬 Ignore this finding

To ignore this, reply with:

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

You can view more details on this finding in the Semgrep AppSec Platform here.

@leaanthony leaanthony changed the base branch from master to v3-alpha January 21, 2026 18:31
@Nurysso
Copy link

Nurysso commented Jan 21, 2026

Isn’t this behavior dependent on the WM/compositor? For example, on KWin (KDE), pointer enter/leave events are typically delivered, but focus-on-hover may be restricted, whereas compositors like Hyprland are more permissive.
How is this handled?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants