Skip to content

Commit 8fa867f

Browse files
sunnamed434claude
andcommitted
feat: add GitHub Action for CI obfuscation
Composite action (action.yml) wrapping the published BitMono.GlobalTool so CI can obfuscate a build artifact without the MSBuild/.csproj integration. Inputs map 1:1 to the CLI (file, output, libraries, protections, preset, no-watermark, no-logo, strong-name-key, config files, version, extra-args); inputs flow through env (shell-injection safe). Rolls forward onto newer runtimes (e.g. a .NET 10-only runner) and opts out of dotnet telemetry/logo. Adds a smoke-test workflow (builds test/Sandbox, obfuscates via the local action, asserts output) and docs (usage/github-actions.rst + README). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2cb7cfa commit 8fa867f

5 files changed

Lines changed: 314 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: BitMono.Action
2+
3+
# Smoke test for the composite action (action.yml): build Sandbox, obfuscate it, assert output exists.
4+
on:
5+
push:
6+
branches: [ main ]
7+
paths:
8+
- 'action.yml'
9+
- '.github/workflows/BitMono.Action.yaml'
10+
pull_request:
11+
branches: [ main ]
12+
paths:
13+
- 'action.yml'
14+
- '.github/workflows/BitMono.Action.yaml'
15+
workflow_dispatch:
16+
17+
jobs:
18+
smoke:
19+
name: "Action smoke test"
20+
runs-on: ubuntu-latest
21+
env:
22+
DOTNET_CLI_TELEMETRY_OPTOUT: true
23+
DOTNET_NOLOGO: true
24+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
25+
steps:
26+
- uses: actions/checkout@v7
27+
28+
- uses: actions/setup-dotnet@v5
29+
with:
30+
# 9.x is BitMono.GlobalTool's runtime ceiling; 10.x is the latest SDK.
31+
dotnet-version: |
32+
10.x
33+
9.x
34+
35+
- name: Build the existing Sandbox app
36+
run: dotnet build test/Sandbox/Sandbox.csproj -c Release
37+
38+
- name: Obfuscate via the local action
39+
uses: ./
40+
with:
41+
file: test/Sandbox/bin/Release/net9.0/Sandbox.dll
42+
libraries: test/Sandbox/bin/Release/net9.0
43+
preset: Minimal
44+
output: test/Sandbox/obf
45+
46+
- name: Assert output exists
47+
run: test -f test/Sandbox/obf/Sandbox.dll

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,22 @@ Obfuscate automatically on every `Release` build by adding one package reference
114114
- Configure with the usual `protections.json` / `criticals.json` / `obfuscation.json` next to your `.csproj`.
115115
- See the [MSBuild integration guide](https://docs.bitmono.dev/en/latest/usage/msbuild-integration.html).
116116

117+
**GitHub Actions (CI/CD):**
118+
Obfuscate in your pipeline without touching your source or `.csproj` — build, then point the action at the artifact:
119+
```yaml
120+
- uses: actions/setup-dotnet@v5
121+
with: { dotnet-version: 9.x }
122+
- run: dotnet build -c Release
123+
- uses: sunnamed434/BitMono@0.43.0 # pin the latest release tag (see Releases)
124+
with:
125+
file: bin/Release/net9.0/MyApp.dll
126+
preset: Maximum
127+
# version: 0.43.0 # optional: pin the obfuscator too (default: latest) for byte-stable builds
128+
```
129+
The action is versioned by the repo's git tags — it ships from its first tagged release onward, so use the newest tag on the [releases page][bitmono_releases]. Pin the tag to lock the wrapper; also set `version:` to the same number to lock the obfuscator.
130+
131+
No action needed if you prefer: it just wraps the global tool, so `dotnet tool install --global BitMono.GlobalTool` then `bitmono.console -f bin/Release/net9.0/MyApp.dll --preset Maximum` does the same thing in two lines. Inputs (`file`, `output`, `libraries`, `protections`, `preset`, `no-watermark`, `no-logo`, `strong-name-key`, config files, `version`, `extra-args`) map 1:1 to the CLI — see [`action.yml`](action.yml).
132+
117133
**NuGet Package Users:**
118134
If you encounter dependency resolution issues when using BitMono as a NuGet package, see the [NuGet configuration guide](https://docs.bitmono.dev/en/latest/usage/nuget-configuration.html) in the documentation.
119135

action.yml

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
name: 'BitMono Obfuscator'
2+
description: 'Obfuscate a compiled .NET assembly with BitMono in CI - no source changes, no .csproj edits.'
3+
author: 'sunnamed434'
4+
branding:
5+
icon: 'lock'
6+
color: 'purple'
7+
8+
inputs:
9+
file:
10+
description: 'Path to the compiled assembly to obfuscate (e.g. bin/Release/net9.0/MyApp.dll).'
11+
required: true
12+
output:
13+
description: 'Output directory for the obfuscated assembly. Default: <file dir>/output.'
14+
required: false
15+
output-name:
16+
description: 'Output file name (-n). Default: keeps the original name.'
17+
required: false
18+
libraries:
19+
description: 'Space-separated dependency directories (-l), e.g. "bin/Release/net9.0".'
20+
required: false
21+
protections:
22+
description: 'Space-separated protections (-p), e.g. "FullRenamer StringsEncryption". Overrides preset/json.'
23+
required: false
24+
preset:
25+
description: 'Protection preset: Minimal, Balanced, or Maximum.'
26+
required: false
27+
obfuscation-file:
28+
description: 'Path to obfuscation.json.'
29+
required: false
30+
protections-file:
31+
description: 'Path to protections.json.'
32+
required: false
33+
criticals-file:
34+
description: 'Path to criticals.json.'
35+
required: false
36+
logging-file:
37+
description: 'Path to logging.json.'
38+
required: false
39+
no-watermark:
40+
description: 'Disable the BitMono watermark.'
41+
required: false
42+
default: 'false'
43+
no-logo:
44+
description: 'Suppress the BitMono ASCII banner on startup (--nologo). Tidier CI logs.'
45+
required: false
46+
default: 'false'
47+
strong-name-key:
48+
description: 'Path to a .snk strong-name key to re-sign the obfuscated assembly.'
49+
required: false
50+
extra-args:
51+
description: 'Extra raw arguments passed through to the BitMono CLI verbatim.'
52+
required: false
53+
version:
54+
description: 'BitMono.GlobalTool version to install. Default: latest. Pin it for reproducible builds.'
55+
required: false
56+
57+
outputs:
58+
output:
59+
description: 'The directory the obfuscated assembly was written to.'
60+
value: ${{ steps.run.outputs.output }}
61+
62+
runs:
63+
using: 'composite'
64+
steps:
65+
- id: run
66+
shell: bash
67+
# Inputs via env, never interpolated into the run script (shell-injection safe).
68+
env:
69+
# Tool targets up to net9.0; roll forward onto newer runtimes (e.g. a .NET 10-only runner).
70+
DOTNET_ROLL_FORWARD: LatestMajor
71+
DOTNET_CLI_TELEMETRY_OPTOUT: true
72+
DOTNET_NOLOGO: true
73+
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
74+
IN_FILE: ${{ inputs.file }}
75+
IN_OUTPUT: ${{ inputs.output }}
76+
IN_OUTPUT_NAME: ${{ inputs.output-name }}
77+
IN_LIBRARIES: ${{ inputs.libraries }}
78+
IN_PROTECTIONS: ${{ inputs.protections }}
79+
IN_PRESET: ${{ inputs.preset }}
80+
IN_OBFUSCATION_FILE: ${{ inputs.obfuscation-file }}
81+
IN_PROTECTIONS_FILE: ${{ inputs.protections-file }}
82+
IN_CRITICALS_FILE: ${{ inputs.criticals-file }}
83+
IN_LOGGING_FILE: ${{ inputs.logging-file }}
84+
IN_NO_WATERMARK: ${{ inputs.no-watermark }}
85+
IN_NO_LOGO: ${{ inputs.no-logo }}
86+
IN_STRONG_NAME_KEY: ${{ inputs.strong-name-key }}
87+
IN_EXTRA_ARGS: ${{ inputs.extra-args }}
88+
IN_VERSION: ${{ inputs.version }}
89+
run: |
90+
set -euo pipefail
91+
92+
# Idempotent local install, no global PATH munging.
93+
TOOL_DIR="${RUNNER_TEMP:-/tmp}/bitmono-tool"
94+
if [ -n "$IN_VERSION" ]; then
95+
dotnet tool install --tool-path "$TOOL_DIR" BitMono.GlobalTool --version "$IN_VERSION"
96+
else
97+
dotnet tool install --tool-path "$TOOL_DIR" BitMono.GlobalTool
98+
fi
99+
100+
out="$IN_OUTPUT"
101+
[ -z "$out" ] && out="$(dirname "$IN_FILE")/output"
102+
103+
args=(-f "$IN_FILE" -o "$out")
104+
[ -n "$IN_OUTPUT_NAME" ] && args+=(-n "$IN_OUTPUT_NAME")
105+
[ -n "$IN_LIBRARIES" ] && args+=(-l $IN_LIBRARIES) # word-split intentional: multiple dirs
106+
[ -n "$IN_PROTECTIONS" ] && args+=(-p $IN_PROTECTIONS) # word-split intentional: multiple names
107+
[ -n "$IN_PRESET" ] && args+=(--preset "$IN_PRESET")
108+
[ -n "$IN_OBFUSCATION_FILE" ] && args+=(--obfuscation-file "$IN_OBFUSCATION_FILE")
109+
[ -n "$IN_PROTECTIONS_FILE" ] && args+=(--protections-file "$IN_PROTECTIONS_FILE")
110+
[ -n "$IN_CRITICALS_FILE" ] && args+=(--criticals-file "$IN_CRITICALS_FILE")
111+
[ -n "$IN_LOGGING_FILE" ] && args+=(--logging-file "$IN_LOGGING_FILE")
112+
[ "$IN_NO_WATERMARK" = "true" ] && args+=(--no-watermark)
113+
[ "$IN_NO_LOGO" = "true" ] && args+=(--nologo)
114+
[ -n "$IN_STRONG_NAME_KEY" ] && args+=(--strong-name-key "$IN_STRONG_NAME_KEY")
115+
116+
echo "::group::BitMono obfuscation"
117+
"$TOOL_DIR/bitmono.console" "${args[@]}" $IN_EXTRA_ARGS
118+
echo "::endgroup::"
119+
120+
echo "output=$out" >> "$GITHUB_OUTPUT"

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Table of Contents:
1717

1818
usage/how-to-use
1919
usage/msbuild-integration
20+
usage/github-actions
2021
usage/native-aot
2122
usage/assembly-signing
2223
usage/nuget-configuration
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
GitHub Actions (CI/CD)
2+
======================
3+
4+
Obfuscate in your CI pipeline without touching your source or your ``.csproj``. You build your project
5+
as usual, then point the action at the compiled assembly and it comes back obfuscated. Nothing about
6+
obfuscation lives in your repo, no package reference, no MSBuild properties, no config files unless you
7+
actually want them.
8+
9+
This is the opposite trade-off from :doc:`msbuild-integration`. The MSBuild package obfuscates *inside*
10+
your build and travels with the project; the action obfuscates *outside* it, in the pipeline only. Pick
11+
whichever fits, if you'd rather keep your project clean and only protect what ships from CI, this is it.
12+
13+
Under the hood the action just installs ``BitMono.GlobalTool`` from nuget.org and runs it, so you can do
14+
the exact same thing by hand (see `Without the action`_ below). The action is only there to save you the
15+
boilerplate.
16+
17+
Requirements
18+
------------
19+
20+
The .NET SDK has to be on the runner. If you built your project in the same job you already have it,
21+
otherwise add ``actions/setup-dotnet`` before the BitMono step. BitMono's tool runs on .NET 6–9; on a
22+
.NET 10-only runner it still works, the action rolls forward automatically.
23+
24+
Quick start
25+
-----------
26+
27+
.. code-block:: yaml
28+
29+
jobs:
30+
build:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@v7
34+
- uses: actions/setup-dotnet@v5
35+
with:
36+
dotnet-version: 9.x
37+
- run: dotnet build -c Release
38+
39+
- uses: sunnamed434/BitMono@0.43.0 # pin the latest release tag
40+
with:
41+
file: bin/Release/net9.0/MyApp.dll
42+
preset: Maximum
43+
44+
That's it. The obfuscated assembly lands in ``bin/Release/net9.0/output/MyApp.dll`` (or set ``output:``
45+
to put it elsewhere). Upload it with ``actions/upload-artifact``, ship it, whatever you need.
46+
47+
Inputs
48+
------
49+
50+
Every input maps one-to-one to a CLI option, so anything ``BitMono.CLI`` can do you can do here. Only
51+
``file`` is required.
52+
53+
.. list-table::
54+
:header-rows: 1
55+
:widths: 25 75
56+
57+
* - Input
58+
- Description
59+
* - ``file``
60+
- **Required.** Path to the compiled assembly to obfuscate, e.g. ``bin/Release/net9.0/MyApp.dll``.
61+
* - ``output``
62+
- Output directory. Default: an ``output`` folder next to ``file``.
63+
* - ``output-name``
64+
- Output file name. Default: keeps the original name.
65+
* - ``libraries``
66+
- Space-separated dependency directories, e.g. ``bin/Release/net9.0``.
67+
* - ``protections``
68+
- Space-separated protections, e.g. ``FullRenamer StringsEncryption``. Overrides preset/JSON.
69+
* - ``preset``
70+
- Protection preset: ``Minimal``, ``Balanced`` or ``Maximum``.
71+
* - ``obfuscation-file`` / ``protections-file`` / ``criticals-file`` / ``logging-file``
72+
- Paths to the JSON config files (same formats as the CLI). Used only when you pass them.
73+
* - ``no-watermark``
74+
- Set to ``true`` to disable the BitMono watermark.
75+
* - ``no-logo``
76+
- Set to ``true`` to suppress the BitMono ASCII banner on startup (``--nologo``).
77+
* - ``strong-name-key``
78+
- Path to a ``.snk`` to re-sign the obfuscated assembly. See :doc:`assembly-signing`.
79+
* - ``extra-args``
80+
- Anything else passed straight to the CLI verbatim, for options not listed above.
81+
* - ``version``
82+
- ``BitMono.GlobalTool`` version to install. Default: latest. See `Versioning`_.
83+
84+
Prefer config files? It works the same as everywhere else, commit your ``protections.json`` /
85+
``criticals.json`` / ``obfuscation.json`` and point the matching inputs at them. See :doc:`how-to-use`
86+
for the schemas.
87+
88+
Versioning
89+
----------
90+
91+
The action lives in the BitMono repo, so it doesn't have a version of its own, you reference it by a
92+
BitMono release tag. ``sunnamed434/BitMono@0.43.0`` reads the action from the repo at tag ``0.43.0``.
93+
Every release you'd normally cut is automatically a usable action version, nothing extra to publish.
94+
95+
There are two things you can pin, and they're independent:
96+
97+
- the **tag** after ``@`` decides which version of the action wrapper you run.
98+
- the **``version``** input decides which version of the obfuscator (the nuget tool) it installs.
99+
Defaults to latest.
100+
101+
For a build that produces the same bytes months from now, pin both to the same number:
102+
103+
.. code-block:: yaml
104+
105+
- uses: sunnamed434/BitMono@0.43.0
106+
with:
107+
file: bin/Release/net9.0/MyApp.dll
108+
version: 0.43.0
109+
110+
Use the newest tag from the `releases page <https://github.com/sunnamed434/BitMono/releases>`_.
111+
112+
Without the action
113+
------------------
114+
115+
You don't actually need the action, it's a thin wrapper over the global tool. If you'd rather not pull a
116+
third-party action into your workflow, two ``run`` steps do the same job:
117+
118+
.. code-block:: yaml
119+
120+
- run: dotnet tool install --global BitMono.GlobalTool
121+
- run: bitmono.console -f bin/Release/net9.0/MyApp.dll --preset Maximum
122+
123+
The action just saves you the install dance, the PATH handling and the argument mapping.
124+
125+
Troubleshooting
126+
---------------
127+
128+
The CLI exits non-zero when obfuscation fails, so a broken run fails the job, no silent pass. Read the
129+
log under the ``BitMono obfuscation`` group in the step output, it has the full command and the
130+
obfuscator's messages. See :doc:`troubleshooting`.

0 commit comments

Comments
 (0)