diff --git a/README.md b/README.md
index f6703565..2821bf70 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,15 @@
+# Why is this forked?
+
+This is hard forked from github.com/rogpeppe/testscript to allow the `gh` maintainers to explore the use of `testscript` in `gh`, while providing the flexibility for us to share modifications and extensions. It is not intended to be a long-term maintained fork and you should not rely on it.
+
+If we determine it is a good fit for the project, we will decide what to do next based on how our needs align with the preferences of the maintainers of rogpeppe/testscript.
+
+## Original README with minor tweaks to indicate upstream/fork relationship
+
 This repository factors out an opinionated selection of internal packages and functionality from the Go standard
 library. Currently this consists mostly of packages and testing code from within the Go tool implementation.
 
-This repo is [primarily maintained](https://github.com/rogpeppe/go-internal/graphs/contributors) by long-time
+The upstream repo is [primarily maintained](https://github.com/rogpeppe/go-internal/graphs/contributors) by long-time
 [Go contributors](https://github.com/golang/go/contributors) who are also currently
 [maintaining CUE](https://github.com/cue-lang/cue/graphs/contributors) (which is primarily written in Go
 and which relies upon several of the packages here).
@@ -25,7 +33,7 @@ Our package will be replaced by it once https://github.com/golang/go/issues/5926
 
 ### testscript
 
-The most popular package here is the [testscript](https://pkg.go.dev/github.com/rogpeppe/go-internal/testscript) package:
+The most popular package here is the [testscript](https://pkg.go.dev/github.com/cli/go-internal/testscript) package:
  * Provides a shell-like test environment that is very nicely tuned for testing Go CLI commands.
  * Extracted from the core Go team's internal testscript package ([cmd/go/internal/script](https://github.com/golang/go/tree/master/src/cmd/go/internal/script)),
  which is [heavily used](https://github.com/golang/go/tree/master/src/cmd/go/testdata/script) to test the `go` command.
@@ -33,10 +41,10 @@ The most popular package here is the [testscript](https://pkg.go.dev/github.com/
  * Integrates well with `go test`, including coverage support.
  * Inputs and sample output files can use the simple [txtar](https://pkg.go.dev/golang.org/x/tools/txtar)
  text archive format, also used by the Go playground.
- * Allows [automatically updating](https://pkg.go.dev/github.com/rogpeppe/go-internal/testscript#Params)
+ * Allows [automatically updating](https://pkg.go.dev/github.com/cli/go-internal/testscript#Params)
  golden files.
  * Built-in support for Go concepts like build tags.
- * Accompanied by a [testscript](https://github.com/rogpeppe/go-internal/tree/master/cmd/testscript) command
+ * Accompanied by a [testscript](https://github.com/cli/go-internal/tree/master/cmd/testscript) command
  for running standalone scripts with files embedded in txtar format.
  
  A nice introduction to using testscripts is this [blog post](https://bitfieldconsulting.com/golang/test-scripts) series.
diff --git a/cache/cache.go b/cache/cache.go
index 93c90535..5061a054 100644
--- a/cache/cache.go
+++ b/cache/cache.go
@@ -19,7 +19,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/rogpeppe/go-internal/lockedfile"
+	"github.com/cli/go-internal/lockedfile"
 )
 
 // An ActionID is a cache action key, the hash of a complete description of a
diff --git a/cmd/testscript/README.md b/cmd/testscript/README.md
index daf9930f..40676a75 100644
--- a/cmd/testscript/README.md
+++ b/cmd/testscript/README.md
@@ -1,5 +1,5 @@
 ```
-The testscript command runs github.com/rogpeppe/go-internal/testscript scripts
+The testscript command runs github.com/cli/go-internal/testscript scripts
 in a fresh temporary work directory tree.
 
 Usage:
@@ -9,15 +9,15 @@ The testscript command is designed to make it easy to create self-contained
 reproductions of command sequences.
 
 Each file is opened as a script and run as described in the documentation for
-github.com/rogpeppe/go-internal/testscript. The special filename "-" is
+github.com/cli/go-internal/testscript. The special filename "-" is
 interpreted as the standard input.
 
 As a special case, supporting files/directories in the .gomodproxy subdirectory
-will be served via a github.com/rogpeppe/go-internal/goproxytest server which
+will be served via a github.com/cli/go-internal/goproxytest server which
 is available to each script via the GOPROXY environment variable. The contents
 of the .gomodproxy subdirectory are not available to the script except via the
 proxy server. See the documentation for
-github.com/rogpeppe/go-internal/goproxytest for details on the format of these
+github.com/cli/go-internal/goproxytest for details on the format of these
 files/directories.
 
 Environment variables can be passed through to each script with the -e flag,
diff --git a/cmd/testscript/help.go b/cmd/testscript/help.go
index 0b3975cd..1845bc0b 100644
--- a/cmd/testscript/help.go
+++ b/cmd/testscript/help.go
@@ -10,7 +10,7 @@ func mainUsage(f io.Writer) {
 }
 
 var mainHelp = `
-The testscript command runs github.com/rogpeppe/go-internal/testscript scripts
+The testscript command runs github.com/cli/go-internal/testscript scripts
 in a fresh temporary work directory tree.
 
 Usage:
@@ -20,15 +20,15 @@ The testscript command is designed to make it easy to create self-contained
 reproductions of command sequences.
 
 Each file is opened as a script and run as described in the documentation for
-github.com/rogpeppe/go-internal/testscript. The special filename "-" is
+github.com/cli/go-internal/testscript. The special filename "-" is
 interpreted as the standard input.
 
 As a special case, supporting files/directories in the .gomodproxy subdirectory
-will be served via a github.com/rogpeppe/go-internal/goproxytest server which
+will be served via a github.com/cli/go-internal/goproxytest server which
 is available to each script via the GOPROXY environment variable. The contents
 of the .gomodproxy subdirectory are not available to the script except via the
 proxy server. See the documentation for
-github.com/rogpeppe/go-internal/goproxytest for details on the format of these
+github.com/cli/go-internal/goproxytest for details on the format of these
 files/directories.
 
 Environment variables can be passed through to each script with the -e flag,
diff --git a/cmd/testscript/main.go b/cmd/testscript/main.go
index 65b340ab..fa7e1395 100644
--- a/cmd/testscript/main.go
+++ b/cmd/testscript/main.go
@@ -15,14 +15,14 @@ import (
 	"strings"
 	"sync/atomic"
 
-	"github.com/rogpeppe/go-internal/goproxytest"
-	"github.com/rogpeppe/go-internal/gotooltest"
-	"github.com/rogpeppe/go-internal/testscript"
+	"github.com/cli/go-internal/goproxytest"
+	"github.com/cli/go-internal/gotooltest"
+	"github.com/cli/go-internal/testscript"
 )
 
 const (
 	// goModProxyDir is the special subdirectory in a txtar script's supporting files
-	// within which we expect to find github.com/rogpeppe/go-internal/goproxytest
+	// within which we expect to find github.com/cli/go-internal/goproxytest
 	// directories.
 	goModProxyDir = ".gomodproxy"
 )
diff --git a/cmd/testscript/main_test.go b/cmd/testscript/main_test.go
index 7bc39028..5307b440 100644
--- a/cmd/testscript/main_test.go
+++ b/cmd/testscript/main_test.go
@@ -13,9 +13,9 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/rogpeppe/go-internal/gotooltest"
-	"github.com/rogpeppe/go-internal/internal/os/execpath"
-	"github.com/rogpeppe/go-internal/testscript"
+	"github.com/cli/go-internal/gotooltest"
+	"github.com/cli/go-internal/internal/os/execpath"
+	"github.com/cli/go-internal/testscript"
 )
 
 func TestMain(m *testing.M) {
diff --git a/cmd/txtar-addmod/addmod.go b/cmd/txtar-addmod/addmod.go
index e58514a6..f9de5f40 100644
--- a/cmd/txtar-addmod/addmod.go
+++ b/cmd/txtar-addmod/addmod.go
@@ -3,7 +3,7 @@
 // license that can be found in the LICENSE file.
 
 // The txtar-addmod command adds a module as a txtar archive to the a testdata module directory
-// as understood by the goproxytest package (see https://godoc.org/github.com/rogpeppe/go-internal/goproxytest).
+// as understood by the goproxytest package (see https://godoc.org/github.com/cli/go-internal/goproxytest).
 //
 // Usage:
 //
@@ -38,7 +38,7 @@ func usage() {
 	fmt.Fprintf(os.Stderr, `
 The txtar-addmod command adds a module as a txtar archive to the
 testdata module directory as understood by the goproxytest package
-(see https://godoc.org/github.com/rogpeppe/go-internal/goproxytest).
+(see https://godoc.org/github.com/cli/go-internal/goproxytest).
 
 The dir argument names to directory to add the module to. If dir is "-",
 the result will instead be written to the standard output in a form
diff --git a/cmd/txtar-addmod/script_test.go b/cmd/txtar-addmod/script_test.go
index b070ef03..cd93b8fd 100644
--- a/cmd/txtar-addmod/script_test.go
+++ b/cmd/txtar-addmod/script_test.go
@@ -9,9 +9,9 @@ import (
 	"os"
 	"testing"
 
-	"github.com/rogpeppe/go-internal/goproxytest"
-	"github.com/rogpeppe/go-internal/gotooltest"
-	"github.com/rogpeppe/go-internal/testscript"
+	"github.com/cli/go-internal/goproxytest"
+	"github.com/cli/go-internal/gotooltest"
+	"github.com/cli/go-internal/testscript"
 )
 
 var proxyURL string
diff --git a/cmd/txtar-c/savedir.go b/cmd/txtar-c/savedir.go
index bc2f692b..0d0ec5f3 100644
--- a/cmd/txtar-c/savedir.go
+++ b/cmd/txtar-c/savedir.go
@@ -22,7 +22,7 @@ import (
 	"strings"
 	"unicode/utf8"
 
-	"github.com/rogpeppe/go-internal/txtar"
+	"github.com/cli/go-internal/txtar"
 )
 
 var flag = stdflag.NewFlagSet(os.Args[0], stdflag.ContinueOnError)
diff --git a/cmd/txtar-c/script_test.go b/cmd/txtar-c/script_test.go
index 94e77328..20a84eca 100644
--- a/cmd/txtar-c/script_test.go
+++ b/cmd/txtar-c/script_test.go
@@ -8,7 +8,7 @@ import (
 	"os"
 	"testing"
 
-	"github.com/rogpeppe/go-internal/testscript"
+	"github.com/cli/go-internal/testscript"
 )
 
 func TestMain(m *testing.M) {
diff --git a/cmd/txtar-goproxy/main.go b/cmd/txtar-goproxy/main.go
index c4f0553a..c882a514 100644
--- a/cmd/txtar-goproxy/main.go
+++ b/cmd/txtar-goproxy/main.go
@@ -21,7 +21,7 @@ import (
 	"log"
 	"os"
 
-	"github.com/rogpeppe/go-internal/goproxytest"
+	"github.com/cli/go-internal/goproxytest"
 )
 
 var proxyAddr = flag.String("addr", "", "run proxy on this network address")
diff --git a/cmd/txtar-x/extract.go b/cmd/txtar-x/extract.go
index 713026e7..3b5856bc 100644
--- a/cmd/txtar-x/extract.go
+++ b/cmd/txtar-x/extract.go
@@ -19,7 +19,7 @@ import (
 	"log"
 	"os"
 
-	"github.com/rogpeppe/go-internal/txtar"
+	"github.com/cli/go-internal/txtar"
 )
 
 var (
diff --git a/cmd/txtar-x/extract_test.go b/cmd/txtar-x/extract_test.go
index 97e8e7e3..1bb646d9 100644
--- a/cmd/txtar-x/extract_test.go
+++ b/cmd/txtar-x/extract_test.go
@@ -9,7 +9,7 @@ import (
 	"os"
 	"testing"
 
-	"github.com/rogpeppe/go-internal/testscript"
+	"github.com/cli/go-internal/testscript"
 )
 
 func TestMain(m *testing.M) {
diff --git a/fmtsort/sort_test.go b/fmtsort/sort_test.go
index 88e5bb88..9ac7931b 100644
--- a/fmtsort/sort_test.go
+++ b/fmtsort/sort_test.go
@@ -11,7 +11,7 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/rogpeppe/go-internal/fmtsort"
+	"github.com/cli/go-internal/fmtsort"
 )
 
 var compareTests = [][]reflect.Value{
diff --git a/go.mod b/go.mod
index 8a504e7b..37f67b73 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/rogpeppe/go-internal
+module github.com/cli/go-internal
 
 go 1.22.0
 
diff --git a/goproxytest/proxy.go b/goproxytest/proxy.go
index e8fe8a7c..3c5ea816 100644
--- a/goproxytest/proxy.go
+++ b/goproxytest/proxy.go
@@ -40,7 +40,7 @@ import (
 	"golang.org/x/mod/semver"
 	"golang.org/x/tools/txtar"
 
-	"github.com/rogpeppe/go-internal/par"
+	"github.com/cli/go-internal/par"
 )
 
 type Server struct {
diff --git a/goproxytest/proxy_test.go b/goproxytest/proxy_test.go
index 35a9452d..fd167383 100644
--- a/goproxytest/proxy_test.go
+++ b/goproxytest/proxy_test.go
@@ -8,9 +8,9 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/rogpeppe/go-internal/goproxytest"
-	"github.com/rogpeppe/go-internal/gotooltest"
-	"github.com/rogpeppe/go-internal/testscript"
+	"github.com/cli/go-internal/goproxytest"
+	"github.com/cli/go-internal/gotooltest"
+	"github.com/cli/go-internal/testscript"
 )
 
 func TestScripts(t *testing.T) {
diff --git a/gotooltest/script_test.go b/gotooltest/script_test.go
index 5beb54a7..78eac978 100644
--- a/gotooltest/script_test.go
+++ b/gotooltest/script_test.go
@@ -9,8 +9,8 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/rogpeppe/go-internal/gotooltest"
-	"github.com/rogpeppe/go-internal/testscript"
+	"github.com/cli/go-internal/gotooltest"
+	"github.com/cli/go-internal/testscript"
 )
 
 func TestSimple(t *testing.T) {
diff --git a/gotooltest/setup.go b/gotooltest/setup.go
index c8d9ec07..19d03f77 100644
--- a/gotooltest/setup.go
+++ b/gotooltest/setup.go
@@ -18,7 +18,7 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/rogpeppe/go-internal/testscript"
+	"github.com/cli/go-internal/testscript"
 )
 
 var (
diff --git a/gotooltest/testdata/cover.txt b/gotooltest/testdata/cover.txt
index 815f4e65..96505a8c 100644
--- a/gotooltest/testdata/cover.txt
+++ b/gotooltest/testdata/cover.txt
@@ -2,7 +2,7 @@ unquote scripts/exec.txt
 
 # The module uses testscript itself.
 # Use the checked out module, based on where the test binary ran.
-go mod edit -replace=github.com/rogpeppe/go-internal=${GOINTERNAL_MODULE}
+go mod edit -replace=github.com/cli/go-internal=${GOINTERNAL_MODULE}
 go mod tidy
 
 # First, a 'go test' run without coverage.
@@ -46,8 +46,8 @@ import (
 	"os"
 	"testing"
 
-	"github.com/rogpeppe/go-internal/gotooltest"
-	"github.com/rogpeppe/go-internal/testscript"
+	"github.com/cli/go-internal/gotooltest"
+	"github.com/cli/go-internal/testscript"
 )
 
 func TestMain(m *testing.M) {
diff --git a/imports/scan_test.go b/imports/scan_test.go
index 253bce1a..4129fa92 100644
--- a/imports/scan_test.go
+++ b/imports/scan_test.go
@@ -10,7 +10,7 @@ import (
 	"runtime"
 	"testing"
 
-	"github.com/rogpeppe/go-internal/testenv"
+	"github.com/cli/go-internal/testenv"
 )
 
 func TestScan(t *testing.T) {
diff --git a/internal/syscall/windows/zsyscall_windows.go b/internal/syscall/windows/zsyscall_windows.go
index 3ed2d9fe..d4efdf91 100644
--- a/internal/syscall/windows/zsyscall_windows.go
+++ b/internal/syscall/windows/zsyscall_windows.go
@@ -6,7 +6,7 @@ import (
 	"syscall"
 	"unsafe"
 
-	"github.com/rogpeppe/go-internal/internal/syscall/windows/sysdll"
+	"github.com/cli/go-internal/internal/syscall/windows/sysdll"
 )
 
 var _ unsafe.Pointer
diff --git a/lockedfile/internal/filelock/filelock_test.go b/lockedfile/internal/filelock/filelock_test.go
index 429969c3..15436ca7 100644
--- a/lockedfile/internal/filelock/filelock_test.go
+++ b/lockedfile/internal/filelock/filelock_test.go
@@ -15,7 +15,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/rogpeppe/go-internal/lockedfile/internal/filelock"
+	"github.com/cli/go-internal/lockedfile/internal/filelock"
 )
 
 func lock(t *testing.T, f *os.File) {
diff --git a/lockedfile/internal/filelock/filelock_windows.go b/lockedfile/internal/filelock/filelock_windows.go
index ceab65b0..a6eeb1eb 100644
--- a/lockedfile/internal/filelock/filelock_windows.go
+++ b/lockedfile/internal/filelock/filelock_windows.go
@@ -10,7 +10,7 @@ import (
 	"io/fs"
 	"syscall"
 
-	"github.com/rogpeppe/go-internal/internal/syscall/windows"
+	"github.com/cli/go-internal/internal/syscall/windows"
 )
 
 type lockType uint32
diff --git a/lockedfile/lockedfile_filelock.go b/lockedfile/lockedfile_filelock.go
index 454c3a42..8b2f4291 100644
--- a/lockedfile/lockedfile_filelock.go
+++ b/lockedfile/lockedfile_filelock.go
@@ -10,7 +10,7 @@ import (
 	"io/fs"
 	"os"
 
-	"github.com/rogpeppe/go-internal/lockedfile/internal/filelock"
+	"github.com/cli/go-internal/lockedfile/internal/filelock"
 )
 
 func openFile(name string, flag int, perm fs.FileMode) (*os.File, error) {
diff --git a/lockedfile/lockedfile_test.go b/lockedfile/lockedfile_test.go
index e4e01dcb..79264886 100644
--- a/lockedfile/lockedfile_test.go
+++ b/lockedfile/lockedfile_test.go
@@ -16,7 +16,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/rogpeppe/go-internal/lockedfile"
+	"github.com/cli/go-internal/lockedfile"
 )
 
 func mustTempDir(t *testing.T) (dir string, remove func()) {
diff --git a/lockedfile/transform_test.go b/lockedfile/transform_test.go
index f7488fa1..6bc0beef 100644
--- a/lockedfile/transform_test.go
+++ b/lockedfile/transform_test.go
@@ -16,7 +16,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/rogpeppe/go-internal/lockedfile"
+	"github.com/cli/go-internal/lockedfile"
 )
 
 func isPowerOf2(x int) bool {
diff --git a/robustio/robustio_windows.go b/robustio/robustio_windows.go
index 961de4ef..b1ac9a09 100644
--- a/robustio/robustio_windows.go
+++ b/robustio/robustio_windows.go
@@ -8,7 +8,7 @@ import (
 	"errors"
 	"syscall"
 
-	"github.com/rogpeppe/go-internal/internal/syscall/windows"
+	"github.com/cli/go-internal/internal/syscall/windows"
 )
 
 const errFileNotFound = syscall.ERROR_FILE_NOT_FOUND
diff --git a/testscript/cmd.go b/testscript/cmd.go
index e04d26e4..255e9429 100644
--- a/testscript/cmd.go
+++ b/testscript/cmd.go
@@ -16,9 +16,9 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/rogpeppe/go-internal/diff"
-	"github.com/rogpeppe/go-internal/testscript/internal/pty"
-	"github.com/rogpeppe/go-internal/txtar"
+	"github.com/cli/go-internal/diff"
+	"github.com/cli/go-internal/testscript/internal/pty"
+	"github.com/cli/go-internal/txtar"
 )
 
 // scriptCmds are the script command implementations.
diff --git a/testscript/doc.go b/testscript/doc.go
index 8ccbb443..b0f16283 100644
--- a/testscript/doc.go
+++ b/testscript/doc.go
@@ -235,7 +235,7 @@ The predefined commands are:
     Rewrite each file by replacing any leading ">" characters from
     each line. This enables a file to contain substrings that look like
     txtar file markers.
-    See also https://godoc.org/github.com/rogpeppe/go-internal/txtar#Unquote
+    See also https://godoc.org/github.com/cli/go-internal/txtar#Unquote
 
   - wait [command]
     Wait for all 'exec' and 'go' commands started in the background (with the '&'
@@ -364,6 +364,6 @@ for manual debugging of failing tests:
 	func F() { p2.F() }
 	$
 
-See also: https://pkg.go.dev/github.com/rogpeppe/go-internal/gotooltest
+See also: https://pkg.go.dev/github.com/cli/go-internal/gotooltest
 */
 package testscript
diff --git a/testscript/testscript.go b/testscript/testscript.go
index e4024837..3c64fc27 100644
--- a/testscript/testscript.go
+++ b/testscript/testscript.go
@@ -29,13 +29,13 @@ import (
 	"testing"
 	"time"
 
-	"github.com/rogpeppe/go-internal/imports"
-	"github.com/rogpeppe/go-internal/internal/misspell"
-	"github.com/rogpeppe/go-internal/internal/os/execpath"
-	"github.com/rogpeppe/go-internal/par"
-	"github.com/rogpeppe/go-internal/testenv"
-	"github.com/rogpeppe/go-internal/testscript/internal/pty"
-	"github.com/rogpeppe/go-internal/txtar"
+	"github.com/cli/go-internal/imports"
+	"github.com/cli/go-internal/internal/misspell"
+	"github.com/cli/go-internal/internal/os/execpath"
+	"github.com/cli/go-internal/par"
+	"github.com/cli/go-internal/testenv"
+	"github.com/cli/go-internal/testscript/internal/pty"
+	"github.com/cli/go-internal/txtar"
 )
 
 var goVersionRegex = regexp.MustCompile(`^go([1-9][0-9]*)\.([1-9][0-9]*)$`)
@@ -581,7 +581,7 @@ func (ts *TestScript) run() {
 
 		markTime()
 		// Flush testScript log to testing.T log.
-		ts.t.Log(ts.abbrev(ts.log.String()))
+		ts.t.Log(ts.abbrev(redactTokens(ts.log.String())))
 	}()
 	defer func() {
 		ts.deferred()
@@ -883,6 +883,26 @@ func (ts *TestScript) condition(cond string) (bool, error) {
 
 // Helpers for command implementations.
 
+// redactTokens looks at the entire string looking for strings that start with a known token prefix,
+// keeps the prefix that distinguishes the token type and masks the rest of the token with asterisks.
+// Note that it does not attempt to distinguish the end of a token from any suffixed characters so
+// gho_mytokenNOTATOKEN will mask the entirity of the string.
+func redactTokens(s string) string {
+	// Regular expression to match "ghp_" or "gho_" followed by any sequence of non-whitespace characters
+	re := regexp.MustCompile(`(gh[pousr]_|github_pat_)\S+`)
+
+	// Replace matched strings with the prefix followed by asterisks
+	return re.ReplaceAllStringFunc(s, func(match string) string {
+		// If the match is gh then we want to keep the first 4 characters and redact everything else,
+		// otherwise, it must be a github_pat_ and then we keep the first 11 characters and redact everything else.
+		prefixLength := 4
+		if strings.HasPrefix(match, "github_pat_") {
+			prefixLength = 11
+		}
+		return match[:prefixLength] + strings.Repeat("*", len(match)-prefixLength)
+	})
+}
+
 // abbrev abbreviates the actual work directory in the string s to the literal string "$WORK".
 func (ts *TestScript) abbrev(s string) string {
 	s = strings.Replace(s, ts.workdir, "$WORK", -1)
diff --git a/testscript/testscript_test.go b/testscript/testscript_test.go
index 9cbab8fa..e40b6fbd 100644
--- a/testscript/testscript_test.go
+++ b/testscript/testscript_test.go
@@ -410,6 +410,68 @@ func TestBadDir(t *testing.T) {
 	}
 }
 
+func TestRedactTokens(t *testing.T) {
+	// https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#githubs-token-formats
+	tokensToRedacted := map[string]string{
+		"ghp_1234567890":        "ghp_**********",
+		"github_pat_1234567890": "github_pat_**********",
+		"gho_1234567890":        "gho_**********",
+		"ghu_1234567890":        "ghu_**********",
+		"ghs_1234567890":        "ghs_**********",
+		"ghr_1234567890":        "ghr_**********",
+	}
+
+	tests := []struct {
+		name  string
+		input string
+		want  string
+	}{
+		{
+			name:  "token at start",
+			input: "%s",
+			want:  "%s",
+		},
+		{
+			name:  "token at end",
+			input: "this is a %s",
+			want:  "this is a %s",
+		},
+		{
+			name:  "token in middle",
+			input: "this is a %s and this is redacted",
+			want:  "this is a %s and this is redacted",
+		},
+		{
+			name:  "token at start with newline",
+			input: "%s\n",
+			want:  "%s\n",
+		},
+		{
+			name:  "token with no preceding word boundary",
+			input: "foo%s",
+			want:  "foo%s",
+		},
+		{
+			name:  "token no word boundary (extends redaction into next word)",
+			input: "%sx",
+			want:  "%s*",
+		},
+	}
+
+	for token, redacted := range tokensToRedacted {
+		for _, test := range tests {
+			t.Run(fmt.Sprintf("%s: %s", token, test.name), func(t *testing.T) {
+				unredacted := fmt.Sprintf(test.input, token)
+				expected := fmt.Sprintf(test.want, redacted)
+
+				if got := redactTokens(unredacted); got != expected {
+					t.Fatalf("redactTokens(%q) == %q, want %q", unredacted, got, expected)
+				}
+			})
+		}
+	}
+}
+
 // catchAbort catches the panic raised by fakeT.FailNow.
 func catchAbort() {
 	if err := recover(); err != nil && err != errAbort {
