security: validate phone recipients (#144)

Co-authored-by: draix <draix@users.noreply.github.com>
This commit is contained in:
Martín Alcalá Rubí 2026-04-21 12:46:01 +08:00 committed by GitHub
parent 54d44b34fc
commit 318421d415
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 30 additions and 4 deletions

View File

@ -13,6 +13,7 @@
### Security
- Auth: reject `?` and `#` in whatsmeow session store paths to avoid SQLite URI parameter injection. (#180 — thanks @shaun0927)
- Send: validate phone-number recipients before constructing WhatsApp JIDs. (#144 — thanks @draix)
- Store: restrict index and session SQLite database files to owner-only permissions. (#147 — thanks @draix)
### Fixed

View File

@ -33,8 +33,17 @@ func TestParseUserOrJID(t *testing.T) {
{name: "phone", input: "1234567890", wantUser: "1234567890", wantServer: types.DefaultUserServer},
{name: "phone with plus", input: "+1234567890", wantUser: "1234567890", wantServer: types.DefaultUserServer},
{name: "phone with spaces and plus", input: " +1234567890 ", wantUser: "1234567890", wantServer: types.DefaultUserServer},
{name: "minimum length phone", input: "1234567", wantUser: "1234567", wantServer: types.DefaultUserServer},
{name: "maximum length phone", input: "123456789012345", wantUser: "123456789012345", wantServer: types.DefaultUserServer},
{name: "group jid", input: "123@g.us", wantUser: "123", wantServer: types.GroupServer},
{name: "empty after plus", input: "+", wantErr: true},
{name: "too short phone", input: "123456", wantErr: true},
{name: "too long phone", input: "1234567890123456", wantErr: true},
{name: "letters in phone", input: "123abc456", wantErr: true},
{name: "punctuation in phone", input: "123-456-7890", wantErr: true},
{name: "spaces inside phone", input: "123 456 7890", wantErr: true},
{name: "plus inside phone", input: "12+34567", wantErr: true},
{name: "double leading plus", input: "++1234567", wantErr: true},
}
for _, tt := range tests {

View File

@ -15,13 +15,29 @@ func ParseUserOrJID(s string) (types.JID, error) {
if strings.Contains(s, "@") {
return types.ParseJID(s)
}
s = strings.TrimPrefix(s, "+")
if s == "" {
return types.JID{}, fmt.Errorf("recipient is required")
phone, err := normalizePhoneRecipient(s)
if err != nil {
return types.JID{}, err
}
return types.JID{User: s, Server: types.DefaultUserServer}, nil
return types.JID{User: phone, Server: types.DefaultUserServer}, nil
}
func IsGroupJID(jid types.JID) bool {
return jid.Server == types.GroupServer
}
func normalizePhoneRecipient(s string) (string, error) {
phone := strings.TrimPrefix(s, "+")
if phone == "" {
return "", fmt.Errorf("recipient is required")
}
if len(phone) < 7 || len(phone) > 15 {
return "", fmt.Errorf("invalid phone number %q: must be 7-15 digits", s)
}
for _, ch := range phone {
if ch < '0' || ch > '9' {
return "", fmt.Errorf("invalid phone number %q: must contain digits only", s)
}
}
return phone, nil
}