From b2ae28c0949854753b4c874ba7a4c91366e0f8c4 Mon Sep 17 00:00:00 2001 From: the-coding-cuzzy Date: Wed, 26 Aug 2026 10:04:23 +1200 Subject: [PATCH 1/5] Add the ability to prevent the masking of clientId --- README.md | 23 ++++++++++++++++- __tests__/LoginConfig.test.ts | 47 +++++++++++++++++++++++++++++++++++ action.yml | 4 +++ src/common/LoginConfig.ts | 6 ++++- 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 486be60c0..d57281bbc 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ - [Security Updates](#security-updates) - [Input Parameters](#input-parameters) - [`client-id`](#client-id) + - [`mask-client-id](#mask-client-id) - [`subscription-id`](#subscription-id) - [`tenant-id`](#tenant-id) - [`creds`](#creds) @@ -144,6 +145,7 @@ Customers using v1 should migrate to v3. End-of-life releases no longer receive |allow-no-subscriptions|false|boolean|false|if login without subscription is allowed| |audience|false|string|api://AzureADTokenExchange|the audience to get the JWT ID token from GitHub OIDC provider| |auth-type|false|string|SERVICE_PRINCIPAL|the auth type| +|mask-client-id|false|boolean|true|if the `client-id` value is masked in workflow logs| ### `client-id` @@ -156,7 +158,26 @@ It's better to create a GitHub Action secret for this parameter when using it. R Refer to [Login With OpenID Connect (OIDC)](#login-with-openid-connect-oidc-recommended) and [Login With User-assigned Managed Identity](#login-with-user-assigned-managed-identity) for its usage. > [!NOTE] -> The action registers the `client-id` value as a secret (via `core.setSecret`) so it is masked in workflow logs. Some enterprises treat the client ID as sensitive, and masking also prevents it from being printed accidentally, which matters in public repositories. `tenant-id` and `subscription-id` are not masked. +> By default the action registers the `client-id` value as a secret (via `core.setSecret`) so it is masked in workflow logs. Some enterprises treat the client ID as sensitive, and masking also prevents it from being printed accidentally, which matters in public repositories. `tenant-id` and `subscription-id` are not masked. Set [`mask-client-id`](#mask-client-id) to `false`to opt out of the masking. + +### `mask-client-id + +The input parameter `mask-client-id` controls whether the login client id is registered as a secret and masked in the workflow logs. It defaults to `true`. + +Set it to `false`when the client id is not treated as sensitive and masking gets in the way, for example when the same value appears in log output or command results that you need to read. + +``yaml + - name: Azure login + uses: azure/login@v3 + with: + tenant-id: ${{ vars.AZURE_TENANT_ID }} + subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + client-id: ${{ vars.AZURE_CLIENT_ID }} + mask-client-id: false +``` + +> [!NOTE] +> The value is only unmasked when it is not a Github Action secret. A value passed omfr `${{ secrets.* }}`is still masked by Github itself, regardless of this input. ### `subscription-id` diff --git a/__tests__/LoginConfig.test.ts b/__tests__/LoginConfig.test.ts index ee5d67b2c..1eba29b24 100644 --- a/__tests__/LoginConfig.test.ts +++ b/__tests__/LoginConfig.test.ts @@ -175,6 +175,53 @@ describe("LoginConfig Test", () => { expect(loginConfig.subscriptionId).toBe("subscription-id-aa"); }); + test('initialize with mask-client-id=true', async () => { + setEnv('environment', 'azureusgovernment'); + setEnv('enable-AzPSSession', 'false'); + setEnv('allow-no-subscriptions', 'true'); + setEnv('auth-type', 'SERVICE_PRINCIPAL'); + setEnv('tenant-id', 'tenant-id'); + setEnv('subscription-id', 'subscription-id'); + setEnv('client-id', 'client-id'); + setEnv('maskclient-id', 'true'); + + let loginConfig = new LoginConfig(); + await loginConfig.initialize(); + expect(loginConfig.maskClientId).toBeTruthy(); + expect(loginConfig.servicePrincipalId).toBe("client-id"); + }); + + test('initialize with mask-client-id=false', async () => { + setEnv('environment', 'azureusgovernment'); + setEnv('enable-AzPSSession', 'false'); + setEnv('allow-no-subscriptions', 'true'); + setEnv('auth-type', 'SERVICE_PRINCIPAL'); + setEnv('tenant-id', 'tenant-id'); + setEnv('subscription-id', 'subscription-id'); + setEnv('client-id', 'client-id'); + setEnv('maskclient-id', 'true'); + + let loginConfig = new LoginConfig(); + await loginConfig.initialize(); + expect(loginConfig.maskClientId).toBeFalsy(); + expect(loginConfig.servicePrincipalId).toBe("client-id"); + }); + + test('initialize without mask-client-id', async () => { + setEnv('environment', 'azureusgovernment'); + setEnv('enable-AzPSSession', 'false'); + setEnv('allow-no-subscriptions', 'true'); + setEnv('auth-type', 'SERVICE_PRINCIPAL'); + setEnv('tenant-id', 'tenant-id'); + setEnv('subscription-id', 'subscription-id'); + setEnv('client-id', 'client-id'); + + let loginConfig = new LoginConfig(); + await loginConfig.initialize(); + expect(loginConfig.maskClientId).toBeTruthy(); + expect(loginConfig.servicePrincipalId).toBe("client-id"); + }); + test('validate with wrong environment', async () => { setEnv('environment', 'aWrongCloud'); setEnv('enable-AzPSSession', 'false'); diff --git a/action.yml b/action.yml index bf52a453b..4897be543 100644 --- a/action.yml +++ b/action.yml @@ -34,6 +34,10 @@ inputs: description: 'The type of authentication. Supported values are SERVICE_PRINCIPAL, IDENTITY. Default value is SERVICE_PRINCIPAL' required: false default: 'SERVICE_PRINCIPAL' + mask-client-id: + description: 'Set this value to false to stop registering the client-id as a secret, so it is not masked in workflow logs' + required: false + default: true branding: icon: 'login.svg' color: 'blue' diff --git a/src/common/LoginConfig.ts b/src/common/LoginConfig.ts index b9939c588..4a2a9020f 100644 --- a/src/common/LoginConfig.ts +++ b/src/common/LoginConfig.ts @@ -25,6 +25,7 @@ export class LoginConfig { enableAzPSSession: boolean; audience: string; federatedToken: string; + maskClientId: boolean; async initialize() { this.environment = core.getInput("environment").toLowerCase(); @@ -42,7 +43,10 @@ export class LoginConfig { this.audience = core.getInput('audience', { required: false }); this.federatedToken = null; - this.mask(this.servicePrincipalId); + this.maskClientId = core.getInput('mask-client-id').toLowerCase() !== "false"; + if (this.maskClientId) { + this.mask(this.servicePrincipalId); + } this.mask(this.servicePrincipalSecret); } From 4ba51e070ae40ad681b40f24c9a9ec25d7d2223e Mon Sep 17 00:00:00 2001 From: the-coding-cuzzy Date: Fri, 4 Sep 2026 16:36:25 +1200 Subject: [PATCH 2/5] Fix formatting in README.md and tests --- README.md | 12 ++++++------ __tests__/LoginConfig.test.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d57281bbc..f833fa816 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - [Security Updates](#security-updates) - [Input Parameters](#input-parameters) - [`client-id`](#client-id) - - [`mask-client-id](#mask-client-id) + - [`mask-client-id`](#mask-client-id) - [`subscription-id`](#subscription-id) - [`tenant-id`](#tenant-id) - [`creds`](#creds) @@ -158,15 +158,15 @@ It's better to create a GitHub Action secret for this parameter when using it. R Refer to [Login With OpenID Connect (OIDC)](#login-with-openid-connect-oidc-recommended) and [Login With User-assigned Managed Identity](#login-with-user-assigned-managed-identity) for its usage. > [!NOTE] -> By default the action registers the `client-id` value as a secret (via `core.setSecret`) so it is masked in workflow logs. Some enterprises treat the client ID as sensitive, and masking also prevents it from being printed accidentally, which matters in public repositories. `tenant-id` and `subscription-id` are not masked. Set [`mask-client-id`](#mask-client-id) to `false`to opt out of the masking. +> By default the action registers the `client-id` value as a secret (via `core.setSecret`) so it is masked in workflow logs. Some enterprises treat the client ID as sensitive, and masking also prevents it from being printed accidentally, which matters in public repositories. `tenant-id` and `subscription-id` are not masked. Set [`mask-client-id`](#mask-client-id) to `false` to opt out of the masking. -### `mask-client-id +### `mask-client-id` The input parameter `mask-client-id` controls whether the login client id is registered as a secret and masked in the workflow logs. It defaults to `true`. -Set it to `false`when the client id is not treated as sensitive and masking gets in the way, for example when the same value appears in log output or command results that you need to read. +Set it to `false` when the client id is not treated as sensitive and masking gets in the way, for example when the same value appears in log output or command results that you need to read. -``yaml +```yaml - name: Azure login uses: azure/login@v3 with: @@ -177,7 +177,7 @@ Set it to `false`when the client id is not treated as sensitive and masking gets ``` > [!NOTE] -> The value is only unmasked when it is not a Github Action secret. A value passed omfr `${{ secrets.* }}`is still masked by Github itself, regardless of this input. +> The value is only unmasked when it is not a Github Action secret. A value passed from `${{ secrets.* }}` is still masked by Github itself, regardless of this input. ### `subscription-id` diff --git a/__tests__/LoginConfig.test.ts b/__tests__/LoginConfig.test.ts index 1eba29b24..c4e25f393 100644 --- a/__tests__/LoginConfig.test.ts +++ b/__tests__/LoginConfig.test.ts @@ -183,7 +183,7 @@ describe("LoginConfig Test", () => { setEnv('tenant-id', 'tenant-id'); setEnv('subscription-id', 'subscription-id'); setEnv('client-id', 'client-id'); - setEnv('maskclient-id', 'true'); + setEnv('mask-client-id', 'true'); let loginConfig = new LoginConfig(); await loginConfig.initialize(); @@ -199,7 +199,7 @@ describe("LoginConfig Test", () => { setEnv('tenant-id', 'tenant-id'); setEnv('subscription-id', 'subscription-id'); setEnv('client-id', 'client-id'); - setEnv('maskclient-id', 'true'); + setEnv('mask-client-id', 'true'); let loginConfig = new LoginConfig(); await loginConfig.initialize(); From 7bd07030faca229fa9a30398548004347515e254 Mon Sep 17 00:00:00 2001 From: the-coding-cuzzy Date: Fri, 4 Sep 2026 16:37:32 +1200 Subject: [PATCH 3/5] Add paragraph to README about client-id being low sensitivity on its own --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f833fa816..3b7d0fc64 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,8 @@ The input parameter `mask-client-id` controls whether the login client id is reg Set it to `false` when the client id is not treated as sensitive and masking gets in the way, for example when the same value appears in log output or command results that you need to read. +The client-id is effectively a username: it is low sensitivity on its own, and only useful to an attacker who already holds the client secret or certificate. Disabling masking is therefore reasonable when the value is treated as configuration rather than as a secret. + ```yaml - name: Azure login uses: azure/login@v3 From 7293f805e2d2f73b645b951b62e97feae46c051a Mon Sep 17 00:00:00 2001 From: the-coding-cuzzy Date: Mon, 7 Sep 2026 20:19:33 +1200 Subject: [PATCH 4/5] Change Github to GitHub for consistent casing --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b7d0fc64..c1670d089 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ The client-id is effectively a username: it is low sensitivity on its own, and o ``` > [!NOTE] -> The value is only unmasked when it is not a Github Action secret. A value passed from `${{ secrets.* }}` is still masked by Github itself, regardless of this input. +> The value is only unmasked when it is not a GitHub Action secret. A value passed from `${{ secrets.* }}` is still masked by GitHub itself, regardless of this input. ### `subscription-id` From f9226c3de7c7c6ade4e5ce0496c1653a6509d3a6 Mon Sep 17 00:00:00 2001 From: the-coding-cuzzy Date: Mon, 7 Sep 2026 20:20:52 +1200 Subject: [PATCH 5/5] Give correct `false` input to test mask-client-id=false works as expected --- __tests__/LoginConfig.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/LoginConfig.test.ts b/__tests__/LoginConfig.test.ts index c4e25f393..497592454 100644 --- a/__tests__/LoginConfig.test.ts +++ b/__tests__/LoginConfig.test.ts @@ -199,7 +199,7 @@ describe("LoginConfig Test", () => { setEnv('tenant-id', 'tenant-id'); setEnv('subscription-id', 'subscription-id'); setEnv('client-id', 'client-id'); - setEnv('mask-client-id', 'true'); + setEnv('mask-client-id', 'false'); let loginConfig = new LoginConfig(); await loginConfig.initialize();