Skip to content

Conversation

@hmacr
Copy link
Contributor

@hmacr hmacr commented Feb 4, 2026

Branch names containing invalid domain characters (like '/') were being used directly when creating VCS preview domains, resulting in invalid domains like 'branch-abc/test.appwrite.network'. This adds a Domain helper class that sanitizes branch names by replacing invalid characters with hyphens before generating domains.

Closes SER-1102

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

Replaces manual VCS branch domain construction with a Transformation-based flow using a new BranchDomain adapter. Controllers and compute/functions modules now create a Transformation, pass inputs (branch, resourceId, projectId, sitesDomain), execute transform, and use the transformation output as the branch preview domain and ruleId source. Adds BranchDomain adapter (sanitization, prefix length, hash suffix) and unit tests validating domain normalization, consistency, and sensitivity to resource/project changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: implementing branch name sanitization for valid domain generation, which matches the core purpose of the changeset.
Description check ✅ Passed The description is directly related to the changeset, explaining the problem (invalid domain characters in branch names) and the solution (a Domain helper class for sanitization).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ser-1102

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 and usage tips.

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

Security Scan Results for PR

Docker Image Scan Results

Package Version Vulnerability Severity
libcrypto3 3.5.4-r0 CVE-2025-15467 CRITICAL
libcrypto3 3.5.4-r0 CVE-2025-69419 HIGH
libcrypto3 3.5.4-r0 CVE-2025-69421 HIGH
libpng 1.6.51-r0 CVE-2025-66293 HIGH
libpng 1.6.51-r0 CVE-2026-22695 HIGH
libpng 1.6.51-r0 CVE-2026-22801 HIGH
libpng-dev 1.6.51-r0 CVE-2025-66293 HIGH
libpng-dev 1.6.51-r0 CVE-2026-22695 HIGH
libpng-dev 1.6.51-r0 CVE-2026-22801 HIGH
libssl3 3.5.4-r0 CVE-2025-15467 CRITICAL
libssl3 3.5.4-r0 CVE-2025-69419 HIGH
libssl3 3.5.4-r0 CVE-2025-69421 HIGH
libxml2 2.13.8-r0 CVE-2025-49794 CRITICAL
libxml2 2.13.8-r0 CVE-2025-49796 CRITICAL
libxml2 2.13.8-r0 CVE-2025-49795 HIGH
libxml2 2.13.8-r0 CVE-2025-6021 HIGH
openssl 3.5.4-r0 CVE-2025-15467 CRITICAL
openssl 3.5.4-r0 CVE-2025-69419 HIGH
openssl 3.5.4-r0 CVE-2025-69421 HIGH
openssl-dev 3.5.4-r0 CVE-2025-15467 CRITICAL
openssl-dev 3.5.4-r0 CVE-2025-69419 HIGH
openssl-dev 3.5.4-r0 CVE-2025-69421 HIGH
py3-urllib3 1.26.20-r0 CVE-2026-21441 HIGH
py3-urllib3-pyc 1.26.20-r0 CVE-2026-21441 HIGH
github.com/containerd/containerd/v2 v2.0.2 CVE-2024-25621 HIGH
golang.org/x/crypto v0.31.0 CVE-2025-22869 HIGH
golang.org/x/oauth2 v0.24.0 CVE-2025-22868 HIGH
stdlib 1.22.10 CVE-2025-47907 HIGH
stdlib 1.22.10 CVE-2025-58183 HIGH
stdlib 1.22.10 CVE-2025-61726 HIGH
stdlib 1.22.10 CVE-2025-61728 HIGH
stdlib 1.22.10 CVE-2025-61729 HIGH

Source Code Scan Results

🎉 No vulnerabilities found!

@hmacr hmacr requested a review from Meldiron February 4, 2026 10:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@app/controllers/api/vcs.php`:
- Line 7: The PSR-12 ordered_imports error is caused by the Appwrite\Vcs\Domain
import being out of alphabetical order; update the use statements in api/vcs.php
so all Appwrite\SDK\* imports come first and then place use Appwrite\Vcs\Domain
after them, ensuring imports within the Appwrite namespace are sorted
alphabetically (and run the linter to confirm); reference the use statement for
Domain to locate and reorder it.

In `@src/Appwrite/Platform/Modules/Compute/Base.php`:
- Line 8: The PSR-12 ordered_imports lint is failing because the `use
Appwrite\Vcs\Domain;` import in src/Appwrite/Platform/Modules/Compute/Base.php
is out of alphabetical order; move the `use Appwrite\Vcs\Domain;` statement so
it appears after other `Appwrite\Platform\*` imports and before any `Utopia\*`
imports (i.e., reorder the `use` block around the `Base` class imports), then
re-run the linter to confirm the `ordered_imports` violation is resolved.

In `@src/Appwrite/Vcs/Domain.php`:
- Around line 41-52: In generateBranchPrefix, handle the case where
sanitizeBranchName(substr(...)) returns an empty string or yields a leading
hyphen: after computing $branchPrefix = self::sanitizeBranchName(...), if
$branchPrefix is empty then derive a safe base by stripping invalid chars from
the original substring (or fallback to a short fixed token like 'br') before
appending the hash; when adding the hash suffix (using self::HASH_SUFFIX_LENGTH)
only prepend the '-' separator if the base is non-empty, and finally trim any
leading '-' from the combined result and ensure it is not empty before
returning; reference generateBranchPrefix, sanitizeBranchName,
BRANCH_PREFIX_MAX_LENGTH, and HASH_SUFFIX_LENGTH when making the change.
🧹 Nitpick comments (1)
tests/unit/Vcs/DomainTest.php (1)

108-127: Consider adding a test for all-invalid prefix edge case.

The test suite is comprehensive. Consider adding a test case for when the first 16 characters are all invalid (e.g., "////////////////something"). This would verify the behavior mentioned in my comment on Domain.php.

Suggested test case
public function testGenerateBranchPrefixAllInvalidFirstChars(): void
{
    // First 16 chars are all slashes, remainder has valid chars
    $prefix = Domain::generateBranchPrefix('////////////////validpart');
    // Verify the prefix doesn't start with a hyphen (invalid DNS label)
    $this->assertDoesNotMatchRegularExpression('/^-/', $prefix);
}

Branch names containing invalid domain characters (like '/') were being
used directly when creating VCS preview domains, resulting in invalid
domains like 'branch-abc/test.appwrite.network'. This adds a Domain
helper class that sanitizes branch names by replacing invalid characters
with hyphens before generating domains.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Feb 4, 2026

✨ Benchmark results

  • Requests per second: 2,617
  • Requests with 200 status code: 471,037
  • P99 latency: 0.058895515

⚡ Benchmark Comparison

Metric This PR Latest version
RPS 2,617 1,252
200 471,037 225,364
P99 0.058895515 0.159726893

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/Appwrite/Vcs/Domain.php`:
- Around line 18-31: The sanitizeBranchName function currently allows
underscores; update it to forbid underscores by changing the regex in
sanitizeBranchName from '/[^a-zA-Z0-9_-]+/' to '/[^a-zA-Z0-9-]+/' so only
letters, digits, and hyphens are permitted, and update the docblock text to
reflect that underscores are not allowed (mentioning RFC 1035/1123 LDH rules and
CA/Browser requirements if desired).


// VCS branch preview
if (!empty($providerBranch)) {
$branchPrefix = substr($providerBranch, 0, 16);
Copy link
Contributor

Choose a reason for hiding this comment

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

Note for future: We will need to apply same fix in Cloud vcs.php

hmacr and others added 4 commits February 4, 2026 17:11
Replace regex with explicit character validation using Utopia Text
constants for better readability and maintainability.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`:
- Around line 12-16: Reorder the import statements to satisfy PSR-12
ordered_imports: sort the Appwrite imports alphabetically by their fully
qualified names (e.g., Appwrite\Transformation\Transformation,
Appwrite\Transformation\Adapter\BranchDomain,
Appwrite\Utopia\Response\Model\Deployment, Appwrite\Vcs\Comment) and place the
global Exception import after a single blank line following the Appwrite group;
update the import block containing Transformation, BranchDomain, Deployment,
Comment, and Exception accordingly.

In `@src/Appwrite/Transformation/Adapter/BranchDomain.php`:
- Around line 78-103: sanitizeBranchName currently preserves uppercase because
allowedChars includes upper and lower; normalize the input to lowercase before
building allowedCharsFlip and iterating. In sanitizeBranchName(string $branch):
string, convert the branch string to lowercase (e.g. $branch =
strtolower($branch) or mb_strtolower for multibyte safety) immediately at the
start so the subsequent loop, allowedChars/allowedCharsFlip checks,
dash-collapsing logic and final trim(...) produce a lowercase sanitized branch
(e.g. 'feature/SER-1234' -> 'branch-feature-ser-1234-') and avoid case-variant
duplicates.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Appwrite/Platform/Modules/Functions/Workers/Builds.php (1)

1056-1074: ⚠️ Potential issue | 🟠 Major

Bug: deploymentResourceId should reference the site resource, not the deployment.

Lines 1066-1067 incorrectly use $deployment->getId() and $deployment->getSequence() for deploymentResourceId and deploymentResourceInternalId. Based on the pattern in Base.php (lines 258-259, 284-285, 324-326) and the semantic meaning of "deploymentResource", these fields should reference the site being deployed, not the deployment itself.

🐛 Proposed fix
                         'deploymentId' => $deployment->getId(),
                         'deploymentInternalId' => $deployment->getSequence(),
                         'deploymentResourceType' => 'site',
-                        'deploymentResourceId' => $deployment->getId(),
-                        'deploymentResourceInternalId' => $deployment->getSequence(),
+                        'deploymentResourceId' => $resource->getId(),
+                        'deploymentResourceInternalId' => $resource->getSequence(),
                         'deploymentVcsProviderBranch' => $branchName,
🧹 Nitpick comments (2)
src/Appwrite/Transformation/Adapter/BranchDomain.php (2)

20-26: Consider removing unused $traits parameter or marking it intentionally unused.

The static analysis tool correctly identifies that the $traits parameter in isValid() is never used since the method always returns true. If this is intentional for interface compliance with the parent Adapter class, consider adding a @SuppressWarnings(PHPMD.UnusedFormalParameter) annotation or prefixing with underscore to indicate intentional non-use.

♻️ Suggested fix
     /**
-     * `@param` array<mixed> $traits
+     * `@param` array<mixed> $traits Unused, required by Adapter interface
+     * `@SuppressWarnings`(PHPMD.UnusedFormalParameter)
      */
     public function isValid(array $traits): bool
     {
         return true;
     }

37-47: Consider validating required inputs to avoid malformed domains.

When branch, resourceId, projectId, or sitesDomain are empty, the generated domain could be malformed (e.g., branch---abc1234. if sitesDomain is also empty). Consider adding validation or throwing an exception for missing required inputs.

🛡️ Suggested validation
     public function transform(): void
     {
         $branch = $this->input['branch'] ?? '';
         $resourceId = $this->input['resourceId'] ?? '';
         $projectId = $this->input['projectId'] ?? '';
         $sitesDomain = $this->input['sitesDomain'] ?? '';

+        if (empty($branch) || empty($resourceId) || empty($projectId) || empty($sitesDomain)) {
+            throw new \InvalidArgumentException('Missing required input: branch, resourceId, projectId, and sitesDomain are required');
+        }
+
         $branchPrefix = $this->generateBranchPrefix($branch);

'sitesDomain' => $sitesDomain,
])
->setTraits([])
->transform();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
->transform();
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
$transformation
->setInput($domain)
->setTraits([ maxLenght: 10, suffixLenght: 7 ])
->transform();

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a bug where branch names containing invalid domain characters (like '/') were being used directly when creating VCS preview domains, resulting in invalid domains. The fix introduces a new BranchDomain transformation adapter class that sanitizes branch names by replacing invalid characters with hyphens before generating domains.

Changes:

  • Created a new BranchDomain transformation adapter class that sanitizes branch names and generates valid domain names
  • Refactored three locations where branch domain generation was implemented inline to use the new adapter
  • Added comprehensive unit tests covering various real-world branch naming patterns

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/Appwrite/Transformation/Adapter/BranchDomain.php New adapter class that sanitizes branch names and generates valid domains with proper character handling
tests/unit/Transformation/TransformationTest.php Added comprehensive tests for the BranchDomain adapter covering various branch naming patterns
src/Appwrite/Platform/Modules/Functions/Workers/Builds.php Refactored to use the new BranchDomain adapter instead of inline domain generation
src/Appwrite/Platform/Modules/Compute/Base.php Refactored to use the new BranchDomain adapter instead of inline domain generation
app/controllers/api/vcs.php Refactored to use the new BranchDomain adapter instead of inline domain generation

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

if (isset($allowedCharsFlip[$char])) {
$sanitized .= $char;
} else {
// Prevents two -- or more in row
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The comment has a typo: "two -- or more in row" should be "two -- or more in a row".

Suggested change
// Prevents two -- or more in row
// Prevents two -- or more in a row

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +47
public function transform(): void
{
$branch = $this->input['branch'] ?? '';
$resourceId = $this->input['resourceId'] ?? '';
$projectId = $this->input['projectId'] ?? '';
$sitesDomain = $this->input['sitesDomain'] ?? '';

$branchPrefix = $this->generateBranchPrefix($branch);
$resourceProjectHash = substr(hash('sha256', $resourceId . $projectId), 0, self::HASH_SUFFIX_LENGTH);
$this->output = strtolower("branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}");
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The transform method doesn't validate that required input values are non-empty. If 'branch', 'resourceId', 'projectId', or 'sitesDomain' are empty strings, this will generate malformed domains. Consider adding validation to ensure these required fields are provided and non-empty, or at least handle the empty branch case to prevent invalid domain generation.

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +67
private function generateBranchPrefix(string $branch): string
{
$branchPrefix = substr($branch, 0, self::BRANCH_PREFIX_MAX_LENGTH);
$branchPrefix = $this->sanitizeBranchName($branchPrefix);

if (strlen($branch) > self::BRANCH_PREFIX_MAX_LENGTH) {
$remainingChars = substr($branch, self::BRANCH_PREFIX_MAX_LENGTH);
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, self::HASH_SUFFIX_LENGTH);
}

return $branchPrefix;
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

If the sanitized branch prefix is empty (which can happen when the first 16 characters of a branch name consist entirely of special characters), the generated domain will have consecutive hyphens (e.g., "branch--{hash}.{domain}"). While technically valid, this is not ideal. Consider either falling back to a default prefix (e.g., "branch") or ensuring at least one character remains after sanitization by checking if the result is empty and handling it appropriately.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +181
public function testBranchDomain(): void
{
$transformer = new Transformation([new BranchDomain()]);

// Branch name with slash
$transformer
->setInput([
'branch' => 'feature/test',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain = $transformer->getOutput();
$this->assertStringNotContainsString('/', $domain);
$this->assertStringStartsWith('branch-feature-test-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);

// Branch domain consistency
$transformer
->setInput([
'branch' => 'feature/test',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain2 = $transformer->getOutput();
$this->assertEquals($domain, $domain2);

// Different resources should produce different domains
$transformer
->setInput([
'branch' => 'feature/test',
'resourceId' => 'site789',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain2 = $transformer->getOutput();
$this->assertNotEquals($domain, $domain2);

// Different projects should produce different domains
$transformer
->setInput([
'branch' => 'feature/test',
'resourceId' => 'site123',
'projectId' => 'proj789',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain2 = $transformer->getOutput();
$this->assertNotEquals($domain, $domain2);

// Some real-world branch names
$transformer
->setInput([
'branch' => 'feature/SER-1234',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain = $transformer->getOutput();
$this->assertStringStartsWith('branch-feature-ser-1234-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);

$transformer
->setInput([
'branch' => 'bugfix/fix-login',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain = $transformer->getOutput();
$this->assertStringStartsWith('branch-bugfix-fix-login-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);

$transformer
->setInput([
'branch' => 'hotfix/v1.2.3',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain = $transformer->getOutput();
$this->assertStringStartsWith('branch-hotfix-v1-2-3-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);

$transformer
->setInput([
'branch' => 'release/2024.01',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain = $transformer->getOutput();
$this->assertStringStartsWith('branch-release-2024-01-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);

$transformer
->setInput([
'branch' => 'user/john/experiment',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain = $transformer->getOutput();
$this->assertStringStartsWith('branch-user-john-experi-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);

$transformer
->setInput([
'branch' => 'dependabot/npm_and_yarn/lodash-4.17.21',
'resourceId' => 'site123',
'projectId' => 'proj456',
'sitesDomain' => 'appwrite.network'
])
->setTraits([])
->transform();
$domain = $transformer->getOutput();
$this->assertStringStartsWith('branch-dependabot-npm-a-', $domain);
$this->assertStringEndsWith('.appwrite.network', $domain);
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The test suite should include edge cases such as: empty branch names, branch names consisting entirely of special characters (e.g., "///"), branch names starting with special characters, very long branch names exceeding typical limits, and branch names with consecutive special characters. These cases would help ensure the sanitization logic handles all scenarios correctly.

Copilot uses AI. Check for mistakes.

$branchPrefix = $this->generateBranchPrefix($branch);
$resourceProjectHash = substr(hash('sha256', $resourceId . $projectId), 0, self::HASH_SUFFIX_LENGTH);
$this->output = strtolower("branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}");
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

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

The new implementation converts the entire domain to lowercase, while the old implementation didn't explicitly do this. While this is good practice for domain names and DNS is case-insensitive, for existing deployments with branch names containing uppercase letters (without special characters), this will generate a different rule ID and create a new rule instead of updating the existing one. Consider documenting this behavior change in release notes or migration guide.

Copilot uses AI. Check for mistakes.
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.

2 participants