Cross-platform App Hardening (DexGuard-class), Enterprise-gated - #5527
Cross-platform App Hardening (DexGuard-class), Enterprise-gated#5527shai-almog wants to merge 19 commits into
Conversation
Adds a single hardening layer that renames classes/methods/fields, encrypts string constants and obfuscates control flow across every port (Android, iOS/ ParparVM, JavaScript, native desktop) from one bytecode transform, integrated with Crash Protection so obfuscated stack traces are still symbolicated. Engine (new maven/cn1-hardening, run as a forked process so it is single-sourced with the build daemon and carries its own ProGuard/ASM): demux the fat jar, rename with ProGuard using a prefixed dictionary that avoids the ParparVM NativeSymbolIndex culler pathology, encrypt LDC literals and static-final ConstantValue strings with a per-class decoder, opaque-predicate control flow on safe platforms, ParparVM mangle-collision guard, CheckClassAdapter verification, and a cross-platform mapping. Android keeps R8 as its sole renamer. Symbolication (new maven/cn1-retrace): ProGuard mapping parse/chain plus the ParparVM trace-string parser that java.lang.Throwable.getStackTrace() now mirrors on device, and a local retrace CLI. Crash payload gains rawStack/traceFormat/ mappingId/hardenLevel; PiiScrubber.scrubRawStack; cause-chain capture. Surface/entitlement: harden.* build hints, HardeningPreflight (fail the build on local/source targets, invalid level, on-device-debug), Executor.hardenSourceJar/ runBuild wiring, a read-only Hardening status API, and the App-Hardening developer guide chapter. Also fixes the invalid build_key literal, the BuildHintEditor grouped-Select values lookup, and the "obfuscates by default" overclaim in the security chapter. Tests: 25 unit tests across the two modules and the crash payload (full ProGuard round-trip, string round-trip + plaintext-absence, control-flow verification, mapping retrace, trace-format detection, pre-flight truth table). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae40837827
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
There was a problem hiding this comment.
Pull request overview
Introduces an Enterprise-gated, cross-platform “App Hardening” pipeline (bytecode-level renaming + string encryption + optional control-flow obfuscation) and integrates it with Crash Protection so hardened builds can still be symbolicated using a retained mapping.
Changes:
- Adds new Maven modules:
cn1-hardening(engine) andcn1-retrace(mapping/trace parsing + retrace CLI). - Integrates hardening into the Maven plugin build flow (preflight checks + engine invocation + platform IDs), and updates Crash Protection payload schema to include
rawStack/traceFormat/mappingId/hardenLevel. - Improves ParparVM
Throwable.getStackTrace()by parsing the native stack string into structured frames.
Reviewed changes
Copilot reviewed 54 out of 55 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| vm/JavaAPI/src/java/lang/Throwable.java | Parse ParparVM stack text into StackTraceElements |
| tests/core/test/com/codename1/crash/CrashReportPayloadTest.java | Tests for traceFormat + hardening fields JSON |
| Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java | Adds hardening build-hint schema defaults |
| Ports/JavaSE/src/com/codename1/impl/javase/BuildHintEditor.java | Fix grouped Select .values resolution |
| maven/pom.xml | Adds hardening/retrace modules; bumps ProGuard |
| maven/codenameone-maven-plugin/src/test/java/com/codename1/maven/HardeningPreflightTest.java | Unit tests for hardening preflight matrix |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/HardeningPreflight.java | Client-side preflight validation logic |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/CN1BuildMojo.java | Invoke preflight; route local builds via runBuild |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/WindowsNativeBuilder.java | Provides hardening platform id |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/LinuxNativeBuilder.java | Provides hardening platform id |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/JavaScriptBuilder.java | Provides hardening platform id |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/IPhoneBuilder.java | Provides hardening platform id |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/Executor.java | Fork/execute hardening engine; mappingId/buildKey handling |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/AndroidGradleBuilder.java | Android: disable engine renaming; mappingId + encoded build_key |
| maven/codenameone-maven-plugin/pom.xml | Embeds shaded hardening jar into plugin resources |
| maven/cn1-retrace/src/test/java/com/codename1/retrace/ParparVmTraceParserTest.java | Golden tests for ParparVM trace parser |
| maven/cn1-retrace/src/test/java/com/codename1/retrace/MappingFileTest.java | Tests for mapping parse + retrace + chaining |
| maven/cn1-retrace/src/main/java/com/codename1/retrace/RetraceMain.java | Standalone retrace CLI entrypoint |
| maven/cn1-retrace/src/main/java/com/codename1/retrace/ParparVmTraceParser.java | Parses ParparVM text traces; rejects JS stacks |
| maven/cn1-retrace/src/main/java/com/codename1/retrace/MappingFile.java | Parses/inverts ProGuard mapping for retrace |
| maven/cn1-retrace/src/main/java/com/codename1/retrace/MappingChain.java | Applies multiple mappings in order |
| maven/cn1-retrace/src/main/java/com/codename1/retrace/Frame.java | Frame DTO for retrace pipeline |
| maven/cn1-retrace/pom.xml | Module build + shaded standalone jar |
| maven/cn1-hardening/src/test/java/com/codename1/hardening/StringEncryptTransformTest.java | End-to-end string encryption tests |
| maven/cn1-hardening/src/test/java/com/codename1/hardening/fixture/Secrets.java | Fixture class for encryption tests |
| maven/cn1-hardening/src/test/java/com/codename1/hardening/fixture/Helper.java | Fixture class for rename tests |
| maven/cn1-hardening/src/test/java/com/codename1/hardening/ControlFlowTransformTest.java | Control-flow guard verification tests |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/StringEncryptTransform.java | ASM transform: encrypt LDC + ConstantValue strings |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/ProGuardRunner.java | Programmatic ProGuard rename runner |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/OutputVerifier.java | CheckClassAdapter verification gate |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/MappingWriter.java | Mapping header + mappingId computation |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/MangleCollisionCheck.java | Guards ParparVM symbol mangle collisions |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/Main.java | Engine CLI front-end + exit codes |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/JarDemuxer.java | Split/rebuild fat jar around class entries |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/InputJarKeepScanner.java | ASM scan for reflective keep rules |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/HardeningResult.java | Engine result model |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/HardeningRequest.java | Engine request model |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/HardeningProfile.java | Hardening levels + defaults |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/HardeningException.java | Engine exception type |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/HardeningEngine.java | Pipeline orchestration + safety gates |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/HardeningConfig.java | Parses harden.* hints into config |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/ControlFlowTransform.java | ASM opaque-predicate entry guards |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/Cn1NameFactory.java | zq-prefixed dictionary generator |
| maven/cn1-hardening/src/main/java/com/codename1/hardening/BuiltinKeepRules.java | Tier-1 keep rules + shared R8 rules |
| maven/cn1-hardening/pom.xml | Engine module deps + shaded standalone jar |
| docs/developer-guide/security.asciidoc | Correct obfuscation claims; reference hardening |
| docs/developer-guide/developer-guide.asciidoc | Include App-Hardening chapter |
| docs/developer-guide/Crash-Protection.asciidoc | Document new crash payload fields + hardening flow |
| docs/developer-guide/App-Hardening.asciidoc | New hardening chapter |
| CodenameOne/src/com/codename1/security/hardening/package-info.java | Package docs for Hardening API |
| CodenameOne/src/com/codename1/security/hardening/Hardening.java | Read-only hardening status API |
| CodenameOne/src/com/codename1/crash/PiiScrubber.java | Add scrubRawStack hook |
| CodenameOne/src/com/codename1/crash/CrashReportPayload.java | Add rawStack/traceFormat/mappingId/hardenLevel |
| CodenameOne/src/com/codename1/crash/CrashProtection.java | Capture/scrub rawStack; include in payload |
Suppressed comments (2)
maven/cn1-hardening/src/main/java/com/codename1/hardening/HardeningConfig.java:99
- Any unrecognized harden.strings value currently falls into the final else branch and enables maximum string encryption. This can turn on expensive/behaviour-changing encryption accidentally (e.g. a typo), so the parser should only treat explicit "all"/"true"/"2"/"3" as 'all' and otherwise fall back to the level defaults (or fail).
} else {
// "all", "true", "2", "3"
encConst = true;
encAll = true;
}
docs/developer-guide/Crash-Protection.asciidoc:93
- This paragraph states that on ParparVM ports the Java trace arrives only as rawStack and is parsed server-side. With Throwable.getStackTrace() now parsing the ParparVM stack string into structured frames on-device, the server may receive both; consider updating this sentence to reflect that rawStack is still
parparvm-textbut frames may be structured too (and rawStack may primarily be for the cause-chain / verbatim trace).
When a build is hardened (see <<app-hardening>>), the build server retains the cross-platform obfuscation mapping and symbolicates incoming reports against it, so a hardened build's crashes still land as readable, correctly-lined issues. Two things follow from how the mapping is retained: a report whose mapping has aged out of retention can no longer be retraced, and a locally hardened build -- whose mapping never reached the server -- can't be symbolicated at all. On the ParparVM ports (iOS, tvOS, watchOS, mac-native, Windows, Linux) the Java trace arrives as `rawStack` in the `parparvm-text` format and is parsed server-side; on the JavaScript port it arrives as a JavaScript engine stack (`js-error`) and is symbolicated best-effort through the source map.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
Cloudflare Preview
|
- Complete GPLv2+Classpath header on the 4 files the copyright gate flagged (BuildHintEditor had none; BuildHintSchemaDefaults + the two new tests were short). - Convert Hardening.java/package-info.java to /// markdown comments (core src gate). - Declare cn1-hardening:standalone as a runtime-scope plugin dependency so the reactor builds the engine before the plugin embeds it (fixes the antrun copy failing in CI); fix an illegal -- inside the new XML comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ProGuard 7.3.2 cannot read class files newer than JDK 20 (it fails on the JDK's own module classes), so the renamer must run on JDK 8-20 -- the cloud daemon forks the engine on JDK 17. The engine now fails with a clear message instead of a cryptic ProGuard error when renaming is requested on a too-new JVM, and the ProGuard-dependent tests skip (JUnit assumption) on JDK 21+ so the PR CI JDK-21 leg stays green. String encryption and control-flow tests have no such limit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc8822afde
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- FrameClassWriter: COMPUTE_FRAMES resolved common superclasses through the engine's own classloader, which lacks the app/library classes when run as a forked jar, so any class with a merge between application types aborted hardening. Resolve the hierarchy from a classloader over the (renamed) input classes plus the library jars, falling back to Object. Threaded through the string-encryption and control-flow transforms; unit-tested. (Codex P1) - Pass the FULLY QUALIFIED main class to the keep rules: getMainClass() is the simple name, so a bare value kept a default-package class and let ProGuard rename the real application class out from under the generated stub. Fixed in both the plugin and daemon config writers. (Codex P1) - The reactor dependency forcing cn1-hardening to build before the plugin (so the engine jar exists for the embed step) already landed in the prior commit. (Codex P1) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 56 out of 57 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Ports/JavaSE/src/com/codename1/impl/javase/BuildHintEditor.java:206
- Select-hint value parsing uses the last character of the values string as the delimiter (
charAt(len-1)), which breaks all of the comma-separated.valuesentries coming fromBuildHintSchemaDefaults(e.g.off,standard,aggressive,paranoidwill split ond). This prevents the new Hardening Select hints (and existing ones likenativeTheme) from populating correctly.
String separator = ""+valuesString.charAt(valuesString.length()-1);
ArrayList<String> values = new ArrayList<String>();
values.add("");
for (String value : valuesString.split(separator)) {
docs/developer-guide/Crash-Protection.asciidoc:86
- This section says ParparVM ports rely on
rawStackbecausegetStackTrace()yields only a formatted string, but this PR updatesvm/JavaAPI/java/lang/Throwable.getStackTrace()to parse the ParparVM text stack into structuredStackTraceElement[]. The docs should be updated to reflect that structured frames are now available on ParparVM (andrawStackis supplemental / for the full rendered text and cause chain).
- `frames[]` -- class / method / file / line / `native` flag per frame
- `rawStack` -- the pre-rendered Java stack (via `printStackTrace`, including the cause chain). On the ParparVM ports this is the readable Java trace, since `getStackTrace()` there yields a formatted string rather than structured frames
- `traceFormat` -- how the server should read `rawStack`: `structured`, `parparvm-text`, `js-error`, or `none`. Derived, never guessed
maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/Executor.java:2457
- The generic
catch (Exception e)wraps the error without the original cause, which makes diagnosing hardening failures much harder (stack trace is lost). Preserve the cause in theBuildException.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efa163b074
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 56 out of 57 changed files in this pull request and generated no new comments.
Suppressed comments (2)
vm/JavaAPI/src/java/lang/Throwable.java:144
- setStackTrace() updates parsedStack, but printStackTrace() prints the pre-rendered "stack" string field. Because setStackTrace() doesn't update "stack", callers that set a custom stack trace will still see the old/empty stack printed (and CrashProtection.safeRawStack() relies on printStackTrace()). Rebuild the "stack" string from the provided StackTraceElement[] so printStackTrace() reflects the updated trace.
CodenameOne/src/com/codename1/crash/CrashReportPayload.java:120 - CrashReportPayload documents hardenLevel as one of "off"/"standard"/"aggressive"/"paranoid", but it currently defaults to the empty string when the property isn't set. This makes the payload ambiguous for unhardened builds and diverges from Hardening.getLevel() which defaults to "off".
this.osVersion = d.getProperty("OSVer", "");
this.mappingId = d.getProperty("cn1.mappingId", "");
this.hardenLevel = d.getProperty("cn1.hardenLevel", "");
Locale loc = Locale.getDefault();
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Codex P1/P2: - harden.keep: split on newlines only (a ';' is legal inside a rule body). - Keep SourceFile,LineNumberTable so ParparVM/native traces keep real line numbers for retrace. - Honor constants-vs-all string mode: 'constants' encrypts only values declared as static-final String constants (and javac's inlined copies), 'all' encrypts every literal. - Propagate cn1.mappingId/cn1.hardened/cn1.hardenLevel into the request before stub generation; Android stub now stamps them (Hardening.isHardened(), crash report mappingId/level). - Supply the compile/platform classpath to ProGuard as library jars so an app method overriding a framework method is not renamed apart from its superclass. - Append harden.keep + the name-bound PropertyBusinessObject keep to Android's R8 config (Android keeps R8 as sole renamer). Build fixes: - CrashProtection.safeRawStack: build the raw stack with StringBuilder instead of java.io.PrintWriter, which the core's CLDC11 bootclasspath (ANT build) lacks. - Embed the engine jar via maven-dependency-plugin:copy (resolves the standalone artifact from the reactor/repo) so partial plugin-only CI builds no longer fail copying from an unbuilt sibling target/. - Keep the test resource bytes ASCII (explicit byte[] rather than a non-ASCII literal). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 605f722c41
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…, Select delimiters) - OutputVerifier: pass the input/library hierarchy classloader to CheckClassAdapter.verify so the final verification pass resolves application types instead of loading them from the engine's classpath (a class with a merge between app types would otherwise fail verification). (P1) - Keep every class named by a META-INF/services/* descriptor (the service interface and each provider), since the descriptors are copied verbatim and ServiceLoader would break if they were renamed; regression-tested. (P1) - Terminate the hardening Select .values lists with their delimiter, which BuildHintEditor reads as the last character, so the simulator shows the real options instead of splitting on a letter. (P2) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Compared 12 screenshots: 12 matched. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0fffb4c59f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 57 out of 57 changed files in this pull request and generated no new comments.
Suppressed comments (5)
maven/cn1-hardening/src/main/java/com/codename1/hardening/JarDemuxer.java:120
rebuild()doesn’t close itsZipOutputStream. Even with an explicitfinish(), closing the stream is the reliable way to release the deflater/native resources and ensure the central directory is flushed.
FileOutputStream fo = new FileOutputStream(outJar);
try {
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fo));
for (Map.Entry<String, byte[]> e : classesByInternalName.entrySet()) {
ZipEntry entry = new ZipEntry(e.getKey() + ".class");
maven/cn1-hardening/src/main/java/com/codename1/hardening/JarDemuxer.java:145
readClasses()creates aZipInputStreambut never closes it. Closing only the underlyingFileInputStreamworks most of the time, but it’s safer and clearer to close the ZIP stream explicitly (try-with-resources).
FileInputStream fi = new FileInputStream(jar);
try {
ZipInputStream zis = new ZipInputStream(fi);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
Ports/JavaSE/src/com/codename1/impl/javase/BuildHintEditor.java:201
propName.indexOf("}}.")is assumed to be present here. If it isn’t (unexpected/third-party schema key), the currentsubstring(... + 3)call will produce an incorrect key (and can throw if the key is shorter), andvaluesString.charAt(valuesString.length()-1)will also throw on an empty values string. Consider guarding the index/length and making the value-splitting robust (comma-separated lists in this repo don’t end with a delimiter).
String valuesKey = propName.substring(0, propName.indexOf("}}.")+3) + "values";
String valuesString = System.getProperty(valuesKey);
if (valuesString == null) {
valuesString = System.getProperty("codename1.arg.{{ "+model.name+" }}.values");
}
CodenameOne/src/com/codename1/crash/CrashReportPayload.java:147
deriveTraceFormat()can classify a raw stack asparparvm-texteven if the frame body contains spaces. That diverges from the on-device ParparVM parser logic (which rejects frames containing spaces) and could send the server down the wrong parse path for some JS engine stack formats.
int at = rawStack.indexOf(" at ");
if (at >= 0) {
int lineEnd = rawStack.indexOf('\n', at);
String body = lineEnd < 0 ? rawStack.substring(at + 7) : rawStack.substring(at + 7, lineEnd);
if (body.indexOf('(') < 0 && body.indexOf('/') < 0 && body.indexOf('@') < 0) {
maven/cn1-hardening/src/main/java/com/codename1/hardening/JarDemuxer.java:103
split()createsZipInputStream/ZipOutputStreambut never closes them. Relying on only closing the underlyingFileInputStream/FileOutputStreamcan leak native resources and (for the output) risks incomplete ZIP finalization on some JVMs/filesystems. Use try-with-resources to ensure ZIP streams are closed.
This issue also appears in the following locations of the same file:
- line 116
- line 141
ZipInputStream zis = new ZipInputStream(fi);
FileOutputStream fo = new FileOutputStream(classesJarOut);
try {
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fo));
ZipEntry entry;
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 889912753c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 144 screenshots: 144 matched. |
Codex: - Preserve the JavaScript engine stack in rawStack: capture the platform's own printStackTrace(PrintStream) rendering instead of rebuilding from structured frames (which are empty on the JS port). Added printStackTrace(PrintStream) to the CLDC11 API and the ParparVM runtime Throwable; PrintStream (unlike PrintWriter) is in the restricted core API. - Stamp hardening metadata in the iOS stub too (shared Executor helper); Android already did. (ParparVM JS/native ports have no runtime-property stub yet -- same gap as build_key there.) - Derive the engine platform for Mac-native builds (harden.mac.enabled now applies). - Don't stamp the empty engine mapping's constant id on Android (R8 owns the real mapping); leave cn1.mappingId empty when the engine doesn't rename. - Fail an Android build that requests hardening renaming while android.enableProguard=false disables R8. Copilot: - Control-flow guard uses the 2-arg System.getProperty so it can't NPE when java.home is absent (Android). - The engine (Main) fails loudly on an invalid harden.level instead of treating it as off, and gates entitlement only when hardening is actually active so a per-platform opt-out works on a non-entitled account. - Clarify the docs/comment: getStackTrace() now returns structured frames on every port; rawStack complements them (and is the JS port's readable trace). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2ebbef816
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- JarDemuxer drops jar signature blocks (META-INF/*.SF|*.RSA|*.DSA|*.EC) when rebuilding, since renaming invalidates them and a verifying JarFile would throw SecurityException: Invalid signature file digest. - MappingFile maps distinct obfuscated->original line ranges (R8 / optimized ProGuard, e.g. 1:2:...:40:41) back to the source line instead of passing the device line through; tested. - HardeningPreflight honors per-platform opt-outs: a target with harden.<platform>.enabled=false is treated as off rather than rejected. - paranoid is now a genuinely stronger tier: control-flow inserts two nested opaque-predicate guards per method (intensity 2) vs one for aggressive; tested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- String encryption now processes Java 8 interface default/static method bodies (previously the whole interface was skipped, leaving their literals plaintext in all/paranoid mode). The synthesized decoder is public in an interface (private statics are 9+); interface constant fields are still left alone. Tested. - The engine no longer marks a build hardened when a non-off level has all its transforms individually disabled (harden.rename=false + harden.strings=off + no control flow): it returns SKIPPED_NOT_REQUESTED instead of rebuilding an unchanged jar and stamping cn1.hardened=true. Tested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fdc77f21a8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
- Interface decoder invocations use itf=true so ASM writes an InterfaceMethodref, not a Methodref -- otherwise an encrypted default/static interface method throws IncompatibleClassChangeError at run time. - Gate entitlement in the engine CLI on willApplyAnyTransform(cfg) rather than isActive(), so a level whose transforms are all disabled is skipped (not rejected as not-entitled), matching the SKIPPED path. - Embed the engine jar at the plugin's prepare-package phase, not generate-resources, so pr.yml's '-pl codenameone-maven-plugin -am ... test' (which only advances the upstream module through test, before its package/shade) no longer fails resolving the standalone classifier; package/install still embed it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Compared 217 screenshots: 217 matched. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6efa3efaf0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Track rename *requested* (intent) vs rename *enabled* (engine does it). On Android renameEnabled is false but R8 performs the requested rename, so willApplyAnyTransform now counts a rename-only Android build as hardened (transform 'rename:r8') instead of skipping it and leaving cn1.hardened=false. Tested. - The Android R8/enableProguard conflict check respects harden.and.enabled=false: an explicitly opted-out Android target no longer fails that check. - Reject an unknown harden.strings value (e.g. a typo 'constant') in the engine CLI instead of silently enabling the most invasive 'all' mode; the config also falls back to the level default rather than 'all' for an unrecognized value. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d354db3eed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 149 screenshots: 149 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
- The synthesized decoder returns an interned String, so reference (==) equality that Java guarantees for string literals/constants still holds after encryption (two decodes of the same literal, and a constant vs its inlined readers, are now the same object). Tested. - MappingFile retains the original range's end bound: a single-line original range (e.g. 1:3:...:40:40) collapses every covered line to that line, and a shorter original range is clamped instead of overshooting. Tested. - The plugin depends on the UNCLASSIFIED cn1-hardening artifact for reactor ordering (resolvable from target/classes during '-am ... test'), provided+optional with a wildcard exclusion so ProGuard/ASM stay off the plugin classpath; the shaded 'standalone' jar is still pulled by the dependency-plugin copy at package. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 71b9c6830c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The round-9 dependency comment contained a literal '--', which strict Maven XML parsers reject (ProjectBuildingException), cascading to every job that reads the plugin POM. Local Maven was lenient; CI is not. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Compared 143 screenshots: 143 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
- willApplyAnyTransform returns false when the platform is opted out (harden.<platform>.enabled=false), so a non-entitled build of an opted-out target is skipped rather than rejected as not-entitled. Tested. - Seed the rename dictionary: Cn1NameFactory.writeDictionary shifts the starting word by the seed / build key, so harden.seed actually changes the mapping (and the same seed reproduces it) instead of every build getting identical names. Tested. - Make ParparVM String.intern() atomic (synchronized on the shared pool), so concurrent interning of equal decoded literals returns the same object and can't corrupt the pool -- the port-specific root cause behind the decoder's canonical-string guarantee. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 495a599908
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Control-flow guard derives its predicate from Runtime.getRuntime(). availableProcessors() (contractually >= 1, unfoldable) instead of a system property whose value could be present-but-empty and collapse the guard into its dead arm. - IPhoneBuilder reports the 'mac' hardening platform whenever macNative.enabled=true (the signal the native-Mac target actually sets), so harden.mac.enabled applies to the Mac output; the previous ios.enabled check was never set by any producer. - Docs: harden.keep is one-rule-per-line (newline-separated only); stop documenting ';'-separation, which the parser can't use because ';' is legal inside a rule body. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c94e6dd9b8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- InputJarKeepScanner now also collects static-final String field ConstantValue attributes, so a class named for reflection only in a constant field (never an LDC) is kept instead of renamed. - The engine exports its derived keep rules to a --r8keep file; on Android (where R8 is the sole renamer and the engine does not rename) Executor passes the file and AndroidGradleBuilder feeds it to proguard.cfg, so reflectively referenced classes reach R8 rather than being renamed out from under the lookup. - MangleCollisionCheck runs only for the ParparVM-C targets (ios/mac/watch/tv/win/linux) whose symbol mangle can actually alias two names; on Android/JavaSE a.b_c and a.b.c stay distinct, so the check no longer aborts legal builds. - The native-Mac targets resolve their harden.<platform>.enabled opt-out from the build target (mac), matching IPhoneBuilder, instead of the platform=ios they run under. - Two regression tests: scanner keeps a class named only by a field constant; Android run exports reflection + main + harden.keep rules to the R8 keep file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b625567eec
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Stamp cn1.mappingId / cn1.hardened / cn1.hardenLevel in the JavaScript, Linux and Windows launchers via the shared hardeningRuntimeProperties helper, so Hardening.isHardened() and crash payloads carry the mapping id / level on those ports too (parity with iOS and Android). On JavaScript the stamp runs right after ParparVMBootstrap.bootstrap returns, when Display is live. - Parse the Android harden.rename / harden.and.enabled opt-outs with a shared tri-state helper (Executor.hardenBoolArg) matching HardeningConfig.boolTri, so harden.rename=off and =0 behave like =false instead of being misread as 'renaming still requested' and rejecting the build with R8 disabled. Regression test HardeningBooleanArgTest. - Add 'symbolicates' to languagetool-accept.txt (fixes the red developer-guide quality gate: 2 MORFOLOGIK matches on the Crash-reports paragraph). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: df007c4400
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -417,6 +422,12 @@ private File writeLauncher(File workDir, String launcherName, String packageName | |||
| } | |||
| } | |||
| pw.println(" ParparVMBootstrap.bootstrap(new " + mainClass + "());"); | |||
There was a problem hiding this comment.
Stamp JavaScript metadata before lifecycle callbacks
In hardened JavaScript builds, this call runs the application's lifecycle before the following metadata stamp: ParparVMBootstrap.bootstrap() calls bootstrap.run(), which invokes both lifecycle.init() and lifecycle.start(). Therefore Hardening.isHardened() returns false during normal initialization, and a crash from either callback is uploaded without the mapping ID or level. Fresh evidence beyond the earlier launcher-metadata fix is the synchronous lifecycle execution inside Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/ParparVMBootstrap.java; arrange for the properties to be set after Display.init() but before run().
Useful? React with 👍 / 👎.
| cfg.getPlatform(), req.getBuildKey()); | ||
| } | ||
|
|
||
| HardeningResult result = HardeningResult.hardened(req.getOutputJar(), mappingFile); |
There was a problem hiding this comment.
Skip builds when no requested transform changes anything
When the effective configuration predicts a transform but the input contains no eligible targets—for example, harden.level=standard, harden.rename=false, and no static-final string longer than two characters—encryptedStrings and guardedMethods remain zero and transformsApplied is empty, yet this still returns HARDENED after adding only the marker. The executor consequently stamps cn1.hardened=true for an otherwise unchanged application. Fresh evidence beyond the earlier empty-configuration fix is this data-dependent no-op path; return a skipped result when the completed transform set is empty.
Useful? React with 👍 / 👎.
| |Class/method/field renaming |-- |yes |yes |yes | ||
| |String encryption |-- |constants |all |all + reflective names | ||
| |Control-flow obfuscation |-- |-- |yes |yes + opaque predicates | ||
| |Debug / line-number stripping |-- |yes |yes |yes |
There was a problem hiding this comment.
Do not claim hardening strips line-number metadata
This table tells users that every enabled level strips debug and line-number data, but the generated ProGuard flags explicitly preserve SourceFile and LineNumberTable, and Android's generated R8 configuration preserves the same attributes so crash lines remain available. A developer choosing a hardening tier on the premise that this metadata is removed therefore receives a materially different security posture; either implement the advertised stripping without breaking retracing or document that these attributes remain.
Useful? React with 👍 / 👎.
The round-13 hardening-metadata stamp emits Display.getInstance().setProperty(...) into the generated JavaScript launcher, but the launcher had no import for com.codename1.ui.Display (unlike the Linux/Windows bootstrap stubs, which already import it), breaking the initializr JavaScript build in the Build website CI step. Add the import. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9fa7384837
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Propagate the mapping id / hardened flag / level into the request BEFORE the | ||
| // builder generates its stubs, so the stubs stamp them as runtime properties | ||
| // (Hardening.isHardened(), the crash report's mappingId/hardenLevel). | ||
| request.putArgument("cn1.mappingId", lastHardeningMappingId); |
There was a problem hiding this comment.
Stamp the Android R8 mapping identifier
For Android rename-enabled builds, the engine intentionally leaves lastHardeningMappingId empty because the actual mapping is produced later by R8, but this assignment then stamps that empty value into the request and AndroidGradleBuilder embeds it in CN1_MAPPING_ID. Consequently hardened Android crash reports cannot identify the exact R8 mapping used for retracing; the R8 mapping's identifier needs to be computed and propagated instead of overwriting the property with the empty engine mapping ID.
Useful? React with 👍 / 👎.
| if (ldc.cst instanceof String && shouldEncryptLiteral((String) ldc.cst)) { | ||
| String plain = (String) ldc.cst; | ||
| ldc.cst = encode(plain, base); |
There was a problem hiding this comment.
Avoid oversized encrypted LDC constants
When an eligible literal is large but valid in the original class file, this replacement can make the transformed class unwritable: for example, a 30,000-character ASCII literal fits below the constant-pool modified-UTF-8 limit, while the XOR result consists mostly of three-byte characters and exceeds the 65,535-byte limit. ASM then throws IllegalArgumentException: UTF8 string too large during cn.accept(cw), so aggressive/paranoid hardening fails for otherwise valid applications; split or otherwise encode large payloads without placing the expanded ciphertext in one UTF-8 constant.
Useful? React with 👍 / 👎.
| // Channel 2: static final String ConstantValue attributes (both modes). Skipped on | ||
| // interfaces, whose fields are implicitly constant and have no rewritable init slot. | ||
| if (!isInterface) { | ||
| changed |= encryptStaticFinalStrings(cn, base); |
There was a problem hiding this comment.
Encrypt interface ConstantValue fields
When an interface declares a string constant such as String TOKEN = "secret", this branch leaves its ConstantValue attribute untouched, so standard, aggressive, and paranoid builds still contain that plaintext despite claiming to handle the field-constant channel. Java 8 interfaces can have a <clinit> for non-constant field initialization, so these values can be moved to a decoder call just as class fields are rather than skipping every interface field.
Useful? React with 👍 / 👎.
|
Compared 146 screenshots: 146 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 181 screenshots: 181 matched. |
What
Adds App Hardening — a single Enterprise-gated layer that renames classes/methods/fields, encrypts string constants, and obfuscates control flow across every port (Android, iOS/ParparVM, JavaScript, native desktop) from one bytecode transform, integrated with Crash Protection so obfuscated traces are still symbolicated.
Runs on the cloud build server; this repo carries the engine and the client surface. The matching BuildDaemon PR turns it on (entitlement gate + engine invocation + mapping upload).
Pieces
maven/cn1-hardening— forked-process ProGuard+ASM engine. Demux → ProGuard rename with azq-prefixed dictionary that avoids the ParparVMNativeSymbolIndexdead-code-culler pathology → string encryption (LDC andstatic finalConstantValue, per-class decoder) → opaque-predicate control flow (safe platforms) → mangle-collision guard →CheckClassAdapterverify → mapping with provenance +mappingId. Android keeps R8 as its sole renamer.maven/cn1-retrace— ProGuard mapping parse/chain + the ParparVM trace-string parser that on-deviceThrowable.getStackTrace()now mirrors, + a local retrace CLI.rawStack/traceFormat/mappingId/hardenLevelpayload fields,PiiScrubber.scrubRawStack, cause-chain capture.harden.*hints,HardeningPreflight(fails local/source targets, invalid level, on-device-debug),Executor.hardenSourceJar/runBuild, read-onlyHardeningstatus API, simulator hint schema, newApp-Hardening.asciidoc.build_keyliteral,BuildHintEditorgrouped-Select.valueslookup,security.asciidoc"obfuscates by default" overclaim, ParparVMgetStackTrace()(fixes today's empty-frames iOS crash reports).Tests
25 unit tests green across the two modules and the crash payload: full ProGuard round-trip + behaviour preservation, string round-trip + plaintext-absence, control-flow verification, mapping retrace + chaining, trace-format detection (incl. V8/SpiderMonkey JS rejection), and the pre-flight truth table. Core, plugin, and javase compile.
Follow-ups (not blocking)
Device end-to-end retrace and the "did obfuscation break the app" matrix need a real cloud build. Android R8
-applymapping/keep export,parparvm-symbols.txtnative-address symbolication, daemon runtime stamping ofcn1.mappingId/cn1.hardened,HardeningVerifier(Check 3), and SpotBugs-gate wiring for the new modules are noted for later.Pairs with the BuildDaemon
app-hardeningPR.🤖 Generated with Claude Code