Repository: faker-ruby/faker (Ruby)

Issue:
Faker is unsafe to use in automated tests - real urls and email addresses are generated 
## Describe the bug
Faker::Internet.url, and Faker::Internet.email generate potentially real domain names and email addresses. Using potentially real urls and email addresses in automated testing environments is a security vulnerability. Faker is extensively used in automated test environments to generate test data. 

## To Reproduce
Call ``` Faker::Internet.url ``` and see that the result is a url that could resolve to a real domain name, with a tld of ``` .com ```. 

## Expected behavior
A TLD of ```.test``` or ```.example``` should be used instead. See [RFC2606](https://datatracker.ietf.org/doc/html/rfc2606)

## Additional context
I understand that some users would like to see ``` .com ``` urls in their Faker output. I'd like to suggest that this should be easy to do by opting in to this behaviour instead of it being the default. Perhaps a parameter for specifying the tld when generating an email address or url? The default behaviour however should be safe. 



Discussion: I can tackle this issue.
@patrick-beep please check https://github.com/faker-ruby/faker/pull/2506

```ruby
Faker::Internet.email # => "user@domain.com"

Faker::Config.internet_safe_mode = true
Faker::Internet.email # => "user@domain.example"
Faker::Internet.url # => "http://marks.example/vern"
Faker::Internet.domain_name # => "brown.example"
```
We currently have `Faker::Internet.safe_email`:

https://github.com/faker-ruby/faker/blob/2333f9ee4a4e1d8329e19f9e4b07e927648add7c/doc/default/internet.md?plain=1#L20

I assume going with this approach of configuring `internet_safe_mode` could include adding a deprecation to `Faker::Internet.safe_email`. Otherwise, I think having both would lead to confusion.

Thoughts?
I concur that `#safe_email` could then be deprecated after internet_safe mode is enabled
I am wondering why we need to add config - if we already have a `safe_email` method?
And can we not also add a safe_url method. 

From my perspective it seems like a smaller change, also more inline with the existing convention perhaps.
@fbuys to be better internet citizens, we want to update Faker to create safe emails by default. Especially since Faker is often used in automated test environments that may accidently spam a real person or system. 
Oh I got it @Zeragamba, what if we did it the other way around though. Instead we remove `safe_email` and return safe email addresses with `email` and then `real_world_email` would generate "unsafe" email addresses.


I'd still prefer to leave it as `#email` so that we can keep the api surface clean. plus we can also allow the user to override the safe mode if needed by allowing them to specify a domain to use instead of `example` or perhaps allow `internet_safe: false` as needed
Oh yes, I like the idea of passing an override in. So I'd vote for: `internet_safe: false` to turn safe emails off.
We would like this but/and...

We use a "dud domain" checker that would break on these emails. ie, our app prevents users from using a `foo@example.com` or even a `foo@thing.example` address.

We do have an internal domain we do allow that is "real", but won't "spam anyone".

Can this feature have a "set what qualifies as a safe domain" config?

eg:
```
internet_safe: false
internet_safe_domains: ["myinternaldomain.com"]
```
These are the changes that will happen when my PR is merged
![image](https://user-images.githubusercontent.com/100819439/195966264-e380a7a4-bf41-444c-8cfc-7c54bcdb157d.png)(https://docs.google.com/spreadsheets/d/1Sec5OcigkhJnlyYd8GoE8kCbs4OyoFOR1oI9fP9c6zY/edit?usp=sharing)

it introduces a few configs too
```
internet_safe_mode= true / false
internet_safe_mode?
internet_safe_domains= ["zzz", "xxx", "ccc"]
internet_safe_domains
```

what do you think?
https://github.com/faker-ruby/faker/pull/2506

- edit, sorry this is a my another GitHub account, same as @saiqulhaq 
I want to revisit this issue at its core before jumping to implementation ideas.

The real issue here is Faker generating real URL domain names and email addresses.

RFC2606 recommends using Reserved Top Level DNS Names as the best current practices for the internet community:

"To reduce the likelihood of conflict and confusion, a few top level domain names are reserved for use in private testing, as examples in documentation, and the like. Depending on the nature of the test or example, it might be best for it to be referencing a TLD  permanently reserved for such purposes.

To safely satisfy these needs, four domain names are reserved as listed and described below.
```
 .test
.example
.invalid
.localhost
```

".test" is recommended for use in testing of current or new DNS related code.

".example" is recommended for use in documentation or as examples."

See [RFC2606](https://datatracker.ietf.org/doc/html/rfc2606) for more details.

So the top-level domain recommended is `.test` for the generators and `.example` for documentation.

The option of disabling a "safe" configuration feels odd to me. We are either safe or not. We choose to follow the security recommendations.

And although `Faker::Internet.safe_email` says it generates an RFC 2606 compliant fake email, it is not entirely true because `example.` is an actual address that is being used and could be delivered. `.test` is the only reserved TLD for testing.

I realize this is not a popular choice, and the decision is not only mine to make, but I'd vote for only using `.test` as the TLD for all URLs and email addresses on Faker. In the docs, we guide the users not to use Faker URLs or email addresses since we would breach the security recommendations.

If users need to test with real users, I recommend using Faker to generate the fake usernames to append to their domain of choice.

We will leave that security choice to them, but as a library many people use, we don't want to ignore the security best practices.

We have "fake" in the name, after all. We shouldn't be generating real URLs and email addresses.
yea agreed
if we do that, then we should release a new major version for breaking change indication to faker's users
@stefannibrasil thanks for the clear explanation. I agree with what you said. And I think the solution will make faker easier to maintain too.
We just released v3 so maybe this one could be released in the beginning of next year :thinking: 

We also need to add deprecation warnings to use in preparation for the breaking change.

I'd like to hear more from the other maintainers too, @vbrazo @thdaraujo @Zeragamba
I think the next steps could be adding a deprecation warning in preparation for the release of Faker 4.0.
Quick update: I'm working on adding the deprecation messages to go out on the next release.
I've opened up the first PR here, constructive feedback is welcome:

https://github.com/faker-ruby/faker/pull/2709

After it's merged, the Changelog will announce why we are moving in this direction, and hopefully it will be a smooth transition for users.

Once we give enough time for users to switch to use the safe_email and safe_url only, faker 4 will be released with this breaking change.



Fix (patch):
diff --git a/.rubocop.yml b/.rubocop.yml
index b61f2ad0a3..2973cfe4cb 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -109,3 +109,7 @@ Security/Eval:
   Description: The use of eval represents a serious security risk.
   Exclude:
     - 'lib/faker/default/json.rb'
+
+Style/IfUnlessModifier:
+  Description: Checks for `if` and `unless` statements that would fit on one line if written as modifier `if`/`unless`. The cop also checks for modifier `if`/`unless` lines that exceed the maximum line length.
+  Enabled: false
diff --git a/doc/default/internet.md b/doc/default/internet.md
index 477f535a30..cb2d9fa88a 100644
--- a/doc/default/internet.md
+++ b/doc/default/internet.md
@@ -1,15 +1,29 @@
 # Faker::Internet
 
+### About faker-ruby transition to be RFC 2606 compliant
+
+faker-ruby is transitioning to no longer generating real email and url addresses.
+
+The migration plan is:
+- First step:
+  - change `email` and `domain_name` to be RFC 2606 compliant. Both now generate safe values by default using the Reserved Top Level DNS Names: `example` and `test`. To maintain backwards compatibility and give users the option to use non-safe domains at their own risk, custom domains are allowed.
+  - add deprecation message for `free_email` and `safe_email` users to switch to `email` instead.
+- Second step (after October 2023):
+  - remove deprecated generators and locales.
+
+To give users time, once the first step is released, users will have until October 2023 to make the necessary changes.
+
 ```ruby
 # Keyword arguments: name, username, email, password, domain_name, user_agent, uuid etc...
-Faker::Internet.user #=> { username: 'alexie', email: 'alexie@example.net' }
-Faker::Internet.user('username', 'email', 'password') #=> { username: 'alexie', email: 'alexie@example.net', password: 'DtEf9P8wS31iMyC' }
+Faker::Internet.user #=> { username: 'alexie', email: 'trudie@grant.test' }
+Faker::Internet.user('username', 'email', 'password') #=> { username: 'alexie', email: 'gayle@kohler.test', password: 'DtEf9P8wS31iMyC' }
 
-# Keyword arguments: name, separators
-Faker::Internet.email #=> "eliza@mann.net"
-Faker::Internet.email(name: 'Nancy') #=> "nancy@terry.biz"
-Faker::Internet.email(name: 'Janelle Santiago', separators: '+') #=> "janelle+santiago@becker.com"
-Faker::Internet.email(domain: 'example') #=> "alice@example.name"
+# Keyword arguments: name, separators, domain
+Faker::Internet.email #=> "eliza@mann.test"
+Faker::Internet.email(name: 'Nancy') #=> "nancy@terry.test"
+Faker::Internet.email(name: 'Janelle Santiago', separators: ['+']) #=> "janelle+santiago@becker.example"
+Faker::Internet.email(domain: 'gmail.com')  #=> "foo@gmail.com"
+Faker::Internet.email(name: 'sam smith', separators: ['-'], domain: 'test') #=> "sam-smith@test.test"
 
 # Keyword arguments: name
 Faker::Internet.free_email #=> "freddy@gmail.com"
@@ -44,13 +58,18 @@ Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true) #=> "3k
 Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true, special_characters: true) #=> "*%NkOnJsH4"
 
 # Keyword arguments: subdomain, domain
-Faker::Internet.domain_name #=> "effertz.info"
-Faker::Internet.domain_name(domain: "example") #=> "example.net"
-Faker::Internet.domain_name(subdomain: true, domain: "example") #=> "horse.example.org"
+Faker::Internet.domain_name #=> "herzog.example"
+Faker::Internet.domain_name(subdomain: true) #=> "tillman.kohler.test"
+Faker::Internet.domain_name(subdomain: true, domain: 'faker') #=> "ebert.faker.example"
+Faker::Internet.domain_name(domain: 'faker-ruby.org')  #=> "faker-ruby.org"
+Faker::Internet.domain_name(subdomain: true, domain: 'faker-ruby.org') #=> "foo.faker-ruby.org"
+Faker::Internet.domain_name(subdomain: true, domain: 'faker.faker-ruby.org')  #=> "faker.faker-ruby.org"
 
 Faker::Internet.domain_word #=> "haleyziemann"
 
+# Keyword arguments: safe ('example' and 'test' suffixes)
 Faker::Internet.domain_suffix #=> "info"
+Faker::Internet.domain_suffix(safe: true) #=> "example"
 
 Faker::Internet.ip_v4_address #=> "24.29.18.175"
 
@@ -71,7 +90,9 @@ Faker::Internet.mac_address #=> "e6:0d:00:11:ed:4f"
 Faker::Internet.mac_address(prefix: '55:44:33') #=> "55:44:33:02:1d:9b"
 
 # Keyword arguments: host, path, scheme
-Faker::Internet.url #=> "http://thiel.com/chauncey_simonis"
+Faker::Internet.url #=> "http://treutel.test/demarcus"
+Faker::Internet.url #=> "http://ullrich.example/fritz_braun"
+Faker::Internet.url(host: 'faker') #=> "http://faker/nakita"
 Faker::Internet.url(host: 'example.com') #=> "http://example.com/clotilde.swift"
 Faker::Internet.url(host: 'example.com', path: '/foobar.html') #=> "http://example.com/foobar.html"
 
diff --git a/doc/default/stripe.md b/doc/default/stripe.md
index e495fd8d90..521d132461 100644
--- a/doc/default/stripe.md
+++ b/doc/default/stripe.md
@@ -31,5 +31,5 @@ Use some of the other handy Faker classes for Stripe charge amounts and email.
 # Keyword arguments: from, to
 Faker::Number.between(from: 3, to: 10) #=> 100
 
-Faker::Internet.free_email #=> "freddy@gmail.com"
+Faker::Internet.email #=> "niesha@swaniawski-lynch.test"
 ```
diff --git a/lib/faker/default/internet.rb b/lib/faker/default/internet.rb
index 275a10708d..0247714341 100644
--- a/lib/faker/default/internet.rb
+++ b/lib/faker/default/internet.rb
@@ -15,6 +15,8 @@ class Internet < Base
     ].each(&:freeze).freeze
 
     class << self
+      extend Gem::Deprecate
+
       ##
       # Returns the email address
       #
@@ -25,10 +27,11 @@ class << self
       # @param domain [String]
       #
       # @example
-      #   Faker::Internet.email                                                           #=> "samsmith@faker.com"
-      #   Faker::Internet.email(name: 'smith')                                            #=> "smith@faker.com"
-      #   Faker::Internet.email(name: 'sam smith', separators: ['-'])                     #=> "sam-smith@faker.com"
-      #   Faker::Internet.email(name: 'sam smith', separators: ['-'], domain: 'gmail')    #=> "sam-smith@gmail.com"
+      #   Faker::Internet.email                                                           #=> "renee@zieme.test"
+      #   Faker::Internet.email(name: 'smith')                                            #=> "smith@bergnaum.test"
+      #   Faker::Internet.email(name: 'sam smith', separators: ['-'])                     #=> "smith-sam@tromp.example"
+      #   Faker::Internet.email(name: 'sam smith', separators: ['-'], domain: 'test')     #=> "sam-smith@test.example"
+      #   Faker::Internet.email(domain: 'gmail.com')                                      #=> "foo@gmail.com"
       def email(name: nil, separators: nil, domain: nil)
         local_part = if separators
                        username(specifier: name, separators: separators)
@@ -37,7 +40,14 @@ def email(name: nil, separators: nil, domain: nil)
                      end
 
         sanitized_local_part = sanitize_email_local_part(local_part)
-        construct_email(sanitized_local_part, domain_name(domain: domain))
+
+        generate_domain = if domain.nil?
+                            domain_name
+                          else
+                            domain_name(domain: domain)
+                          end
+
+        construct_email(sanitized_local_part, generate_domain)
       end
 
       ##
@@ -56,6 +66,7 @@ def free_email(name: nil)
           fetch('internet.free_email')
         )
       end
+      deprecate :free_email, :email, 2023, 10
 
       ##
       # Returns the email address with fixed domain name as 'example'
@@ -73,6 +84,7 @@ def safe_email(name: nil)
           "example.#{sample(%w[org com net])}"
         )
       end
+      deprecate :safe_email, :email, 2023, 10
 
       ##
       # Returns the username
@@ -200,10 +212,12 @@ def password(min_length: 8, max_length: 16, mix_case: true, special_characters:
       # @param domain [String]
       #
       # @example
-      #   Faker::Internet.domain_name                                       #=> "test.net"
-      #   Faker::Internet.domain_name(subdomain: true)                      #=> "test.faker.io"
-      #   Faker::Internet.domain_name(subdomain: true, domain: 'example')   #=> "faker.example.com"
-      #   Faker::Internet.domain_name(domain: 'faker')                      #=> "faker.org"
+      #   Faker::Internet.domain_name                                                   #=> "altenwerth-gerhold.example"
+      #   Faker::Internet.domain_name(subdomain: true)                                  #=> "metz.mclaughlin-brekke.test"
+      #   Faker::Internet.domain_name(subdomain: true, domain: 'faker')                 #=> "foo.faker.test"
+      #   Faker::Internet.domain_name(domain: 'faker-ruby.org')                         #=> "faker-ruby.org"
+      #   Faker::Internet.domain_name(subdomain: true, domain: 'faker-ruby.org')        #=> "foo.faker-ruby.org"
+      #   Faker::Internet.domain_name(subdomain: true, domain: 'faker.faker-ruby.org')  #=> "faker.faker-ruby.org"
       def domain_name(subdomain: false, domain: nil)
         with_locale(:en) do
           if domain
@@ -211,12 +225,18 @@ def domain_name(subdomain: false, domain: nil)
               .split('.')
               .map { |domain_part| Char.prepare(domain_part) }
               .tap do |domain_elements|
-                domain_elements << domain_suffix if domain_elements.length < 2
-                domain_elements.unshift(Char.prepare(domain_word)) if subdomain && domain_elements.length < 3
+                if domain_elements.length < 2
+                  domain_elements << domain_suffix(safe: true)
+                end
+                if subdomain && domain_elements.length < 3
+                  domain_elements.unshift(Char.prepare(domain_word))
+                end
               end.join('.')
           else
-            [domain_word, domain_suffix].tap do |domain_elements|
-              domain_elements.unshift(Char.prepare(domain_word)) if subdomain
+            [domain_word, domain_suffix(safe: true)].tap do |domain_elements|
+              if subdomain
+                domain_elements.unshift(Char.prepare(domain_word))
+              end
             end.join('.')
           end
         end
@@ -253,10 +273,16 @@ def domain_word
       # @return [String]
       #
       # @example
-      #   Faker::Internet.domain_suffix   #=> "com"
-      #   Faker::Internet.domain_suffix   #=> "biz"
-      def domain_suffix
-        fetch('internet.domain_suffix')
+      #   Faker::Internet.domain_suffix              #=> "com"
+      #   Faker::Internet.domain_suffix              #=> "biz"
+      #   Faker::Internet.domain_suffix(safe: true)  #=> "example"
+      #   Faker::Internet.domain_suffix(safe: true)  #=> "test"
+      def domain_suffix(safe: nil)
+        if safe
+          fetch('internet.safe_domain_suffix')
+        else
+          fetch('internet.domain_suffix')
+        end
       end
 
       ##
@@ -421,10 +447,10 @@ def ip_v6_cidr
       # @param scheme [String]
       #
       # @example
-      #   Faker::Internet.url                                                           #=> "http://sipes-okon.com/hung.macejkovic"
+      #   Faker::Internet.url                                                           #=> "http://treutel.test/demarcus"
       #   Faker::Internet.url(host: 'faker')                                            #=> "http://faker/shad"
-      #   Faker::Internet.url(host: 'faker', path: '/fake_test_path')                   #=> "http://faker/fake_test_path"
-      #   Faker::Internet.url(host: 'faker', path: '/fake_test_path', scheme: 'https')  #=> "https://faker/fake_test_path"
+      #   Faker::Internet.url(host: 'faker', path: '/docs')                             #=> "http://faker/docs"
+      #   Faker::Internet.url(host: 'faker', path: '/docs', scheme: 'https')            #=> "https://faker/docs"
       def url(host: domain_name, path: "/#{username}", scheme: 'http')
         "#{scheme}://#{host}#{path}"
       end
@@ -542,10 +568,10 @@ def base64(length: 16, padding: false, urlsafe: true)
       ##
       # Produces a randomized hash of internet user details
       # @example
-      #   Faker::Internet.user #=> { username: 'alexie', email: 'alexie@example.net' }
+      #   Faker::Internet.user #=> { username: 'alexie', email: 'trudie@grant.test' }
       #
       # @example
-      #   Faker::Internet.user('username', 'email', 'password') #=> { username: 'alexie', email: 'alexie@example.net', password: 'DtEf9P8wS31iMyC' }
+      #   Faker::Internet.user('username', 'email', 'password') #=> { username: 'alexie', email: 'gayle@kohler.test', password: 'DtEf9P8wS31iMyC' }
       #
       # @return [hash]
       #
diff --git a/lib/faker/default/omniauth.rb b/lib/faker/default/omniauth.rb
index 44b51bddfd..d6793247fb 100644
--- a/lib/faker/default/omniauth.rb
+++ b/lib/faker/default/omniauth.rb
@@ -12,7 +12,7 @@ def initialize(name: nil, email: nil)
       super()
 
       @name = name || "#{Name.first_name} #{Name.last_name}"
-      @email = email || Internet.safe_email(name: self.name)
+      @email = email || Internet.email(name: self.name)
       @first_name, @last_name = self.name.split
     end
 
diff --git a/lib/faker/default/twitter.rb b/lib/faker/default/twitter.rb
index 870aca0ce2..7ea67b1fad 100644
--- a/lib/faker/default/twitter.rb
+++ b/lib/faker/default/twitter.rb
@@ -63,7 +63,7 @@ def user(include_status: true, include_email: false)
           verified: Faker::Boolean.boolean(true_ratio: 0.1)
         }
         user[:status] = Faker::Twitter.status(include_user: false) if include_status
-        user[:email] = Faker::Internet.safe_email if include_email
+        user[:email] = Faker::Internet.email if include_email
         user
       end
 
diff --git a/lib/locales/en/internet.yml b/lib/locales/en/internet.yml
index 03b87deec0..a2bdd23cf9 100644
--- a/lib/locales/en/internet.yml
+++ b/lib/locales/en/internet.yml
@@ -10,6 +10,9 @@ en:
         - org
         - io
         - co
+      safe_domain_suffix:
+        - example
+        - test
       free_email:
         - gmail.com
         - yahoo.com