opdotenv 1.0.0 → 1.0.2

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: 484498d0c2471d83e66fd42819feef80da6432e79c2166ceb15b1ece037b2046
4
- data.tar.gz: fce7dbd63416abb37af84c4bdf78792d1172fa1bea972d467f7a871ca1d58e12
3
+ metadata.gz: ab16f9144c5c1ce14ba72552fb42fabdfa649be3fd30888cf9778cb85a107dca
4
+ data.tar.gz: 93da9bbea0c621726e3d9df051efeeb1dc3e9647653d11cb28501aa1bda3f901
5
5
  SHA512:
6
- metadata.gz: 319700ee96600edbb66bc90db27dadb07cfe269cc0f10570b582ae349f8f6234ff29a5950590a57763450c48a95ab1fcd3925bf0d929a1604d2e15a5cc335b11
7
- data.tar.gz: 351b895887435dabeb2dec23884d741f192a16aa3c26be083490ff9a7be8189b06d03d8c8f0e7e3373a875d3d8edda5aabc620e5b2a157f05390293fb5cfbaf9
6
+ metadata.gz: 82046c290a5d56508f7c51b49481d8c0442d41093da3a8fac56edc65361a58070ec629d8c3d52c866bab1fff93f7f04c953b54ef5cf999222f94f1b050aeca36
7
+ data.tar.gz: d8f041eaf6f2f309b9c2a5f7fda1dc50b5295c7d54f51c7897d1f96410a5667cc9b6a3a1ff700e06b8b05944cc8797ce88b9b0dc43d684ff927a042629161120
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.0.2 (2025-11-05)
4
+
5
+ - Enhance error handling and security measures across the codebase
6
+ - Improved error logging to avoid leaking sensitive information (uses exception class names instead of messages)
7
+ - Enhanced API error handling with generic messages for server errors to prevent sensitive data exposure
8
+ - Updated CLI output to clarify that secrets may be displayed intentionally for command-line usage
9
+ - Update Rails appraisals: remove support for Rails 6.0, 7.0, 7.1, 8.0; maintain support for Rails 6.1, 7.2, 8.1
10
+
11
+ ## 1.0.1 (2025-11-05)
12
+
13
+ - Add configurable op CLI path support
14
+ - Support for `OP_CLI_PATH` and `OPDOTENV_CLI_PATH` environment variables
15
+ - Rails configuration option `config.opdotenv.cli_path`
16
+ - Direct API support via `OpClient.new(cli_path: ...)` and `ClientFactory.create(cli_path: ...)`
17
+
3
18
  ## 1.0.0 (2025-11-04)
4
19
 
5
20
  - Initial stable release
data/README.md CHANGED
@@ -1,11 +1,15 @@
1
1
  # opdotenv
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/opdotenv.svg)](https://badge.fury.io/rb/opdotenv) [![Test Status](https://github.com/amkisko/opdotenv/actions/workflows/ci.yml/badge.svg)](https://github.com/amkisko/opdotenv/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/amkisko/opdotenv/graph/badge.svg?token=YOUR_TOKEN)](https://codecov.io/gh/amkisko/opdotenv/graph/badge.svg?token=YOUR_TOKEN)
3
+ [![Gem Version](https://badge.fury.io/rb/opdotenv.svg?v=1.0.2)](https://badge.fury.io/rb/opdotenv) [![Test Status](https://github.com/amkisko/opdotenv.rb/actions/workflows/ci.yml/badge.svg)](https://github.com/amkisko/opdotenv.rb/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/amkisko/opdotenv.rb/graph/badge.svg?token=U4FMVZGO8R)](https://codecov.io/gh/amkisko/opdotenv.rb)
4
4
 
5
5
  Load environment variables from 1Password using the `op` CLI or 1Password Connect Server API. Supports dotenv, JSON, and YAML formats.
6
6
 
7
7
  Sponsored by [Kisko Labs](https://www.kiskolabs.com).
8
8
 
9
+ <a href="https://www.kiskolabs.com">
10
+ <img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
11
+ </a>
12
+
9
13
  ## Installation
10
14
 
11
15
  Add to your Gemfile:
@@ -18,6 +22,8 @@ gem "opdotenv"
18
22
 
19
23
  Choose one:
20
24
  - **1Password CLI** (`op`) - must be installed and authenticated (`op signin`)
25
+ - By default, `op` is expected to be in your `PATH`
26
+ - You can configure a custom path via `OP_CLI_PATH` or `OPDOTENV_CLI_PATH` environment variables, or via Rails config (see below)
21
27
  - **1Password Connect Server** - set `OP_CONNECT_URL` and `OP_CONNECT_TOKEN` environment variables
22
28
 
23
29
  Ruby 2.7+ supported.
@@ -56,6 +62,22 @@ Rails.application.configure do
56
62
  end
57
63
  ```
58
64
 
65
+ ### Configure op CLI path
66
+
67
+ If your `op` CLI command is not in your `PATH` or you want to use a custom path:
68
+
69
+ ```ruby
70
+ Rails.application.configure do
71
+ config.opdotenv.cli_path = "/usr/local/bin/op"
72
+ end
73
+ ```
74
+
75
+ Alternatively, you can set the `OP_CLI_PATH` or `OPDOTENV_CLI_PATH` environment variable:
76
+
77
+ ```bash
78
+ export OP_CLI_PATH=/usr/local/bin/op
79
+ ```
80
+
59
81
  ### Disable automatic loading
60
82
 
61
83
  ```ruby
data/bin/opdotenv CHANGED
@@ -15,6 +15,8 @@ when "read"
15
15
  end.parse!(ARGV)
16
16
  abort("--path required") unless path
17
17
  data = Opdotenv::Loader.load(path)
18
+ # Note: This CLI intentionally outputs secrets to stdout for CLI usage
19
+ # This is expected behavior for the command-line tool
18
20
  puts Opdotenv::Exporter.serialize_by_format(data, :dotenv)
19
21
  when "export"
20
22
  path = file = nil
@@ -118,8 +118,8 @@ begin
118
118
  rescue => e
119
119
  # Only warn if debugging is enabled, as this is expected when Anyway Config isn't used
120
120
  if ENV["OPDOTENV_DEBUG"] == "true"
121
- warn "[opdotenv] Failed to register Anyway loader: #{e.message}"
122
- warn "[opdotenv] Error details: #{e.class}: #{e.message}"
121
+ # Avoid leaking exception messages
122
+ warn "[opdotenv] Failed to register Anyway loader: #{e.class}"
123
123
  warn "[opdotenv] Backtrace: #{e.backtrace.first(3).join("\n")}" if e.backtrace
124
124
  end
125
125
  end
@@ -2,7 +2,7 @@ module Opdotenv
2
2
  class ClientFactory
3
3
  # Creates appropriate client based on configuration or environment
4
4
  # Supports both op CLI and Connect API
5
- def self.create(env: ENV)
5
+ def self.create(env: ENV, cli_path: nil)
6
6
  # Check for Connect API configuration
7
7
  connect_url = env["OP_CONNECT_URL"] || env["OPDOTENV_CONNECT_URL"]
8
8
  connect_token = env["OP_CONNECT_TOKEN"] || env["OPDOTENV_CONNECT_TOKEN"]
@@ -10,7 +10,7 @@ module Opdotenv
10
10
  if connect_url && connect_token
11
11
  ConnectApiClient.new(base_url: connect_url, access_token: connect_token, env: env)
12
12
  else
13
- OpClient.new(env: env)
13
+ OpClient.new(env: env, cli_path: cli_path)
14
14
  end
15
15
  end
16
16
  end
@@ -261,17 +261,24 @@ module Opdotenv
261
261
  when 404
262
262
  raise ConnectApiError, "Not found: #{path}"
263
263
  when 500..599
264
- raise ConnectApiError, "API error (#{code}): #{extract_error_message(response)}"
264
+ raise ConnectApiError, "API error (#{code}): Server error"
265
265
  else
266
- raise ConnectApiError, "API error (#{code}): #{extract_error_message(response)}"
266
+ # Extract safe error message without leaking response body
267
+ safe_message = extract_safe_error_message(response)
268
+ raise ConnectApiError, "API error (#{code}): #{safe_message}"
267
269
  end
268
270
  end
269
271
 
270
- def extract_error_message(response)
272
+ def extract_safe_error_message(response)
273
+ # Only extract structured error messages from JSON responses
274
+ # Never include raw response body to avoid leaking secrets
275
+
271
276
  parsed = JSON.parse(response.body)
272
- parsed["message"] || parsed["error"] || response.body
277
+ # Only return known safe fields that are typically error messages
278
+ parsed["message"] || parsed["error"] || "Request failed"
273
279
  rescue JSON::ParserError
274
- response.body
280
+ # For non-JSON responses, return generic message to avoid leaking body
281
+ "Request failed"
275
282
  end
276
283
 
277
284
  def validate_url(url)
@@ -8,18 +8,19 @@ module Opdotenv
8
8
  SECURE_NOTE_CATEGORY = "secure-note"
9
9
  LOGIN_CATEGORY = "LOGIN"
10
10
 
11
- def initialize(env: ENV)
11
+ def initialize(env: ENV, cli_path: nil)
12
12
  @env = env
13
+ @cli_path = cli_path || env["OP_CLI_PATH"] || env["OPDOTENV_CLI_PATH"] || "op"
13
14
  end
14
15
 
15
16
  def read(path)
16
17
  validate_path(path)
17
- out = capture(["op", "read", path])
18
+ out = capture([@cli_path, "read", path])
18
19
  out.strip
19
20
  end
20
21
 
21
22
  def item_get(item, vault: nil)
22
- args = ["op", "item", "get", item, "--format", "json"]
23
+ args = [@cli_path, "item", "get", item, "--format", "json"]
23
24
  args += ["--vault", vault] if vault
24
25
  capture(args)
25
26
  end
@@ -28,7 +29,7 @@ module Opdotenv
28
29
  # Create a Secure Note with given title and notesPlain
29
30
  # Use shell escaping to prevent injection
30
31
  args = [
31
- "op", "item", "create",
32
+ @cli_path, "item", "create",
32
33
  "--category", SECURE_NOTE_CATEGORY,
33
34
  "--title", title,
34
35
  "--vault", vault,
@@ -43,10 +44,10 @@ module Opdotenv
43
44
  fields.each do |k, v|
44
45
  # Use shell escaping to prevent injection
45
46
  field_arg = "#{k}=#{v}"
46
- capture(["op", "item", "edit", item, "--vault", vault, "--set", field_arg])
47
+ capture([@cli_path, "item", "edit", item, "--vault", vault, "--set", field_arg])
47
48
  end
48
49
  else
49
- args = ["op", "item", "create", "--title", item, "--vault", vault]
50
+ args = [@cli_path, "item", "create", "--title", item, "--vault", vault]
50
51
  fields.each do |k, v|
51
52
  args += ["--set", "#{k}=#{v}"]
52
53
  end
@@ -57,7 +58,7 @@ module Opdotenv
57
58
  private
58
59
 
59
60
  def item_exists?(item, vault: nil)
60
- args = ["op", "item", "get", item]
61
+ args = [@cli_path, "item", "get", item]
61
62
  args += ["--vault", vault] if vault
62
63
  system(*args, out: File::NULL, err: File::NULL)
63
64
  end
@@ -87,7 +88,13 @@ module Opdotenv
87
88
  end
88
89
  end
89
90
 
90
- raise OpError, out if status.nil? || !status.success?
91
+ if status.nil? || !status.success?
92
+ # Never leak command output in error messages for security
93
+ # Extract safe error information without exposing secrets
94
+ exit_code = status&.exitstatus || "unknown"
95
+ command_name = args.first || "op"
96
+ raise OpError, "Command failed: #{command_name} (exit code: #{exit_code})"
97
+ end
91
98
  out
92
99
  end
93
100
  end
@@ -10,6 +10,8 @@ module Opdotenv
10
10
  # Optional 1Password Connect settings (alternatively set via ENV)
11
11
  config.opdotenv.connect_url = nil
12
12
  config.opdotenv.connect_token = nil
13
+ # Optional op CLI path (defaults to "op", can also be set via OP_CLI_PATH or OPDOTENV_CLI_PATH env vars)
14
+ config.opdotenv.cli_path = nil
13
15
  config.opdotenv.overwrite = true
14
16
  config.opdotenv.auto_load = true
15
17
 
@@ -25,6 +27,11 @@ module Opdotenv
25
27
  ENV["OP_CONNECT_TOKEN"] = config.connect_token
26
28
  end
27
29
 
30
+ # Set op CLI path from Rails configuration if provided
31
+ if config.cli_path
32
+ ENV["OPDOTENV_CLI_PATH"] = config.cli_path
33
+ end
34
+
28
35
  # Load from configured sources
29
36
  # Sources can be strings (simplified format) or hashes (backward compatibility)
30
37
  (config.sources || []).each do |source|
@@ -47,7 +54,9 @@ module Opdotenv
47
54
  )
48
55
  rescue => e
49
56
  # Only log errors, not warnings, to avoid noise in production
50
- Rails.logger&.error("Opdotenv: Failed to load #{parsed[:path]}: #{e.message}")
57
+ # Never log exception messages that might contain secrets from command output
58
+ # Use exception class name instead of message for security
59
+ Rails.logger&.error("Opdotenv: Failed to load #{parsed[:path]}: #{e.class.name}")
51
60
  end
52
61
  end
53
62
  end
@@ -1,3 +1,3 @@
1
1
  module Opdotenv
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opdotenv
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - amkisko
@@ -169,6 +169,20 @@ dependencies:
169
169
  - - "~>"
170
170
  - !ruby/object:Gem::Version
171
171
  version: '3.0'
172
+ - !ruby/object:Gem::Dependency
173
+ name: anyway_config
174
+ requirement: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '2.0'
179
+ type: :development
180
+ prerelease: false
181
+ version_requirements: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: '2.0'
172
186
  description: Read environment variables from 1Password fields (dotenv/json/yaml format)
173
187
  or all fields using the op CLI or 1Password Connect Server API. Export local .env
174
188
  files back to 1Password.