Skip to content

Fix .conf downloads so WireGuard apps accept the import#11

Merged
iamfarhad merged 1 commit into
mainfrom
feat/ui-redesign-production-docker
Jul 18, 2026
Merged

Fix .conf downloads so WireGuard apps accept the import#11
iamfarhad merged 1 commit into
mainfrom
feat/ui-redesign-production-docker

Conversation

@iamfarhad

@iamfarhad iamfarhad commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Two fixes for configs downloaded from the panel failing to import into the WireGuard app (Android):

  • "Invalid name" on import — WireGuard for Android derives the tunnel name from the imported file's name and requires 1–15 chars of [a-zA-Z0-9_=+.-] (the wg-quick interface-name limit). The account dialog's Download .conf button named files <account label>-<node name>.conf with no sanitization or length cap, so almost any real label (long, spaces, Persian/non-Latin) produced a file the app rejects. The frontend now applies the same rule the backend's subscription endpoint already uses (confFilename in backend/internal/httpapi/subscription.go): lowercase, collapse illegal characters to -, truncate to 15 chars, trim trailing -/., fall back to wgpanel.
  • Download saved as .conf.txt — Android Chrome renames text/plain downloads whose extension it doesn't associate with that MIME type, appending .txt — and the WireGuard app refuses .conf.txt. Both download surfaces (the frontend blob download and GET /api/v1/sub/{token}'s attachment response) now serve application/octet-stream; the OpenAPI spec documents the change.

Testing

  • go build ./..., gofmt -l, and npx tsc --noEmit all pass.
  • Verified against the WireGuard Android app: sanitized .conf filenames within the 15-char charset import cleanly, and octet-stream downloads keep their .conf extension on Android Chrome.

🤖 Generated with Claude Code

WireGuard for Android derives the tunnel name from the imported file's
name and requires 1-15 chars of [a-zA-Z0-9_=+.-]. The Download .conf
button used the raw account label + node name with no sanitization or
length cap, so almost any real label (long, spaces, non-Latin) produced
a file the app rejects with "Invalid name". Reuse the backend's
confFilename rule (subscription.go) on the frontend: lowercase, collapse
illegal chars to "-", truncate to 15, trim trailing separators.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@iamfarhad
iamfarhad merged commit 68c4e37 into main Jul 18, 2026
1 check passed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a confFilename helper function in AccountsPage.tsx to format and sanitize WireGuard configuration filenames, ensuring they conform to the 1-15 character limit and allowed character set. The reviewer pointed out that concatenating the account label and node name before truncating to 15 characters can cause collisions and subsequent browser renaming issues (which introduces invalid characters). They suggested updating confFilename to accept and truncate the label and node name individually, and provided code suggestions to implement this change.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +94 to +101
function confFilename(label: string): string {
let stem = label.toLowerCase().replace(/[^a-z0-9._-]+/g, '-')
if (stem.length > 15) stem = stem.slice(0, 15)
// Trim AFTER truncating so a cut landing on a separator doesn't leave a
// trailing "-"/"." right before the extension.
stem = stem.replace(/^[-.]+|[-.]+$/g, '')
return `${stem === '' ? 'wgpanel' : stem}.conf`
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When downloading configs for multiple nodes under the same account, combining the account label and node name and then truncating the entire string to 15 characters can easily lead to identical filenames if the account label is long (e.g., verylongaccountlabel-node1 and verylongaccountlabel-node2 both truncate to verylongaccount).

This causes two major issues:

  1. The user cannot distinguish which file belongs to which node.
  2. Browsers will automatically append suffixes like (1) to avoid overwriting (e.g., verylongaccount (1).conf), which contains spaces and parentheses. These characters are invalid under WireGuard's strict [a-zA-Z0-9_=+.-] pattern, causing the WireGuard app to reject the file upon import.

To prevent this, we should truncate the account label and node name individually before joining them, ensuring the total length remains within 15 characters.

function confFilename(label: string, node?: string): string {
  let stem = label.toLowerCase().replace(/[^a-z0-9._-]+/g, '-')
  if (node) {
    const nodeStem = node.toLowerCase().replace(/[^a-z0-9._-]+/g, '-')
    // Budget 15 chars: 9 for label, 1 for '-', 5 for node
    const labelPart = stem.slice(0, 9)
    const nodePart = nodeStem.slice(0, 5)
    stem = labelPart + '-' + nodePart
  } else if (stem.length > 15) {
    stem = stem.slice(0, 15)
  }
  // Trim AFTER truncating so a cut landing on a separator doesn't leave a
  // trailing "-"/"." right before the extension.
  stem = stem.replace(/^[-.]+|[-.]+$/g, '')
  return (stem === '' ? 'wgpanel' : stem) + '.conf'
}

const link = document.createElement('a')
link.href = url
link.download = `${account.label}-${slug}.conf`
link.download = confFilename(`${account.label}-${configNodeName ?? 'node'}`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Pass the account label and node name as separate arguments to the updated confFilename function to ensure they are truncated individually and fit within the 15-character limit without collisions.

Suggested change
link.download = confFilename(`${account.label}-${configNodeName ?? 'node'}`)
link.download = confFilename(account.label, configNodeName ?? 'node')

@iamfarhad iamfarhad changed the title Fix .conf download filename so WireGuard apps accept the import Fix .conf downloads so WireGuard apps accept the import Jul 18, 2026
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