Skip to content
Source
Sign inSign up

[Bug]: KDE helper authorization fails when the XDG data path contains a percent sign #35972

Send to my agent
Closedcoygeek(GitHub profile, opens in a new tab) opened this issue 1 comment

Summary

KDE SnapShot setup cannot authorize its installed helper when the data directory contains a percent sign on Plasma releases that enforce desktop-entry permissions. Reinstalling writes the same entry and leaves capture unavailable. A focused fixture reproduced the executable-path mismatch using the checked-out encoder and Qt's real parser; the resulting authorization failure is established from the tagged KWin source rather than a live Plasma session.

Before submitting

  • Related issues: no matching report found in searches of open and closed issues and pull requests for KDE permissions and percent-containing capture paths.
  • Removed credentials, tokens, private paths, and sensitive data.

Area

apps/desktop

Observed behavior

The focused fixture creates synthetic helper files under a normal data directory and a directory containing %, invokes the repository's actual desktop-entry encoder, and passes the generated command to Qt's QProcess.splitCommand and QFileInfo.canonicalFilePath, matching KWin 6.7.6's executable comparison. The normal-directory control matches, but the percent-directory entry contains data%%test while the installed helper is under data%test, so the canonical executable comparison fails.

Source-only desktop consequence: KWin 6.7.6 cannot find this helper's desktop entry through that comparison and denies its screenshot interface. T3's readiness probe then returns the instruction to reinstall the helper, but reinstalling generates the same doubled-percent path. No Linux desktop, service cache, or screenshot was run for this investigation.

Reproduction

  1. Use T3 Code commit 59e64eef881632a23f89c4138bcf37f23d92bf93 and Python with PySide6-Essentials 6.12.0 plus Node with native TypeScript support.
  2. Run the script below against the checkout. It extracts and runs the checked-out encoding functions, creates only synthetic files, and invokes Qt's actual executable-token parser and canonical path lookup.
  3. Compare the passing ordinary-directory control with the percent-containing directory. The latter fails the same executable lookup KWin 6.7.6 uses for authorization.

One completed fixture run reproduced the mismatch. An earlier fixture attempt was inconclusive because its function extraction omitted escapeDesktopEntryString; the script below includes both functions. Full SnapShot setup and capture on Linux were not run. NOT_ENOUGH_INFO: a disposable Plasma 6.7.6 session would establish the desktop error end to end, including its actual KService cache.

Save the script as repro.sh and run sh repro.sh /path/to/t3code. Set PYTHON to an interpreter with PySide6-Essentials installed, and optionally NODE to the Node executable. All generated inputs, HOME, TMPDIR, and XDG directories are disposable and removed on exit; the checkout is read only. Exit 1 means the mismatch reproduced, exit 0 means both paths match, and exit 2 means setup or the control failed. This tests the authorization lookup boundary, not a running compositor.

Reproduction script: repro.sh
#!/bin/sh
set -u
if [ "$#" -ne 1 ]; then
  echo "RESULT: inconclusive"
  exit 2
fi
PYTHON=${PYTHON:-python3}
NODE=${NODE:-node}
probe=$(mktemp -d /tmp/kde-permission-probe.XXXXXX) || exit 2
trap 'rm -rf "$probe"' EXIT HUP INT TERM
export HOME="$probe/home" TMPDIR="$probe/tmp"
export XDG_DATA_HOME="$probe/data" XDG_CONFIG_HOME="$probe/config"
export XDG_STATE_HOME="$probe/state" XDG_CACHE_HOME="$probe/cache"
mkdir -p "$HOME" "$TMPDIR" "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_STATE_HOME" "$XDG_CACHE_HOME"
"$PYTHON" - "$1" "$probe" "$NODE" <<'PY'
import json
import pathlib
import subprocess
import sys
try:
    from PySide6.QtCore import QFileInfo, QProcess, qVersion
    repo, scratch = map(pathlib.Path, sys.argv[1:3])
    node = sys.argv[3]
    print("Python " + sys.version.split()[0])
    print("Qt " + qVersion())
    print("Node " + subprocess.check_output([node, "--version"], text=True).strip())
    source = (repo / "apps/desktop/src/app/DesktopLinuxUrlHandler.ts").read_text()
    start = source.index("const escapeDesktopEntryString =")
    function_start = source.index("export function escapeDesktopEntryExecArgument(", start)
    end = source.index("\n}\n", function_start) + 3
    # Execute the checked-out TypeScript function without importing the Electron app.
    targets = [scratch / "normal data/t3code/kde-capture/t3-kde-snap-shot",
               scratch / "data%test/t3code/kde-capture/t3-kde-snap-shot"]
    for target in targets:
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_bytes(b"synthetic helper identity; never executed")
    module = scratch / "encoder.ts"
    module.write_text(source[start:end] + "\nconsole.log(JSON.stringify(" + json.dumps([str(p) for p in targets]) + ".map(escapeDesktopEntryExecArgument)));\n")
    encoded = json.loads(subprocess.check_output([node, str(module)], text=True))
    outcomes = []
    for label, target, command in zip(["control", "percent"], targets, encoded):
        # Neither fixture needs KConfig backslash decoding. KService preserves the
        # percent field codes in Exec; KWin 6.7.6 then uses splitCommand directly.
        if "\\" in command:
            raise RuntimeError("fixture unexpectedly needs KConfig decoding")
        first = QProcess.splitCommand(command + " check")[0]
        matched = QFileInfo(first).canonicalFilePath() == QFileInfo(str(target)).canonicalFilePath()
        outcomes.append(matched)
        print(label + " desktop Exec=" + command.replace(str(scratch), "$SCRATCH") + " check")
        print(label + " KWin executable match=" + str(matched).lower())
    if not outcomes[0]:
        raise RuntimeError("normal-directory control failed")
    status = 0 if outcomes[1] else 1
except Exception as error:
    print(type(error).__name__ + ": " + str(error))
    status = 2
print("RESULT: " + {0: "expected behavior", 1: "defect reproduced", 2: "inconclusive"}[status])
sys.exit(status)
PY

Expected behavior

A helper installed under the configured XDG data directory should resolve to the same executable identity when KWin reads its desktop entry. T3 explicitly derives the applications directory from XDG_DATA_HOME without excluding percent signs in apps/desktop/src/app/DesktopEnvironment.ts#L181-L184, and installs the helper beneath that directory in apps/desktop/src/snapShot/KdeSnapShot.ts#L25-L43. KWin's actual supported permission contract uses the first token of QProcess::splitCommand(service->exec()) as the canonical executable path in src/utils/serviceutils.h#L28-L45.

Impact and scope

Impact: P2 - A valid but unusual configured data path prevents KDE SnapShot authorization on KWin 6.7.6. The executable lookup mismatch is reproduced; the application-level denial follows the tagged source and was not run on Linux.

The ordinary-directory control matches successfully. Choosing a percent-free data path avoids this specific parser mismatch, but changing an existing installation's data directory and completing setup were not tested. NOT_ENOUGH_INFO: the number of affected installations and the earliest affected compositor release are unknown. KWin 6.8.3 has removed this permission lookup, so this report does not claim the same failure there.

Version

T3 Code commit 59e64eef881632a23f89c4138bcf37f23d92bf93. KWin permission implementation examined at tag v6.7.6, with v6.8.3 checked separately. Fixture versions: Python 3.15.2, Node v26.12.1, PySide6-Essentials 6.12.0, Qt 6.12.0.

Runtime and platform

Fixture executed on macOS arm64, using QtCore only and synthetic files. The Linux behavior is established from KWin 6.7.6 source and KService 6.20.0 source, not a running Linux compositor. NOT_ENOUGH_INFO: exact Linux distribution and installed KService version; a disposable matching Plasma session would supply them.

Interface

UI: Settings → SnapShots → install KDE helper. Integration: KWin ScreenShot2 readiness check and desktop-entry executable authorization.

Earliest failure stage

Setup or configuration.

Relevant output or evidence

The completed fixture printed the following output and exited 1. $SCRATCH replaces its random temporary root; no real helper, bus, credentials, or windows were used.

Python 3.15.2
Qt 6.12.0
Node v26.12.1
control desktop Exec="$SCRATCH/normal data/t3code/kde-capture/t3-kde-snap-shot" check
control KWin executable match=true
percent desktop Exec="$SCRATCH/data%%test/t3code/kde-capture/t3-kde-snap-shot" check
percent KWin executable match=false
RESULT: defect reproduced
  1. tanstack-source added labelsbug

  2. juliusmarmingecommented

    I tried to reproduce this on the current build and could not.

    Commit b7a224c7dd (origin/main). No Plasma 6.7 session is available (Ubuntu 24.04 offers only KWin 5.27; T3's KDE helper requires Plasma 6 / kbuildsycoca6), so the end-to-end KWin denial was not observed in a running app. Verified the authorization boundary instead: (1) read KWin v6.7.6 src/utils/serviceutils.h, which matches services by QFileInfo(QProcess::splitCommand(service->exec()).first()).canonicalFilePath() == executablePath with no %% decoding; (2) ran main's escapeDesktopEntryExecArgument through real Qt 6.12 (PySide6) QProcess.splitCommand + canonicalFilePath on synthetic files: the normal-folder control matched, but data%test was encoded as "…/data%%test/…" and did not match; (3) added a test to apps/desktop/src/snapShot/KdeSnapShot.test.ts that runs KdeCaptureSetup.perform('install-kde-helper') with dataHome '/data%test', reads the written Exec line, and splits it the way KWin does. It fails on main: expected '.../data%test/t3code/kde-capture/t3-kde-snap-shot', received '.../data%%test/...'. The other 9 tests in the file pass.

    Closing as not planned. If it still happens for you, reply with the exact steps and the version you are on and it will be looked at again.

  3. juliusmarminge closed this

Sign in to join this conversation.