Skip to content

Commit 50da220

Browse files
committed
Merge branch 'main' into impl-lenient-http-client
2 parents 4f1c279 + 0ad31dd commit 50da220

File tree

14 files changed

+260
-16
lines changed

14 files changed

+260
-16
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22

33
## Unreleased
44

5+
### Added
6+
7+
- support for skipping CLI signature verification
8+
59
### Changed
610

11+
- URL validation is stricter in the connection screen and URI protocol handler
712
- the http client has relaxed syntax rules when deserializing JSON responses
813

914
## 0.6.0 - 2025-07-25

JETBRAINS_COMPLIANCE.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ This configuration includes JetBrains-specific rules that check for:
3939
- **ForbiddenImport**: Detects potentially bundled libraries
4040
- **Standard code quality rules**: Complexity, naming, performance, etc.
4141

42-
43-
4442
## CI/CD Integration
4543

4644
The GitHub Actions workflow `.github/workflows/jetbrains-compliance.yml` runs compliance checks on every PR and push.
@@ -55,8 +53,6 @@ The GitHub Actions workflow `.github/workflows/jetbrains-compliance.yml` runs co
5553
open build/reports/detekt/detekt.html
5654
```
5755

58-
59-
6056
## Understanding Results
6157

6258
### Compliance Check Results

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,69 @@ If `ide_product_code` and `ide_build_number` is missing, Toolbox will only open
109109
page. Coder Toolbox will attempt to start the workspace if it’s not already running; however, for the most reliable
110110
experience, it’s recommended to ensure the workspace is running prior to initiating the connection.
111111

112+
## GPG Signature Verification
113+
114+
The Coder Toolbox plugin starting with version *0.5.0* implements a comprehensive GPG signature verification system to
115+
ensure the authenticity and integrity of downloaded Coder CLI binaries. This security feature helps protect users from
116+
running potentially malicious or tampered binaries.
117+
118+
### How It Works
119+
120+
1. **Binary Download**: When connecting to a Coder deployment, the plugin downloads the appropriate Coder CLI binary for
121+
the user's operating system and architecture from the deployment's `/bin/` endpoint.
122+
123+
2. **Signature Download**: After downloading the binary, the plugin attempts to download the corresponding `.asc`
124+
signature file from the same location. The signature file is named according to the binary (e.g.,
125+
`coder-linux-amd64.asc` for `coder-linux-amd64`).
126+
127+
3. **Fallback Signature Sources**: If the signature is not available from the deployment, the plugin can optionally fall
128+
back to downloading signatures from `releases.coder.com`. This is controlled by the `fallbackOnCoderForSignatures`
129+
setting.
130+
131+
4. **GPG Verification**: The plugin uses the BouncyCastle library to verify the detached GPG signature against the
132+
downloaded binary using Coder's trusted public key.
133+
134+
5. **User Interaction**: If signature verification fails or signatures are unavailable, the plugin presents security
135+
warnings to users, allowing them to accept the risk and continue or abort the operation.
136+
137+
### Verification Process
138+
139+
The verification process involves several components:
140+
141+
- **`GPGVerifier`**: Handles the core GPG signature verification logic using BouncyCastle
142+
- **`VerificationResult`**: Represents the outcome of verification (Valid, Invalid, Failed, SignatureNotFound)
143+
- **`CoderDownloadService`**: Manages downloading both binaries and their signatures
144+
- **`CoderCLIManager`**: Orchestrates the download and verification workflow
145+
146+
### Configuration Options
147+
148+
Users can control signature verification behavior through plugin settings:
149+
150+
- **`disableSignatureVerification`**: When enabled, skips all signature verification. This is useful for clients running
151+
custom CLI builds, or customers with old deployment versions that don't have a signature published on
152+
`releases.coder.com`.
153+
- **`fallbackOnCoderForSignatures`**: When enabled, allows downloading signatures from `releases.coder.com` if not
154+
available from the deployment.
155+
156+
### Security Considerations
157+
158+
- The plugin embeds Coder's trusted public key in the plugin resources
159+
- Verification uses detached signatures, which are more secure than attached signatures
160+
- Users are warned about security risks when verification fails
161+
- The system gracefully handles cases where signatures are unavailable
162+
- All verification failures are logged for debugging purposes
163+
164+
### Error Handling
165+
166+
The system handles various failure scenarios:
167+
168+
- **Missing signatures**: Prompts user to accept risk or abort
169+
- **Invalid signatures**: Warns user about potential tampering and prompts user to accept risk or abort
170+
- **Verification failures**: Prompts user to accept risk or abort
171+
172+
This signature verification system ensures that users can trust the Coder CLI binaries they download through the plugin,
173+
protecting against supply chain attacks and ensuring binary integrity.
174+
112175
## Configuring and Testing workspace polling with HTTP & SOCKS5 Proxy
113176

114177
This section explains how to set up a local proxy and verify that

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
version=0.6.0
1+
version=0.6.1
22
group=com.coder.toolbox
33
name=coder-toolbox

src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@ class CoderCLIManager(
181181
}
182182
}
183183

184+
if (context.settingsStore.disableSignatureVerification) {
185+
downloader.commit()
186+
context.logger.info("Skipping over CLI signature verification, it is disabled by the user")
187+
return true
188+
}
189+
184190
var signatureResult = withContext(Dispatchers.IO) {
185191
downloader.downloadSignature(showTextProgress)
186192
}

src/main/kotlin/com/coder/toolbox/settings/ReadOnlyCoderSettings.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ interface ReadOnlyCoderSettings {
2929
val binaryDirectory: String?
3030

3131
/**
32-
* Controls whether we fall back release.coder.com
32+
* Controls whether we verify the cli signature
33+
*/
34+
val disableSignatureVerification: Boolean
35+
36+
/**
37+
* Controls whether we fall back on release.coder.com for signatures if signature validation is enabled
3338
*/
3439
val fallbackOnCoderForSignatures: SignatureFallbackStrategy
3540

src/main/kotlin/com/coder/toolbox/store/CoderSettingsStore.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class CoderSettingsStore(
3838
override val defaultURL: String get() = store[DEFAULT_URL] ?: "https://dev.coder.com"
3939
override val binarySource: String? get() = store[BINARY_SOURCE]
4040
override val binaryDirectory: String? get() = store[BINARY_DIRECTORY]
41+
override val disableSignatureVerification: Boolean
42+
get() = store[DISABLE_SIGNATURE_VALIDATION]?.toBooleanStrictOrNull() ?: false
4143
override val fallbackOnCoderForSignatures: SignatureFallbackStrategy
4244
get() = SignatureFallbackStrategy.fromValue(store[FALLBACK_ON_CODER_FOR_SIGNATURES])
4345
override val defaultCliBinaryNameByOsAndArch: String get() = getCoderCLIForOS(getOS(), getArch())
@@ -166,6 +168,10 @@ class CoderSettingsStore(
166168
store[ENABLE_DOWNLOADS] = shouldEnableDownloads.toString()
167169
}
168170

171+
fun updateDisableSignatureVerification(shouldDisableSignatureVerification: Boolean) {
172+
store[DISABLE_SIGNATURE_VALIDATION] = shouldDisableSignatureVerification.toString()
173+
}
174+
169175
fun updateSignatureFallbackStrategy(fallback: Boolean) {
170176
store[FALLBACK_ON_CODER_FOR_SIGNATURES] = when (fallback) {
171177
true -> SignatureFallbackStrategy.ALLOW.toString()

src/main/kotlin/com/coder/toolbox/store/StoreKeys.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ internal const val BINARY_SOURCE = "binarySource"
1010

1111
internal const val BINARY_DIRECTORY = "binaryDirectory"
1212

13+
internal const val DISABLE_SIGNATURE_VALIDATION = "disableSignatureValidation"
14+
1315
internal const val FALLBACK_ON_CODER_FOR_SIGNATURES = "signatureFallbackStrategy"
1416

1517
internal const val BINARY_NAME = "binaryName"

src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.coder.toolbox.sdk.CoderRestClient
99
import com.coder.toolbox.sdk.v2.models.Workspace
1010
import com.coder.toolbox.sdk.v2.models.WorkspaceAgent
1111
import com.coder.toolbox.sdk.v2.models.WorkspaceStatus
12+
import com.coder.toolbox.util.WebUrlValidationResult.Invalid
1213
import com.jetbrains.toolbox.api.remoteDev.connection.RemoteToolsHelper
1314
import kotlinx.coroutines.Job
1415
import kotlinx.coroutines.TimeoutCancellationException
@@ -107,6 +108,11 @@ open class CoderProtocolHandler(
107108
context.logAndShowError(CAN_T_HANDLE_URI_TITLE, "Query parameter \"$URL\" is missing from URI")
108109
return null
109110
}
111+
val validationResult = deploymentURL.validateStrictWebUrl()
112+
if (validationResult is Invalid) {
113+
context.logAndShowError(CAN_T_HANDLE_URI_TITLE, "\"$URL\" is invalid: ${validationResult.reason}")
114+
return null
115+
}
110116
return deploymentURL
111117
}
112118

src/main/kotlin/com/coder/toolbox/util/URLExtensions.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,44 @@
11
package com.coder.toolbox.util
22

3+
import com.coder.toolbox.util.WebUrlValidationResult.Invalid
4+
import com.coder.toolbox.util.WebUrlValidationResult.Valid
35
import java.net.IDN
46
import java.net.URI
57
import java.net.URL
68

79
fun String.toURL(): URL = URI.create(this).toURL()
810

11+
fun String.validateStrictWebUrl(): WebUrlValidationResult = try {
12+
val uri = URI(this)
13+
14+
when {
15+
uri.isOpaque -> Invalid(
16+
"The URL \"$this\" is invalid because it is not in the standard format. " +
17+
"Please enter a full web address like \"https://example.com\""
18+
)
19+
20+
!uri.isAbsolute -> Invalid(
21+
"The URL \"$this\" is missing a scheme (like https://). " +
22+
"Please enter a full web address like \"https://example.com\""
23+
)
24+
uri.scheme?.lowercase() !in setOf("http", "https") ->
25+
Invalid(
26+
"The URL \"$this\" must start with http:// or https://, not \"${uri.scheme}\""
27+
)
28+
uri.authority.isNullOrBlank() ->
29+
Invalid(
30+
"The URL \"$this\" does not include a valid website name. " +
31+
"Please enter a full web address like \"https://example.com\""
32+
)
33+
else -> Valid
34+
}
35+
} catch (_: Exception) {
36+
Invalid(
37+
"The input \"$this\" is not a valid web address. " +
38+
"Please enter a full web address like \"https://example.com\""
39+
)
40+
}
41+
942
fun URL.withPath(path: String): URL = URL(
1043
this.protocol,
1144
this.host,
@@ -30,3 +63,8 @@ fun URI.toQueryParameters(): Map<String, String> = (this.query ?: "")
3063
parts[0] to ""
3164
}
3265
}
66+
67+
sealed class WebUrlValidationResult {
68+
object Valid : WebUrlValidationResult()
69+
data class Invalid(val reason: String) : WebUrlValidationResult()
70+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy