Skip to content

Anthropic

Claude found about 4,500 OpenSSH lines reachable pre-auth

9 min read

Written by an AI model from the linked sources and checked against them by a second model. How we work

Image: anthropic.com

In release 10.4, OpenSSH’s sshd would parse an ML-DSA-44 key from any unauthenticated client before checking whether the administrator allowed that algorithm. Claude found the problem while mapping which code outsiders can reach. Anthropic described the method in an October 21 research post on attack surface reduction. Across 25 open-source projects, the approach flagged 493 issues without first having to find a single exploitable bug.

ML-DSA-44 is a post-quantum signature algorithm, and it is not enabled by default. Even so, its roughly 4,500 lines of new, experimental C were reachable before authentication, even when an administrator had explicitly excluded the algorithm. A first fix landed in 10.5 and still left two gaps. The version that shipped in 10.6 removed roughly a fifth of the code an unauthenticated client could previously reach in a default build.

Why go after reachability

The post, written by Chris Rohlf, argues that finding and fixing bugs one at a time scales poorly, because fixing even a critical vulnerability tells you nothing about the ones that remain. Making code harder to reach does nothing to the unknown bugs inside it. It does lower their severity, including for any that may already be under active exploitation. The ways to do that are familiar: move code from pre-authentication to post-authentication, from 0-click to 1-click, from on-by-default to explicit opt-in, or from the first message of a protocol to a later point in the session state machine.

Bug hunting has a capacity problem. Over six months, Anthropic’s models turned up more than 29,000 candidate vulnerabilities in important open-source projects, and humans have been able to review and triage only about 6,000 of them, the company said in its OSS Scanner launch post. Gating code that nothing uses doesn’t require anyone to confirm a bug first.

Rohlf’s explanation for why so much code is exposed is code churn. Abstractions, dependencies and features pile up until software behaves differently from what its authors intended, and attackers study how it actually behaves.

Three questions, 17,000 options

Anthropic gave Claude three plain-language descriptions of unnecessary exposure:

  • Default configuration: code that is reachable in the shipped defaults but that no default feature uses.
  • Configuration interaction: pairs of options that look fine on their own but, set together, silently disable a security control or leave a feature undefined while its code stays reachable.
  • Early reachability: complex, security-sensitive code that runs before authentication, without any action from the victim, or before a protocol state transition.

The 25 projects add up to more than five million lines of C, C++, Java, PHP and Python. Claude built an inventory of more than 17,000 configuration options across them, and 3,842 of those configured a security-relevant control or feature. Anthropic sorted the projects into three groups. Ten were public, built to serve anyone or to consume data from parties they cannot identify, such as a DNS resolver parsing upstream answers. Eleven were gated: they accept arbitrary client requests but are meant to offer nothing until the user authenticates. Four were private, deployed inside the operator’s own environment, where authentication is optional and outside the software’s scope.

The grouping sets expectations. A large reachable share is normal for a public project. For a gated project, it should be close to nothing.

Where the gated projects leaked

All eleven gated projects had code that was reachable when it shouldn’t have been, though in none of them was it a large share of total lines. Claude counted 93 unnecessary pre-authentication surfaces in their default configurations, 37 places where code executed or state was recorded before authentication, and 61 configuration combinations that silently weakened a pre-authentication control the administrator had set. Anthropic says none of them was immediately exploitable on its own. The cost is a much larger attack surface than the software needs.

The configuration-interaction pass produced what the post calls some of the most interesting findings across all three groups. Claude found 162 security-sensitive combinations that the software accepted with no error or warning and with no documentation of how the options interact. In 94 of them, the combination silently turned off a security control. The other 68 were inconsistencies: a listener or login path stayed reachable while the configuration said it was disabled, two settings conflicted and the winner depended on their order in the file, or a protection the administrator had configured did nothing while its code stayed reachable.

One pattern showed up independently in four unrelated projects. An administrator writes an access rule for remote clients using a hostname or a wildcard pattern instead of an IP address. Whether the server ever learns the client’s hostname depends on a separate option for reverse DNS lookups, which resolve an IP address back to a name, and most projects had that option off. Without the lookup, the server compares the rule as plain text against the client’s IP address, which a hostname pattern can never match. The deny rule never fires, and the list fails open, letting through the clients it was meant to block. Some of the projects document that hostname rules need DNS lookups. None says that a deny list written this way fails open.

In another project, an administrator could require every client to present a certificate. A second option was meant only to defer that check until it was needed. In practice it disabled the check entirely. A server configured to accept only trusted clients would accept anyone, which exposed a large body of request-handling code that was supposed to sit behind the certificate check. The software accepted both options together without a warning, the documentation never mentioned that the requirement was dropped, and the conflict had been in the code for 21 years.

The fourth case turned on how a setting is spelled. In a project that leaves authentication off by default, the option requiring TLS client certificates worked on most network endpoints but was silently ignored on a few that loaded their TLS settings under a different namespace. A client with no certificate could complete the handshake and open a session on those endpoints, and nothing was logged. One form of the setting name works everywhere. The shorter form, which most examples use, is dropped on the affected endpoints.

OpenSSH, from 10.4 to 10.6

Measured as a share of total code, OpenSSH was the project where Claude cut the most attack surface. Claude traced the input paths of the 10.4 daemon and found that it parsed whatever key type the client sent, and for certificates also verified the embedded CA signature, before checking whether that key type was allowed. The ML-DSA-44 code came to about 16% of OpenSSH’s own pre-authentication code in a default sshd. Anthropic reported the issue to the OpenSSH developers with patches, and release 10.5 moved the check ahead of parsing.

The 10.5 check still had two gaps. The first was a mismatch between what the client claimed and what it sent. The check read the algorithm name the client advertised, but the parser was chosen by a type string embedded inside the key blob. A client could advertise ssh-ed25519, pass the check, and still have an ML-DSA key parsed.

The second gap was in certificates, whose key types are enabled by default. Given a certificate supplied by an attacker, sshd would deserialize the embedded CA public key and run a full signature verification with it, for any supported algorithm, before looking at the configuration. User key signatures were properly gated by the list of public keys allowed to connect. CA signatures were not. As a result, every public key signature verifier in the binary was reachable by any client that could complete a key exchange.

Anthropic built a proof of concept and went through several iterations of a fix with the maintainers. The final change moved the checks into the parser itself. The encoded key type and the certificate’s CA signature algorithm are now compared against the configured allow lists before deserialization or signature verification. The new ML-DSA code is unreachable even for a client that lies about its key type or hides an ML-DSA CA key inside an otherwise acceptable certificate. Certificates signed with an allowed CA algorithm are still parsed and verified, but the check now comes first. The maintainers shipped the changes in 10.6.

OpenSSH is some of the most heavily audited code in the world. If unnecessary reachability survives there, it can survive anywhere.

How the skill runs

Anthropic has combined the three techniques into one reusable Claude skill and is working to integrate it into OSS-Scanner. OSS-Scanner is its free opt-in service, and its first scans in October sent maintainers model-generated findings without human review. The skill makes three passes over a codebase. Each starts with an empty context window, apart from a short handoff from the previous pass.

The first pass looks for code that the default configuration makes reachable but no intended feature uses. The second looks at code that runs before hard gates such as authentication, or before entry points into the protocol state machine, in any configuration. The third looks for configuration combinations that silently switch off a security control or weaken it. In every pass, Claude inventories the code’s entry points and confirms each finding with a dynamic trace of the running program. Anthropic adds that Claude can also compare runtime logs against configurations to disable parsers, legacy protocols and dashboards a deployment never uses.

For the second and third passes, Claude only proposes changes: reordering a configuration check, adding a guard that enforces state ordering, or rejecting a bad combination when the configuration loads. Default-configuration findings go further. Claude designs and implements a gate, then runs the project’s own test suite and a black-box regression suite against the baseline build, and the modified build must produce identical results. Any gate that would change a non-default configuration is set aside for a human to decide.

No comments yet. Be the first.

Join the conversation

Sign in to leave a comment. It appears after moderation.