Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions .github/workflows/sca-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +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
json: true
args: --fail-on=all --all-projects --json-file-output=snyk.json
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
15 changes: 9 additions & 6 deletions Scripts/generate_test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,22 @@
--collect:"XPlat Code Coverage" \\
--results-directory contentstack.model.generator.tests/TestResults

Uses only the Python standard library (xml.etree.ElementTree) - no extra
dependencies to install. The .trx/coverage files parsed here are always
locally generated by `dotnet test`, not untrusted input.
Uses only the Python standard library. If defusedxml is installed
(pip install -r Scripts/requirements.txt) it is used for hardened XML parsing.
"""

import glob
import os
import sys
import xml.etree.ElementTree as ET
from datetime import datetime

try:
# Prefer defusedxml (hardens against XXE / entity expansion) when it is installed.
from defusedxml import ElementTree as ET
except ImportError:
# Fall back to the stdlib parser so the script keeps working without extra packages.
import xml.etree.ElementTree as ET

TRX_NS = {'': 'http://microsoft.com/schemas/VisualStudio/TeamTest/2010'}


Expand Down Expand Up @@ -55,7 +60,6 @@ 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()

Expand Down Expand Up @@ -118,7 +122,6 @@ 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()

Expand Down
1 change: 1 addition & 0 deletions Scripts/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
defusedxml>=0.7.1
5 changes: 2 additions & 3 deletions contentstack.model.generator.tests/ConfigTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,12 @@ public void ApiKey_AppUid_AuthToken_CanBeSetAndRetrieved()
{
var config = new Config
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
AppUid = "test_app_uid",
AuthToken = "test_auth_token"
};

Assert.Equal("test_api_key", config.ApiKey);
Assert.Equal(TestSecrets.ApiKey, config.ApiKey);
Assert.Equal("test_app_uid", config.AppUid);
Assert.Equal("test_auth_token", config.AuthToken);
}
Expand Down
40 changes: 14 additions & 26 deletions contentstack.model.generator.tests/ContentstackClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ public void Constructor_ShouldInitializeWithOAuthOptions()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io",
IsOAuth = true,
Authorization = "Bearer test_access_token",
Expand All @@ -28,7 +27,7 @@ public void Constructor_ShouldInitializeWithOAuthOptions()


Assert.NotNull(client);
Assert.Equal("test_api_key", client.StackApiKey);
Assert.Equal(TestSecrets.ApiKey, client.StackApiKey);
}

[Fact]
Expand All @@ -37,8 +36,7 @@ public void Constructor_ShouldInitializeWithTraditionalAuth()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io",
IsOAuth = false,
Authtoken = "test_authtoken"
Expand All @@ -49,7 +47,7 @@ public void Constructor_ShouldInitializeWithTraditionalAuth()


Assert.NotNull(client);
Assert.Equal("test_api_key", client.StackApiKey);
Assert.Equal(TestSecrets.ApiKey, client.StackApiKey);
}

[Fact]
Expand All @@ -66,8 +64,7 @@ public void Constructor_ShouldSetSerializerOptions()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io"
};

Expand All @@ -87,8 +84,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",
ApiKey = TestSecrets.ApiKey,
Host = inputHost
};

Expand All @@ -105,8 +101,7 @@ public void GetHeader_ShouldReturnLocalHeadersWhenMainHeadersIsNull()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io"
};
var client = new ContentstackClient(options);
Expand All @@ -132,8 +127,7 @@ public void GetHeader_ShouldReturnMainHeadersWhenLocalHeadersIsNull()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io"
};
var client = new ContentstackClient(options);
Expand All @@ -152,8 +146,7 @@ public void GetHeader_ShouldMergeLocalAndMainHeaders()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io"
};
var client = new ContentstackClient(options);
Expand All @@ -178,8 +171,7 @@ public void GetHeader_ShouldPrioritizeLocalHeadersOverMainHeaders()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io"
};
var client = new ContentstackClient(options);
Expand All @@ -202,8 +194,7 @@ public void GetHeader_ShouldHandleEmptyLocalHeaders()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io"
};
var client = new ContentstackClient(options);
Expand All @@ -223,8 +214,7 @@ public void GetHeader_ShouldHandleNullValues()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io"
};
var client = new ContentstackClient(options);
Expand All @@ -249,8 +239,7 @@ public void GetHeader_ShouldHandleSpecialCharacters()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io"
};
var client = new ContentstackClient(options);
Expand Down Expand Up @@ -278,8 +267,7 @@ public void GetHeader_ShouldHandleCaseSensitiveKeys()

var options = new ContentstackOptions
{
// deepcode ignore NoHardcodedCredentials: test fixture value, not a real secret
ApiKey = "test_api_key",
ApiKey = TestSecrets.ApiKey,
Host = "api.contentstack.io"
};
var client = new ContentstackClient(options);
Expand Down
10 changes: 4 additions & 6 deletions contentstack.model.generator.tests/ContentstackOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,30 @@ 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.ApiKey = TestSecrets.ApiKey;
options.Authtoken = "test_authtoken";
options.Host = "api.contentstack.io";
options.Branch = "main";
options.Version = "v3";
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.OAuthClientSecret = TestSecrets.ClientSecret;
options.OAuthRedirectUri = "http://localhost:8080";
options.OAuthAppId = "test_app_id";
options.OAuthScopes = testScopes;
options.AccessToken = "test_access_token";
options.RefreshToken = "test_refresh_token";
options.TokenExpiresAt = testDate;
Assert.Equal("test_api_key", options.ApiKey);
Assert.Equal(TestSecrets.ApiKey, options.ApiKey);
Assert.Equal("test_authtoken", options.Authtoken);
Assert.Equal("api.contentstack.io", options.Host);
Assert.Equal("main", options.Branch);
Assert.Equal("v3", options.Version);
Assert.True(options.IsOAuth);
Assert.Equal("Bearer test_token", options.Authorization);
Assert.Equal("test_client_id", options.OAuthClientId);
Assert.Equal("test_client_secret", options.OAuthClientSecret);
Assert.Equal(TestSecrets.ClientSecret, options.OAuthClientSecret);
Assert.Equal("http://localhost:8080", options.OAuthRedirectUri);
Assert.Equal("test_app_id", options.OAuthAppId);
Assert.Equal(testScopes, options.OAuthScopes);
Expand Down
16 changes: 8 additions & 8 deletions contentstack.model.generator.tests/HTTPRequestHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public async Task ProcessRequest_ShouldHandleOAuthBearerToken()
var headers = new Dictionary<string, object>
{
{ "Authorization", "Bearer test_access_token_123" },
{ "api_key", "test_api_key" }
{ "api_key", TestSecrets.ApiKey }
};
var bodyJson = new Dictionary<string, object>
{
Expand All @@ -52,7 +52,7 @@ public async Task ProcessRequest_ShouldHandleBearerTokenWithoutPrefix()
var headers = new Dictionary<string, object>
{
{ "Authorization", "test_access_token_123" }, // No "Bearer " prefix
{ "api_key", "test_api_key" }
{ "api_key", TestSecrets.ApiKey }
};
var bodyJson = new Dictionary<string, object>
{
Expand Down Expand Up @@ -100,7 +100,7 @@ public async Task ProcessRequest_ShouldHandleStringArrayValues()
var url = "https://api.contentstack.io/v3/stacks";
var headers = new Dictionary<string, object>
{
{ "api_key", "test_api_key" }
{ "api_key", TestSecrets.ApiKey }
};
var bodyJson = new Dictionary<string, object>
{
Expand All @@ -118,7 +118,7 @@ public async Task ProcessRequest_ShouldHandleDictionaryValues()
var url = "https://api.contentstack.io/v3/stacks";
var headers = new Dictionary<string, object>
{
{ "api_key", "test_api_key" }
{ "api_key", TestSecrets.ApiKey }
};
var bodyJson = new Dictionary<string, object>
{
Expand All @@ -136,7 +136,7 @@ public async Task ProcessRequest_ShouldHandleEmptyBodyJson()
var url = "https://api.contentstack.io/v3/stacks";
var headers = new Dictionary<string, object>
{
{ "api_key", "test_api_key" }
{ "api_key", TestSecrets.ApiKey }
};
var bodyJson = new Dictionary<string, object>();

Expand All @@ -151,7 +151,7 @@ public async Task ProcessRequest_ShouldHandleNullBodyJson()
var url = "https://api.contentstack.io/v3/stacks";
var headers = new Dictionary<string, object>
{
{ "api_key", "test_api_key" }
{ "api_key", TestSecrets.ApiKey }
};
Dictionary<string, object> bodyJson = null;

Expand All @@ -166,7 +166,7 @@ public async Task ProcessRequest_ShouldHandleInvalidUrl()
var url = "invalid-url";
var headers = new Dictionary<string, object>
{
{ "api_key", "test_api_key" }
{ "api_key", TestSecrets.ApiKey }
};
var bodyJson = new Dictionary<string, object>
{
Expand All @@ -184,7 +184,7 @@ public async Task ProcessRequest_ShouldHandleSpecialCharactersInParameters()
var url = "https://api.contentstack.io/v3/stacks";
var headers = new Dictionary<string, object>
{
{ "api_key", "test_api_key" }
{ "api_key", TestSecrets.ApiKey }
};
var bodyJson = new Dictionary<string, object>
{
Expand Down
Loading
Loading