fix(v0.1.10): security hardening — close the six surviving 2026-06 review findings - #153
Conversation
…date tycoon.yml identifiers and paths (#62, #65) - Env-var expansion now applies only to connection/credential fields (database paths, sources.*.config subtree, Fivetran api_key/secret, sync endpoints) instead of the whole config — a shared tycoon.yml can no longer exfiltrate arbitrary env secrets via ${VAR} in innocuous fields. ${VAR:-default} semantics unchanged where allowed. - Source keys, schema_name, and table names get strict identifier validation (source keys additionally allow '-', which the shipped templates use and which is inert in both SQL and path contexts). - dbt_project_dir/rill_dir reject control chars and must resolve within the project root's parent — sibling layouts (the wizard default) stay first-class while /etc-style escapes fail loudly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nit escaping (#66, #67) - .tycoon/secrets.yml is created 0600 and pre-existing loose-perms files are tightened on load; tokens failing ^[A-Za-z0-9_-]+$ are rejected before they can reach DuckDB SQL literals. - schedule add() rejects newline/CR/NUL in command args and project root before rendering; systemd ExecStart uses quoted-argv form (WorkingDirectory stays raw — systemd does no quote removal on single-path settings — protected by the control-char rejection). launchd path unchanged (plistlib already escapes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… gates, pin runtime dlt, SECURITY.md (#64, #68) - Every third-party action pinned to a full commit SHA with version comment; pypa/gh-action-pypi-publish moves off the mutable release/v1 branch ref onto the v1.14.0 release SHA. permissions: floors added (contents: read on ci/e2e/nightly, {} on publish with job-level grants). .github/dependabot.yml covers actions + pip. - gitleaks (binary, sha256-verified) runs as a CI job over full history and as a pre-commit hook; bandit gates src/ (subprocess/ assert checks skipped by design — this CLI shells out on purpose; hardcoded-creds, shell=True, weak-crypto, unsafe-YAML all active). - Runtime dlt[extra] installs pinned to the installed dlt version; dlt init call site now states what it downloads and from where. - SECURITY.md documents reporting + the deliberate dlt-init runtime- download trust boundary. - Snapshot dir excluded from whitespace hooks (they strip Rich's trailing padding and break every snapshot assertion); repo-wide hygiene auto-fixes from the new hooks' first run included. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces several security hardening measures, including restricting environment variable interpolation to an allowlist, validating source, schema, and table names as safe identifiers, enforcing path containment for project directories, tightening secrets file permissions, and escaping systemd arguments. The review feedback highlights a potential path traversal bypass when the project is in a root subdirectory, recommends validating the type field in SourceConfig, and suggests escaping literal $ characters in systemd service files to prevent unintended environment variable substitution.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Code Review
This pull request introduces several security hardening measures, including path containment checks for project directories, restricted environment variable interpolation, strict identifier validation for sources, schemas, and tables, owner-only permissions for secrets, and argument escaping for systemd services. The review feedback highlights critical improvements to these security features: addressing a potential path containment bypass when running in top-level directories (e.g., in Docker), validating the 'type' field in 'SourceConfig' to prevent path traversal, handling 'OSError' during file permission adjustments on non-POSIX filesystems, and escaping '%' and '$' characters in systemd service arguments to prevent unintended expansion.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…ype validation, systemd $/% escaping, best-effort perms - Path containment: a project in a top-level dir (/app) made the parent boundary the filesystem root, which contains everything — fall back to root-scoped containment there. (Gemini, valid HIGH) - SourceConfig.type now validated with the strict identifier charset: it reaches dlt init argv and filesystem paths. - _systemd_quote doubles $ and % so env-var/specifier expansion can't rewrite argument content. - ensure_token perms are best-effort on restricted mounts, matching _tighten_permissions semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All four review findings addressed in the latest commit (Gemini posted two overlapping reviews — deduped to four distinct issues):
Full suite: 680 passed, 2 skipped. ruff clean. |
JesuFemi-O
left a comment
There was a problem hiding this comment.
The allowlist scoping on ${VAR} expansion, path containment with the root-boundary fallback, quack token validation, systemd quoting, and the gitleaks SHA-verification approach are all solid. Approving.
One thing worth tracking as a follow-up: load_token() now raises QuackTokenError where it previously returned None. The call path is _preflight_checks → quack.ensure_token() → load_token(), so a tampered secrets.yml will surface as an unhandled exception traceback rather than a clean CLI error in tycoon start. The error message itself is actionable ("delete the 'quack' entry and rerun"), but the presentation isn't. Small quality-of-life item — catch QuackTokenError in _preflight_checks and hand it to error() before raising typer.Exit(1).
|
Follow-up from the review is tracked as #157 (catch |
Summary
Implements the six security-review findings that survived the refactor triage (#62, #64–#68). One commit per stream, plus a review-fixes commit.
tycoon.yml as untrusted input (#62, #65)
${VAR}expansion limited to connection/credential fields — no more env-secret exfiltration through arbitrary fieldstypedbt_project_dir/rill_dirmust resolve near the project (sibling layouts still work;/etc-style escapes fail)Runtime hardening (#66, #67)
schedulerejects control chars; systemdExecStartis fully quoted/escaped ($, %, quotes, backslashes)Supply chain (#64, #68)
permissions:floors, Dependabotdlt[extra]installs pinned;SECURITY.mddocuments the dlt-init trust boundaryAll four Gemini findings addressed (see review thread). Note for ship time: the release PR to main needs
Closes #62 #64 #65 #66 #67 #68.Tests
680 passed (69 new) / ruff clean / pre-commit clean (gitleaks + bandit) / workflow YAML validated
🤖 Generated with Claude Code