diff --git a/docs/contributing/ci.md b/docs/contributing/ci.md
new file mode 100644
index 000000000..8055fe095
--- /dev/null
+++ b/docs/contributing/ci.md
@@ -0,0 +1,48 @@
+---
+name: Running Tests on Untrusted Forks
+sidebar_position: 99
+---
+
+# Running CI Scripts on Untrusted Forks
+
+Untrusted forks could contain malicious code to mine cryptocurrency, steal secrets, or otherwise harm the CI server.
+
+For PRs from untrusted forks, to run the CI scripts, we need to:
+
+1. Review the code to ensure that it is safe to run on the CI server.
+2. If the code is safe, run the `ci:trust` script to push the commits to a branch on the main repository, where the CI scripts can be run.
+3. Once the tests have run, the status of the PR will be updated automatically (because the commits are the same).
+
+
+## How to run the CI scripts on untrusted forks:
+
+1. Copy the name of the branch from the PR.
+
+2. From your local clone of the main repository, run the `ci:trust` script.
+ ```bash
+ yarn ci:trust
+ ```
+3. The branch will be pushed and the tests will run
+
+
+
+## What does ci:trust do?
+
+The `ci:trust` script does the following:
+
+1. Adds and fetches the untrusted fork as a temporary remote in your local repository.
+2. Pushes the specific branch from the untrusted fork to a designated temporary branch in your original repository.
+3. Pushing to a local branch triggers the continuous integration (CI) tests on the commits of the branch.
+4. Because the commits are the same, the status of the PR will be updated automatically.
+
+
+### Notes
+1. The ci:trust script will only work if you have write access to the main repository. This prevents malicious users from running the script on the main repository.
+2. The ci:trust script pushes the commits to a branch called `temp-branch-to-test-fork`.
+
+::: warning
+
+The `temp-branch-to-test-fork` branch will be deleted and recreated if it already exists. This allows the script to
+clean up its own temporary branches.
+
+:::
diff --git a/docs/contributing/images/ci-copy-fork-branch.png b/docs/contributing/images/ci-copy-fork-branch.png
new file mode 100644
index 000000000..8d401229a
Binary files /dev/null and b/docs/contributing/images/ci-copy-fork-branch.png differ
diff --git a/docs/contributing/images/ci-tests-running.png b/docs/contributing/images/ci-tests-running.png
new file mode 100644
index 000000000..25e465fd0
Binary files /dev/null and b/docs/contributing/images/ci-tests-running.png differ
diff --git a/scripts/git-push-fork-to-upstream-repo.sh b/scripts/git-push-fork-to-upstream-repo.sh
new file mode 100755
index 000000000..662a4182f
--- /dev/null
+++ b/scripts/git-push-fork-to-upstream-repo.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+set -eo pipefail
+
+: "${GPF_REACTOTRON_BRANCH:=build-trusted-commits}"
+
+REACTOTRON_REPO="git@github.com:infinitered/reactotron.git"
+BRANCH_SPEC=$1
+NUM_COLONS=$(echo "$BRANCH_SPEC" | awk -F: '{print NF-1}')
+
+if [ "$#" -ne 1 ] || [ "$NUM_COLONS" -ne 1 ] ; then
+ echo "Usage: :"
+ exit 1
+fi
+
+SOURCE_GH_USER=$(echo "$BRANCH_SPEC" | awk -F: '{print $1}')
+SOURCE_BRANCH=$(echo "$BRANCH_SPEC" | awk -F: '{print $2}')
+REPO_NAME=$(git remote get-url --push origin | awk -F/ '{print $NF}' | sed 's/\.git$//')
+
+# Check if 'temp-branch-to-test-fork' remote exists and then remove it
+if git config --get "remote.temp-branch-to-test-fork.url" > /dev/null; then
+ git remote remove temp-branch-to-test-fork
+ echo "Removed remote temp-branch-to-test-fork"
+else
+ echo "Remote temp-branch-to-test-fork does not exist, no need to remove it"
+fi
+
+git remote add temp-branch-to-test-fork "git@github.com:$SOURCE_GH_USER/$REPO_NAME.git"
+
+git fetch --all
+git push --force "$REACTOTRON_REPO" "refs/remotes/temp-branch-to-test-fork/$SOURCE_BRANCH:refs/heads/$GPF_REACTOTRON_BRANCH"
+git remote remove temp-branch-to-test-fork || echo "Removed new remote temp-branch-to-test-fork"
+
+cat <