VYPR
High severity8.1NVD Advisory· Published May 4, 2026· Updated May 8, 2026

CVE-2026-42084

CVE-2026-42084

Description

OpenC3 COSMOS provides the functionality needed to send commands to and receive data from one or more embedded systems. Prior to versions 6.10.5 and 7.0.0-rc3, the OpenC3 password change functionality allows a user to change their password without providing the old password, by accepting a valid session token instead. In assumed breach scenarios, this behaviour can be exploited by an attacker who has already obtained a valid session token, to gain persistence in hijacked account (including admin) and prevent legitimate users from accessing the account. This issue has been patched in versions 6.10.5 and 7.0.0-rc3.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openc3RubyGems
< 6.10.56.10.5
openc3RubyGems
>= 7.0.0.pre.rc1, < 7.0.0-rc37.0.0-rc3

Affected products

3
  • Openc3/Cosmos3 versions
    cpe:2.3:a:openc3:cosmos:7.0.0:rc1:*:*:open_source:*:*:*+ 2 more
    • cpe:2.3:a:openc3:cosmos:7.0.0:rc1:*:*:open_source:*:*:*
    • cpe:2.3:a:openc3:cosmos:7.0.0:rc2:*:*:open_source:*:*:*
    • cpe:2.3:a:openc3:cosmos:*:*:*:*:open_source:*:*:*range: <6.10.5

Patches

1
2e623714e342

Merge pull request #2905 from OpenC3/bug/set-password

https://github.com/OpenC3/cosmosRyan PrattMar 3, 2026via ghsa
4 files changed · +30 23
  • openc3-cosmos-cmd-tlm-api/app/controllers/auth_controller.rb+1 1 modified
    @@ -44,7 +44,7 @@ def verify
         end
     
         begin
    -      if OpenC3::AuthModel.verify_no_service(params[:password], no_password: false)
    +      if OpenC3::AuthModel.verify_no_service(params[:password], mode: :password)
             render :plain => OpenC3::AuthModel.generate_session()
           else
             record_user_bad_attempt
    
  • openc3/lib/openc3/models/auth_model.rb+25 18 modified
    @@ -58,36 +58,43 @@ def self.verify(token, no_password: true, service_only: false)
     
           return false if service_only
     
    -      return verify_no_service(token, no_password: no_password)
    +      mode = no_password ? :token : :any
    +      return verify_no_service(token, mode: mode)
         end
     
         # Checks whether the provided token is a valid user password or session token.
         # @param token [String] the plaintext password or session token to check (required)
    -    # @param no_password [Boolean] enforces use of a session token (default: true)
    +    # @param mode [String] optionally restrict verification to just the password or token. Valid values: :password, :token, or :any (default :token)
         # @return [Boolean] whether the provided password/token is valid
    -    def self.verify_no_service(token, no_password: true)
    +    def self.verify_no_service(token, mode: :token)
    +      modes = [:password, :token, :any]
    +      raise ArgumentError, "Invalid mode '#{mode}': must be one of #{modes}" unless modes.include?(mode)
    +
           return false if token.nil? or token.empty?
     
           # Check cached session tokens and password hash
           time = Time.now
    -      return true if @@session_cache and (time - @@session_cache_time) < SESSION_CACHE_TIMEOUT and @@session_cache[token]
    -      unless no_password
    -        return true if @@pw_hash_cache and (time - @@pw_hash_cache_time) < PW_HASH_CACHE_TIMEOUT and Argon2::Password.verify_password(token, @@pw_hash_cache)
    +      unless mode == :password
    +        return true if @@session_cache and (time - @@session_cache_time) < SESSION_CACHE_TIMEOUT and @@session_cache[token]
    +
    +        # Check stored session tokens
    +        @@session_cache = Store.hgetall(SESSIONS_KEY)
    +        @@session_cache_time = time
    +        return true if @@session_cache[token]
           end
     
    -      # Check stored session tokens
    -      @@session_cache = Store.hgetall(SESSIONS_KEY)
    -      @@session_cache_time = time
    -      return true if @@session_cache[token]
    +      unless mode == :token
    +        return true if @@pw_hash_cache and (time - @@pw_hash_cache_time) < PW_HASH_CACHE_TIMEOUT and Argon2::Password.verify_password(token, @@pw_hash_cache)
     
    -      return false if no_password
    +        # Check stored password hash
    +        pw_hash = Store.get(PRIMARY_KEY)
    +        raise "invalid password hash" if pw_hash.nil? || !pw_hash.start_with?("$argon2") # Catch users who didn't run the migration utility when upgrading to COSMOS 7
    +        @@pw_hash_cache = pw_hash
    +        @@pw_hash_cache_time = time
    +        return true if Argon2::Password.verify_password(token, @@pw_hash_cache)
    +      end
     
    -      # Check stored password hash
    -      pw_hash = Store.get(PRIMARY_KEY)
    -      raise "invalid password hash" if pw_hash.nil? || !pw_hash.start_with?("$argon2") # Catch users who didn't run the migration utility when upgrading to COSMOS 7
    -      @@pw_hash_cache = pw_hash
    -      @@pw_hash_cache_time = time
    -      return Argon2::Password.verify_password(token, @@pw_hash_cache)
    +      return false
         end
     
         def self.set(password, old_password, key = PRIMARY_KEY)
    @@ -96,7 +103,7 @@ def self.set(password, old_password, key = PRIMARY_KEY)
     
           if set?(key)
             raise "old_password must not be nil or empty" if old_password.nil? or old_password.empty?
    -        raise "old_password incorrect" unless verify_no_service(old_password, no_password: false)
    +        raise "old_password incorrect" unless verify_no_service(old_password, mode: :password)
           end
           pw_hash = Argon2::Password.create(password, profile: ARGON2_PROFILE)
           Store.set(key, pw_hash)
    
  • openc3/spec/models/auth_model_spec.rb+3 3 modified
    @@ -43,8 +43,8 @@ module OpenC3
               raise_error(/old_password incorrect/)
     
             AuthModel.set('newpassword', AUTH_INITIAL_PASSWORD)
    -        expect(AuthModel.verify_no_service(AUTH_INITIAL_PASSWORD, no_password: false)).to eq(false)
    -        expect(AuthModel.verify_no_service('newpassword', no_password: false)).to eq(true)
    +        expect(AuthModel.verify_no_service(AUTH_INITIAL_PASSWORD, mode: :any)).to eq(false)
    +        expect(AuthModel.verify_no_service('newpassword', mode: :any)).to eq(true)
           end
     
           it "self.verify" do
    @@ -63,7 +63,7 @@ module OpenC3
     
           it "raises when stored password hash is SHA256" do
             @redis.set(PW_HASH_PRIMARY_KEY, Digest::SHA256.hexdigest(AUTH_INITIAL_PASSWORD))
    -        expect{ AuthModel.verify_no_service(AUTH_INITIAL_PASSWORD, no_password: false) }.to \
    +        expect{ AuthModel.verify_no_service(AUTH_INITIAL_PASSWORD, mode: :any) }.to \
               raise_error(/invalid password hash/)
           end
         end
    
  • playwright/tests/auth.p.spec.ts+1 1 modified
    @@ -17,7 +17,7 @@ test.describe('Auth API', () => {
           return
         }
     
    -    const maxRequests = Number.parseInt(process.env.OPENC3_AUTH_RATE_LIMIT_TO || '10')
    +    const maxRequests = Number.parseInt(process.env.OPENC3_AUTH_RATE_LIMIT_TO || '10') + 10
     
         let gotRateLimited = false
         for (let i = 0; i <= maxRequests; i++) {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.