We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Ruby sass gem used at build time has reached end-of-life
Per https://github.com/sass/ruby-sass (which is archived) and https://sass-lang.com/ruby-sass, this gem, which is [used at package build time](https://github.com/freedomofpress/securedrop/blob/develop/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/tasks/sass.yml) for CSS generation, has reached end-of-life. We should consider migrating to a maintained codebase, like [sassc](https://github.com/sass/sassc) / `libsass`.

Note that we're also using a pinned outdated version due to #1594; we could update now that we're on Xenial, but that no longer seems worth resolving at this point.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `securedrop/management/run.py`
Content:
```
1 import atexit
2 import os
3 import select
4 import signal
5 import subprocess
6 import sys
7 
8 __all__ = ["run"]
9 
10 from typing import Any, Callable, List, TextIO
11 
12 
13 def colorize(s: str, color: str, bold: bool = False) -> str:
14     """
15     Returns the string s surrounded by shell metacharacters to display
16     it with the given color and optionally bolded.
17     """
18     # List of shell colors from https://www.siafoo.net/snippet/88
19     shell_colors = {
20         "gray": "30",
21         "red": "31",
22         "green": "32",
23         "yellow": "33",
24         "blue": "34",
25         "magenta": "35",
26         "cyan": "36",
27         "white": "37",
28         "crimson": "38",
29         "highlighted_red": "41",
30         "highlighted_green": "42",
31         "highlighted_brown": "43",
32         "highlighted_blue": "44",
33         "highlighted_magenta": "45",
34         "highlighted_cyan": "46",
35         "highlighted_gray": "47",
36         "highlighted_crimson": "48",
37     }
38 
39     # Based on http://stackoverflow.com/a/2330297/1093000
40     attrs = []
41     attrs.append(shell_colors[color])
42     if bold:
43         attrs.append("1")
44 
45     return "\x1b[{}m{}\x1b[0m".format(";".join(attrs), s)
46 
47 
48 class DevServerProcess(subprocess.Popen):  # pragma: no cover
49     def __init__(self, label: str, cmd: List[str], color: str) -> None:
50         self.label = label
51         self.cmd = cmd
52         self.color = color
53 
54         super(DevServerProcess, self).__init__(  # type: ignore
55             self.cmd,
56             stdin=subprocess.PIPE,
57             stdout=subprocess.PIPE,
58             stderr=subprocess.STDOUT,
59             preexec_fn=os.setsid,
60         )
61 
62     def print_label(self, to: TextIO) -> None:
63         label = "\n => {} <= \n\n".format(self.label)
64         if to.isatty():
65             label = colorize(label, self.color, True)
66         to.write(label)
67 
68     def fileno(self) -> int:
69         """Implement fileno() in order to use DevServerProcesses with
70         select.select directly.
71 
72         Note this method assumes we only want to select this process'
73         stdout. This is a reasonable assumption for a DevServerProcess
74         because the __init__ redirects stderr to stdout, so all output is
75         available on stdout.
76 
77         """
78         if not self.stdout:
79             raise RuntimeError()
80         return self.stdout.fileno()
81 
82 
83 class DevServerProcessMonitor:  # pragma: no cover
84     def __init__(self, proc_funcs: List[Callable]) -> None:
85         self.procs = []
86         self.last_proc = None
87         atexit.register(self.cleanup)
88 
89         for pf in proc_funcs:
90             self.procs.append(pf())
91 
92     def monitor(self) -> None:
93         while True:
94             rprocs, _, _ = select.select(self.procs, [], [])
95 
96             for proc in rprocs:
97                 # To keep track of which process output what, print a
98                 # helpful label every time the process sending output
99                 # changes.
100                 if proc != self.last_proc:
101                     proc.print_label(sys.stdout)
102                     self.last_proc = proc
103 
104                 line = proc.stdout.readline()
105                 sys.stdout.write(line.decode("utf-8"))
106                 sys.stdout.flush()
107 
108             if any(proc.poll() is not None for proc in self.procs):
109                 # If any of the processes terminates (for example, due to
110                 # a syntax error causing a reload to fail), kill them all
111                 # so we don't get stuck.
112                 sys.stdout.write(
113                     colorize(
114                         "\nOne of the development servers exited unexpectedly. "
115                         "See the traceback above for details.\n"
116                         "Once you have resolved the issue, you can re-run "
117                         "'./manage.py run' to continue developing.\n\n",
118                         "red",
119                         True,
120                     )
121                 )
122                 self.cleanup()
123                 break
124 
125         for proc in self.procs:
126             proc.wait()
127 
128     def cleanup(self) -> None:
129         for proc in self.procs:
130             if proc.poll() is None:
131                 # When the development servers use automatic reloading, they
132                 # spawn new subprocesses frequently. In order to make sure we
133                 # kill all of the subprocesses, we need to send SIGTERM to
134                 # the process group and not just the process we initially
135                 # created. See http://stackoverflow.com/a/4791612/1093000
136                 os.killpg(proc.pid, signal.SIGTERM)
137                 proc.terminate()
138 
139 
140 def run(args: Any) -> None:  # pragma: no cover
141     """
142     Starts development servers for both the Source Interface and the
143     Journalist Interface concurrently. Their output is collected,
144     labeled, and sent to stdout to present a unified view to the
145     developer.
146 
147     Ctrl-C will kill the servers and return you to the terminal.
148 
149     Useful resources:
150     * https://stackoverflow.com/q/22565606/837471
151 
152     """
153     print(
154         """
155  ____                                        ____
156 /\\  _`\\                                     /\\  _`\\
157 \\ \\,\\L\\_\\     __    ___   __  __  _ __    __\\ \\ \\/\\ \\  _ __   ___   _____
158  \\/_\\__ \\   /'__`\\ /'___\\/\\ \\/\\ \\/\\`'__\\/'__`\\ \\ \\ \\ \\/\\`'__\\/ __`\\/\\ '__`\\
159    /\\ \\L\\ \\/\\  __//\\ \\__/\\ \\ \\_\\ \\ \\ \\//\\  __/\\ \\ \\_\\ \\ \\ \\//\\ \\L\\ \\ \\ \\L\\ \\  # noqa: E501
160    \\ `\\____\\ \\____\\ \\____\\\\ \\____/\\ \\_\\\\ \\____\\\\ \\____/\\ \\_\\\\ \\____/\\ \\ ,__/
161     \\/_____/\\/____/\\/____/ \\/___/  \\/_/ \\/____/ \\/___/  \\/_/ \\/___/  \\ \\ \\/
162                                                                       \\ \\_\\
163                                                                        \\/_/
164 """
165     )  # noqa
166 
167     procs = [
168         lambda: DevServerProcess("Source Interface", ["python", "source.py"], "blue"),
169         lambda: DevServerProcess("Journalist Interface", ["python", "journalist.py"], "cyan"),
170         lambda: DevServerProcess(
171             "SASS Compiler", ["sass", "--watch", "sass:static/css"], "magenta"
172         ),
173     ]
174 
175     monitor = DevServerProcessMonitor(procs)
176     monitor.monitor()
177
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/securedrop/management/run.py b/securedrop/management/run.py
--- a/securedrop/management/run.py
+++ b/securedrop/management/run.py
@@ -167,9 +167,6 @@
     procs = [
         lambda: DevServerProcess("Source Interface", ["python", "source.py"], "blue"),
         lambda: DevServerProcess("Journalist Interface", ["python", "journalist.py"], "cyan"),
-        lambda: DevServerProcess(
-            "SASS Compiler", ["sass", "--watch", "sass:static/css"], "magenta"
-        ),
     ]
 
     monitor = DevServerProcessMonitor(procs)

{"golden_diff": "diff --git a/securedrop/management/run.py b/securedrop/management/run.py\n--- a/securedrop/management/run.py\n+++ b/securedrop/management/run.py\n@@ -167,9 +167,6 @@\n     procs = [\n         lambda: DevServerProcess(\"Source Interface\", [\"python\", \"source.py\"], \"blue\"),\n         lambda: DevServerProcess(\"Journalist Interface\", [\"python\", \"journalist.py\"], \"cyan\"),\n-        lambda: DevServerProcess(\n-            \"SASS Compiler\", [\"sass\", \"--watch\", \"sass:static/css\"], \"magenta\"\n-        ),\n     ]\n \n     monitor = DevServerProcessMonitor(procs)\n", "issue": "Ruby sass gem used at build time has reached end-of-life\nPer https://github.com/sass/ruby-sass (which is archived) and https://sass-lang.com/ruby-sass, this gem, which is [used at package build time](https://github.com/freedomofpress/securedrop/blob/develop/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/tasks/sass.yml) for CSS generation, has reached end-of-life. We should consider migrating to a maintained codebase, like [sassc](https://github.com/sass/sassc) / `libsass`.\r\n\r\nNote that we're also using a pinned outdated version due to #1594; we could update now that we're on Xenial, but that no longer seems worth resolving at this point.\n", "before_files": [{"content": "import atexit\nimport os\nimport select\nimport signal\nimport subprocess\nimport sys\n\n__all__ = [\"run\"]\n\nfrom typing import Any, Callable, List, TextIO\n\n\ndef colorize(s: str, color: str, bold: bool = False) -> str:\n    \"\"\"\n    Returns the string s surrounded by shell metacharacters to display\n    it with the given color and optionally bolded.\n    \"\"\"\n    # List of shell colors from https://www.siafoo.net/snippet/88\n    shell_colors = {\n        \"gray\": \"30\",\n        \"red\": \"31\",\n        \"green\": \"32\",\n        \"yellow\": \"33\",\n        \"blue\": \"34\",\n        \"magenta\": \"35\",\n        \"cyan\": \"36\",\n        \"white\": \"37\",\n        \"crimson\": \"38\",\n        \"highlighted_red\": \"41\",\n        \"highlighted_green\": \"42\",\n        \"highlighted_brown\": \"43\",\n        \"highlighted_blue\": \"44\",\n        \"highlighted_magenta\": \"45\",\n        \"highlighted_cyan\": \"46\",\n        \"highlighted_gray\": \"47\",\n        \"highlighted_crimson\": \"48\",\n    }\n\n    # Based on http://stackoverflow.com/a/2330297/1093000\n    attrs = []\n    attrs.append(shell_colors[color])\n    if bold:\n        attrs.append(\"1\")\n\n    return \"\\x1b[{}m{}\\x1b[0m\".format(\";\".join(attrs), s)\n\n\nclass DevServerProcess(subprocess.Popen):  # pragma: no cover\n    def __init__(self, label: str, cmd: List[str], color: str) -> None:\n        self.label = label\n        self.cmd = cmd\n        self.color = color\n\n        super(DevServerProcess, self).__init__(  # type: ignore\n            self.cmd,\n            stdin=subprocess.PIPE,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n            preexec_fn=os.setsid,\n        )\n\n    def print_label(self, to: TextIO) -> None:\n        label = \"\\n => {} <= \\n\\n\".format(self.label)\n        if to.isatty():\n            label = colorize(label, self.color, True)\n        to.write(label)\n\n    def fileno(self) -> int:\n        \"\"\"Implement fileno() in order to use DevServerProcesses with\n        select.select directly.\n\n        Note this method assumes we only want to select this process'\n        stdout. This is a reasonable assumption for a DevServerProcess\n        because the __init__ redirects stderr to stdout, so all output is\n        available on stdout.\n\n        \"\"\"\n        if not self.stdout:\n            raise RuntimeError()\n        return self.stdout.fileno()\n\n\nclass DevServerProcessMonitor:  # pragma: no cover\n    def __init__(self, proc_funcs: List[Callable]) -> None:\n        self.procs = []\n        self.last_proc = None\n        atexit.register(self.cleanup)\n\n        for pf in proc_funcs:\n            self.procs.append(pf())\n\n    def monitor(self) -> None:\n        while True:\n            rprocs, _, _ = select.select(self.procs, [], [])\n\n            for proc in rprocs:\n                # To keep track of which process output what, print a\n                # helpful label every time the process sending output\n                # changes.\n                if proc != self.last_proc:\n                    proc.print_label(sys.stdout)\n                    self.last_proc = proc\n\n                line = proc.stdout.readline()\n                sys.stdout.write(line.decode(\"utf-8\"))\n                sys.stdout.flush()\n\n            if any(proc.poll() is not None for proc in self.procs):\n                # If any of the processes terminates (for example, due to\n                # a syntax error causing a reload to fail), kill them all\n                # so we don't get stuck.\n                sys.stdout.write(\n                    colorize(\n                        \"\\nOne of the development servers exited unexpectedly. \"\n                        \"See the traceback above for details.\\n\"\n                        \"Once you have resolved the issue, you can re-run \"\n                        \"'./manage.py run' to continue developing.\\n\\n\",\n                        \"red\",\n                        True,\n                    )\n                )\n                self.cleanup()\n                break\n\n        for proc in self.procs:\n            proc.wait()\n\n    def cleanup(self) -> None:\n        for proc in self.procs:\n            if proc.poll() is None:\n                # When the development servers use automatic reloading, they\n                # spawn new subprocesses frequently. In order to make sure we\n                # kill all of the subprocesses, we need to send SIGTERM to\n                # the process group and not just the process we initially\n                # created. See http://stackoverflow.com/a/4791612/1093000\n                os.killpg(proc.pid, signal.SIGTERM)\n                proc.terminate()\n\n\ndef run(args: Any) -> None:  # pragma: no cover\n    \"\"\"\n    Starts development servers for both the Source Interface and the\n    Journalist Interface concurrently. Their output is collected,\n    labeled, and sent to stdout to present a unified view to the\n    developer.\n\n    Ctrl-C will kill the servers and return you to the terminal.\n\n    Useful resources:\n    * https://stackoverflow.com/q/22565606/837471\n\n    \"\"\"\n    print(\n        \"\"\"\n ____                                        ____\n/\\\\  _`\\\\                                     /\\\\  _`\\\\\n\\\\ \\\\,\\\\L\\\\_\\\\     __    ___   __  __  _ __    __\\\\ \\\\ \\\\/\\\\ \\\\  _ __   ___   _____\n \\\\/_\\\\__ \\\\   /'__`\\\\ /'___\\\\/\\\\ \\\\/\\\\ \\\\/\\\\`'__\\\\/'__`\\\\ \\\\ \\\\ \\\\ \\\\/\\\\`'__\\\\/ __`\\\\/\\\\ '__`\\\\\n   /\\\\ \\\\L\\\\ \\\\/\\\\  __//\\\\ \\\\__/\\\\ \\\\ \\\\_\\\\ \\\\ \\\\ \\\\//\\\\  __/\\\\ \\\\ \\\\_\\\\ \\\\ \\\\ \\\\//\\\\ \\\\L\\\\ \\\\ \\\\ \\\\L\\\\ \\\\  # noqa: E501\n   \\\\ `\\\\____\\\\ \\\\____\\\\ \\\\____\\\\\\\\ \\\\____/\\\\ \\\\_\\\\\\\\ \\\\____\\\\\\\\ \\\\____/\\\\ \\\\_\\\\\\\\ \\\\____/\\\\ \\\\ ,__/\n    \\\\/_____/\\\\/____/\\\\/____/ \\\\/___/  \\\\/_/ \\\\/____/ \\\\/___/  \\\\/_/ \\\\/___/  \\\\ \\\\ \\\\/\n                                                                      \\\\ \\\\_\\\\\n                                                                       \\\\/_/\n\"\"\"\n    )  # noqa\n\n    procs = [\n        lambda: DevServerProcess(\"Source Interface\", [\"python\", \"source.py\"], \"blue\"),\n        lambda: DevServerProcess(\"Journalist Interface\", [\"python\", \"journalist.py\"], \"cyan\"),\n        lambda: DevServerProcess(\n            \"SASS Compiler\", [\"sass\", \"--watch\", \"sass:static/css\"], \"magenta\"\n        ),\n    ]\n\n    monitor = DevServerProcessMonitor(procs)\n    monitor.monitor()\n", "path": "securedrop/management/run.py"}], "after_files": [{"content": "import atexit\nimport os\nimport select\nimport signal\nimport subprocess\nimport sys\n\n__all__ = [\"run\"]\n\nfrom typing import Any, Callable, List, TextIO\n\n\ndef colorize(s: str, color: str, bold: bool = False) -> str:\n    \"\"\"\n    Returns the string s surrounded by shell metacharacters to display\n    it with the given color and optionally bolded.\n    \"\"\"\n    # List of shell colors from https://www.siafoo.net/snippet/88\n    shell_colors = {\n        \"gray\": \"30\",\n        \"red\": \"31\",\n        \"green\": \"32\",\n        \"yellow\": \"33\",\n        \"blue\": \"34\",\n        \"magenta\": \"35\",\n        \"cyan\": \"36\",\n        \"white\": \"37\",\n        \"crimson\": \"38\",\n        \"highlighted_red\": \"41\",\n        \"highlighted_green\": \"42\",\n        \"highlighted_brown\": \"43\",\n        \"highlighted_blue\": \"44\",\n        \"highlighted_magenta\": \"45\",\n        \"highlighted_cyan\": \"46\",\n        \"highlighted_gray\": \"47\",\n        \"highlighted_crimson\": \"48\",\n    }\n\n    # Based on http://stackoverflow.com/a/2330297/1093000\n    attrs = []\n    attrs.append(shell_colors[color])\n    if bold:\n        attrs.append(\"1\")\n\n    return \"\\x1b[{}m{}\\x1b[0m\".format(\";\".join(attrs), s)\n\n\nclass DevServerProcess(subprocess.Popen):  # pragma: no cover\n    def __init__(self, label: str, cmd: List[str], color: str) -> None:\n        self.label = label\n        self.cmd = cmd\n        self.color = color\n\n        super(DevServerProcess, self).__init__(  # type: ignore\n            self.cmd,\n            stdin=subprocess.PIPE,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n            preexec_fn=os.setsid,\n        )\n\n    def print_label(self, to: TextIO) -> None:\n        label = \"\\n => {} <= \\n\\n\".format(self.label)\n        if to.isatty():\n            label = colorize(label, self.color, True)\n        to.write(label)\n\n    def fileno(self) -> int:\n        \"\"\"Implement fileno() in order to use DevServerProcesses with\n        select.select directly.\n\n        Note this method assumes we only want to select this process'\n        stdout. This is a reasonable assumption for a DevServerProcess\n        because the __init__ redirects stderr to stdout, so all output is\n        available on stdout.\n\n        \"\"\"\n        if not self.stdout:\n            raise RuntimeError()\n        return self.stdout.fileno()\n\n\nclass DevServerProcessMonitor:  # pragma: no cover\n    def __init__(self, proc_funcs: List[Callable]) -> None:\n        self.procs = []\n        self.last_proc = None\n        atexit.register(self.cleanup)\n\n        for pf in proc_funcs:\n            self.procs.append(pf())\n\n    def monitor(self) -> None:\n        while True:\n            rprocs, _, _ = select.select(self.procs, [], [])\n\n            for proc in rprocs:\n                # To keep track of which process output what, print a\n                # helpful label every time the process sending output\n                # changes.\n                if proc != self.last_proc:\n                    proc.print_label(sys.stdout)\n                    self.last_proc = proc\n\n                line = proc.stdout.readline()\n                sys.stdout.write(line.decode(\"utf-8\"))\n                sys.stdout.flush()\n\n            if any(proc.poll() is not None for proc in self.procs):\n                # If any of the processes terminates (for example, due to\n                # a syntax error causing a reload to fail), kill them all\n                # so we don't get stuck.\n                sys.stdout.write(\n                    colorize(\n                        \"\\nOne of the development servers exited unexpectedly. \"\n                        \"See the traceback above for details.\\n\"\n                        \"Once you have resolved the issue, you can re-run \"\n                        \"'./manage.py run' to continue developing.\\n\\n\",\n                        \"red\",\n                        True,\n                    )\n                )\n                self.cleanup()\n                break\n\n        for proc in self.procs:\n            proc.wait()\n\n    def cleanup(self) -> None:\n        for proc in self.procs:\n            if proc.poll() is None:\n                # When the development servers use automatic reloading, they\n                # spawn new subprocesses frequently. In order to make sure we\n                # kill all of the subprocesses, we need to send SIGTERM to\n                # the process group and not just the process we initially\n                # created. See http://stackoverflow.com/a/4791612/1093000\n                os.killpg(proc.pid, signal.SIGTERM)\n                proc.terminate()\n\n\ndef run(args: Any) -> None:  # pragma: no cover\n    \"\"\"\n    Starts development servers for both the Source Interface and the\n    Journalist Interface concurrently. Their output is collected,\n    labeled, and sent to stdout to present a unified view to the\n    developer.\n\n    Ctrl-C will kill the servers and return you to the terminal.\n\n    Useful resources:\n    * https://stackoverflow.com/q/22565606/837471\n\n    \"\"\"\n    print(\n        \"\"\"\n ____                                        ____\n/\\\\  _`\\\\                                     /\\\\  _`\\\\\n\\\\ \\\\,\\\\L\\\\_\\\\     __    ___   __  __  _ __    __\\\\ \\\\ \\\\/\\\\ \\\\  _ __   ___   _____\n \\\\/_\\\\__ \\\\   /'__`\\\\ /'___\\\\/\\\\ \\\\/\\\\ \\\\/\\\\`'__\\\\/'__`\\\\ \\\\ \\\\ \\\\ \\\\/\\\\`'__\\\\/ __`\\\\/\\\\ '__`\\\\\n   /\\\\ \\\\L\\\\ \\\\/\\\\  __//\\\\ \\\\__/\\\\ \\\\ \\\\_\\\\ \\\\ \\\\ \\\\//\\\\  __/\\\\ \\\\ \\\\_\\\\ \\\\ \\\\ \\\\//\\\\ \\\\L\\\\ \\\\ \\\\ \\\\L\\\\ \\\\  # noqa: E501\n   \\\\ `\\\\____\\\\ \\\\____\\\\ \\\\____\\\\\\\\ \\\\____/\\\\ \\\\_\\\\\\\\ \\\\____\\\\\\\\ \\\\____/\\\\ \\\\_\\\\\\\\ \\\\____/\\\\ \\\\ ,__/\n    \\\\/_____/\\\\/____/\\\\/____/ \\\\/___/  \\\\/_/ \\\\/____/ \\\\/___/  \\\\/_/ \\\\/___/  \\\\ \\\\ \\\\/\n                                                                      \\\\ \\\\_\\\\\n                                                                       \\\\/_/\n\"\"\"\n    )  # noqa\n\n    procs = [\n        lambda: DevServerProcess(\"Source Interface\", [\"python\", \"source.py\"], \"blue\"),\n        lambda: DevServerProcess(\"Journalist Interface\", [\"python\", \"journalist.py\"], \"cyan\"),\n    ]\n\n    monitor = DevServerProcessMonitor(procs)\n    monitor.monitor()\n", "path": "securedrop/management/run.py"}]}