The Problem
When Apate obfuscates a crate that uses clap derives for CLI parsing, the obfuscated binary's CLI becomes unusable:
// Before obfuscation — clean CLI
#[derive(Parser)]
struct Cli {
#[arg(long)]
input: PathBuf, // generates --input flag
#[command(subcommand)]
command: Commands,
}
enum Commands {
Encrypt { ... }, // generates `encrypt` subcommand
Decrypt { ... }, // generates `decrypt` subcommand
}
After obfuscation, --input becomes --__a3b2 and encrypt becomes b9c5. The binary compiles and runs, but the CLI is gibberish. You can see this in action by running --help on Apate's own Level 1 output.
The Fix
Detect structs/enums annotated with #[derive(Parser)], #[derive(Subcommand)], #[derive(Args)], or #[derive(ValueEnum)], and preserve their field names, variant names, and doc comments — since clap uses all of these at runtime.
Important: This is source code obfuscation, not binary obfuscation. Apate works at the AST level using syn. No compiler internals, no LLVM, no binary manipulation.
Scope
What to preserve:
- Field idents on clap-derived structs (they become
--long flag names)
- Enum variant idents on
#[derive(Subcommand)] enums (they become subcommand names)
- Doc comments (
///) on clap-annotated items (clap uses them as --help text)
What to still obfuscate:
- All internal logic, local variables, non-clap types
- Everything that isn't part of the external CLI interface
Implementation
Step 1: Detection helper (beginner-friendly)
Add a has_clap_derive() function that checks if an item has a clap derive attribute:
fn has_clap_derive(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
if attr.path().is_ident("derive") {
if let Ok(nested) = attr.parse_args_with(
syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated,
) {
return nested.iter().any(|path| {
path.segments.last().map_or(false, |seg| {
matches!(
seg.ident.to_string().as_str(),
"Parser" | "Subcommand" | "Args" | "ValueEnum"
)
})
});
}
}
false
})
}
Step 2: Collect preserved names
Walk the AST pre-rename, find clap-derived structs/enums, and collect their field/variant names into a HashSet<String> of names to skip.
Step 3: Wire into rename pass
Add the preserved names to the blocklist in both the heuristic renamer and the SemanticRenamer path.
Step 4: Preserve doc comments in strip pass
Skip stripping doc attributes on items that have clap derives.
Verification
- Self-hosting all 3 levels:
cargo build succeeds
- Obfuscated binary:
apate --help shows correct flag names and subcommands
- Roundtrip:
apate decrypt still restores byte-for-byte
Files involved
| File |
Change |
src/passes/strip.rs |
Skip doc-stripping on clap items |
src/passes/rename.rs |
Skip renaming clap field/variant names |
src/passes/homoglyph.rs |
No change needed (inherits from rename) |
src/passes/strings.rs |
No change needed (already skips attributes) |
The Problem
When Apate obfuscates a crate that uses
clapderives for CLI parsing, the obfuscated binary's CLI becomes unusable:After obfuscation,
--inputbecomes--__a3b2andencryptbecomesb9c5. The binary compiles and runs, but the CLI is gibberish. You can see this in action by running--helpon Apate's own Level 1 output.The Fix
Detect structs/enums annotated with
#[derive(Parser)],#[derive(Subcommand)],#[derive(Args)], or#[derive(ValueEnum)], and preserve their field names, variant names, and doc comments — since clap uses all of these at runtime.Important: This is source code obfuscation, not binary obfuscation. Apate works at the AST level using
syn. No compiler internals, no LLVM, no binary manipulation.Scope
What to preserve:
--longflag names)#[derive(Subcommand)]enums (they become subcommand names)///) on clap-annotated items (clap uses them as--helptext)What to still obfuscate:
Implementation
Step 1: Detection helper (beginner-friendly)
Add a
has_clap_derive()function that checks if an item has a clap derive attribute:Step 2: Collect preserved names
Walk the AST pre-rename, find clap-derived structs/enums, and collect their field/variant names into a
HashSet<String>of names to skip.Step 3: Wire into rename pass
Add the preserved names to the blocklist in both the heuristic renamer and the
SemanticRenamerpath.Step 4: Preserve doc comments in strip pass
Skip stripping doc attributes on items that have clap derives.
Verification
cargo buildsucceedsapate --helpshows correct flag names and subcommandsapate decryptstill restores byte-for-byteFiles involved
src/passes/strip.rssrc/passes/rename.rssrc/passes/homoglyph.rssrc/passes/strings.rs