Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ jobs:
./build-freerdp-static.sh
make rdp-sidecar-static

# Windows ships the same sidecar, built static via vcpkg so it carries no
# loose DLLs. Cache the vcpkg-built FreeRDP so it isn't recompiled every run.
- name: Cache vcpkg FreeRDP (Windows)
if: runner.os == 'Windows'
uses: actions/cache@v4
with:
path: |
C:/vcpkg/installed
C:/vcpkg/packages
key: vcpkg-freerdp-x64-windows-static-${{ hashFiles('native/rdp-spike/CMakeLists.txt') }}

Comment on lines +59 to +70

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify floating action refs in the workflow
rg -nP 'uses:\s*[^@\s]+@v[0-9]+' .github/workflows/release.yml

# Inspect cache usage and key scoping for the Windows vcpkg cache
rg -nP 'actions/cache|key:\s*vcpkg-freerdp' .github/workflows/release.yml -C 2

Repository: Meanski/noxed

Length of output: 815


Pin actions/cache and scope cache key to release refs.

Line 61 uses actions/cache@v4 (floating tag instead of pinned commit SHA), and the cache key at line 66 is not scoped to a specific ref or release tag. This allows the same cache to be reused across different builds and branches, increasing cache-poisoning exposure in a release workflow.

Recommendations:

  • Pin the action to a specific commit: actions/cache@<commit-sha> (not a floating major version)
  • Scope the cache key to the release ref or commit to prevent unintended cache reuse
🧰 Tools
🪛 zizmor (1.25.2)

[error] 61-61: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 61-61: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 59 - 67, The Cache vcpkg FreeRDP
(Windows) step uses a floating tag `actions/cache@v4` instead of a pinned commit
SHA, and the cache key does not include the current ref or release information.
To fix this, pin the actions/cache action to a specific commit SHA instead of
the floating v4 tag, and modify the cache key by appending the GitHub ref or
release tag (such as using ${{ github.ref }}) to ensure the cache is scoped to
the specific release and prevent unintended cache reuse across different
branches and builds.

Source: Linters/SAST tools

- name: Build RDP sidecar (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
& "$env:VCPKG_INSTALLATION_ROOT\vcpkg.exe" install freerdp:x64-windows-static
cmake -S native/rdp-spike -B native/rdp-spike/build `
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" `
-DVCPKG_TARGET_TRIPLET=x64-windows-static `
-DCMAKE_BUILD_TYPE=Release
cmake --build native/rdp-spike/build --config Release
$exe = Get-ChildItem -Recurse -Filter rdp-sidecar.exe native/rdp-spike/build | Select-Object -First 1
if (-not $exe) { throw "rdp-sidecar.exe not produced by the build" }
Copy-Item $exe.FullName native/rdp-spike/rdp-sidecar.exe -Force

# Notarization uses an App Store Connect API key. electron-builder reads
# APPLE_API_KEY as a path to the .p8, so materialize it from the secret.
- name: Prepare App Store Connect API key
Expand Down Expand Up @@ -151,7 +176,10 @@ jobs:
Pop-Location
}

# Only publish on a real tag push. workflow_dispatch on a branch still runs
# the full build (useful for verifying the sidecars) but skips publishing.
- name: Attach artifacts to release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: |
Expand Down
6 changes: 6 additions & 0 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ mac:
- zip
win:
icon: build/icon.png
# Ship the self-contained RDP sidecar (built via native/rdp-spike/CMakeLists.txt
# with vcpkg's x64-windows-static FreeRDP). Lands at resources/rdp-sidecar.exe —
# see sidecarPath() in ipc/rdp.ts and the win32 check in verify-sidecar.js.
extraResources:
- from: native/rdp-spike/rdp-sidecar.exe
to: rdp-sidecar.exe
target:
- nsis
linux:
Expand Down
40 changes: 40 additions & 0 deletions native/rdp-spike/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Windows build for the RDP sidecar.
#
# macOS builds the sidecar via the Makefile + build-freerdp-static.sh (a trimmed
# from-source static FreeRDP). On Windows the easiest self-contained path is
# vcpkg's static triplet, which ships static FreeRDP 3 plus its transitive deps
# (OpenSSL, zlib, ...). Configure with the vcpkg toolchain so find_package
# resolves everything:
#
# vcpkg install freerdp:x64-windows-static
# cmake -S native/rdp-spike -B native/rdp-spike/build \
# -DCMAKE_TOOLCHAIN_FILE=<vcpkg>/scripts/buildsystems/vcpkg.cmake \
# -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_BUILD_TYPE=Release
# cmake --build native/rdp-spike/build --config Release
#
# Produces rdp-sidecar.exe, which electron-builder bundles via win.extraResources.
cmake_minimum_required(VERSION 3.16)
project(rdp_sidecar C)

set(CMAKE_C_STANDARD 11)

# Link the CRT statically so the .exe doesn't depend on the VC++ redistributable.
if(MSVC)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()

# FreeRDP 3 from vcpkg. The CONFIG packages export imported targets that carry
# their static transitive dependencies, so we don't enumerate OpenSSL/zlib here.
find_package(FreeRDP CONFIG REQUIRED)
find_package(FreeRDP-Client CONFIG REQUIRED)
find_package(WinPR CONFIG REQUIRED)

add_executable(rdp-sidecar sidecar.c)
set_target_properties(rdp-sidecar PROPERTIES OUTPUT_NAME rdp-sidecar)

target_link_libraries(rdp-sidecar PRIVATE freerdp freerdp-client winpr)

if(WIN32)
# Sockets + crypto/security backends FreeRDP/WinPR pull in on Windows.
target_link_libraries(rdp-sidecar PRIVATE ws2_32 crypt32 secur32 winmm bcrypt)
endif()
12 changes: 12 additions & 0 deletions native/rdp-spike/sidecar.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
#include <stdlib.h>
#include <string.h>

#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
#endif

#include <freerdp/freerdp.h>
#include <freerdp/client.h>
#include <freerdp/gdi/gdi.h>
Expand Down Expand Up @@ -201,6 +206,13 @@ static int sidecar_entry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)

int main(int argc, char* argv[])
{
#ifdef _WIN32
/* Windows opens stdout in text mode, which translates every \n to \r\n and
* would corrupt our binary frame stream (silent pixel desync). Force binary
* before any frame is written. */
_setmode(_fileno(stdout), _O_BINARY);
#endif
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if (argc < 4 || argc > 6)
{
fprintf(stderr, "usage: %s <host> <port> <user> [width] [height]\n", argv[0]);
Expand Down
37 changes: 35 additions & 2 deletions scripts/verify-sidecar.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,40 @@ exports.default = async function verifySidecar(context) {
return
}

// Windows/Linux sidecars are a separate build (not yet produced); nothing to
// verify until extraResources ships them for those platforms.
if (platform === 'win32') {
// win.extraResources lands under <appOutDir>/resources/.
const sidecar = join(appOutDir, 'resources', 'rdp-sidecar.exe')
if (!existsSync(sidecar)) {
throw new Error(
`[verify-sidecar] RDP sidecar missing from package: ${sidecar}\n` +
`Build it first (vcpkg static FreeRDP): see native/rdp-spike/CMakeLists.txt`,
)
}
// The vcpkg x64-windows-static build + static CRT should leave no FreeRDP/
// OpenSSL/vcpkg DLL dependencies — only Windows system DLLs. Best-effort
// check via dumpbin; skip quietly if the VS toolchain isn't on PATH.
try {
const deps = execFileSync('dumpbin', ['/dependents', sidecar], { encoding: 'utf8' })
const leaked = deps
.split('\n')
.map((l) => l.trim())
.filter((l) => /\.dll$/i.test(l))
.filter((l) => /freerdp|winpr|libssl|libcrypto|zlib|vcruntime|msvcp/i.test(l))
if (leaked.length > 0) {
throw new Error(
`[verify-sidecar] RDP sidecar links non-redistributable DLLs:\n ${leaked.join('\n ')}\n` +
`Rebuild static: vcpkg install freerdp:x64-windows-static + the static CRT (see CMakeLists.txt).`,
)
}
console.log('[verify-sidecar] Windows RDP sidecar is self-contained ✓')
} catch (err) {
if (err.message && err.message.startsWith('[verify-sidecar]')) throw err
console.log('[verify-sidecar] Windows RDP sidecar present (dumpbin unavailable, skipped dep scan) ✓')
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return
}

// Linux sidecar is a separate build (not yet produced); nothing to verify
// until extraResources ships it for that platform.
console.log(`[verify-sidecar] no sidecar check for ${platform} (not bundled yet)`)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from 'lucide-react'
import { useAppStore } from '../../store'
import { ipcErrorMessage } from '../../lib/format'
import { rdpSupported } from '../../lib/platform'

interface K8sContextEntry {
name: string
Expand Down Expand Up @@ -559,9 +560,9 @@ function TypeSelector({ selected, onSelect }: {
selected: ConnectionType
onSelect: (t: ConnectionType) => void
}) {
// RDP needs the bundled FreeRDP sidecar, which currently ships on macOS only.
// RDP needs the bundled FreeRDP sidecar, which ships on macOS + Windows.
// Hide the type elsewhere so we never offer a connection that can't run.
const options = TYPE_OPTIONS.filter(o => o.type !== 'rdp' || window.api.platform === 'darwin')
const options = TYPE_OPTIONS.filter(o => o.type !== 'rdp' || rdpSupported)
return (
<div className="px-6 py-4">
<div className="grid grid-cols-2 gap-3">
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/src/components/Dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from 'lucide-react'
import { useAppStore, Session } from '../../store'
import { groupColor } from '../../lib/colors'
import { rdpSupported } from '../../lib/platform'
import { CompactServerCard, HealthCard, ServerListRow } from './ServerViews'
import { ServerContextMenu } from '../ServerContextMenu'

Expand Down Expand Up @@ -403,7 +404,7 @@ export default function Dashboard() {
onOpenDocker={(ctxMenu.session.type ?? 'ssh') === 'ssh'
? () => { openDockerTab(ctxMenu.session); setCtxMenu(null) }
: undefined}
onOpenRdp={window.api.platform === 'darwin' && ctxMenu.session?.type === 'rdp'
onOpenRdp={rdpSupported && ctxMenu.session?.type === 'rdp'
? () => { openRdpTab(ctxMenu.session); setCtxMenu(null) }
: undefined}
onColorChange={c => handleColorChange(ctxMenu.session, c)}
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { useAppStore, Session, groupColor } from '../../store'
import K8sIcon from '../K8sIcon'
import { ServerContextMenu, MenuItem, useMenuBehavior, COLORS } from '../ServerContextMenu'
import { rdpSupported } from '../../lib/platform'

// Sorts a session list by a saved id order; unknown ids keep their position at the end.
function applySavedOrder(list: Session[], order?: string[]): Session[] {
Expand Down Expand Up @@ -245,7 +246,7 @@ export default function Sidebar() {
/>
)}

{rdpSessions.length > 0 && window.api.platform === 'darwin' && (
{rdpSessions.length > 0 && rdpSupported && (
<DraggableSection
label="Remote Desktop"
sessions={applyOrder('rdp', rdpSessions)}
Expand Down Expand Up @@ -288,7 +289,7 @@ export default function Sidebar() {
onOpenDocker={(ctxMenu.session.type ?? 'ssh') === 'ssh'
? () => { openDockerTab(ctxMenu.session!); setCtxMenu(null) }
: undefined}
onOpenRdp={window.api.platform === 'darwin' && (ctxMenu.session?.type ?? 'ssh') === 'rdp'
onOpenRdp={rdpSupported && (ctxMenu.session?.type ?? 'ssh') === 'rdp'
? () => { openRdpTab(ctxMenu.session!); setCtxMenu(null) }
: undefined}
onColorChange={c => handleColorChange(ctxMenu.session!, c)}
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/src/lib/platform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Platforms with a bundled RDP sidecar. The native FreeRDP sidecar is built and
// shipped per-platform (macOS + Windows so far); the RDP UI is gated to these so
// builds without a sidecar don't surface a feature that can't run. Add 'linux'
// here once a Linux sidecar ships.
const RDP_PLATFORMS: ReadonlyArray<string> = ['darwin', 'win32']

export const rdpSupported: boolean = RDP_PLATFORMS.includes(window.api.platform)
Loading