Skip to content

Add Google Play Integrity attestation for mobile clients - #3951

Merged
ThaminduDilshan merged 1 commit into
thunder-id:mainfrom
Malith-19:feat/3452-play-integrity-attestation
Jul 16, 2026
Merged

Add Google Play Integrity attestation for mobile clients#3951
ThaminduDilshan merged 1 commit into
thunder-id:mainfrom
Malith-19:feat/3452-play-integrity-attestation

Conversation

@Malith-19

@Malith-19 Malith-19 commented Jul 13, 2026

Copy link
Copy Markdown
Member

Purpose

Mobile applications cannot safely hold a Flow Secret, so they currently cannot initiate an authentication flow directly via POST /flow/execute — a public authorization_code app is classified as not permitted to initiate directly, and embedded apps must present a Flow Secret. This PR lets an application prove its binary identity at flow initiation instead, using Google Play Integrity attestation.

When an application configures attestation, it initiates a new authentication flow by presenting a valid Play Integrity token in an Attestation-Token request header. ThunderID verifies the token against Google's Play Integrity API and confirms the attested app matches the registered package name and signing certificate before starting the flow.

Approach

Attestation is a client-level identity check, not an OAuth2 setting. It is modeled on the inbound client (providers.InboundClient), independent of any protocol profile, so it applies to any application type — embedded or OAuth2 — and is persisted in the existing INBOUND_CLIENT.PROPERTIES JSON column, requiring no schema change. In the API and console it is a top-level attestation field on the application.

Enforcement at flow initiation (flowexec). resolveFlowInitiationMode resolves the inbound client first. If attestation is configured it returns flowInitiationAttestation, which takes precedence over the protocol-based classification and is the only credential checked — any Flow Secret is ignored. The guard runs solely on new-flow initiation of an AUTHENTICATION flow; flow continuation (requests carrying an executionId) and other flow types are unaffected.

  • Missing token → 401 FES-1014 (ErrorAttestationRequired)
  • Token that fails verification → 401 FES-1015 (ErrorAttestationInvalid)

Verification (internal/attestation). A VerifierInterface decodes the token via Google's server-side decodeIntegrityToken API (google.golang.org/api/playintegrity/v1, authenticated with the app's service-account credentials via cloud.google.com/go/auth). The Google call sits behind a small internal seam so it is fully mocked in unit tests — no live calls in CI. Verification confirms the attested packageName, an intersection of the configured signing-certificate SHA-256 digests, and an appRecognitionVerdict of PLAY_RECOGNIZED. The verifier is behind an interface so Apple App Attest can be added later without touching the flow layer.

Write-only credentials. The Google service-account JSON is encrypted at rest with AES-GCM (via the runtime crypto provider), never returned in any GET/list/echo response, and preserved when omitted on update.

Console. The application's Advanced tab exposes an Attestation section (package name, signing-certificate digests, service-account credentials); the value is stored at the application level. The section is gated by a declarative per-template capability flag (capabilities.attestation in the template file), so it appears only for templates that support it — the mobile and wallet templates (covering their embedded variants via id normalization).

Key design notes / out of scope (follow-ups): nonce/challenge replay-binding is not yet implemented; Apple App Attest plugs into the same verifier interface later. Note that once attestation is configured there is no Flow-Secret fallback — a misconfigured attestation blocks initiation by design.

UI Preview

Screenshot 2026-07-13 at 14 05 39

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (docs/content/guides/guides/applications.mdx)
    • Ran Vale and fixed all errors and warnings
  • Tests provided.
    • Unit Tests (internal/attestation, flowexec, application)
    • Integration Tests (tests/integration/flow/authentication/attestation_flow_test.go, tests/integration/application)
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features
    • Added Android Google Play Integrity attestation configuration for applications.
    • Added Console settings to configure package names, signing certificates, and credentials.
    • Mobile applications can now submit an Attestation-Token when starting authentication flows.
    • Attestation credentials are securely stored and never returned in application responses.
  • Bug Fixes
    • Added clear unauthorized and forbidden responses for missing or invalid attestation.
  • Documentation
    • Documented attestation setup, token usage, and flow continuation behavior.

@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.20.0
Approved: ❌ No - Module not found in dependency registry

Dependency name: google.golang.org/api
Version: v0.287.1
Approved: ❌ No - Module not found in dependency registry

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@Malith-19 Malith-19 changed the title Add Google Play Integrity attestation for mobile clients [WIP] Add Google Play Integrity attestation for mobile clients Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from d99e269 to 4826108 Compare July 13, 2026 08:47
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.20.0
Approved: ❌ No - Module not found in dependency registry

Dependency name: google.golang.org/api
Version: v0.287.1
Approved: ❌ No - Module not found in dependency registry

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 6

🧹 Nitpick comments (3)
backend/internal/application/service_test.go (1)

1608-1682: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Good coverage for create+encrypt+strip; consider adding update-preserve and lookup-error cases.

This test validates the create path well. Given the error-handling gap flagged in encryptAttestationCredentialsForPersist (service.go, Lines 1659-1695), adding a test for UpdateApplication with omitted credentials — including a case where GetInboundClientByEntityID returns a non-"not found" error — would catch regressions on the preserve-on-update contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/application/service_test.go` around lines 1608 - 1682, The
current coverage only verifies credential handling during creation; extend the
attestation tests to cover UpdateApplication when credentials are omitted. Add a
successful update case that preserves existing encrypted credentials, and a
GetInboundClientByEntityID non-“not found” error case that propagates the error
instead of silently continuing, targeting
encryptAttestationCredentialsForPersist and the UpdateApplication flow.
api/application.yaml (1)

815-816: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

serviceAccountCredentials isn't marked writeOnly and is reused verbatim across request/response schemas.

The prose says the field is "Write-only: never returned in responses," but the same AndroidAttestation schema (via Attestation) is $ref'd from ApplicationRequest, ApplicationCompleteResponse, and ApplicationGetResponse alike, without writeOnly: true. This contradicts the field's own documentation and diverges from this file's existing convention — flowSecret is deliberately omitted from ApplicationGetResponse and duplicated as a separate response-only definition in ApplicationCompleteResponse rather than sharing one schema. Generated client SDKs/docs (Scalar) will show serviceAccountCredentials as present on GET/PUT/POST responses even though the backend strips it.

📝 Proposed fix: mark the field writeOnly
         serviceAccountCredentials:
           type: string
+          writeOnly: true
           description: >
             Google Cloud service account credentials (JSON) used to call the Play Integrity API.
             Write-only: never returned in responses. Omit on update to preserve the stored value.

Also applies to: 945-946, 1059-1060, 1340-1370

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/application.yaml` around lines 815 - 816, Update the
serviceAccountCredentials property in the AndroidAttestation schema to set its
OpenAPI writeOnly attribute to true, preserving the existing documentation and
schema references. Ensure this metadata applies wherever Attestation is reused
by ApplicationRequest, ApplicationCompleteResponse, and ApplicationGetResponse
so generated clients do not expose the field in responses.
frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/AttestationSection.tsx (1)

141-163: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Digest fields lack individually accessible names.

FormLabel htmlFor="attestation-digests-section" targets a <Stack> (a div), not a form control, and each digest TextField (line 154) has no distinguishing accessible name for screen reader users navigating the repeatable list.

♿ Proposed fix
-        <FormControl fullWidth>
-          <FormLabel htmlFor="attestation-digests-section">
+        <FormControl fullWidth>
+          <FormLabel id="attestation-digests-label">
             {t('applications:edit.advanced.attestation.labels.certificateSha256Digests')}
           </FormLabel>
@@
                   <TextField
                     fullWidth
                     id={`attestation-digest-${index}-input`}
+                    aria-label={`${t('applications:edit.advanced.attestation.labels.certificateSha256Digests')} ${index + 1}`}
                     value={digest}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/AttestationSection.tsx`
around lines 141 - 163, Update the mapped digest TextField in the digests
section to provide a unique accessible name for each repeated field, using the
digest index or equivalent position in its label/aria-label. Do not rely on
FormLabel htmlFor pointing to the surrounding Stack; preserve the existing
digest editing behavior and identifiers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/internal/application/service.go`:
- Around line 1659-1695: Update encryptAttestationCredentialsForPersist so a
failed GetInboundClientByEntityID lookup distinguishes a not-found result from
other errors; preserve the existing credential only when the record is found,
treat not-found as having no value to preserve, and return an internal error for
any other lookup failure so omitted credentials cannot overwrite stored data.

In `@backend/internal/attestation/playintegrity.go`:
- Around line 77-85: Update the attestation validation around the package-name
and certificate checks in the relevant validation function so configured
applications must provide both a package name and at least one registered
certificate digest. Reject incomplete configuration before accepting the
PLAY_RECOGNIZED verdict, and require the attested package and certificate digest
to match their configured identities.

In `@backend/internal/flow/flowexec/service.go`:
- Around line 273-295: Update verifyAttestation and the attestationVsvc.Verify
call to distinguish definitive invalid-token responses from verifier outages,
timeouts, and other transient failures; return ErrorAttestationInvalid only for
the former and the appropriate retriable server error for the latter. Create or
enforce a bounded request context with a deadline for this outbound verification
call, preserving cancellation and existing logging.

In `@docs/content/guides/guides/applications.mdx`:
- Line 196: Split the overlong sentence in the attestation-token documentation
into shorter sentences while preserving the existing 401 rejection conditions,
continuation-request exception, and Flow Execution API Reference link. Keep the
wording clear and ensure each resulting sentence stays within Vale’s 35-word
limit.

In
`@frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/AttestationSection.tsx`:
- Around line 64-69: Update the AttestationSection state synchronization around
packageName, digests, and credentials so local values refresh when the
attestation prop changes, including when Android configuration is replaced or
cleared. Add an effect keyed to the relevant attestation data that updates
packageName and digests from the current Android config and resets write-only
credentials appropriately, while preserving user edits until the prop changes.

In
`@frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsx`:
- Around line 95-103: Update currentAttestation in the edit-application advanced
settings flow to distinguish an explicitly cleared null from an undefined,
not-yet-edited value: use editedApp.attestation whenever it has been set,
including null, and fall back to application.attestation only when it is
undefined. Keep handleAttestationChange and the existing save behavior
unchanged.

---

Nitpick comments:
In `@api/application.yaml`:
- Around line 815-816: Update the serviceAccountCredentials property in the
AndroidAttestation schema to set its OpenAPI writeOnly attribute to true,
preserving the existing documentation and schema references. Ensure this
metadata applies wherever Attestation is reused by ApplicationRequest,
ApplicationCompleteResponse, and ApplicationGetResponse so generated clients do
not expose the field in responses.

In `@backend/internal/application/service_test.go`:
- Around line 1608-1682: The current coverage only verifies credential handling
during creation; extend the attestation tests to cover UpdateApplication when
credentials are omitted. Add a successful update case that preserves existing
encrypted credentials, and a GetInboundClientByEntityID non-“not found” error
case that propagates the error instead of silently continuing, targeting
encryptAttestationCredentialsForPersist and the UpdateApplication flow.

In
`@frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/AttestationSection.tsx`:
- Around line 141-163: Update the mapped digest TextField in the digests section
to provide a unique accessible name for each repeated field, using the digest
index or equivalent position in its label/aria-label. Do not rely on FormLabel
htmlFor pointing to the surrounding Stack; preserve the existing digest editing
behavior and identifiers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d6431ea3-01aa-498b-a53c-aba76c952b73

📥 Commits

Reviewing files that changed from the base of the PR and between a242b32 and d99e269.

⛔ Files ignored due to path filters (3)
  • backend/go.sum is excluded by !**/*.sum
  • backend/tests/mocks/flow/flowexecmock/FlowExecServiceInterface_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/flow/flowexecmock/attestationVerifier_mock.go is excluded by !**/*_mock.go
📒 Files selected for processing (43)
  • api/application.yaml
  • api/flow-execution.yaml
  • backend/.mockery.private.yml
  • backend/cmd/server/servicemanager.go
  • backend/go.mod
  • backend/internal/application/handler.go
  • backend/internal/application/init.go
  • backend/internal/application/init_test.go
  • backend/internal/application/service.go
  • backend/internal/application/service_test.go
  • backend/internal/attestation/VerifierInterface_mock_test.go
  • backend/internal/attestation/errors.go
  • backend/internal/attestation/integrityTokenDecoder_mock_test.go
  • backend/internal/attestation/interface.go
  • backend/internal/attestation/playintegrity.go
  • backend/internal/attestation/playintegrity_test.go
  • backend/internal/flow/flowexec/FlowExecServiceInterface_mock_test.go
  • backend/internal/flow/flowexec/attestationVerifier_mock_test.go
  • backend/internal/flow/flowexec/constants.go
  • backend/internal/flow/flowexec/error_constants.go
  • backend/internal/flow/flowexec/handler.go
  • backend/internal/flow/flowexec/handler_test.go
  • backend/internal/flow/flowexec/init.go
  • backend/internal/flow/flowexec/interface.go
  • backend/internal/flow/flowexec/service.go
  • backend/internal/flow/flowexec/service_test.go
  • backend/internal/inboundclient/store.go
  • backend/internal/system/constants/server_constants.go
  • backend/internal/system/i18n/core/defaults.go
  • backend/pkg/thunderidengine/providers/model.go
  • docs/content/guides/guides/applications.mdx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/AttestationSection.tsx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/EditAdvancedSettings.tsx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/AttestationSection.roundtrip.test.tsx
  • frontend/apps/console/src/features/applications/components/edit-application/advanced-settings/__tests__/AttestationSection.test.tsx
  • frontend/apps/console/src/features/applications/models/application.ts
  • frontend/apps/console/src/features/applications/models/oauth.ts
  • frontend/packages/i18n/src/locales/en-US.ts
  • tests/integration/application/application_api_test.go
  • tests/integration/application/model.go
  • tests/integration/flow/authentication/attestation_flow_test.go
  • tests/integration/testutils/api_utils.go
  • tests/integration/testutils/models.go

Comment thread backend/internal/application/service.go Outdated
Comment thread backend/internal/attestation/playintegrity.go Outdated
Comment thread backend/internal/flow/flowexec/service.go
Comment thread docs/content/guides/guides/applications.mdx Outdated
@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from 4826108 to 131a347 Compare July 13, 2026 09:10
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.20.0
Approved: ❌ No - Module not found in dependency registry

Dependency name: google.golang.org/api
Version: v0.287.1
Approved: ❌ No - Module not found in dependency registry

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from 131a347 to 13d00ac Compare July 13, 2026 14:07
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.20.0
Approved: ❌ No - Module not found in dependency registry

Dependency name: google.golang.org/api
Version: v0.287.1
Approved: ❌ No - Module not found in dependency registry

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from 13d00ac to 216b9ee Compare July 13, 2026 14:09
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.20.0
Approved: ❌ No - Module not found in dependency registry

Dependency name: google.golang.org/api
Version: v0.287.1
Approved: ❌ No - Module not found in dependency registry

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from 216b9ee to b1acec7 Compare July 13, 2026 14:38
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.20.0
Approved: ❌ No - Module not found in dependency registry

Dependency name: google.golang.org/api
Version: v0.287.1
Approved: ❌ No - Module not found in dependency registry

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from b1acec7 to a136739 Compare July 13, 2026 15:00
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.20.0
Approved: ❌ No - Module not found in dependency registry

Dependency name: google.golang.org/api
Version: v0.287.1
Approved: ❌ No - Module not found in dependency registry

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from a136739 to 838187f Compare July 13, 2026 15:04
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.20.0
Approved: ❌ No - Module not found in dependency registry

Dependency name: google.golang.org/api
Version: v0.287.1
Approved: ❌ No - Module not found in dependency registry

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from 838187f to f568300 Compare July 13, 2026 16:10
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.20.0
Approved: ❌ No - Module not found in dependency registry

Dependency name: google.golang.org/api
Version: v0.287.1
Approved: ❌ No - Module not found in dependency registry

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes


Next Steps

  1. Review the validation failures listed above
  2. Check if dependencies are in the approved dependency list
  3. Options to resolve:
    • Remove the unapproved dependencies from this PR
    • OR submit a PR to add these dependencies to the approved list in engineering-governance
  4. Once resolved, push changes to re-run validation

This PR is blocked until all dependencies are approved.

⚠️ Please verify the scope of the dependencies usage is necessary

@Malith-19 Malith-19 changed the title [WIP] Add Google Play Integrity attestation for mobile clients Add Google Play Integrity attestation for mobile clients Jul 14, 2026
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.22.0
Allowed range: >=v0.22.0
Approved: ✅ Yes

Dependency name: google.golang.org/api
Version: v0.288.0
Allowed range: >=v0.288.0
Approved: ✅ Yes

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from f1ea237 to 3f2dcb4 Compare July 15, 2026 18:15
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.22.0
Allowed range: >=v0.22.0
Approved: ✅ Yes

Dependency name: github.com/cloudflare/circl
Version: v1.6.4
Allowed range: >=v1.6.4
Approved: ✅ Yes

Dependency name: google.golang.org/api
Version: v0.288.0
Allowed range: >=v0.288.0
Approved: ✅ Yes

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

Comment thread backend/internal/attestation/playintegrity.go Outdated
Comment thread backend/internal/attestation/error_constants.go Outdated
Comment thread backend/internal/attestation/interface.go Outdated
Comment thread backend/internal/attestation/play_integrity.go Outdated
// Integrity API.
func newPlayIntegrityVerifier(cryptoSvc kmprovider.RuntimeCryptoProvider) providers.AttestationProvider {
return &playIntegrityVerifier{
decoder: &googlePlayIntegrityDecoder{},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than following this pattern, shall we stick to project's guidelines?

  • Define a private constructor method starting with new...
  • Wire it as a dependency via the package's init.go. No need to pass it from service manager

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add newGooglePlayIntegrityDecoder method and wire it from attestation/init.go

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check now?

Comment thread backend/internal/attestation/play_integrity.go
Comment thread backend/go.mod
require (
cloud.google.com/go/auth v0.22.0
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/cloudflare/circl v1.6.4

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something not introduced by this PR right? Maybe missed to run go mod tidy in a previous PR?
cc: @hwupathum

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No these are not from me.

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from 3f2dcb4 to af26c78 Compare July 16, 2026 05:04
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.22.0
Allowed range: >=v0.22.0
Approved: ✅ Yes

Dependency name: github.com/cloudflare/circl
Version: v1.6.4
Allowed range: >=v1.6.4
Approved: ✅ Yes

Dependency name: google.golang.org/api
Version: v0.288.0
Allowed range: >=v0.288.0
Approved: ✅ Yes

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@coderabbitai coderabbitai Bot 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.

♻️ Duplicate comments (1)
backend/internal/attestation/decoder.go (1)

42-63: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Recreating the API client on every request prevents connection pooling.

Creating a new *playintegrity.Service (which internally instantiates a new http.Client and http.Transport) on every Decode call prevents TCP connection pooling and TLS session resumption. This will cause significant latency and resource exhaustion under load.

Consider caching the *playintegrity.Service (or a configured http.Client) keyed by a hash of the credentials to reuse connections across requests. When caching, use context.Background() for NewService and continue passing the request context to .Context(ctx).Do().

Additionally, fail fast if credentialsJSON is empty to prevent credentials.DetectDefault from falling back to the server's Application Default Credentials (ADC).

⚡ Proposed fix with client caching
@@ -21,6 +21,8 @@
 import (
 	"context"
+	"crypto/sha256"
 	"fmt"
+	"sync"
 
 	"cloud.google.com/go/auth/credentials"
@@ -36,8 +38,10 @@
 
 // googlePlayIntegrityDecoder decodes tokens by calling the Google Play Integrity API using the
 // application's service account credentials.
-type googlePlayIntegrityDecoder struct{}
+type googlePlayIntegrityDecoder struct {
+	cache sync.Map
+}
 
 // Decode calls the Play Integrity decodeIntegrityToken endpoint for the given package.
 func (d *googlePlayIntegrityDecoder) Decode(ctx context.Context, credentialsJSON, packageName, token string) (
 	*playintegrity.TokenPayloadExternal, error) {
+	if credentialsJSON == "" {
+		return nil, fmt.Errorf("empty play integrity credentials")
+	}
+
+	hash := sha256.Sum256([]byte(credentialsJSON))
+	cacheKey := fmt.Sprintf("%x", hash)
+
+	var svc *playintegrity.Service
+	if cached, ok := d.cache.Load(cacheKey); ok {
+		svc = cached.(*playintegrity.Service)
+	} else {
-	creds, err := credentials.DetectDefault(&credentials.DetectOptions{
-		CredentialsJSON: []byte(credentialsJSON),
-		Scopes:          []string{playintegrity.PlayintegrityScope},
-	})
-	if err != nil {
-		return nil, fmt.Errorf("failed to parse play integrity credentials: %w", err)
-	}
-
-	svc, err := playintegrity.NewService(ctx, option.WithAuthCredentials(creds))
-	if err != nil {
-		return nil, fmt.Errorf("failed to create play integrity client: %w", err)
-	}
+		creds, err := credentials.DetectDefault(&credentials.DetectOptions{
+			CredentialsJSON: []byte(credentialsJSON),
+			Scopes:          []string{playintegrity.PlayintegrityScope},
+		})
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse play integrity credentials: %w", err)
+		}
+
+		// Use context.Background() for client creation to ensure connection pooling across requests
+		newSvc, err := playintegrity.NewService(context.Background(), option.WithAuthCredentials(creds))
+		if err != nil {
+			return nil, fmt.Errorf("failed to create play integrity client: %w", err)
+		}
+		
+		d.cache.Store(cacheKey, newSvc)
+		svc = newSvc
+	}
 
 	resp, err := svc.V1.DecodeIntegrityToken(packageName,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/internal/attestation/decoder.go` around lines 42 - 63, Update
googlePlayIntegrityDecoder.Decode to reject empty credentialsJSON before calling
credentials.DetectDefault, and reuse a cached playintegrity.Service keyed by the
credentials rather than constructing one per request. Initialize cached services
with context.Background(), while continuing to pass the request ctx to
DecodeIntegrityToken(...).Context(ctx).Do(); preserve the existing credential,
client-creation, and decode error wrapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@backend/internal/attestation/decoder.go`:
- Around line 42-63: Update googlePlayIntegrityDecoder.Decode to reject empty
credentialsJSON before calling credentials.DetectDefault, and reuse a cached
playintegrity.Service keyed by the credentials rather than constructing one per
request. Initialize cached services with context.Background(), while continuing
to pass the request ctx to DecodeIntegrityToken(...).Context(ctx).Do(); preserve
the existing credential, client-creation, and decode error wrapping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e48d1319-283a-46a9-b37f-20286927bd81

📥 Commits

Reviewing files that changed from the base of the PR and between f1ea237 and af26c78.

⛔ Files ignored due to path filters (4)
  • backend/go.sum is excluded by !**/*.sum
  • backend/tests/mocks/attestationprovidermock/AttestationProvider_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/flow/flowexecmock/FlowExecServiceInterface_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/flow/flowexecmock/attestationVerifier_mock.go is excluded by !**/*_mock.go
📒 Files selected for processing (32)
  • api/application.yaml
  • api/flow-execution.yaml
  • backend/.mockery.private.yml
  • backend/.mockery.public.yml
  • backend/cmd/server/servicemanager.go
  • backend/go.mod
  • backend/internal/application/handler.go
  • backend/internal/application/init.go
  • backend/internal/application/init_test.go
  • backend/internal/application/service.go
  • backend/internal/application/service_test.go
  • backend/internal/attestation/decoder.go
  • backend/internal/attestation/error_constants.go
  • backend/internal/attestation/init.go
  • backend/internal/attestation/integrityTokenDecoder_mock_test.go
  • backend/internal/attestation/play_integrity.go
  • backend/internal/attestation/play_integrity_test.go
  • backend/internal/flow/flowexec/FlowExecServiceInterface_mock_test.go
  • backend/internal/flow/flowexec/constants.go
  • backend/internal/flow/flowexec/error_constants.go
  • backend/internal/flow/flowexec/handler.go
  • backend/internal/flow/flowexec/handler_test.go
  • backend/internal/flow/flowexec/init.go
  • backend/internal/flow/flowexec/interface.go
  • backend/internal/flow/flowexec/service.go
  • backend/internal/flow/flowexec/service_test.go
  • backend/internal/inboundclient/store.go
  • backend/internal/system/constants/server_constants.go
  • backend/internal/system/i18n/core/defaults.go
  • backend/pkg/thunderidengine/engine.go
  • backend/pkg/thunderidengine/providers/interface.go
  • backend/pkg/thunderidengine/providers/model.go
🚧 Files skipped from review as they are similar to previous changes (25)
  • backend/internal/system/constants/server_constants.go
  • api/flow-execution.yaml
  • backend/internal/flow/flowexec/error_constants.go
  • backend/internal/system/i18n/core/defaults.go
  • backend/.mockery.private.yml
  • backend/internal/application/init_test.go
  • backend/internal/flow/flowexec/constants.go
  • backend/internal/flow/flowexec/interface.go
  • backend/internal/attestation/init.go
  • backend/.mockery.public.yml
  • backend/pkg/thunderidengine/providers/interface.go
  • backend/pkg/thunderidengine/providers/model.go
  • backend/internal/application/handler.go
  • backend/internal/flow/flowexec/handler_test.go
  • backend/internal/application/init.go
  • backend/internal/attestation/play_integrity.go
  • backend/internal/inboundclient/store.go
  • api/application.yaml
  • backend/internal/attestation/play_integrity_test.go
  • backend/internal/flow/flowexec/FlowExecServiceInterface_mock_test.go
  • backend/internal/attestation/integrityTokenDecoder_mock_test.go
  • backend/go.mod
  • backend/internal/flow/flowexec/service.go
  • backend/internal/application/service.go
  • backend/internal/flow/flowexec/service_test.go

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from af26c78 to 1a0fbd0 Compare July 16, 2026 05:27
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.22.0
Allowed range: >=v0.22.0
Approved: ✅ Yes

Dependency name: github.com/cloudflare/circl
Version: v1.6.4
Allowed range: >=v1.6.4
Approved: ✅ Yes

Dependency name: google.golang.org/api
Version: v0.288.0
Allowed range: >=v0.288.0
Approved: ✅ Yes

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from 1a0fbd0 to 3405887 Compare July 16, 2026 05:33
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.22.0
Allowed range: >=v0.22.0
Approved: ✅ Yes

Dependency name: github.com/cloudflare/circl
Version: v1.6.4
Allowed range: >=v1.6.4
Approved: ✅ Yes

Dependency name: google.golang.org/api
Version: v0.288.0
Allowed range: >=v0.288.0
Approved: ✅ Yes

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

Comment thread backend/internal/flow/flowexec/init.go Outdated
flowProvider, graphBuilder)
flowExecService := newFlowExecService(flowProvider, flowStore, flowEngine,
actorProvider, observabilitySvc, transactioner, cryptoSvc, graphBuilder, cfg)
actorProvider, observabilitySvc, transactioner, cryptoSvc, attestation.Initialize(cryptoSvc),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attestation should be passed from service manager. This is not the correct pattern

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

// Integrity API.
func newPlayIntegrityVerifier(cryptoSvc kmprovider.RuntimeCryptoProvider) providers.AttestationProvider {
return &playIntegrityVerifier{
decoder: &googlePlayIntegrityDecoder{},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add newGooglePlayIntegrityDecoder method and wire it from attestation/init.go

@Malith-19
Malith-19 force-pushed the feat/3452-play-integrity-attestation branch from 3405887 to 21b7c85 Compare July 16, 2026 06:56
@github-actions

Copy link
Copy Markdown

Dependency Validation Results

Dependency name: cloud.google.com/go/auth
Version: v0.22.0
Allowed range: >=v0.22.0
Approved: ✅ Yes

Dependency name: github.com/cloudflare/circl
Version: v1.6.4
Allowed range: >=v1.6.4
Approved: ✅ Yes

Dependency name: google.golang.org/api
Version: v0.288.0
Allowed range: >=v0.288.0
Approved: ✅ Yes

Dependency name: golang.org/x/net
Version: v0.56.0 (was v0.55.0)
Allowed range: >=v0.55.0
Approved: ✅ Yes

⚠️ Please verify the scope of the dependencies usage is necessary

@ThaminduDilshan
ThaminduDilshan added this pull request to the merge queue Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 16, 2026
@ThaminduDilshan
ThaminduDilshan added this pull request to the merge queue Jul 16, 2026
Merged via the queue into thunder-id:main with commit 08aeb38 Jul 16, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add platform attestation (Apple App Attest / Google Play Integrity) for mobile clients at flow initiation

2 participants