Feature: Add post-build Authenticode signing support for --compile -t exe
- Dominant language
- C#
- Stars
- 154
- Forks
- 4
- Avg merge
- 2h 46m
- Merged PRs (30d)
- 189
Description
## Motivation
Compiled single-file executables (`sharpts --compile script.ts -t exe`) are unsigned and get flagged by behavioral AV engines as suspected packers/droppers — see #4 (WithSecure DeepGuard quarantines the output as `W32/Malware!DeepGuard.n`).
This pattern is intrinsic to the apphost-plus-appended-bundle format that both our `ManualBundler` and the `SdkBundler` (Microsoft's `HostWriter`) produce. Rewriting the bundler does not fix it — a differently-shaped unsigned bundle is still an unsigned bundle. Authenticode signing is the real long-term mitigation: once the signing cert earns some reputation, heuristic flags largely stop firing.
## Proposed behavior
Add post-build signing as an opt-in step after bundling, for `-t exe` output only.
CLI surface (rough sketch, open to revision):
```
sharpts --compile script.ts -t exe \
--sign \
[--sign-pass ] \
[--sign-timestamp ] \
[--sign-digest sha256]
```
- `--sign` accepts either a `.pfx` path or a certificate store thumbprint (`sha1:ABCD...`). Default digest `sha256`. Default timestamp server is the DigiCert RFC3161 endpoint; overridable.
- Password can also come from `SHARPTS_SIGN_PASS` env var to keep it out of shell history.
- Signing runs after the bundler produces the final `.exe`. On failure, the `.exe` remains on disk unsigned and the tool exits non-zero with a clear error.
## Implementation notes
- On Windows, shell out to `signtool.exe` if it is on `PATH` or resolvable from a Windows SDK install. Fallback to `SignTool` from the .NET SDK if present.
- On Linux/macOS, shell out to `osslsigncode` if available; otherwise emit a clear error saying the platform needs `osslsigncode` installed and point at install instructions.
- Keep the signing logic in `PEPacker` (separate repo/package) alongside the bundling code, so the `--sign` flag is thin glue in SharpTS.
- The apphost is already signed by Microsoft when shipped in the SDK; our byte-patching invalidates that signature, which is expected. Our re-signing replaces it.
## Out of scope for this issue
- EV / OV certificate procurement guidance — covered in a separate docs issue.
- Authenticode signing for DLL output (`-t dll`) — possible, but drop-in `dotnet script.dll` execution does not really need it. Can be a follow-up.
## Docs work
- Add `docs/antivirus-false-positives.md` explaining why single-file .NET outputs trigger behavioral AV and pointing to `--sign` as the fix.
- README section on `--sign` with a worked example (self-test with a self-signed cert on a dev machine; production example with a real code-signing cert).
## Acceptance criteria
- `sharpts --compile hello.ts -t exe --sign test.pfx --sign-pass ...` produces an `.exe` that `signtool verify /pa /v hello.exe` reports as `Successfully verified`.
- On sign failure (bad password, expired cert, missing `signtool`), the tool prints an actionable error and exits non-zero.
- CI gains a test that produces a signed exe with a self-generated test cert and verifies the signature. Cross-platform via `osslsigncode` if feasible.
## Related
- #4 — WithSecure / F-Secure DeepGuard false-positive on compiled exe. Signing does not guarantee the FP goes away on day one (cert reputation needs to accrue) but is the recognized long-term path.
Contributor guide
Assessment
This issue has not been assessed yet.