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 ee6c384..8fc577d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,29 @@ 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`. +- 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 ### Added diff --git a/lib/agentic_dev_workflow/cli.rb b/lib/agentic_dev_workflow/cli.rb index b736a20..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) if options['example_domain'] + 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 5a6a538..a1c129c 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/ + 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..549a2c1 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 + [ + '../../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