You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue tracks the medium and low severity findings from the full-codebase audit of FerrFlow. Critical and high findings are filed as individual issues. Each item below is independently fixable; check off as addressed.
Medium
[docs/CLI] JSON Schema rejects the snake_case keys used in the README's own examples
- schema/ferrflow.json:148
- The schema sets additionalProperties: false and declares package fields only in camelCase (versionedFiles, sharedPaths, dependsOn, tagTemplate). The Rust config accepts BOTH forms via serde aliases (package.rs uses serde name versioned_files with alias="versionedFiles", etc.), and the README JSON/JSON5 examples (README.md:156, 215, 241) use the snake_case forms versioned_files and …
- Fix: Either add the snake_case aliases to the schema properties (and relax additionalProperties), or standardize the README examples and the serde model on one casing and document the other as a deprecated alias.
[ux/CLI] Error message tells users to run ferrflow release --recover, a flag that does not exist
- src/git/push.rs:373-378
- When HEAD is detached during a non-fast-forward rebase recovery, the error reads: 'Refusing to rebase: HEAD is detached. ... (To recover an in-progress release, use ferrflow release --recover or reset the branch manually.)'. The CLI (src/cli.rs Commands::Release) defines only --force, --force-version, --channel, --draft, --force-unlock; there is no --recover flag (grep confirms the s…
- Fix: Remove the --recover reference or implement the flag. The real recovery levers are --force-unlock and manual git reset; point at those.
[bug/CLI] Changelog breaking-change classification uses naive substring matching, mis-bucketing entries
- src/changelog.rs:103-114
- build_section classifies each commit with commit.message.contains("BREAKING CHANGE") (anywhere in the message, no line-start, no colon) and first_line.contains("!:"). This diverges from determine_bump and produces wrong changelog sections: (1) any commit whose body mentions the words 'BREAKING CHANGE' in prose is listed under '### Breaking Changes'; (2) first_line.contains("!:") flags a su…
- Fix: Reuse determine_bump / the same regexes used for version detection to classify commits, instead of ad-hoc contains/starts_with on a lowercased first line.
Low
[bug/CLI]bump_zerover does not clear pre-release/build metadata before bumping
- src/versioning/strategies.rs:76-98
- bump_semver explicitly resets v.pre = Prerelease::EMPTY and v.build = BuildMetadata::EMPTY before incrementing, but bump_zerover does not. If a zerover-strategy package's current version carries a pre-release or build segment (e.g. 0.4.0-beta.1+build), the bumped output retains that metadata (0.5.0-beta.1+build), producing a non-clean stable version. Same idempotency/cleanliness contra…
- Fix: Clear v.pre and v.build at the top of bump_zerover as bump_semver does, and add a test covering a zerover bump from a pre-release input.
[security/CLI] Config-driven file paths are joined without repo-root containment check (path traversal in bot/PR context)
- src/formats/mod.rs:64-74
- read_version/write_version do repo_root.join(&vf.path) with no normalization or containment check; the same pattern applies to the changelog path (src/changelog.rs:69-82 root.join(rel)). A versioned_files[].path (or changelog) of ../../etc/something or an absolute path escapes the repository and lets FerrFlow write outside the working tree. For a developer running locally on their ow…
- Fix: Canonicalize the joined path and assert it stays within repo_root (reject absolute paths and .. escapes) before any read/write, at minimum when running under bot/Action mode.
[security/CLI] Remote-validation --ref and config paths are interpolated into API URLs without encoding
- src/validate/source.rs:72-83
- GitHubSource::read_file builds .../contents/{path}?ref={r} and GitLabSource builds a URL with ?ref={r}, interpolating the user-supplied --ref value and the config path directly without URL-encoding (the GitLab path is percent-encoded, but the ref is not, and the GitHub path is not). A ref or path containing ?, &, #, or spaces will silently break the request or smuggle extra query par…
- Fix: Percent-encode the git_ref and the path segments before interpolation (the GitLab source already does it for the project id and path — apply the same to the ref and to the GitHub source).
[security/CLI] Forge API base host is taken verbatim from the git remote with no scheme/host validation
- src/forge/mod.rs:142-175
- build_forge constructs https://{host}/api/v3 (GitHub) or https://{host}/api/v4 (GitLab) from host extracted out of the repo's remote URL (extract_host), then sends the bearer token to that host. extract_host correctly strips userinfo so a evil@github.com-style URL can't masquerade, and the host comes from the local repo's own remote (trusted), so this is not a live SSRF. It is, however, …
- Fix: Optionally validate that the derived host is a syntactically valid hostname (no path/port-injection, no embedded credentials) before building the api_base and attaching the token, and document that a self-hosted forge host receives the token.
[ux/CLI] GitLab forge silently degrades draft and draft-publish semantics versus GitHub
- src/forge/gitlab.rs:29-65
- On GitLab, --draft only prints a warning and creates a published release (create_release ignores draft after warning), find_draft_release always returns Ok(None), and publish_release is a no-op. So the documented draft workflow ('Create releases as drafts ... a subsequent ferrflow release without --draft will detect and publish existing drafts automatically', cli.rs:57-58) silently does …
- Fix: Document the GitLab draft limitation in the README/CLI help, and consider erroring (not just warning) when --draft is combined with a GitLab remote so the user isn't surprised by an immediately-public release.
Found during a full-codebase audit of the FerrLabs workspace.
This issue tracks the medium and low severity findings from the full-codebase audit of
FerrFlow. Critical and high findings are filed as individual issues. Each item below is independently fixable; check off as addressed.Medium
-
schema/ferrflow.json:148- The schema sets
additionalProperties: falseand declares package fields only in camelCase (versionedFiles,sharedPaths,dependsOn,tagTemplate). The Rust config accepts BOTH forms via serde aliases (package.rs uses serde nameversioned_fileswithalias="versionedFiles", etc.), and the README JSON/JSON5 examples (README.md:156, 215, 241) use the snake_case formsversioned_filesand …- Fix: Either add the snake_case aliases to the schema properties (and relax additionalProperties), or standardize the README examples and the serde model on one casing and document the other as a deprecated alias.
ferrflow release --recover, a flag that does not exist-
src/git/push.rs:373-378- When HEAD is detached during a non-fast-forward rebase recovery, the error reads: 'Refusing to rebase: HEAD is detached. ... (To recover an in-progress release, use
ferrflow release --recoveror reset the branch manually.)'. The CLI (src/cli.rs Commands::Release) defines only--force,--force-version,--channel,--draft,--force-unlock; there is no--recoverflag (grep confirms the s…- Fix: Remove the
--recoverreference or implement the flag. The real recovery levers are--force-unlockand manual git reset; point at those.-
src/changelog.rs:103-114-
build_sectionclassifies each commit withcommit.message.contains("BREAKING CHANGE")(anywhere in the message, no line-start, no colon) andfirst_line.contains("!:"). This diverges from determine_bump and produces wrong changelog sections: (1) any commit whose body mentions the words 'BREAKING CHANGE' in prose is listed under '### Breaking Changes'; (2)first_line.contains("!:")flags a su…- Fix: Reuse determine_bump / the same regexes used for version detection to classify commits, instead of ad-hoc
contains/starts_withon a lowercased first line.Low
bump_zeroverdoes not clear pre-release/build metadata before bumping-
src/versioning/strategies.rs:76-98-
bump_semverexplicitly resetsv.pre = Prerelease::EMPTYandv.build = BuildMetadata::EMPTYbefore incrementing, butbump_zeroverdoes not. If a zerover-strategy package's current version carries a pre-release or build segment (e.g.0.4.0-beta.1+build), the bumped output retains that metadata (0.5.0-beta.1+build), producing a non-clean stable version. Same idempotency/cleanliness contra…- Fix: Clear
v.preandv.buildat the top of bump_zerover as bump_semver does, and add a test covering a zerover bump from a pre-release input.-
src/formats/mod.rs:64-74-
read_version/write_versiondorepo_root.join(&vf.path)with no normalization or containment check; the same pattern applies to the changelog path (src/changelog.rs:69-82root.join(rel)). Aversioned_files[].path(orchangelog) of../../etc/somethingor an absolute path escapes the repository and lets FerrFlow write outside the working tree. For a developer running locally on their ow…- Fix: Canonicalize the joined path and assert it stays within repo_root (reject absolute paths and
..escapes) before any read/write, at minimum when running under bot/Action mode.--refand config paths are interpolated into API URLs without encoding-
src/validate/source.rs:72-83- GitHubSource::read_file builds
.../contents/{path}?ref={r}and GitLabSource builds a URL with?ref={r}, interpolating the user-supplied--refvalue and the configpathdirectly without URL-encoding (the GitLab path is percent-encoded, but the ref is not, and the GitHub path is not). A ref or path containing?,&,#, or spaces will silently break the request or smuggle extra query par…- Fix: Percent-encode the git_ref and the path segments before interpolation (the GitLab source already does it for the project id and path — apply the same to the ref and to the GitHub source).
-
src/forge/mod.rs:142-175-
build_forgeconstructshttps://{host}/api/v3(GitHub) orhttps://{host}/api/v4(GitLab) fromhostextracted out of the repo's remote URL (extract_host), then sends the bearer token to that host. extract_host correctly strips userinfo so aevil@github.com-style URL can't masquerade, and the host comes from the local repo's own remote (trusted), so this is not a live SSRF. It is, however, …- Fix: Optionally validate that the derived host is a syntactically valid hostname (no path/port-injection, no embedded credentials) before building the api_base and attaching the token, and document that a self-hosted
forgehost receives the token.-
src/forge/gitlab.rs:29-65- On GitLab,
--draftonly prints a warning and creates a published release (create_release ignores draft after warning),find_draft_releasealways returnsOk(None), andpublish_releaseis a no-op. So the documented draft workflow ('Create releases as drafts ... a subsequentferrflow releasewithout --draft will detect and publish existing drafts automatically', cli.rs:57-58) silently does …- Fix: Document the GitLab draft limitation in the README/CLI help, and consider erroring (not just warning) when
--draftis combined with a GitLab remote so the user isn't surprised by an immediately-public release.Found during a full-codebase audit of the FerrLabs workspace.