Skip to content

Feature/zero config r8#23

Merged
adriantuk merged 26 commits into
mainfrom
feature/zero-config-r8
Jun 12, 2026
Merged

Feature/zero config r8#23
adriantuk merged 26 commits into
mainfrom
feature/zero-config-r8

Conversation

@ivolz

@ivolz ivolz commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Description

This PR hardens the Retrofit service layer with zero-config R8 compatibility, BouncyCastle relocation, initialization robustness improvements, and documentation updates.

R8 / ProGuard Hardening

  • Zero-config R8 rules: Added consumer-rules.pro with keep rules for Approov SDK, BouncyCastle, and message signing classes. These are automatically applied to consuming apps via consumerProguardFiles, eliminating the need for manual ProGuard configuration.
  • BouncyCastle relocation: Relocated BouncyCastle to io.approov.internal.retrofit.bouncycastle using the Shadow Gradle plugin to avoid classpath conflicts with apps that bundle their own BouncyCastle version.

Initialization Robustness

  • Simplified initialization: Delegated re-initialization decisions to the native platform SDK instead of maintaining complex local state tracking. The service layer now forwards the call and interprets the SDK response.
  • Null config rejection: initialize(null) now throws IllegalArgumentException with a clear message instead of silently failing.
  • Empty-config bypass guard: When already initialized with a valid config, a subsequent empty-config call is silently ignored — the service layer cannot be downgraded from protected to bypass mode.
  • State preservation on failure: SDK initialization failure no longer wipes the existing service-layer state — the previous operating mode (protected or bypass) is fully preserved.
  • Comment/options support: initialize now accepts a comment parameter forwarded to the native SDK for options: and reinit flows, documented in REFERENCE.md.

Documentation

  • README.md: Integrated quickstart content directly into the README for a self-contained onboarding experience.
  • REFERENCE.md: Documented initialize comment options, reinitialization semantics, and null/empty config behavior.
  • CHANGELOG.md: Added entries for all changes.
  • LICENSE: Updated copyright year.

CI

  • Removed hardcoded worker URL fallbacks from the build and test workflow.

Files Changed

File Change
ApproovService.java Initialization rewrite, null guard, empty-config bypass, state preservation
consumer-rules.pro New — zero-config R8 keep rules
build.gradle Shadow plugin for BouncyCastle relocation, consumer ProGuard rules
pom.xml Version updates
ApproovDefaultMessageSigning.java Minor adjustments for relocated BouncyCastle
README.md Integrated quickstart content
REFERENCE.md Initialization docs update
CHANGELOG.md New entries
ApproovServiceContractTest.java Test updates for new initialization behavior
ApproovServiceMiniSdkTest.java Test updates for null config and empty-config flows

Related: approov/core-project-approov#558

ivol and others added 16 commits May 19, 2026 20:30
- Remove service layer re-initialization guard (allowEnableAfterEmptyInitialization,
  reinit comment check, same-config early return)
- Service layer always resets its state then forwards non-empty config to the
  platform SDK; SDK returns boolean (false = already initialized) or throws
- Remove null guard for config parameter; null is not a valid contract value
- Fix test: different-config rejection now throws platform SDK exception, not
  service layer message
- Fix test: null config test updated to use empty string (bypass mode)
- Update REFERENCE.md: clarify 2-arg form is default (comment=null), 3-arg
  is the extended form; fix Kotlin comment type to String?
- Add null guard at top of initialize(): passing null config now throws
  IllegalArgumentException with a clear message directing callers to use ""
  for bypass mode; NPE is no longer the failure mode
- Clarify javadoc: config does not accept null, comment accepts null
- REFERENCE.md: expand isInitialized/isApproovEnabled descriptions with
  bypass-mode semantics and SDK-gating note; consistent with okhttp docs
- CHANGELOG.md [3.5.7]: add simplified initialize change (delegate to SDK),
  replace inaccurate Fixed entries with accurate null-rejection and
  2-arg null-comment fix descriptions
State is now only reset after Approov.initialize() confirms success.
When the SDK throws (e.g. different config), all prior operating state
(isInitialized, configString, pinningInterceptor, OkHttp builder) is preserved.

Per TESTING_REQUIREMENTS §17-18 (core-service-layers-testing).
Updated security policy to reflect supported versions and reporting process.
Use a service-layer-specific relocation prefix to prevent duplicate class
conflicts when approov-service-retrofit is combined with other Approov
service layers in the same Android application.
The default fallback URLs (replay.ivol.workers.dev / replay-unprotected.ivol.workers.dev)
were hardcoded in the workflow file, making them discoverable in the public
repository. Replaced with an explicit check: the workflow now fails with a
clear error if TESTING_REPLY_URL or TESTING_REPLY_URL_UNPROTECTED are not
configured as GitHub organisation or repository variables.

Copilot AI left a comment

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.

Pull request overview

Updates the Approov Retrofit service to be more “zero-config” for consumers by adding consumer ProGuard rules and shading BouncyCastle to avoid dependency collisions, while also simplifying/clarifying initialization semantics (including bypass mode and nullable comments).

Changes:

  • Added consumer ProGuard rules and wired them into the Android library build.
  • Shaded/relocated BouncyCastle and updated message-signing code to use the relocated package.
  • Simplified ApproovService.initialize behavior and updated tests/docs to reflect nullable comments and bypass/enable flows.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
REFERENCE.md Clarifies initialize overloads (incl. nullable comment) and semantics of isInitialized/isApproovEnabled.
README.md Adds dependency/setup/usage documentation for integrating the library.
LICENSE Updates copyright holder/year.
CHANGELOG.md Documents 3.5.7 changes (consumer rules, shading, initialization behavior adjustments).
approov-service/src/test/java/io/approov/service/retrofit/ApproovServiceMiniSdkTest.java Updates/reinforces initialization behavior expectations with Mini SDK tests.
approov-service/src/test/java/io/approov/service/retrofit/ApproovServiceContractTest.java Updates contract tests for nullable comment and null-config error behavior.
approov-service/src/main/java/io/approov/service/retrofit/ApproovService.java Implements new initialization flow and minor formatting/doc adjustments.
approov-service/src/main/java/io/approov/service/retrofit/ApproovDefaultMessageSigning.java Switches BouncyCastle imports to the relocated/shaded namespace.
approov-service/pom.xml Removes the transitive BouncyCastle runtime dependency entry.
approov-service/consumer-rules.pro Introduces consumer ProGuard rules to preserve Approov SDK/JNI interfaces.
approov-service/build.gradle Adds Shadow plugin + shading task, consumer ProGuard wiring, and dependencies for shaded jar.
.vscode/settings.json Adds VS Code Java build configuration setting.
.github/workflows/build_and_test.yml Requires worker endpoints via GitHub variables (no hardcoded fallback) and updates CI behavior accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread approov-service/src/main/java/io/approov/service/retrofit/ApproovService.java Outdated
Comment thread approov-service/build.gradle
Comment thread approov-service/consumer-rules.pro Outdated
Comment thread .github/workflows/build_and_test.yml Outdated
…ocument empty-token prefix-only behaviour

- ApproovTokenInterceptor: add null+empty guard before writing substituted
  header values and query parameter values. When getSecureString() returns
  null or empty, the original placeholder is preserved in place per
  TESTING_REQUIREMENTS §2 Missing Artifacts Fallback.
- REFERENCE.md: document token header emission behaviour when no real token
  is available, including prefix-only case when useApproovStatusIfNoToken=false.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 9 comments.

Comment thread CHANGELOG.md Outdated
Comment thread REFERENCE.md
Comment thread README.md
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread .github/workflows/build_and_test.yml
…, CI comments

- README.md: fix gradle.build→build.gradle (C1); add Groovy DSL variant for
  dependency snippet (C2); fix missing word in line 70 (C3); fix missing verb
  in line 89 (C4); fix crytographically→cryptographically (C5)
- CHANGELOG.md: fix BC relocated package name io.approov.internal.bouncycastle
  →io.approov.internal.retrofit.bouncycastle (C6)
- SECURITY.md: fix supported version table (<3.3→<3.5.0/3.4.0); fix
  recieve→receive typo (merged from feature/3.6.0 PR#21)
- consumer-rules.pro: fix comment to accurately describe what is retained (C9)
- .github/workflows/build_and_test.yml: remove stale fallback/precedence
  comment; script hard-fails on missing variables, no fallback (C10)
- ApproovService.java: fix succesfull→successful in getLastARC Javadoc (C11)

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 8 comments.

Comment thread SECURITY.md
Comment thread SECURITY.md Outdated
Comment thread REFERENCE.md
Comment thread .github/workflows/build_and_test.yml
Comment thread CHANGELOG.md Outdated
Comment thread REFERENCE.md Outdated
Comment thread CHANGELOG.md Outdated
…, SECURITY, CI

CHANGELOG.md:
- 'cryptography bounds' -> 'cryptography bindings' (B2)
- Clarify initialize state-reset wording: state reset only after SDK confirms
  success; failed call preserves existing state (B3)

REFERENCE.md:
- isInitialized(): prior state is preserved on failure, not flipped to false (C1)
- setUseApproovStatusIfNoToken: covers both empty-token-on-success AND failure
  statuses allowed to proceed by the active mutator (C2)

ApproovService.java:
- initialize() Javadoc: 'empty for no comment' -> 'or null for no comment' (D1)

SECURITY.md:
- 'less functionalities' -> 'less functionality' (G1)
- 'vulnerabilities to this project' -> 'in this project'; fix run-on sentence (G2)

.github/workflows/build_and_test.yml:
- Remove reference to non-existent CONTRIBUTING.md in CI error message (F2)

Not fixed:
- build.gradle shadow task (H1): works in CI; risk of regression without testing
- initialize state reset on same-config (I1): intentional per TESTING_REQUIREMENTS §1

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

adriantuk and others added 2 commits June 11, 2026 12:26
Mirror the parity improvement made on the Alamofire service layer
(approov-service-alamofire c10e11a) for cross-platform consistency.

A successful initialize — including the benign same-config no-op — resets
all service-layer configuration to defaults, as required by the shared
TESTING_REQUIREMENTS spec. This was undocumented and silent. Now:

- Log.w when a re-init discards previously applied configuration.
- Javadoc on initialize(context, config, comment) documents the reset
  contract and the full prior-state/config behavior matrix.
- REFERENCE.md gains a "Re-initialization and state reset" subsection.

No behavior change beyond the added warning log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
testInitializeWithEmptyConfigBuildsPlainClient and
testInitializeWithEmptyConfigCanLaterEnableApproov asserted that an empty
config applied to an already-protected service layer downgrades it to bypass
mode. That is the pre-rework behaviour; the new initialize correctly refuses
to downgrade a protected layer on a later empty config
(TESTING_REQUIREMENTS §1 "Empty Configuration after Valid Configuration"),
which is now covered by testInitializeWithValidThenEmptyConfigIgnoresEmptyConfig.

These two tests are meant to verify genuine bypass-from-scratch behaviour, so
reset the service layer to an uninitialized state (resetApproovServiceState)
before the empty-config initialize, restoring their original intent without
duplicating the new "stays protected" test. Full mini-SDK suite: 20/20.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@adriantuk adriantuk left a comment

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.

LGTM

@adriantuk adriantuk merged commit a1bd769 into main Jun 12, 2026
3 checks passed
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.

5 participants