Skip to content

fix(server): security audit JSON parse error on duplicate sshd_config directives#4889

Open
reikjarloekl wants to merge 1 commit into
Dokploy:canaryfrom
automaze-me:fix/security-audit-json-parse
Open

fix(server): security audit JSON parse error on duplicate sshd_config directives#4889
reikjarloekl wants to merge 1 commit into
Dokploy:canaryfrom
automaze-me:fix/security-audit-json-parse

Conversation

@reikjarloekl

@reikjarloekl reikjarloekl commented Jul 22, 2026

Copy link
Copy Markdown

What is this PR about?

The Security tab for remote servers fails with a red banner — Failed to parse output: Bad control character in string literal in JSON at position 148 (line 1 column 149) — whenever the remote /etc/ssh/sshd_config contains more than one active line for the same directive (e.g. a global PermitRootLogin plus one added by a provisioning/hardening script or a Match block). All values in the tab then fall back to defaults, which also explains the "false negatives" reported in #2478.

Root cause: serverAudit() builds JSON on the remote host by interpolating raw grep | awk output into string literals:

permitRootLogin=$(sudo grep -i "^PermitRootLogin" "$sshd_config" | grep -v "#" | awk '{print $2}')

With two matching lines this yields prohibit-password\nyes. Command substitution strips only trailing newlines, so the interior newline lands inside a JSON string literal and JSON.parse rejects it.

Fix: append | head -n1 | tr -d '\r' to the value captures (permitRootLogin, passwordAuth, usePam, ufw defaultIncoming) and | head -n1 to the pubkey_line check. head -n1 mirrors sshd's own first-match-wins semantics, so the reported value is the one actually in effect; tr -d '\r' guards against CRLF-edited config files causing the same parse error.

How this was tested

1. Local instance against a real affected server. Ran a local dev instance (canary + this patch), added a remote Ubuntu server whose sshd_config has two active PermitRootLogin lines (line 42 prohibit-password, line 132 yes), and opened the Security tab — see before/after screenshots below.

2. Byte-for-byte reproduction of the generated script. Extracted the exact bash that serverAudit() generates from the TypeScript source, ran it against a replica of that sshd_config, and fed the output to JSON.parse exactly as server-audit.ts does.

Before (byte-for-byte the reported error):

{"ufw": {...}, "ssh": {..., "permitRootLogin": "prohibit-password
yes", ...}
SyntaxError: Bad control character in string literal in JSON at position 148 (line 1 column 149)

After:

{"ufw": {"installed": true, "active": false, "defaultIncoming": ""}, "ssh": {"enabled": true, "keyAuth": true, "permitRootLogin": "prohibit-password", "passwordAuth": "yes", "usePam": "yes"}, "fail2ban": {"installed": false, ...}}
PARSE OK

permitRootLogin correctly reports prohibit-password — the first match, which is the value sshd actually applies.

What this intentionally does not cover

The audit reads only the main sshd_config, not files pulled in via Include /etc/ssh/sshd_config.d/*.conf (mentioned in a comment on #2478, e.g. cloud-init's 50-cloud-init.conf setting PasswordAuthentication yes). Resolving effective values (e.g. via sudo sshd -T) would be a good follow-up, but is a separate accuracy concern — this PR is scoped to the parse failure that breaks the tab entirely.

Checklist

Before submitting this PR, please make sure that:

  • You created a dedicated branch based on the canary branch.
  • You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request
  • You have tested this PR in your local instance. If you have not tested it yet, please do so before submitting. This helps avoid wasting maintainers' time reviewing code that has not been verified by you.

Issues related (if applicable)

Fixes the JSON parse error and the resulting default/false values reported in #2478 (the sshd_config.d include handling from that issue is noted above as a follow-up).

Screenshots (if applicable)

Before — parse error banner, every field showing false defaults:

Security tab before the fix

After (same server) — real state rendered: UFW detected, SSH enabled, key auth on:

Security tab after the fix

Duplicate directives in sshd_config (e.g. two active PermitRootLogin
lines from Match blocks or provisioning scripts) made the grep|awk
captures multi-line. The embedded newline ended up inside a JSON string
literal, so JSON.parse failed with "Bad control character in string
literal" and the Security tab showed a red banner plus default values.

Take the first match (mirroring sshd's first-match-wins semantics) and
strip carriage returns from CRLF-edited configs.
@reikjarloekl
reikjarloekl marked this pull request as ready for review July 22, 2026 03:32
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. bug Something isn't working labels Jul 22, 2026
AminDhouib added a commit to DevinoSolutions/dokploy-community that referenced this pull request Jul 22, 2026
port: fix security audit parse error on duplicate sshd_config directives (upstream Dokploy#4889)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant