From 53fb1134e68dc2c92694372270446fe67d7fd9c4 Mon Sep 17 00:00:00 2001 From: Hirley Esmeraldo Ribeiro Date: Sun, 30 Aug 2026 11:13:49 -0300 Subject: [PATCH 1/3] =?UTF-8?q?Valida=20entity=5Fname=20em=20--example-dom?= =?UTF-8?q?ain=20contra=20path=20traversal=20e=20nomes=20inv=C3=A1lidos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ExampleDomainGenerator interpolava entity_name sem validação em caminhos de arquivo, permitindo path traversal (--example-domain '../../../../tmp/evil') e quebrando com NoMethodError ou constantes Ruby inválidas para nomes malformados (task_, _task, task__item, 1task). Adiciona validação por regex que rejeita esses casos com ArgumentError antes de qualquer escrita em disco. CLI#init também tratava --example-domain '' como valor informado (truthy em Ruby); agora string vazia é tratada como opção omitida, sem instanciar o gerador nem levantar erro. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 18 +++++++++++ lib/agentic_dev_workflow/cli.rb | 2 +- .../generators/example_domain_generator.rb | 10 ++++++ spec/cli_spec.rb | 8 +++++ .../example_domain_generator_spec.rb | 31 +++++++++++++++++++ 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee6c384..d46be73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ e este projeto segue [Semantic Versioning](https://semver.org/lang/pt-BR/). ## [Unreleased] +### Fixed + +- **Segurança**: `ExampleDomainGenerator` (usado por `--example-domain`) aceitava + qualquer string como `entity_name` e a interpolava sem validação em caminhos + de arquivo (`lib/domain/#{entity_name}.rb` etc.), permitindo path traversal + (ex.: `--example-domain '../../../../tmp/evil'`) e escrita fora do diretório + alvo. Também quebrava com `NoMethodError` para nomes com separador + inicial/final/duplicado (ex.: `task_`, `_task`, `task__item`) e gerava + constantes Ruby inválidas para nomes iniciados por dígito (ex.: `1task`). + Adicionada validação (`VALID_ENTITY_NAME`) que rejeita esses casos com + `ArgumentError` antes de qualquer escrita em disco. Nova spec: + `spec/generators/example_domain_generator_spec.rb`. +- `CLI#init` tratava `--example-domain ''` (string vazia) como valor informado + (truthy em Ruby), instanciando `ExampleDomainGenerator` desnecessariamente + e, após o fix acima, propagando um `ArgumentError`. Agora string vazia é + tratada como "não informado", igual a omitir a opção: nenhum gerador é + instanciado, nenhum erro é levantado. Nova spec: `spec/cli_spec.rb`. + ## [0.1.0] - 2026-08-29 ### Added diff --git a/lib/agentic_dev_workflow/cli.rb b/lib/agentic_dev_workflow/cli.rb index b736a20..c5af40d 100644 --- a/lib/agentic_dev_workflow/cli.rb +++ b/lib/agentic_dev_workflow/cli.rb @@ -54,7 +54,7 @@ def optional_generators(target_dir) generators = [] generators.concat(docker_generators(target_dir)) if options['docker'] generators << Generators::GitHubActionsGenerator.new(target_dir: target_dir) if options['ci'] - generators << example_domain_generator(target_dir) if options['example_domain'] + generators << example_domain_generator(target_dir) unless options['example_domain'].to_s.empty? generators << Generators::ObservabilityGenerator.new(target_dir: target_dir) if options['observability'] generators end diff --git a/lib/agentic_dev_workflow/generators/example_domain_generator.rb b/lib/agentic_dev_workflow/generators/example_domain_generator.rb index 5a6a538..e0c080e 100644 --- a/lib/agentic_dev_workflow/generators/example_domain_generator.rb +++ b/lib/agentic_dev_workflow/generators/example_domain_generator.rb @@ -12,7 +12,17 @@ class ExampleDomainGenerator < BaseGenerator PORT_TEMPLATE = load_template('ports_task_repository.rb.erb') ADAPTER_TEMPLATE = load_template('adapters_in_memory_task_repository.rb.erb') + # Letras/dígitos, com '_' ou '-' apenas como separador interno (nunca no + # início, no fim, nem repetido). Bloqueia caminhos ('/', '..'), nomes + # vazios e nomes que gerariam uma constante Ruby inválida (ex.: dígito + # inicial). + VALID_ENTITY_NAME = /\A[a-zA-Z][a-zA-Z0-9]*([_-][a-zA-Z0-9]+)*\z/.freeze + def initialize(target_dir:, entity_name: 'task') + unless entity_name.to_s.match?(VALID_ENTITY_NAME) + raise ArgumentError, "entity_name inválido: #{entity_name.inspect}" + end + super(target_dir: target_dir) @entity_name = entity_name @class_name = camelize(entity_name) diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb index ea3a802..852fd73 100644 --- a/spec/cli_spec.rb +++ b/spec/cli_spec.rb @@ -64,6 +64,14 @@ def path_in_target(*parts) expect(Dir.exist?(path_in_target('lib', 'domain'))).to be false end + it 'não gera exemplo de domínio nem levanta erro quando --example-domain é string vazia' do + expect do + described_class.start(['init', target_dir, '--example-domain', '']) + end.not_to raise_error + + expect(Dir.exist?(path_in_target('lib', 'domain'))).to be false + end + it 'aplica o perfil strict no .rubocop.yml quando --profile=strict' do described_class.start(['init', target_dir, '--profile', 'strict']) diff --git a/spec/generators/example_domain_generator_spec.rb b/spec/generators/example_domain_generator_spec.rb index f73a2e9..4ecae47 100644 --- a/spec/generators/example_domain_generator_spec.rb +++ b/spec/generators/example_domain_generator_spec.rb @@ -50,5 +50,36 @@ expect(File.exist?(invoice_path)).to be true expect(File.read(invoice_path)).to include('class Invoice') end + + it 'aceita entity_name com hífen ou underscore internos' do + custom_generator = described_class.new(target_dir: target_dir, entity_name: 'my-task_item') + + expect { custom_generator.generate }.not_to raise_error + end + end + + describe 'validação de entity_name' do + %w[ + ../../etc/passwd + task/../../evil + 1task + task_ + _task + task__item + my\ task + task.rb + ].each do |invalid_name| + it "rejeita entity_name inválido: #{invalid_name.inspect}" do + expect do + described_class.new(target_dir: target_dir, entity_name: invalid_name) + end.to raise_error(ArgumentError) + end + end + + it 'rejeita entity_name vazio' do + expect do + described_class.new(target_dir: target_dir, entity_name: '') + end.to raise_error(ArgumentError) + end end end From fddd1eeb3d8084fde17c61d31a33a64537ed1ff4 Mon Sep 17 00:00:00 2001 From: Hirley Esmeraldo Ribeiro Date: Sun, 30 Aug 2026 11:28:11 -0300 Subject: [PATCH 2/3] =?UTF-8?q?Corrige=20ofensas=20de=20RuboCop=20introduz?= =?UTF-8?q?idas=20pela=20valida=C3=A7=C3=A3o=20de=20entity=5Fname?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLI#optional_generators excedia Metrics/AbcSize após a checagem de string vazia; extraída para o predicado example_domain_requested?. - Style/RedundantFreeze: regexp literal já é imutável, .freeze é redundante. - Style/WordArray: convertido %w[...] para array literal (um dos nomes inválidos de teste contém espaço escapado, o que o cop não aceita em %w). Confirmado localmente: bundle exec rspec (54 examples, 0 failures, 100% coverage) e bundle exec rubocop (0 offenses, exceto Layout/EndOfLine pré-existente em todo o repo ao rodar no Windows — nativo é CRLF mas .gitattributes força LF; não é regressão desta mudança). Co-Authored-By: Claude Sonnet 5 --- lib/agentic_dev_workflow/cli.rb | 6 +++++- .../generators/example_domain_generator.rb | 2 +- .../example_domain_generator_spec.rb | 18 +++++++++--------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/agentic_dev_workflow/cli.rb b/lib/agentic_dev_workflow/cli.rb index c5af40d..a06a5f9 100644 --- a/lib/agentic_dev_workflow/cli.rb +++ b/lib/agentic_dev_workflow/cli.rb @@ -54,11 +54,15 @@ def optional_generators(target_dir) generators = [] generators.concat(docker_generators(target_dir)) if options['docker'] generators << Generators::GitHubActionsGenerator.new(target_dir: target_dir) if options['ci'] - generators << example_domain_generator(target_dir) unless options['example_domain'].to_s.empty? + generators << example_domain_generator(target_dir) if example_domain_requested? generators << Generators::ObservabilityGenerator.new(target_dir: target_dir) if options['observability'] generators end + def example_domain_requested? + !options['example_domain'].to_s.empty? + end + def docker_generators(target_dir) [ Generators::DockerfileGenerator.new(target_dir: target_dir), diff --git a/lib/agentic_dev_workflow/generators/example_domain_generator.rb b/lib/agentic_dev_workflow/generators/example_domain_generator.rb index e0c080e..a1c129c 100644 --- a/lib/agentic_dev_workflow/generators/example_domain_generator.rb +++ b/lib/agentic_dev_workflow/generators/example_domain_generator.rb @@ -16,7 +16,7 @@ class ExampleDomainGenerator < BaseGenerator # início, no fim, nem repetido). Bloqueia caminhos ('/', '..'), nomes # vazios e nomes que gerariam uma constante Ruby inválida (ex.: dígito # inicial). - VALID_ENTITY_NAME = /\A[a-zA-Z][a-zA-Z0-9]*([_-][a-zA-Z0-9]+)*\z/.freeze + VALID_ENTITY_NAME = /\A[a-zA-Z][a-zA-Z0-9]*([_-][a-zA-Z0-9]+)*\z/ def initialize(target_dir:, entity_name: 'task') unless entity_name.to_s.match?(VALID_ENTITY_NAME) diff --git a/spec/generators/example_domain_generator_spec.rb b/spec/generators/example_domain_generator_spec.rb index 4ecae47..549a2c1 100644 --- a/spec/generators/example_domain_generator_spec.rb +++ b/spec/generators/example_domain_generator_spec.rb @@ -59,15 +59,15 @@ end describe 'validação de entity_name' do - %w[ - ../../etc/passwd - task/../../evil - 1task - task_ - _task - task__item - my\ task - task.rb + [ + '../../etc/passwd', + 'task/../../evil', + '1task', + 'task_', + '_task', + 'task__item', + 'my task', + 'task.rb' ].each do |invalid_name| it "rejeita entity_name inválido: #{invalid_name.inspect}" do expect do From c4bbe42ca8eccf863595d6d134b0e6b3263ee4be Mon Sep 17 00:00:00 2001 From: Hirley Esmeraldo Ribeiro Date: Sun, 30 Aug 2026 12:11:42 -0300 Subject: [PATCH 3/3] Atualiza pin do ruby/setup-ruby para desbloquear o CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O pin em v1.146.0 (SHA 55283cc) usa um manifesto de versões desatualizado: o action não reconhece mais a imagem atual do runner ubuntu-latest (ubuntu-24.04) e não tem build de Ruby 3.3 registrado para ela, quebrando os 3 jobs da matrix (3.1, 3.2, 3.3) com "Unknown version" / runner detectado como self-hosted. Atualiza o pin para v1.321.0 (SHA 95ef2b0), release atual verificada via GitHub API. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ruby.yml | 2 +- CHANGELOG.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index e28f4a3..95cfb2f 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -30,7 +30,7 @@ jobs: # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, # change this to (see https://github.com/ruby/setup-ruby#versioning): # uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0 + uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true # runs 'bundle install' and caches installed gems automatically diff --git a/CHANGELOG.md b/CHANGELOG.md index d46be73..8fc577d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,11 @@ e este projeto segue [Semantic Versioning](https://semver.org/lang/pt-BR/). e, após o fix acima, propagando um `ArgumentError`. Agora string vazia é tratada como "não informado", igual a omitir a opção: nenhum gerador é instanciado, nenhum erro é levantado. Nova spec: `spec/cli_spec.rb`. +- CI (`.github/workflows/ruby.yml`) quebrado em todas as versões da matrix: + o pin de `ruby/setup-ruby` estava em um SHA antigo (v1.146.0) cujo + manifesto de versões não reconhece a imagem atual do runner `ubuntu-latest` + (`ubuntu-24.04`) nem tem build de Ruby 3.3 para ela. Atualizado o pin para + `95ef2b0` (v1.321.0). ## [0.1.0] - 2026-08-29