{"id":14048,"library":"ssh-config","title":"SSH Config Parser and Stringifier","description":"The `ssh-config` library offers robust parsing and stringification capabilities for SSH configuration files, typically located at `~/.ssh/config`. As of its current stable version, 5.1.0, it enables developers to programmatically read, modify, and write SSH configurations while diligently preserving original formatting, comments, and whitespace. The project maintains an active release cadence, frequently addressing bugs and introducing features, such as Deno support in v5.1.0 and continuous improvements to TypeScript typings. Its primary differentiator lies in its ability to parse an SSH config into an Abstract Syntax Tree (AST)-like structure for easy manipulation and then reliably serialize it back into a valid SSH config string, ensuring changes are applied correctly without data loss. Additionally, it provides convenient helper methods like `compute` to derive the effective configuration for a specific host, and `find` for targeted section modification, offering granular control over SSH settings.","status":"active","version":"5.1.0","language":"javascript","source_language":"en","source_url":"ssh://git@github.com/cyjake/ssh-config","tags":["javascript","typescript"],"install":[{"cmd":"npm install ssh-config","lang":"bash","label":"npm"},{"cmd":"yarn add ssh-config","lang":"bash","label":"yarn"},{"cmd":"pnpm add ssh-config","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"While CommonJS `require` works, ESM `import` is the recommended modern approach, especially with Deno support.","wrong":"const SSHConfig = require('ssh-config')","symbol":"SSHConfig","correct":"import SSHConfig from 'ssh-config'"},{"note":"`parse` is a method of the default exported `SSHConfig` object, not a named export itself.","wrong":"import { parse } from 'ssh-config'","symbol":"parse","correct":"import SSHConfig from 'ssh-config'; SSHConfig.parse(configString)"},{"note":"Available as a named export since v5.1.0 for finer-grained AST manipulation.","symbol":"LineType","correct":"import { LineType } from 'ssh-config'"}],"quickstart":{"code":"import SSHConfig from 'ssh-config';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nconst configContent = `\n# This is a comment\nIdentityFile ~/.ssh/id_rsa\n\nHost devserver\n  HostName 192.168.1.100\n  User admin\n\nHost *\n  User keanu\n  ForwardAgent true\n`;\n\n// Parse the SSH config string\nconst config = SSHConfig.parse(configContent);\n\n// Find and modify a specific host section\nconst devserverSection = config.find({ Host: 'devserver' });\nif (devserverSection && devserverSection.config) {\n  for (const line of devserverSection.config) {\n    if (line.param === 'HostName') {\n      line.value = 'dev.example.com';\n      break;\n    }\n  }\n}\n\n// Add a new host section\nconfig.add({\n  Host: 'staging',\n  config: [\n    { param: 'HostName', value: 'staging.example.com' },\n    { param: 'User', value: 'deploy' }\n  ]\n});\n\n// Compute the effective configuration for a host\nconst computedConfig = config.compute('staging', { ignoreCase: true });\nconsole.log('Computed config for staging:', computedConfig);\n\n// Stringify the modified configuration back to a string\nconst newConfigString = SSHConfig.stringify(config);\nconsole.log('\\n--- Modified SSH Config ---\\n');\nconsole.log(newConfigString);\n\n// Example: writing to a temporary file (ensure directory exists)\nconst tempDir = path.join(process.cwd(), 'temp');\nif (!fs.existsSync(tempDir)) {\n  fs.mkdirSync(tempDir);\n}\nconst tempConfigFile = path.join(tempDir, 'ssh_config_modified.tmp');\nfs.writeFileSync(tempConfigFile, newConfigString);\nconsole.log(`\\nModified config written to: ${tempConfigFile}`);","lang":"typescript","description":"This quickstart demonstrates parsing an SSH config string, modifying an existing host entry, adding a new host, computing effective parameters for a host, and finally stringifying the updated configuration."},"warnings":[{"fix":"Review any code that programmatically modifies or expects specific formatting of values with quotation marks. Adjust parsing or stringification logic if relying on previous behavior.","message":"Version 5.0.0 introduced a breaking change in how quotation marks are reserved when handling multiple values within a configuration directive. Previously, quotation marks might have been stripped or normalized, but now they are preserved in the AST.","severity":"breaking","affected_versions":">=5.0.0"},{"fix":"When using `config.compute(host)`, always pass `{ ignoreCase: true }` as the second argument if you require case-normalized output keys (e.g., `hostname` instead of `hOsTnaME`).","message":"The `.compute()` method, by default, preserves the original casing of SSH directive names (e.g., `hOsTnaME`). OpenSSH itself treats directives case-insensitively. To ensure directive names are normalized to lowercase, matching OpenSSH behavior, you must explicitly use the `{ ignoreCase: true }` option.","severity":"gotcha","affected_versions":">=4.0.0"},{"fix":"Ensure that general settings are placed at the end of your SSH config file so they act as defaults, and more specific host-based settings override them earlier in the file.","message":"According to `ssh_config(5)`, the first obtained parameter value for a given directive will be used. This library's `.compute()` method respects this rule. If multiple directives exist (e.g., `User` defined in different `Host` blocks), the one encountered first (based on specificity and order) takes precedence, and subsequent definitions for the same parameter will be ignored.","severity":"gotcha","affected_versions":">=4.0.0"},{"fix":"Always expect `IdentityFile` to be an array when accessing it from the output of `config.compute()`. Iterate over the array even if you anticipate a single value.","message":"The `IdentityFile` parameter, when computed via `config.compute()`, is always returned as an array, even if only one `IdentityFile` directive is present. This is to accommodate multiple `IdentityFile` settings that can coexist in an SSH config.","severity":"gotcha","affected_versions":">=4.0.0"},{"fix":"Use `config.find({ Host: 'name' })` only when you intend to modify the structure or parameters of a specific host section directly. For retrieving the final, applied SSH configuration for a host, always use `config.compute('hostname')`.","message":"The `.find()` method is strictly for locating and manipulating sections within the parsed config's Abstract Syntax Tree (AST). It is not designed to compute the effective configuration for a given host, which involves inheritance and precedence rules. For computed parameters, use `.compute(host)` instead.","severity":"gotcha","affected_versions":">=4.0.0"}],"env_vars":null,"search_vec":"'/.ssh/config':24 '5.1.0':31 'abil':82 'abstract':90 'activ':55 'addit':119 'address':59 'appli':114 'ast':93 'back':104 'bug':60 'cadenc':57 'capabl':16 'chang':112 'comment':48 'comput':126 'config':2,9,87,109 'configur':19,42,131 'continu':71 'control':144 'conveni':122 'correct':115 'current':28 'data':117 'deno':66 'deriv':128 'develop':34 'differenti':78 'dilig':44 'easi':97 'effect':130 'enabl':33 'ensur':111 'featur':63 'file':20 'find':137 'format':47 'frequent':58 'granular':143 'helper':123 'host':135 'improv':72 'introduc':62 'javascript':148 'librari':10 'lie':79 'like':94,125 'locat':22 'loss':118 'maintain':53 'manipul':98 'method':124 'modif':141 'modifi':38 'offer':11,142 'origin':46 'pars':13,84 'parser':3 'preserv':45 'primari':77 'programmat':36 'project':52 'provid':121 'read':37 'releas':56 'reliabl':101 'robust':12 'section':140 'serial':102 'set':147 'specif':134 'ssh':1,8,18,41,86,108,146 'ssh-config':7 'stabl':29 'string':110 'stringif':15 'stringifi':5 'structur':95 'support':67 'syntax':91 'target':139 'tree':92 'type':75 'typescript':74,149 'typic':21 'v5.1.0':69 'valid':107 'version':30 'whitespac':50 'without':116 'write':40","created_at":"2026-04-20T01:57:39.252936+00:00","updated_at":"2026-04-20T01:57:39.252936+00:00","problems":[{"fix":"Ensure the input string to `SSHConfig.parse()` is a valid, non-empty SSH configuration. Version 5.0.4 fixed a crash on empty files, but robust error handling for invalid formats is still recommended.","cause":"Attempting to parse an empty or malformed SSH configuration string.","error":"TypeError: Cannot read properties of null (reading 'config') or (reading 'add')"},{"fix":"Update to `ssh-config@^5.0.1` or later, as `v5.0.1` specifically addressed wrong typings for the `compute` method. Ensure your `tsconfig.json` includes `\"allowSyntheticDefaultImports\": true` if using `import SSHConfig from 'ssh-config'`.","cause":"Incorrect TypeScript typings or version mismatch causing `compute` method signature issues.","error":"Property 'compute' does not exist on type 'typeof SSHConfig' or similar TypeScript errors about method types."},{"fix":"Version 4.4.3 specifically addressed this. Ensure you are on `ssh-config@^4.4.3` or newer. If the issue persists in highly restrictive environments, you might need to mock `os.userInfo()` or ensure the execution environment provides necessary user context.","cause":"`os.userInfo()` (used internally for default paths) might throw a `SystemError` if the user's home directory or other user information is unavailable or inaccessible (e.g., in a restricted environment).","error":"SystemError: EPERM: operation not permitted, uv_os_get_passwd for null"}],"ecosystem":"npm","meta_description":null,"install_score":null,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":"0.1.6","cli_name":"","cli_version":null,"type":"library","homepage":null,"github":"https://github.com/cyjake/ssh-config","docs":null,"changelog":null,"pypi":null,"npm":"https://www.npmjs.com/package/ssh-config","openapi_spec":null,"status_page":null,"smithery":null,"categories":["devops","serialization"],"base_url":null,"auth_type":null,"provenance":{"verified_status":null,"verified_at":null,"last_verified":"2026-06-17","next_check":"2026-07-18","install_tag":null}}