Skip to content

Include - #1166

Open
KaliszAd wants to merge 7 commits into
mwiede:masterfrom
KaliszAd:include
Open

Include#1166
KaliszAd wants to merge 7 commits into
mwiede:masterfrom
KaliszAd:include

Conversation

@KaliszAd

@KaliszAd KaliszAd commented Sep 25, 2026 •

Copy link
Copy Markdown

Include and Match for OpenSSH config files

This teaches OpenSSHConfig two directives that most real ~/.ssh/config
files use today and that JSch silently ignored: Include, which pulls in
further files, and Match, which applies settings conditionally. The goal
is that an application can load the same config an administrator maintains
for OpenSSH, including the split-out files under ~/.ssh/config.d/, and get
the same hosts, users and keys OpenSSH would.

Include ~/.ssh/config.d/*.conf

Match host *.internal user deploy
  IdentityFile ~/.ssh/deploy_ed25519

Host bastion
  HostName bastion.example.org
  Include ~/.ssh/bastion-options

Include

  • Paths are resolved like OpenSSH resolves them for a user config: relative
    names live under ~/.ssh, ~ and ~/ expand to the current user's home,
    and globs are expanded per path segment, so config.d/*/*.conf works.
  • A pattern that matches nothing is not an error, as in OpenSSH.
  • Nesting is limited to 16 levels, OpenSSH's own limit, and a file that
    includes itself, directly or through another file, is rejected with an
    error naming the file.
  • An Include inside a Host or Match block applies only within that
    block; one at top level applies globally.
  • A bare Host, Match or Include without an argument is an error
    instead of silently continuing the previous block.

Match

  • Criteria: all, host, originalhost, user and localuser, with
    OpenSSH's comma-separated pattern lists and ! negation, for example
    Match host *.internal,!test.internal.
  • Every Match condition is evaluated once per lookup, against the values
    known at that point, so a later HostName or User inside an included
    file cannot flip a decision that was already taken. This is what OpenSSH
    does and it keeps evaluation order predictable.
  • Criteria JSch cannot evaluate, such as exec, fail closed: the config is
    rejected with an error naming the criterion rather than being applied
    with a block silently skipped or silently matched.
  • ConfigRepository gains a getConfig(host, user) overload, with a
    default that delegates to the old method, so Match user can see the
    username the application passed to getSession.

Comments

Trailing comments are handled the way OpenSSH handles them: an unquoted
# that starts a word ends the value, so Port 2222 # bastion gives 2222,
while h#1 and quoted text stay intact. Previously such a line silently
lost its value.

Session

A Session resolves its config once, when it is created, and reuses that
result for channels and port forwardings. Before, a second evaluation at
channel time could see a different User or HostName and, for example,
enable ForwardAgent for a channel although the session had not been
configured with it.

Security notes

  • Include reads files, so config text should come from a trusted source.
    JSch does not enforce OpenSSH's owner and mode checks on included files,
    because embedded applications often manage config on filesystems without
    POSIX permissions; the Javadoc says so and leaves the trust policy to the
    caller.
  • Unknown Match criteria fail closed rather than open.

Testing

  • Unit tests cover include resolution, globbing, scoping, the depth and
    recursion limits, pattern lists and negation, evaluation order across
    included files, unsupported criteria, comment handling and the session's
    reuse of its resolved config.
  • Behaviour was checked against OpenSSH 10.0 with ssh -F <config> -G,
    and live against sshd chains whose ProxyJump and IdentityFile
    settings come from included files and Match blocks.

Notes for reviewers

  • A follow-up PR, stacked on this one, adds the remaining Match criteria
    (canonical, final, tagged, localnetwork, version), OpenSSH's
    percent tokens and ${ENV} in paths, and file-and-line locations in
    parse errors.
  • The ProxyJump PR touches the same two files at unrelated places; the
    merge conflict between them is a Javadoc list line and a block of fields.

Development

  • The initial implementation was heavily assisted by OpenAI ChatGPT 6.0 Sol with medium effort.
  • Work continued using Anthropic Claude Opus 5.5 with medium effort and Fable 5.1 with high effort.
  • Second opinions used mostly ChatGPT 6.0 Sol with high effort.

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant