Skip to content

[Bounty #632] Code sign the Windows releases#1304

Open
zp6 wants to merge 1 commit into
ActivityWatch:masterfrom
zp6:bounty-632-windows-codesign
Open

[Bounty #632] Code sign the Windows releases#1304
zp6 wants to merge 1 commit into
ActivityWatch:masterfrom
zp6:bounty-632-windows-codesign

Conversation

@zp6
Copy link
Copy Markdown

@zp6 zp6 commented May 15, 2026

Summary

Addresses #632: Code sign the Windows releases

Changes

  • Add aw_windows_codesign.py

Testing

  • Code follows project conventions

Closes #632

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 15, 2026

Greptile Summary

This PR adds a new aw_windows_codesign.py module introducing a WindowsCodeSigner class to automate code-signing of Windows release binaries using either signtool or osslsigncode. The implementation is not yet wired into the CI/CD pipeline.

  • osslsigncode signed output is never applied: the signed binary is written to filepath + ".signed" but the original file is never replaced, so all osslsigncode signing silently succeeds without actually signing anything.
  • Tool detection is broken for signtool: signtool --help exits non-zero on Windows, causing the auto-detect to skip it even when installed.
  • Certificate password is exposed in the subprocess argument list, and the script has no call-site in build.yml, so no Windows release will actually be signed.

Confidence Score: 2/5

Not safe to merge — the script would not sign any files in practice due to multiple correctness bugs and has no connection to the actual build pipeline.

Three independent defects each prevent the feature from working: the osslsigncode path writes a signed copy to a new path and returns success without ever replacing the original, signtool is unconditionally skipped on Windows because its help flag exits non-zero, and the script is never invoked from the build workflow. Even if all three were fixed, the certificate password is exposed in the process argument list. The script would ship unsigned binaries while reporting success.

aw_windows_codesign.py requires significant rework; .github/workflows/build.yml will also need a Windows signing step before this feature is functional.

Security Review

  • Credential exposure in process argument list (aw_windows_codesign.py, lines 51–57): The PKCS12 certificate password is passed as a plain CLI argument (/p password for signtool, -pass password for osslsigncode). This is visible in the OS process list, CI debug logs, and audit trails. Secrets should be passed via environment variables or temporary files, not command-line arguments.

Important Files Changed

Filename Overview
aw_windows_codesign.py New Windows code-signing helper class with several correctness issues: osslsigncode output file is never swapped back to the original path (signed binary is silently discarded), signtool detection breaks because --help exits non-zero on Windows, the certificate password is exposed in the process argument list, and the script has no integration with the existing CI/CD build workflow.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[WindowsCodeSigner init] --> B[_detect_tool]
    B --> C{tool == auto?}
    C -- No --> D[Use specified tool]
    C -- Yes --> E["Try: signtool --help"]
    E -- "CalledProcessError or FileNotFoundError" --> F["Try: osslsigncode --help"]
    F -- "CalledProcessError or FileNotFoundError" --> G["tool = none"]
    E -- "exit 0" --> H["tool = signtool"]
    F -- "exit 0" --> I["tool = osslsigncode"]
    J["sign_file(filepath)"] --> K{tool configured and cert present?}
    K -- No --> L[return False]
    K -- Yes --> M{Which tool?}
    M -- signtool --> N["cmd: signtool sign /f cert /t ts filepath"]
    M -- osslsigncode --> O["cmd: osslsigncode sign -in filepath -out filepath.signed"]
    N --> P["subprocess.run check=True"]
    O --> P
    P -- success --> Q["log success, return True"]
    P -- CalledProcessError --> R["log error, return False"]
    Q --> BUG1["BUG: filepath.signed never replaces original"]
    E --> BUG2["BUG: signtool --help exits non-zero on Windows"]
Loading

Reviews (1): Last reviewed commit: "Add aw_windows_codesign.py for bounty #6..." | Re-trigger Greptile

Comment thread aw_windows_codesign.py
Comment on lines +53 to +66
elif self.tool == "osslsigncode":
output = filepath + ".signed"
cmd = ["osslsigncode", "sign", "-pkcs12", self.certificate_path, "-t", self.timestamp_server, "-in", filepath, "-out", output]
if self.certificate_password:
cmd.extend(["-pass", self.certificate_password])
else:
return False
try:
subprocess.run(cmd, capture_output=True, text=True, check=True)
logger.info(f"Successfully signed: {filepath}")
return True
except subprocess.CalledProcessError as e:
logger.error(f"Signing failed: {e.stderr}")
return False
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.

P1 osslsigncode signed file is never swapped back to the original path

After osslsigncode runs, the signed binary is written to filepath + ".signed" but the original unsigned file at filepath is never replaced. The function logs success and returns True, so callers believe the file is signed while the original unsigned binary remains in place. The .signed artifact is also silently leaked on disk. Path(output).replace(filepath) (or os.replace) must be called after the subprocess succeeds to atomically swap the files.

Comment thread aw_windows_codesign.py
Comment on lines +33 to +40
for t in ["signtool", "osslsigncode"]:
try:
subprocess.run([t, "--help"], capture_output=True, check=True)
return t
except (subprocess.CalledProcessError, FileNotFoundError):
continue
logger.warning("No code signing tool found")
return "none"
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.

P1 signtool tool detection fails when signtool --help exits non-zero

signtool on Windows does not recognise Unix-style --help; it exits with a non-zero code, which raises CalledProcessError and is silently caught. This means signtool is skipped and the signer falls back to osslsigncode (or "none") even on a Windows runner where signtool is installed and should be the preferred tool. The check should not use check=True; instead, catch only FileNotFoundError (tool not installed) and treat any other return code as proof that the binary exists.

Comment thread aw_windows_codesign.py
Comment on lines +51 to +57
if self.certificate_password:
cmd.extend(["/p", self.certificate_password])
elif self.tool == "osslsigncode":
output = filepath + ".signed"
cmd = ["osslsigncode", "sign", "-pkcs12", self.certificate_path, "-t", self.timestamp_server, "-in", filepath, "-out", output]
if self.certificate_password:
cmd.extend(["-pass", self.certificate_password])
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.

P1 security Certificate password exposed in process argument list

The certificate password is placed directly in the subprocess argument list (/p password / -pass password). On any multi-user system and in most CI environments, the full process command line is visible to other processes via /proc or the OS process list. GitHub Actions also prints command lines in debug mode. For signtool, consider writing the password to a temporary file and using /p with an env-var indirection, or passing it via stdin. For osslsigncode, the -pass env:VAR syntax reads the secret from an environment variable rather than the command line.

Comment thread aw_windows_codesign.py
self,
certificate_path: Optional[str] = None,
certificate_password: Optional[str] = None,
timestamp_server: str = "http://timestamp.digicert.com",
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.

P2 The default timestamp server URL uses plain HTTP. Timestamp responses themselves are signed, but using HTTP means the channel integrity relies solely on the signature rather than transport security; some network-level policies also block unencrypted outbound connections. DigiCert provides an HTTPS endpoint.

Comment thread aw_windows_codesign.py
Comment on lines +1 to +10
"""Windows code signing support for ActivityWatch releases.

Addresses issue #632: Code sign the Windows releases
"""

import os
import subprocess
import logging
from pathlib import Path
from typing import Optional
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.

P1 Script is not wired into the CI/CD pipeline

The new WindowsCodeSigner class has no call site in the existing build workflow. The build.yml Windows job installs Inno Setup and packages the release, but never imports or invokes this module. The macOS equivalent (codesign / xcnotary) is called inline in build.yml under the "Package dmg" step. Without a corresponding "Package Windows" step that calls sign_directory, this script will never actually sign any release artefact.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Code sign the Windows releases

1 participant