Skip to content

Commit f43f496

Browse files
Merge pull request #65 from browserstack/release_3.1.1
Release 3.1.1
2 parents a2d2795 + 1bee695 commit f43f496

9 files changed

Lines changed: 344 additions & 57 deletions

File tree

‎.github/dependabot.yml‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Keeps the commit-SHA pins in .github/workflows/ maintained.
2+
# Pinning actions to an immutable SHA (rather than a mutable tag) is what stops a
3+
# compromised or re-pointed action tag from executing in the release job; Dependabot
4+
# is what stops those pins from going stale.
5+
version: 2
6+
updates:
7+
- package-ecosystem: "github-actions"
8+
directory: "/"
9+
schedule:
10+
interval: "weekly"
11+
# Wait 7 days before proposing a bump to a newly published action version,
12+
# so a compromised or yanked release is caught upstream before it reaches CI.
13+
cooldown:
14+
default-days: 7
15+
commit-message:
16+
prefix: "ci"

‎.github/workflows/cd.yml‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ name: .NET package CD
22

33
on: [workflow_dispatch]
44

5+
# Least privilege: no step in this workflow writes to the repo,
6+
# releases or packages via GITHUB_TOKEN (publishing uses NUGET_API_KEY,
7+
# signing uses GCP_SA_KEY). Fail closed regardless of the org default.
8+
permissions:
9+
contents: read
10+
511
defaults:
612
run:
713
working-directory: BrowserStackLocal
@@ -11,9 +17,9 @@ jobs:
1117
runs-on: windows-latest
1218

1319
steps:
14-
- uses: actions/checkout@v2
20+
- uses: actions/checkout@0717577d45739eb3c851188b29f50ed6c0b2194e # v2
1521
- name: Add msbuild to PATH
16-
uses: microsoft/setup-msbuild@v1.0.2
22+
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1.0.2
1723
- name: Build BrowserStackLocal
1824
run: |
1925
msbuild BrowserStackLocal -t:restore -p:Configuration=Release
@@ -23,7 +29,7 @@ jobs:
2329
msbuild BrowserStackLocalIntegrationTests -t:restore -p:Configuration=Release
2430
msbuild BrowserStackLocalIntegrationTests -t:build -p:Configuration=Release
2531
- name: Setup .NET Core
26-
uses: actions/setup-dotnet@v3
32+
uses: actions/setup-dotnet@55ec9447dda3d1cf6bd587150f3262f30ee10815 # v3
2733
with:
2834
dotnet-version: 6.0.x
2935
- name: Setup GCP credentials
@@ -40,7 +46,7 @@ jobs:
4046
run: python3 -m pip install google-auth requests
4147
shell: bash
4248
- name: Setup Java
43-
uses: actions/setup-java@v4
49+
uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4
4450
with:
4551
distribution: 'temurin'
4652
java-version: '17'
@@ -58,7 +64,7 @@ jobs:
5864
./scripts/sign_nupkg.sh
5965
shell: bash
6066
- name: Save artifact
61-
uses: actions/upload-artifact@v4
67+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
6268
with:
6369
name: BrowserStackLocal.nupkg
6470
path: .\BrowserStackLocal\BrowserStackLocal\bin\Release\*.nupkg

‎.github/workflows/ci.yml‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ name: .NET package CI
22

33
on: [workflow_dispatch]
44

5+
# Least privilege: no step in this workflow writes to the repo,
6+
# releases or packages via GITHUB_TOKEN (publishing uses NUGET_API_KEY,
7+
# signing uses GCP_SA_KEY). Fail closed regardless of the org default.
8+
permissions:
9+
contents: read
10+
511
defaults:
612
run:
713
working-directory: BrowserStackLocal
@@ -11,9 +17,9 @@ jobs:
1117
runs-on: windows-latest
1218

1319
steps:
14-
- uses: actions/checkout@v2
20+
- uses: actions/checkout@0717577d45739eb3c851188b29f50ed6c0b2194e # v2
1521
- name: Add msbuild to PATH
16-
uses: microsoft/setup-msbuild@v1.0.2
22+
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1.0.2
1723
- name: Build BrowserStackLocal
1824
run: |
1925
msbuild BrowserStackLocal -t:restore -p:Configuration=Release
@@ -23,7 +29,7 @@ jobs:
2329
msbuild BrowserStackLocalIntegrationTests -t:restore -p:Configuration=Release
2430
msbuild BrowserStackLocalIntegrationTests -t:build -p:Configuration=Release
2531
- name: Setup .NET Core
26-
uses: actions/setup-dotnet@v3
32+
uses: actions/setup-dotnet@55ec9447dda3d1cf6bd587150f3262f30ee10815 # v3
2733
with:
2834
dotnet-version: 6.0.x
2935
- name: Run Integration Tests
@@ -34,7 +40,7 @@ jobs:
3440
- name: Pack NuGet Package
3541
run: msbuild BrowserStackLocal -t:pack -p:Configuration=Release
3642
- name: Save artifact
37-
uses: actions/upload-artifact@v4
43+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
3844
with:
3945
name: BrowserStackLocal.nupkg
4046
path: .\BrowserStackLocal\BrowserStackLocal\bin\Release\*.nupkg

‎BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs‎

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using NUnit.Framework;
77
using BrowserStack;
8+
using System.Collections.Generic;
89
using System.Text;
910
using System.IO;
1011

@@ -91,15 +92,15 @@ public void TestBinaryPathOnNoMoreFallback()
9192
public void TestBinaryArguments()
9293
{
9394
tunnel = new TunnelClass();
94-
tunnel.addBinaryArguments("dummyArguments");
95-
Assert.AreEqual(tunnel.getBinaryArguments(), "dummyArguments");
95+
tunnel.addBinaryArguments(new List<string> { "-dummyFlag", "dummyValue" });
96+
CollectionAssert.AreEqual(new List<string> { "-dummyFlag", "dummyValue" }, tunnel.getBinaryArguments());
9697
}
9798
[TestMethod]
9899
public void TestBinaryArgumentsAreEmptyOnNull()
99100
{
100101
tunnel = new TunnelClass();
101102
tunnel.addBinaryArguments(null);
102-
Assert.AreEqual(tunnel.getBinaryArguments(), "");
103+
Assert.IsEmpty(tunnel.getBinaryArguments());
103104
}
104105

105106

@@ -130,9 +131,74 @@ public void testFallbackException()
130131
{
131132
tunnel.fallbackPaths();
132133
}
134+
135+
// Regression for the chmod shell-metacharacter injection (F-001): binaryAbsolute must
136+
// reach chmod as a single argument, never interpolated into a shell command line. On
137+
// pre-fix code (`bash -c "chmod 0755 <path>"`) the payload below runs `touch <marker>`
138+
// and never chmods the real file, so BOTH asserts fail; the fix (`/bin/chmod` +
139+
// ArgumentList) creates no marker and chmods the real path. Unix-only: on Windows
140+
// modifyBinaryPermission takes the ACL branch, not chmod.
141+
[TestMethod]
142+
public void TestModifyBinaryPermissionDoesNotInterpretShellMetacharacters()
143+
{
144+
if (os.Platform.ToString() != "Unix")
145+
{
146+
Assert.Ignore("Unix-only: Windows takes the ACL branch in modifyBinaryPermission, not chmod");
147+
return;
148+
}
149+
150+
string prevCwd = Directory.GetCurrentDirectory();
151+
// Space-free working dir so the injected `touch pwned` (if it runs) lands here deterministically.
152+
string work = Path.Combine(Path.GetTempPath(), "bsloc" + Guid.NewGuid().ToString("N"));
153+
Directory.CreateDirectory(work);
154+
Directory.SetCurrentDirectory(work);
155+
try
156+
{
157+
// Filename carries a space AND a shell-injection payload. A filename cannot contain '/',
158+
// so the injected command targets the (deterministic) CWD, not an absolute path.
159+
string binaryPath = Path.Combine(work, "bs local; touch pwned; #");
160+
File.WriteAllText(binaryPath, "#!/bin/sh\n"); // default perms ~0644 (not executable)
161+
162+
tunnel = new TunnelClass();
163+
((TunnelClass)tunnel).setBinaryAbsolute(binaryPath);
164+
tunnel.modifyBinaryPermission();
165+
166+
Assert.IsFalse(File.Exists(Path.Combine(work, "pwned")),
167+
"shell metacharacters in binaryAbsolute were interpreted - OS command injection");
168+
Assert.IsTrue(IsExecutable(binaryPath),
169+
"chmod 0755 was not applied to the real binary path (the path was mangled by the shell)");
170+
}
171+
finally
172+
{
173+
Directory.SetCurrentDirectory(prevCwd);
174+
try { Directory.Delete(work, true); } catch { }
175+
}
176+
}
177+
178+
// Returns true iff `path` has the execute bit set. Uses sh's `$0` positional so the
179+
// path (which contains a space + metacharacters) is passed safely, not re-parsed.
180+
private static bool IsExecutable(string path)
181+
{
182+
var psi = new System.Diagnostics.ProcessStartInfo("/bin/sh") { UseShellExecute = false };
183+
psi.ArgumentList.Add("-c");
184+
psi.ArgumentList.Add("test -x \"$0\"");
185+
psi.ArgumentList.Add(path);
186+
using (var p = System.Diagnostics.Process.Start(psi))
187+
{
188+
p.WaitForExit();
189+
return p.ExitCode == 0;
190+
}
191+
}
133192
public class TunnelClass : BrowserStackTunnel
134193
{
135194
public TunnelClass() : base("test-user-agent") {}
195+
// Stub the network boundary so these binary-path/fallback unit tests exercise
196+
// the real path-resolution logic without making a live HTTP call to the
197+
// endpoint API (which addBinaryPath triggers on first invocation).
198+
protected override string fetchSourceUrl(string accessKey)
199+
{
200+
return null;
201+
}
136202
public StringBuilder getOutputBuilder()
137203
{
138204
return output;
@@ -141,10 +207,14 @@ public string getBinaryAbsolute()
141207
{
142208
return binaryAbsolute;
143209
}
144-
public string getBinaryArguments()
210+
public List<string> getBinaryArguments()
145211
{
146212
return binaryArguments;
147213
}
214+
public void setBinaryAbsolute(string path)
215+
{
216+
binaryAbsolute = path;
217+
}
148218
}
149219
}
150220
}

0 commit comments

Comments
 (0)