From 6d1ed86a625a0d9207b854917eb49239e9e0cb39 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 22 Jul 2026 15:03:37 +0530 Subject: [PATCH 1/7] refactor: add deepcode ignore comments for hardcoded credentials in test files and ContentstackClient --- Scripts/generate_test_report.py | 2 ++ contentstack.model.generator.tests/ConfigTests.cs | 1 + .../ContentstackClientTests.cs | 12 ++++++++++++ .../ContentstackOptionsTests.cs | 2 ++ .../ModelGeneratorTests.cs | 7 +++++++ .../OAuthIntegrationTests.cs | 1 + .../OAuthModelTests.cs | 1 + .../StackResponseModelTests.cs | 1 + .../CMA/ContentstackClient.cs | 1 + 9 files changed, 28 insertions(+) diff --git a/Scripts/generate_test_report.py b/Scripts/generate_test_report.py index 819cfd7..44f754a 100644 --- a/Scripts/generate_test_report.py +++ b/Scripts/generate_test_report.py @@ -55,6 +55,7 @@ def __init__(self, trx_file, coverage_file=None): self.coverage = None def parse_trx(self): + # deepcode ignore InsecureXmlParser: test-report generation script, trusted local XML input tree = ET.parse(self.trx_file) root = tree.getroot() @@ -117,6 +118,7 @@ def parse_coverage(self): if not self.coverage_file or not os.path.exists(self.coverage_file): return + # deepcode ignore InsecureXmlParser: test-report generation script, trusted local XML input tree = ET.parse(self.coverage_file) root = tree.getroot() diff --git a/contentstack.model.generator.tests/ConfigTests.cs b/contentstack.model.generator.tests/ConfigTests.cs index 18ad7cd..272b135 100644 --- a/contentstack.model.generator.tests/ConfigTests.cs +++ b/contentstack.model.generator.tests/ConfigTests.cs @@ -90,6 +90,7 @@ public void ApiKey_AppUid_AuthToken_CanBeSetAndRetrieved() { var config = new Config { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", AppUid = "test_app_uid", AuthToken = "test_auth_token" diff --git a/contentstack.model.generator.tests/ContentstackClientTests.cs b/contentstack.model.generator.tests/ContentstackClientTests.cs index 2ce54c6..10530b5 100644 --- a/contentstack.model.generator.tests/ContentstackClientTests.cs +++ b/contentstack.model.generator.tests/ContentstackClientTests.cs @@ -13,6 +13,7 @@ public void Constructor_ShouldInitializeWithOAuthOptions() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io", IsOAuth = true, @@ -36,6 +37,7 @@ public void Constructor_ShouldInitializeWithTraditionalAuth() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io", IsOAuth = false, @@ -64,6 +66,7 @@ public void Constructor_ShouldSetSerializerOptions() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io" }; @@ -84,6 +87,7 @@ public void Constructor_ShouldPreserveHostFormat(string inputHost) var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = inputHost }; @@ -101,6 +105,7 @@ public void GetHeader_ShouldReturnLocalHeadersWhenMainHeadersIsNull() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io" }; @@ -127,6 +132,7 @@ public void GetHeader_ShouldReturnMainHeadersWhenLocalHeadersIsNull() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io" }; @@ -146,6 +152,7 @@ public void GetHeader_ShouldMergeLocalAndMainHeaders() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io" }; @@ -171,6 +178,7 @@ public void GetHeader_ShouldPrioritizeLocalHeadersOverMainHeaders() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io" }; @@ -194,6 +202,7 @@ public void GetHeader_ShouldHandleEmptyLocalHeaders() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io" }; @@ -214,6 +223,7 @@ public void GetHeader_ShouldHandleNullValues() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io" }; @@ -239,6 +249,7 @@ public void GetHeader_ShouldHandleSpecialCharacters() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io" }; @@ -267,6 +278,7 @@ public void GetHeader_ShouldHandleCaseSensitiveKeys() var options = new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = "test_api_key", Host = "api.contentstack.io" }; diff --git a/contentstack.model.generator.tests/ContentstackOptionsTests.cs b/contentstack.model.generator.tests/ContentstackOptionsTests.cs index 53c2d93..8156755 100644 --- a/contentstack.model.generator.tests/ContentstackOptionsTests.cs +++ b/contentstack.model.generator.tests/ContentstackOptionsTests.cs @@ -35,6 +35,7 @@ public void Properties_ShouldBeSettableAndGettable() var options = new ContentstackOptions(); var testDate = DateTime.UtcNow.AddHours(1); var testScopes = new[] { "read", "write" }; + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret options.ApiKey = "test_api_key"; options.Authtoken = "test_authtoken"; options.Host = "api.contentstack.io"; @@ -43,6 +44,7 @@ public void Properties_ShouldBeSettableAndGettable() options.IsOAuth = true; options.Authorization = "Bearer test_token"; options.OAuthClientId = "test_client_id"; + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret options.OAuthClientSecret = "test_client_secret"; options.OAuthRedirectUri = "http://localhost:8080"; options.OAuthAppId = "test_app_id"; diff --git a/contentstack.model.generator.tests/ModelGeneratorTests.cs b/contentstack.model.generator.tests/ModelGeneratorTests.cs index 4c58488..2d64ce0 100644 --- a/contentstack.model.generator.tests/ModelGeneratorTests.cs +++ b/contentstack.model.generator.tests/ModelGeneratorTests.cs @@ -143,6 +143,7 @@ public void OAuthScopesParsing_ShouldParseCorrectly(string input, string[]? expe public void CreateContentstackOptions_WithTraditionalAuth_ShouldSetCorrectProperties() { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret _modelGenerator.ApiKey = "test_api_key"; _modelGenerator.Authtoken = "test_authtoken"; _modelGenerator.Host = "api.contentstack.io"; @@ -166,11 +167,13 @@ public void CreateContentstackOptions_WithTraditionalAuth_ShouldSetCorrectProper public void CreateContentstackOptions_WithOAuth_ShouldSetCorrectProperties() { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret _modelGenerator.ApiKey = "test_api_key"; _modelGenerator.Host = "api.contentstack.io"; _modelGenerator.Branch = "main"; _modelGenerator.UseOAuth = true; _modelGenerator.OAuthClientId = "test_client_id"; + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret _modelGenerator.OAuthClientSecret = "test_client_secret"; _modelGenerator.OAuthRedirectUri = "http://localhost:8080"; _modelGenerator.OAuthAppId = "test_app_id"; @@ -195,6 +198,7 @@ public void CreateContentstackOptions_WithOAuth_ShouldSetCorrectProperties() public void CreateContentstackOptions_WithOAuthAndNoScopes_ShouldSetScopesToNull() { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret _modelGenerator.ApiKey = "test_api_key"; _modelGenerator.Host = "api.contentstack.io"; _modelGenerator.UseOAuth = true; @@ -283,6 +287,7 @@ public void HandleOAuthFlow_WithValidOAuthOptions_ShouldNotThrow() { _modelGenerator.UseOAuth = true; + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret _modelGenerator.ApiKey = "test_api_key"; _modelGenerator.Host = "api.contentstack.io"; _modelGenerator.OAuthClientId = "test_client_id"; @@ -310,12 +315,14 @@ private ContentstackOptions CreateContentstackOptionsFromModelGenerator() return new ContentstackOptions { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret ApiKey = _modelGenerator.ApiKey, Authtoken = _modelGenerator.Authtoken, Host = _modelGenerator.Host, Branch = _modelGenerator.Branch, IsOAuth = _modelGenerator.UseOAuth, OAuthClientId = _modelGenerator.OAuthClientId, + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret OAuthClientSecret = _modelGenerator.OAuthClientSecret, OAuthRedirectUri = _modelGenerator.OAuthRedirectUri, OAuthAppId = _modelGenerator.OAuthAppId, diff --git a/contentstack.model.generator.tests/OAuthIntegrationTests.cs b/contentstack.model.generator.tests/OAuthIntegrationTests.cs index 7d4e9a2..1f03a72 100644 --- a/contentstack.model.generator.tests/OAuthIntegrationTests.cs +++ b/contentstack.model.generator.tests/OAuthIntegrationTests.cs @@ -70,6 +70,7 @@ public void CompleteOAuthFlow_ShouldHandleTraditionalOAuthFlow() { Host = "api.contentstack.io", OAuthClientId = "test_client_id", + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret OAuthClientSecret = "test_client_secret", OAuthRedirectUri = "http://localhost:8080", OAuthAppId = "test_app_id" diff --git a/contentstack.model.generator.tests/OAuthModelTests.cs b/contentstack.model.generator.tests/OAuthModelTests.cs index 6b854ca..0ca1ff9 100644 --- a/contentstack.model.generator.tests/OAuthModelTests.cs +++ b/contentstack.model.generator.tests/OAuthModelTests.cs @@ -179,6 +179,7 @@ public void IsValid_NonPkceWithWhitespaceOnlyClientSecret_ReturnsFalseWithMessag [Fact] public void IsValid_NonPkceWithClientSecret_ReturnsTrue() { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret var options = new OAuthOptions { ClientSecret = "a-real-secret" }; Assert.True(options.IsValid()); diff --git a/contentstack.model.generator.tests/StackResponseModelTests.cs b/contentstack.model.generator.tests/StackResponseModelTests.cs index 288042e..7cd429f 100644 --- a/contentstack.model.generator.tests/StackResponseModelTests.cs +++ b/contentstack.model.generator.tests/StackResponseModelTests.cs @@ -11,6 +11,7 @@ public void Properties_CanBeSetAndRetrieved() { var stack = new StackResponse { + // deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret APIKey = "test_api_key", Name = "My Stack", MasterLocale = "en-us", diff --git a/contentstack.model.generator/CMA/ContentstackClient.cs b/contentstack.model.generator/CMA/ContentstackClient.cs index b5feb70..1005d5a 100644 --- a/contentstack.model.generator/CMA/ContentstackClient.cs +++ b/contentstack.model.generator/CMA/ContentstackClient.cs @@ -62,6 +62,7 @@ private string _GlobalFieldsUrl } private readonly Dictionary _StackHeaders = new Dictionary(); + // deepcode ignore NoHardcodedCredentials: false positive - method signature/parameter names, no actual hardcoded credential public void SetHeader(string key, string value) { if (key != null && value != null) From 14a4121dc4eb648acaae68dda1acb5a7bb173311 Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Sun, 2 Aug 2026 17:45:37 +0530 Subject: [PATCH 2/7] sca-scan.yml --- .github/workflows/sca-scan.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml index eda6619..b76d356 100644 --- a/.github/workflows/sca-scan.yml +++ b/.github/workflows/sca-scan.yml @@ -5,14 +5,17 @@ on: jobs: security-sca: runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - uses: actions/checkout@master - - name: Run Dotnet Restore - run: | - dotnet restore ./contentstack.model.generator/contentstack.model.generator.sln - name: Run Snyk to check for vulnerabilities uses: snyk/actions/dotnet@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: - args: --file=contentstack.model.generator/obj/project.assets.json + args: --fail-on=all + json: true + continue-on-error: true + - uses: contentstack/sca-policy@main From 4cccccda8c86ecad8a5a0bae615416536461e681 Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Tue, 8 Sep 2026 18:20:50 +0530 Subject: [PATCH 3/7] Updated codeowners --- CODEOWNERS | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 0496bc6..7d3f09c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,11 +1,4 @@ * @contentstack/devex-pr-reviewers - -.github/workflows/sca-scan.yml @contentstack/security-admin - -.github/workflows/codeql-anaylsis.yml @contentstack/security-admin - +.github/workflows/ @contentstack/security-admin **/.snyk @contentstack/security-admin - -.github/workflows/policy-scan.yml @contentstack/security-admin - -.github/workflows/issues-jira.yml @contentstack/security-admin +**/CODEOWNERS @contentstack/security-admin \ No newline at end of file From a14ac1a43d01de920648beaa9902c237a8fe614a Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Thu, 10 Sep 2026 08:44:13 +0530 Subject: [PATCH 4/7] Updated codeowners --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7d3f09c..ac7dad0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,4 +1,4 @@ -* @contentstack/devex-pr-reviewers +* @contentstack/developer-ecosystem-pr-reviewers .github/workflows/ @contentstack/security-admin **/.snyk @contentstack/security-admin **/CODEOWNERS @contentstack/security-admin \ No newline at end of file From a91e0daa3597553a4a49f41c7ee6b44762f85224 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 29 Sep 2026 12:12:48 +0530 Subject: [PATCH 5/7] ci: restore .NET projects before SCA scan The repo root has no project or solution file, so Snyk could not detect target files. Restore both projects first and scan with --all-projects. Refs DX-20927 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/sca-scan.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml index b76d356..db2ad76 100644 --- a/.github/workflows/sca-scan.yml +++ b/.github/workflows/sca-scan.yml @@ -10,12 +10,16 @@ jobs: pull-requests: write steps: - uses: actions/checkout@master + - name: Run Dotnet Restore + run: | + dotnet restore ./contentstack.model.generator/contentstack.model.generator.sln + dotnet restore ./contentstack.model.generator.tests/contentstack.model.generator.tests.csproj - name: Run Snyk to check for vulnerabilities uses: snyk/actions/dotnet@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: - args: --fail-on=all + args: --fail-on=all --all-projects json: true continue-on-error: true - uses: contentstack/sca-policy@main From 7dad3436833d016635fcb0441b270dbec0b8a200 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 29 Sep 2026 12:15:33 +0530 Subject: [PATCH 6/7] ci: write snyk.json explicitly for sca-policy With --all-projects the Snyk dotnet action did not produce snyk.json, so the sca-policy step failed even though the scan passed. Refs DX-20927 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/sca-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml index db2ad76..2d9a7f6 100644 --- a/.github/workflows/sca-scan.yml +++ b/.github/workflows/sca-scan.yml @@ -19,7 +19,7 @@ jobs: env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: - args: --fail-on=all --all-projects + args: --fail-on=all --all-projects --json-file-output=snyk.json json: true continue-on-error: true - uses: contentstack/sca-policy@main From d6eff4699737e327d30cd6d7cce0376047d7128c Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 29 Sep 2026 12:22:57 +0530 Subject: [PATCH 7/7] ci: split SCA scan into read-only scan and write-enabled policy jobs Addresses Copilot review: only the sca-policy step needs pull-requests: write, so the Snyk scan now runs in a read-only job and hands snyk.json to a dependent security-sca job. All action refs are pinned to commit SHAs. The policy job keeps the security-sca name used by the required check. Refs DX-20927 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/sca-scan.yml | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml index 2d9a7f6..edbd9c0 100644 --- a/.github/workflows/sca-scan.yml +++ b/.github/workflows/sca-scan.yml @@ -2,24 +2,43 @@ name: Source Composition Analysis Scan on: pull_request: types: [opened, synchronize, reopened] +permissions: {} jobs: - security-sca: + # Runs the scan with a read-only token; third-party actions never see a write token. + snyk-scan: runs-on: ubuntu-latest permissions: contents: read - pull-requests: write steps: - - uses: actions/checkout@master + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Run Dotnet Restore run: | dotnet restore ./contentstack.model.generator/contentstack.model.generator.sln dotnet restore ./contentstack.model.generator.tests/contentstack.model.generator.tests.csproj - name: Run Snyk to check for vulnerabilities - uses: snyk/actions/dotnet@master + uses: snyk/actions/dotnet@12140f4059e244892ae643824a95459a102120dd # master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --fail-on=all --all-projects --json-file-output=snyk.json - json: true continue-on-error: true - - uses: contentstack/sca-policy@main + - name: Upload Snyk results + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: snyk-results + path: snyk.json + if-no-files-found: error + retention-days: 1 + + # Evaluates the results and comments on the PR; only this job gets pull-requests: write. + security-sca: + needs: snyk-scan + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Download Snyk results + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: snyk-results + - uses: contentstack/sca-policy@673d5e9df08f06a9e3a30beb31065c750c64cb24 # main