Summary
Add a Pydantic validation layer to tycoon.yml so that identifiers and paths from a shared/untrusted config cannot reach SQL strings or the filesystem unchecked. This is the root-cause fix backing the SQL-injection and path-traversal findings.
Evidence
src/tycoon/project.py:113-130, 251-292 — SourceConfig.schema_name, source map keys (source_name), dbt_project_dir, rill_dir are all plain str, no constraints.
source_name flows into file paths: output_dir / f"{model_name}.sql", dbt_project_dir / "models" / "staging" / source_name (commands/explore.py:142, dbt_generator.py:267) → path traversal (../../...) on tycoon data analyze.
source_name/table_name flow into the dbt template {{ source('{source_name}', '{table_name}') }} (dbt_generator.py:113) → break-out into generated SQL.
dbt_project_dir/rill_dir accept absolute / .. paths, .resolve()d with no containment check (config.py:78-87).
Fix
- Constrain source keys and
schema_name to ^[A-Za-z_][A-Za-z0-9_]*$.
- After resolving
dbt_project_dir/rill_dir, assert resolved.is_relative_to(self.root) unless the user interactively opted into an external path.
schedule.py's _NAME_RE is the model to mirror.
Severity: Medium (enables the High SQL-injection / traversal findings)
Summary
Add a Pydantic validation layer to
tycoon.ymlso that identifiers and paths from a shared/untrusted config cannot reach SQL strings or the filesystem unchecked. This is the root-cause fix backing the SQL-injection and path-traversal findings.Evidence
src/tycoon/project.py:113-130, 251-292—SourceConfig.schema_name, source map keys (source_name),dbt_project_dir,rill_dirare all plainstr, no constraints.source_nameflows into file paths:output_dir / f"{model_name}.sql",dbt_project_dir / "models" / "staging" / source_name(commands/explore.py:142,dbt_generator.py:267) → path traversal (../../...) ontycoon data analyze.source_name/table_nameflow into the dbt template{{ source('{source_name}', '{table_name}') }}(dbt_generator.py:113) → break-out into generated SQL.dbt_project_dir/rill_diraccept absolute /..paths,.resolve()d with no containment check (config.py:78-87).Fix
schema_nameto^[A-Za-z_][A-Za-z0-9_]*$.dbt_project_dir/rill_dir, assertresolved.is_relative_to(self.root)unless the user interactively opted into an external path.schedule.py's_NAME_REis the model to mirror.Severity: Medium (enables the High SQL-injection / traversal findings)