Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
37 changes: 36 additions & 1 deletion .github/workflows/desktop-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,27 @@ jobs:
matrix:
include:
- os: macos-latest
target: --mac dmg
artifact: macos-dmg
pattern: release/*.dmg
- os: ubuntu-latest
target: --linux AppImage
artifact: linux-appimage
pattern: release/*.AppImage
- os: ubuntu-latest
target: --linux flatpak
artifact: linux-flatpak
pattern: release/*.flatpak
container: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08
container-options: --privileged
Comment on lines +25 to +26

Copy link
Copy Markdown

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
repo="flathub-infra/flatpak-github-actions"
tag="freedesktop-25.08"

token="$(python - <<'PY'
import json, urllib.request
u = urllib.request.urlopen("https://ghcr.io/token?scope=repository:flathub-infra/flatpak-github-actions:pull")
print(json.load(u)["token"])
PY
)"

digest="$(curl -fsSI \
  -H "Authorization: Bearer ${token}" \
  -H "Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json" \
  "https://ghcr.io/v2/${repo}/manifests/${tag}" \
  | tr -d '\r' \
  | awk -F': ' 'tolower($1)=="docker-content-digest"{print $2}')"

echo "Resolved digest: ${digest}"

Repository: teidesu/tweb

Length of output: 2638


🏁 Script executed:

# Check if the file exists and read lines around 19-20
if [ -f .github/workflows/desktop-build.yml ]; then
  echo "=== File exists, showing lines 15-25 ==="
  sed -n '15,25p' .github/workflows/desktop-build.yml | cat -n
else
  echo "File not found"
fi

Repository: teidesu/tweb

Length of output: 514


🌐 Web query:

container image pinning by digest security best practice GitHub Actions

💡 Result:

Pinning container images by digest (SHA256) is a critical security best practice in GitHub Actions to prevent supply chain attacks and ensure build reproducibility [1][2][3]. Why Pinning by Digest is Necessary Container image tags (e.g.,:latest,:v1, or:18) are mutable, meaning the underlying image content associated with a tag can be changed at any time by the publisher without notice [2][3]. Relying on tags exposes your workflow to several risks: - Supply Chain Attacks: If a registry account is compromised, an attacker can push a malicious image to an existing tag, causing your workflows to execute unauthorized code [2][3]. - Non-reproducible Builds: Because the image can change, different runs of the same workflow version may use different image layers, leading to inconsistent build results [1][2]. - Silent Compromise: There is no inherent indication or warning when the content of a tag changes, making it difficult to detect that your build environment has been altered [2]. Best Practices for Implementation - Use Immutable Digests: Always reference images using the @sha256: suffix (e.g., node@sha256:a5e0ed...) rather than relying solely on tags [1][2][4]. You can optionally keep the tag for human readability if formatted as image:tag@sha256:... [2]. - Automate Updates: Manually updating digests is error-prone. Use tools like Dependabot or Renovate to automatically monitor for new digests and open pull requests to update your workflow files [3]. - Verify Image Integrity: Where possible, use technologies like Cosign or Docker Content Trust to verify image signatures alongside digest pinning [4]. - Use Trusted Sources: Whenever possible, pull images from secure, private mirrors or registries that support scanning and policy enforcement before the images are available to CI runners [2][3]. How to Find a Digest You can identify the current digest of a container image using the following commands: - Docker CLI: docker buildx imagetools inspect <image:tag> or docker inspect --format='{{index .RepoDigests 0}}' <image:tag> [5][3]. - Third-party tools: Utilities like crane digest <image:tag> can retrieve digests without requiring a local Docker daemon [2][6][5].

Citations:


Pin the Flatpak container image by digest.

Line 19 uses a mutable tag in a privileged job container. Pinning to a digest materially reduces CI supply-chain risk. Mutable tags can be reassigned by a registry account compromise, potentially injecting malicious code into privileged builds.

Proposed fix
-            container: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08
+            container: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-25.08@sha256:<resolved-digest>
🤖 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/desktop-build.yml around lines 19 - 20, The container
field in the desktop-build workflow is using a mutable tag (freedesktop-25.08)
which creates a supply-chain security risk in privileged builds. Replace the
mutable tag reference in the container field with an immutable image digest by
appending `@sha256`: followed by the specific digest hash of the desired image
version. This ensures the exact image version is locked and cannot be reassigned
by a registry compromise.

Source: Linters/SAST tools


runs-on: ${{ matrix.os }}

# Ignored if empty
container:
image: ${{ matrix.container || '' }}
options: ${{ matrix.container-options || '' }}
Comment on lines +30 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does GitHub Actions officially support setting jobs.<job_id>.container.image to an empty string to disable job containers for non-Linux matrix entries?

💡 Result:

As of June 2026, GitHub Actions does not natively support disabling a job container by setting jobs.<job_id>.container.image to an empty string [1][2]. While recent updates to the workflow parser have relaxed schema validation to allow empty strings at parse time [2], the runtime behavior remains restrictive. Specifically, setting the container image to an empty string for a job will typically result in a workflow error or failure, as the runner expects a valid container image identifier when the container key is defined [1][2]. For matrix strategies that require running jobs both inside and outside of containers (such as across Linux, Windows, and macOS), the standard and supported approach is to set the entire container key to null rather than using an empty string for the image [3]. This can be achieved effectively by using a matrix variable combined with the fromJSON function [3]: jobs: test: runs-on: ${{ matrix.os }} container: ${{ fromJSON(matrix.container) }} strategy: matrix: os: [ubuntu-latest, macos-latest] container: - '{"image": "ubuntu:latest"}' # For Linux jobs - null # For non-Linux/no-container jobs This method allows you to dynamically control container usage per matrix entry, ensuring jobs on platforms that do not support Docker (like macOS or Windows) or jobs intended to run directly on the runner host can bypass containerization entirely [3].

Citations:


Replace empty-string container config with null to avoid runtime failures on non-Linux runners.

GitHub Actions does not support disabling containers by setting jobs.<job_id>.container.image to an empty string—this causes runtime errors. The standard approach is to use null with fromJSON and a matrix variable:

container: ${{ fromJSON(matrix.container) }}
strategy:
  matrix:
    include:
      - os: ubuntu-latest
        container: '{"image": "ubuntu:latest"}'
      - os: macos-latest
        container: null

This ensures jobs on non-Linux platforms (macOS, Windows) or jobs intended to run directly on the runner bypass containerization entirely.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 26-26: unpinned image references (unpinned-images): container image may be unpinned

(unpinned-images)

🤖 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/desktop-build.yml around lines 24 - 27, Replace the
current empty-string container configuration approach with a `fromJSON` function
combined with matrix variables that properly use `null` values. Update the
container section to use `container: ${{ fromJSON(matrix.container) }}` instead
of the separate image and options fields with empty-string defaults. Then modify
the matrix strategy to include conditional entries where non-Linux runners (such
as macos-latest or windows-latest) have their container variable set to `null`,
while Linux runners have a JSON object string containing the image
configuration. This ensures containerization is properly disabled for
non-container-capable platforms rather than attempting to set an empty string
which GitHub Actions does not support.


steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -39,11 +52,33 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

# electron-builder always auto-installs the Electron base app via
# "flatpak install --user". flatpak-github-actions' container only has
# flathub as a system remote. Work this around by pre-installing
# (then the bundler just updates it).
- name: Install Flatpak deps
if: matrix.artifact == 'linux-flatpak'
run: flatpak install --system --noninteractive flathub org.electronjs.Electron2.BaseApp//25.08

# flatpak-builder's rofiles-fuse doesn't work in containers (no fuse),
# and electron-builder has no way to pass arguments to flatpak-builder,
# so make a wrapper.
- name: Make a flatpak-builder wrapper
if: matrix.artifact == 'linux-flatpak'
run: |
fb="$(command -v flatpak-builder)"
mv "$fb" "$fb.real"
printf '#!/bin/sh\nexec "%s" --disable-rofiles-fuse "$@"\n' "$fb.real" >"$fb"
chmod +x "$fb"

- name: Build
run: pnpm run electron:dist
run: pnpm run electron:dist ${{ matrix.target }}
env:
# don't try to codesign / notarize in CI
CSC_IDENTITY_AUTO_DISCOVERY: false
# flatpak-bundler pipes flatpak's stdout/stderr through 'debug',
# hiding all the diagnostics.
DEBUG: "@malept/flatpak-bundler"

- name: Upload artifact
uses: actions/upload-artifact@v4
Expand Down
31 changes: 23 additions & 8 deletions assets/desktop-icon/build.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// Generate the electron-builder desktop icons from the glyph SVG, all in this
// dir: icon.png (full-bleed square, for Linux/Windows) and icon.icns (macOS
// squircle with Apple's standard padding, since macOS doesn't auto-round).
// Re-run after changing the glyph: `node assets/desktop-icon/build.mjs`.
// Requires ImageMagick (`magick`) and, for the .icns, macOS `iconutil`.
/* Generate the electron-builder desktop icons from the glyph SVG, all in this
* dir. None of Windows/Linux/macOS auto-round, so generate the icons here:
*
* 1. icon.png - full-bleed square for Windows,
* 2. icon-linux.{svg,png} - Linux squircle (GNOME HIG padding and rounding),
* 3. icon.icns - macOS squircle with Apple's standard padding.
*
* Re-run after changing the glyph: `node assets/desktop-icon/build.mjs`.
* Requires ImageMagick (`magick`) and, for the .icns, macOS `iconutil`. */
import {spawnSync} from 'child_process';
import {mkdirSync, readFileSync, rmSync} from 'fs';
import {mkdirSync, readFileSync, rmSync, writeFileSync} from 'fs';
Comment on lines 10 to +11

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix ESLint object-curly-spacing violations.

Line 10, Line 11, and Line 73 violate the configured spacing rule and can break lint-gated checks.

Proposed fix
-import {spawnSync} from 'child_process';
-import {mkdirSync, readFileSync, rmSync, writeFileSync} from 'fs';
+import { spawnSync } from 'child_process';
+import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
...
-const linux = buildSvg({size: GNOME_CONTENT, offset: GNOME_MARGIN, radius: GNOME_RADIUS});
+const linux = buildSvg({ size: GNOME_CONTENT, offset: GNOME_MARGIN, radius: GNOME_RADIUS });

Also applies to: 73-73

🧰 Tools
🪛 ESLint

[error] 10-10: A space is required after '{'.

(object-curly-spacing)


[error] 10-10: A space is required before '}'.

(object-curly-spacing)


[error] 11-11: A space is required after '{'.

(object-curly-spacing)


[error] 11-11: A space is required before '}'.

(object-curly-spacing)

🤖 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 `@assets/desktop-icon/build.mjs` around lines 10 - 11, The import statements
using destructuring syntax violate the configured ESLint object-curly-spacing
rule, which requires spaces inside curly braces. Add spaces after the opening
brace and before the closing brace in all object destructuring patterns,
including the imports on lines 10 and 11 (from 'child_process' and 'fs'
respectively) and the destructuring pattern on line 73. Change patterns like
{spawnSync} to { spawnSync } and {mkdirSync, readFileSync, rmSync,
writeFileSync} to { mkdirSync, readFileSync, rmSync, writeFileSync }.

Source: Linters/SAST tools

import {dirname, join} from 'path';
import {fileURLToPath} from 'url';

Expand All @@ -18,6 +22,12 @@ const BG_TO = '#D59EFF';
const MAC_CONTENT = 824;
const MAC_MARGIN = (SIZE - MAC_CONTENT) / 2;
const MAC_RADIUS = 185;
// GNOME app-icon: a 103×103 rounded square (8px corners) centered in the
// 128 grid. Our icon is 1024x1024, so scaled dimensions are: 824px content,
// 100px margin, 64px radius (same content box as macOS, softer corners).
const GNOME_CONTENT = 824;
const GNOME_MARGIN = (SIZE - GNOME_CONTENT) / 2;
const GNOME_RADIUS = 64;

const glyph = readFileSync(join(dir, 'icon.svg'), 'utf8');
const viewBox = glyph.match(/viewBox\s*=\s*"\s*0\s+0\s+([\d.]+)\s+([\d.]+)\s*"/);
Expand Down Expand Up @@ -57,10 +67,15 @@ function render(svg, out) {
}

const square = buildSvg({size: SIZE, offset: 0, radius: 0});
const pngPath = join(dir, 'icon.png');
render(square, pngPath);
render(square, join(dir, 'icon.png'));
console.log('wrote assets/desktop-icon/icon.png');

const linux = buildSvg({size: GNOME_CONTENT, offset: GNOME_MARGIN, radius: GNOME_RADIUS});
writeFileSync(join(dir, 'icon-linux.svg'), linux);
console.log('wrote assets/desktop-icon/icon-linux.svg');
render(linux, join(dir, 'icon-linux.png'));
console.log('wrote assets/desktop-icon/icon-linux.png');

if (process.platform !== 'darwin') {
console.log('[build-app-icon] skipping .icns (needs macOS iconutil)');
process.exit(0);
Expand Down
Binary file added assets/desktop-icon/icon-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions assets/desktop-icon/icon-linux.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 18 additions & 2 deletions electron-builder.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
appId: desu.inutweb
# Flatpak requires appId to be a valid D-Bus bus name. A valid D-Bus bus name
# contains at least 3 segments (intended for reverse dns notation).
appId: pet.tei.inutweb
Comment on lines +1 to +3

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In electron-builder v26, does changing top-level appId affect macOS bundle identifier and Windows app identity, and is there a Flatpak-only appId override?

💡 Result:

Yes, changing the top-level appId property in electron-builder (including in v26) does affect both the macOS bundle identifier and the Windows application identity [1][2][3]. For macOS, the appId serves as the CFBundleIdentifier [1]. It is strongly recommended to set this explicitly; changing it after an initial release can break existing user data paths, such as NSUserDefaults and sandboxed containers [1]. For Windows, the appId is used as the Application User Model ID (AUMID) for the NSIS target [1][4]. Note that this functionality is specific to NSIS and is not supported by Squirrel.Windows [1][4]. Regarding Flatpak, electron-builder does not provide a specific top-level "appId override" key just for Flatpak. While you can configure Flatpak-specific options under the flatpak configuration block, the appId itself is generally handled globally [5]. Flatpak identifiers (often formatted like org.domain.appname) are typically defined within the Flatpak-specific manifest or configuration settings, and you should ensure your application identity aligns with the requirements of the Flatpak ecosystem [6][7].

Citations:


Confirm this is an intentional breaking change; appId migration affects macOS user data and Windows app identity.

Changing the top-level appId from io.github.MarkusBirkl.inutweb to pet.tei.inutweb will break existing installations on macOS (invalidates NSUserDefaults paths and sandboxed containers) and change the Windows AUMID for NSIS. There is no Flatpak-only override for appId; the global change applies to all platforms. Confirm this is intentional before release, especially if existing users exist.

🤖 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 `@electron-builder.yml` around lines 1 - 3, The appId value in
electron-builder.yml has been changed globally from
`io.github.MarkusBirkl.inutweb` to `pet.tei.inutweb` to satisfy Flatpak D-Bus
naming requirements, but this change will break existing macOS installations
(affecting NSUserDefaults and sandboxed containers) and modify the Windows
AUMID. Confirm with the team whether this breaking change to appId is
intentional for all platforms. If it is not intentional, either revert appId to
the original value and explore platform-specific Flatpak configuration options
in electron-builder.yml, or implement a migration strategy for existing users.
If the change is intentional, ensure the release notes and upgrade documentation
clearly communicate this breaking change and any necessary migration steps for
end users.

productName: inutweb
copyright: abolish copyright law

Expand Down Expand Up @@ -33,9 +35,23 @@ win:

linux:
category: Network
icon: assets/desktop-icon/icon.png
icon: assets/desktop-icon/icon-linux.png
syncDesktopName: true
target:
- AppImage
- flatpak

# electron-builder defaults to 20.08 for some reason; it's not even
# on Flathub anymore.
flatpak:
runtimeVersion: "25.08"
baseVersion: "25.08"
# electron-builder doesn't add the png icon specified above because it's
# 1024px, and flatpak target skips anything >512px.
# Add a proper scalable icon manually.
files:
- - assets/desktop-icon/icon-linux.svg
- /share/icons/hicolor/scalable/apps/pet.tei.inutweb.svg

nsis:
oneClick: false
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "1.0.0",
"license": "GPL-3.0-only",
"main": "dist-electron/main.js",
"desktopName": "pet.tei.inutweb.desktop",
"engines": {
"node": ">=22"
},
Expand Down Expand Up @@ -72,4 +73,4 @@
"vitest": "^4.1.0"
},
"packageManager": "pnpm@10.34.3+sha512.f2c531b08829d7be7f03c90addc266615f9c5477463e4bf1d275cb263ce9ea57cf0d5599110d94649a9bb3fed9f0b5efcea74a7016b61532a24cb9ad87860c1d"
}
}
Loading