kwtsms 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30bcbad536b8b98f9bbf6c3612d5f01de9d8d14d8133746b1a493aa9c49ae7e4
4
- data.tar.gz: 6bd53db09b4acdd136779c1a1c8a032814a0472ac9e564d00347b99070e5b5c1
3
+ metadata.gz: 93dfa6c3d929f5f5767a1dd561078bf3d2e099dfbc8aea04e9d5f0ffa81fbf6a
4
+ data.tar.gz: 4b0c380d47b356060cbc976a1d035c167b03718572f88e2417cbd9f74d5137cd
5
5
  SHA512:
6
- metadata.gz: '0876d1fff5adf4ae9175b841aa434453d7cfd7ff771b915390411edc54f31697b770740580c3c9129f8b7459c4bf09ba04580380457aa0527e75361c63fced61'
7
- data.tar.gz: 5aceda9bda7fbf0a94a2f78f75238b9fcf7e82ac3d6f2a9574b2612228058a894cd31b4f2160702d3648f7469c95c0442a75dee5380987b2bc706858a040ec59
6
+ metadata.gz: 02ef8c6d2c00049be8195aab4b359cbbd0d567c42d77f092ac50d31cb66cd021479b95ad51d46c8fae5205a0bd62b82756f6a6fd11bec40248639974a5dc7a29
7
+ data.tar.gz: 35bf19319f4548a648d96c2802d1176b1e5ed1c2279d4b5d29dcbcedc155b25d94fc50464165aa1c9cf4d6756566b7a0314785efd28c3e6cc36d01445e096976
data/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.3.1] - 2026-03-15
9
+
10
+ ### Fixed
11
+
12
+ - Password no longer visible in `Client#inspect` or `#to_s` output (redacted)
13
+ - Sanitize raw input in phone validation errors to prevent log injection
14
+ - Command injection risk in dependabot auto-merge workflow (use PR number not URL)
15
+ - Explicit SSL `VERIFY_PEER` on all HTTP connections
16
+ - Log file path traversal protection: rejects `..`, absolute paths, null bytes, pipes
17
+ - `.env` parser now handles `export KEY=VALUE` syntax
18
+ - CI workflow scoped to `permissions: contents: read`
19
+ - Missing error codes ERR019-ERR023 added to `API_ERRORS`
20
+ - Em dashes replaced with commas/colons per style rules
21
+ - SECURITY.md updated to reflect 0.3.x support
22
+ - CONTRIBUTING.md expanded with all required sections
23
+ - Error code test coverage includes ERR019-ERR023
24
+
8
25
  ## [0.3.0] - 2026-03-15
9
26
 
10
27
  ### Added
@@ -31,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
31
48
 
32
49
  ### Added
33
50
 
34
- - Raw API example (`examples/00_raw_api.rb`) call all 7 kwtSMS endpoints using only Ruby stdlib, no gem needed
51
+ - Raw API example (`examples/00_raw_api.rb`): call all 7 kwtSMS endpoints using only Ruby stdlib, no gem needed
35
52
  - Step-by-step guide (`examples/00_raw_api.md`) with helper function reference, endpoints table, key rules, and going-live checklist
36
53
 
37
54
  ### Removed
@@ -69,6 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
69
86
  - Comprehensive test suite: unit tests, mocked API tests, and real integration tests
70
87
  - Examples: basic usage, OTP flow, bulk SMS, Rails endpoint, error handling, production OTP
71
88
 
89
+ [0.3.1]: https://github.com/boxlinknet/kwtsms-ruby/releases/tag/v0.3.1
72
90
  [0.3.0]: https://github.com/boxlinknet/kwtsms-ruby/releases/tag/v0.3.0
73
91
  [0.2.0]: https://github.com/boxlinknet/kwtsms-ruby/releases/tag/v0.2.0
74
92
  [0.1.0]: https://github.com/boxlinknet/kwtsms-ruby/releases/tag/v0.1.0
data/README.md CHANGED
@@ -344,7 +344,7 @@ See the [examples/](examples/) directory:
344
344
 
345
345
  | # | Example | Description |
346
346
  |---|---------|-------------|
347
- | 00 | [Raw API](examples/00_raw_api.rb) | Call all 7 endpoints directly no gem needed |
347
+ | 00 | [Raw API](examples/00_raw_api.rb) | Call all 7 endpoints directly, no gem needed |
348
348
  | 01 | [Basic Usage](examples/01_basic_usage.rb) | Connect, verify, send SMS, validate |
349
349
  | 02 | [OTP Flow](examples/02_otp_flow.rb) | Send OTP codes |
350
350
  | 03 | [Bulk SMS](examples/03_bulk_sms.rb) | Send to many recipients |
data/lib/kwtsms/client.rb CHANGED
@@ -43,6 +43,13 @@ module KwtSMS
43
43
  @cached_purchased = nil
44
44
  end
45
45
 
46
+ # Redact password from inspect/to_s output to prevent accidental exposure
47
+ # in logs, error trackers (Sentry, Bugsnag), or REPL sessions.
48
+ def inspect
49
+ "#<#{self.class} @username=#{@username.inspect} @sender_id=#{@sender_id.inspect} @test_mode=#{@test_mode}>"
50
+ end
51
+ alias_method :to_s, :inspect
52
+
46
53
  # Load credentials from environment variables, falling back to .env file.
47
54
  #
48
55
  # Required env vars:
@@ -9,6 +9,7 @@ module KwtSMS
9
9
  File.foreach(env_file, encoding: "utf-8") do |line|
10
10
  line = line.strip
11
11
  next if line.empty? || line.start_with?("#")
12
+ line = line.sub(/\Aexport\s+/, "")
12
13
  next unless line.include?("=")
13
14
 
14
15
  key, _, value = line.partition("=")
data/lib/kwtsms/errors.rb CHANGED
@@ -17,6 +17,11 @@ module KwtSMS
17
17
  "ERR011" => "Insufficient balance for this send. Buy more credits at kwtsms.com.",
18
18
  "ERR012" => "Message is too long (over 6 SMS pages). Shorten your message.",
19
19
  "ERR013" => "Send queue is full (1000 messages). Wait a moment and try again.",
20
+ "ERR019" => "No delivery reports found for this message.",
21
+ "ERR020" => "Message ID does not exist. Make sure you saved the msg-id from the send response.",
22
+ "ERR021" => "No delivery report available for this message yet.",
23
+ "ERR022" => "Delivery reports are not ready yet. Try again after 24 hours.",
24
+ "ERR023" => "Unknown delivery report error. Contact kwtSMS support.",
20
25
  "ERR024" => "Your IP address is not in the API whitelist. Add it at kwtsms.com > Account > API > IP Lockdown, or disable IP lockdown.",
21
26
  "ERR025" => "Invalid phone number. Make sure the number includes the country code (e.g., 96598765432 for Kuwait, not 98765432).",
22
27
  "ERR026" => "This country is not activated on your account. Contact kwtSMS support to enable the destination country.",
data/lib/kwtsms/logger.rb CHANGED
@@ -4,10 +4,27 @@ require "json"
4
4
  require "time"
5
5
 
6
6
  module KwtSMS
7
+ # Validate a log file path. Returns [valid, error].
8
+ # Rejects path traversal, absolute paths outside CWD, and device paths.
9
+ def self.validate_log_path(log_file)
10
+ return [false, "log_file must be a String"] unless log_file.is_a?(String)
11
+ return [false, "log_file must not be empty"] if log_file.strip.empty?
12
+ return [false, "log_file must not contain '..'"] if log_file.include?("..")
13
+ return [false, "log_file must not contain null bytes"] if log_file.include?("\0")
14
+ return [false, "log_file must not start with /"] if log_file.start_with?("/")
15
+ return [false, "log_file must not start with ~"] if log_file.start_with?("~")
16
+ return [false, "log_file must not be a pipe or device"] if log_file.start_with?("|") || log_file.match?(%r{\A/dev/})
17
+
18
+ [true, nil]
19
+ end
20
+
7
21
  # Append a JSONL log entry. Never raises. Logging must not break main flow.
8
22
  def self.write_log(log_file, entry)
9
23
  return if log_file.nil? || log_file.empty?
10
24
 
25
+ valid, = validate_log_path(log_file)
26
+ return unless valid
27
+
11
28
  begin
12
29
  File.open(log_file, "a", encoding: "utf-8") do |f|
13
30
  f.puts(JSON.generate(entry))
data/lib/kwtsms/phone.rb CHANGED
@@ -222,24 +222,28 @@ module KwtSMS
222
222
  # 1. Empty / blank
223
223
  return [false, "Phone number is required", ""] if raw.empty?
224
224
 
225
+ # Sanitize raw input for safe interpolation in error messages:
226
+ # strip control chars and truncate to prevent log injection
227
+ safe = raw.gsub(/[[:cntrl:]]/, "")[0, 50]
228
+
225
229
  # 2. Email address entered by mistake
226
- return [false, "'#{raw}' is an email address, not a phone number", ""] if raw.include?("@")
230
+ return [false, "'#{safe}' is an email address, not a phone number", ""] if raw.include?("@")
227
231
 
228
232
  # 3. Normalize
229
233
  normalized = normalize_phone(raw)
230
234
 
231
235
  # 4. No digits survived normalization
232
- return [false, "'#{raw}' is not a valid phone number, no digits found", ""] if normalized.empty?
236
+ return [false, "'#{safe}' is not a valid phone number, no digits found", ""] if normalized.empty?
233
237
 
234
238
  # 5. Too short
235
239
  if normalized.length < 7
236
240
  digit_word = normalized.length == 1 ? "digit" : "digits"
237
- return [false, "'#{raw}' is too short to be a valid phone number (#{normalized.length} #{digit_word}, minimum is 7)", normalized]
241
+ return [false, "'#{safe}' is too short to be a valid phone number (#{normalized.length} #{digit_word}, minimum is 7)", normalized]
238
242
  end
239
243
 
240
244
  # 6. Too long
241
245
  if normalized.length > 15
242
- return [false, "'#{raw}' is too long to be a valid phone number (#{normalized.length} digits, maximum is 15)", normalized]
246
+ return [false, "'#{safe}' is too long to be a valid phone number (#{normalized.length} digits, maximum is 15)", normalized]
243
247
  end
244
248
 
245
249
  # 7. Country-specific format validation
@@ -37,6 +37,7 @@ module KwtSMS
37
37
  begin
38
38
  http = Net::HTTP.new(url.host, url.port)
39
39
  http.use_ssl = true
40
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
40
41
  http.open_timeout = 15
41
42
  http.read_timeout = 15
42
43
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KwtSMS
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kwtsms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - boxlink