diff --git a/contrib/hg-ssh b/contrib/hg-ssh
index a355f2b..994f6a9 100755
--- a/contrib/hg-ssh
+++ b/contrib/hg-ssh
@@ -32,7 +32,7 @@ command="hg-ssh --read-only repos/*"
# enable importing on demand to reduce startup time
from mercurial import demandimport; demandimport.enable()
-from mercurial import dispatch
+from mercurial import dispatch, ui as uimod
import sys, os, shlex
@@ -61,14 +61,15 @@ def main():
repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
if repo in allowed_paths:
cmd = ['-R', repo, 'serve', '--stdio']
+ req = dispatch.request(cmd)
if readonly:
- cmd += [
- '--config',
- 'hooks.pretxnopen.hg-ssh=python:__main__.rejectpush',
- '--config',
- 'hooks.prepushkey.hg-ssh=python:__main__.rejectpush'
- ]
- dispatch.dispatch(dispatch.request(cmd))
+ if not req.ui:
+ req.ui = uimod.ui()
+ req.ui.setconfig('hooks', 'pretxnopen.hg-ssh',
+ 'python:__main__.rejectpush', 'hg-ssh')
+ req.ui.setconfig('hooks', 'prepushkey.hg-ssh',
+ 'python:__main__.rejectpush', 'hg-ssh')
+ dispatch.dispatch(req)
else:
sys.stderr.write('Illegal repository "%s"\n' % repo)
sys.exit(255)
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
index 92c4df5..6feb470 100644
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -151,6 +151,37 @@ def _runcatch(req):
pass # happens if called in a thread
try:
+ realcmd = None
+ try:
+ cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
+ cmd = cmdargs[0]
+ aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
+ realcmd = aliases[0]
+ except (error.UnknownCommand, error.AmbiguousCommand,
+ IndexError, fancyopts.getopt.GetoptError):
+ # Don't handle this here. We know the command is
+ # invalid, but all we're worried about for now is that
+ # it's not a command that server operators expect to
+ # be safe to offer to users in a sandbox.
+ pass
+ if realcmd == 'serve' and '--stdio' in cmdargs:
+ # We want to constrain 'hg serve --stdio' instances pretty
+ # closely, as many shared-ssh access tools want to grant
+ # access to run *only* 'hg -R $repo serve --stdio'. We
+ # restrict to exactly that set of arguments, and prohibit
+ # any repo name that starts with '--' to prevent
+ # shenanigans wherein a user does something like pass
+ # --debugger or --config=ui.debugger=1 as a repo
+ # name. This used to actually run the debugger.
+ if (len(req.args) != 4 or
+ req.args[0] != '-R' or
+ req.args[1].startswith('--') or
+ req.args[2] != 'serve' or
+ req.args[3] != '--stdio'):
+ raise error.Abort(
+ _('potentially unsafe serve --stdio invocation: %r') %
+ (req.args,))
+
try:
debugger = 'pdb'
debugtrace = {
diff --git a/tests/test-ssh.t b/tests/test-ssh.t
index 2436fe2..69be386 100644
--- a/tests/test-ssh.t
+++ b/tests/test-ssh.t
@@ -345,6 +345,19 @@ Test (non-)escaping of remote paths with spaces when cloning (issue3145):
abort: destination 'a repo' is not empty
[255]
+Make sure hg is really paranoid in serve --stdio mode. It used to be
+possible to get a debugger REPL by specifying a repo named --debugger.
+ $ hg -R --debugger serve --stdio
+ abort: potentially unsafe serve --stdio invocation: ['-R', '--debugger', 'serve', '--stdio']
+ [255]
+ $ hg -R --config=ui.debugger=yes serve --stdio
+ abort: potentially unsafe serve --stdio invocation: ['-R', '--config=ui.debugger=yes', 'serve', '--stdio']
+ [255]
+Abbreviations of 'serve' also don't work, to avoid shenanigans.
+ $ hg -R narf serv --stdio
+ abort: potentially unsafe serve --stdio invocation: ['-R', 'narf', 'serv', '--stdio']
+ [255]
+
Test hg-ssh using a helper script that will restore PYTHONPATH (which might
have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
parameters: