Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion lib/agentic_dev_workflow/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
10 changes: 10 additions & 0 deletions lib/agentic_dev_workflow/generators/example_domain_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions spec/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'])

Expand Down
31 changes: 31 additions & 0 deletions spec/generators/example_domain_generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading