Skip to content

CLI: include commit hash in --version output#35713

Closed
echoVic wants to merge 3 commits intoopenclaw:mainfrom
echoVic:codex/include-commit-sha-in-version-output-34972
Closed

CLI: include commit hash in --version output#35713
echoVic wants to merge 3 commits intoopenclaw:mainfrom
echoVic:codex/include-commit-sha-in-version-output-34972

Conversation

@echoVic
Copy link
Copy Markdown
Contributor

@echoVic echoVic commented Mar 5, 2026

Summary

  • include commit SHA in top-level -v/--version output when available
  • keep existing behavior for environments where commit metadata is unavailable
  • add test coverage for the updated version output format

Closes #34972

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 5, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium DoS via blocking read of attacker-controlled .git/HEAD or ref file (FIFO/device)

1. 🟡 DoS via blocking read of attacker-controlled .git/HEAD or ref file (FIFO/device)

Property Value
Severity Medium
CWE CWE-400
Location src/infra/git-commit.ts:23-28

Description

resolveCommitHash() reads .git/HEAD (and potentially the referenced refs/... file) using synchronous fs.openSync()/fs.readSync() without validating that the target is a regular file.

In an untrusted repository/worktree, an attacker can create .git/HEAD or .git/refs/... as a FIFO (named pipe) or a symlink to a blocking device (e.g., a FIFO with no writer, /dev/random). On POSIX systems:

  • opening a FIFO for reading can block indefinitely until a writer connects
  • reading from certain device files can also block
  • because this uses *Sync APIs, the entire Node.js event loop is blocked, resulting in a denial-of-service/hang

Vulnerable code:

const safeReadFilePrefix = (filePath: string, limit = 256) => {
  const fd = fs.openSync(filePath, "r");
  try {
    const buf = Buffer.alloc(limit);
    const bytesRead = fs.readSync(fd, buf, 0, limit, 0);
    return buf.subarray(0, bytesRead).toString("utf-8");
  } finally {
    fs.closeSync(fd);
  }
};

And it is invoked on paths derived from the working directory:

  • headPath = resolveGitHeadPath(options.cwd ?? process.cwd())
  • safeReadFilePrefix(headPath)
  • if HEAD points to a ref, safeReadFilePrefix(refPath)

The surrounding try/catch in resolveCommitHash() does not mitigate hangs because a blocked openSync()/readSync() never throws.

Recommendation

Reject non-regular files (and optionally symlinks) before opening, and/or open in non-blocking mode.

Option A: lstat and only allow regular files (and disallow symlinks):

const safeReadFilePrefix = (filePath: string, limit = 256) => {
  const st = fs.lstatSync(filePath);// Avoid symlinks, FIFOs, devices, sockets, etc.
  if (!st.isFile() || st.isSymbolicLink()) {
    throw new Error(`Refusing to read non-regular file: ${filePath}`);
  }

  const fd = fs.openSync(filePath, fs.constants.O_RDONLY);
  try {
    const buf = Buffer.alloc(limit);
    const bytesRead = fs.readSync(fd, buf, 0, limit, 0);
    return buf.subarray(0, bytesRead).toString("utf-8");
  } finally {
    fs.closeSync(fd);
  }
};

Option B: non-blocking open (still consider lstat as defense-in-depth):

const fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NONBLOCK);

Handle EAGAIN/empty reads by returning null/"" and treating commit as unavailable.

Also consider using async I/O to avoid blocking the event loop if commit resolution occurs on latency-sensitive paths.


Analyzed PR: #35713 at commit 7ebb3f3

Last updated on: 2026-03-06T03:39:55Z

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 5, 2026

Greptile Summary

This PR appends the short commit SHA to the --version / -v / -V output by calling the existing resolveCommitHash() helper and wrapping it in a ternary. The change is well-scoped, but there are two findings:

Inconsistent fallback format — The ternary at line 114 produces OpenClaw 9.9.9-test (abc1234) when commit metadata is available, but falls back to bare 9.9.9-test (without the OpenClaw prefix) when unavailable. This inconsistency contradicts the PR's stated goal of "keep existing behavior" — the format shape differs depending on environment. Both branches should include the OpenClaw label for consistency.

Missing test coverage — Only the happy path (with commit hash) is tested. A second test case with resolveCommitHashMock.mockReturnValue(null) would document and guard the fallback behaviour, ensuring both branches of the ternary are covered.

Confidence Score: 3/5

  • Safe to merge after fixing the inconsistent fallback output format and adding test coverage for the null branch.
  • The logic bug in the ternary fallback means the --version output format differs depending on whether a commit hash is resolvable, which is likely unintentional and could surprise users or break tooling that parses the version string. The fix is straightforward (one-line change to include OpenClaw in the fallback), but it should be resolved before merging. Additionally, the incomplete test coverage leaves the fallback path untested.
  • src/cli/program/help.ts — the fallback branch of the version format ternary (line 114). src/cli/program/help.test.ts — add a test case for the null commit hash scenario.

Comments Outside Diff (1)

  1. General comment

    Missing test for the null/falsy commit hash case

    The PR only tests the happy path where resolveCommitHash returns "abc1234". The conditional fallback branch (commit is null) has no coverage, which means the inconsistent output format would go undetected by the test suite. A second it block that mocks resolveCommitHashMock.mockReturnValue(null) and asserts the expected output would catch regressions and document the intended behaviour for environments without commit metadata.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/cli/program/help.test.ts
    Line: 116-130
    
    Comment:
    **Missing test for the null/falsy commit hash case**
    
    The PR only tests the happy path where `resolveCommitHash` returns `"abc1234"`. The conditional fallback branch (`commit` is `null`) has no coverage, which means the inconsistent output format would go undetected by the test suite. A second `it` block that mocks `resolveCommitHashMock.mockReturnValue(null)` and asserts the expected output would catch regressions and document the intended behaviour for environments without commit metadata.
    
    How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 8c80fa3

) {
console.log(ctx.programVersion);
const commit = resolveCommitHash();
console.log(commit ? `OpenClaw ${ctx.programVersion} (${commit})` : ctx.programVersion);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent output format in fallback case

When resolveCommitHash() returns a falsy value the output falls back to the bare ctx.programVersion string (e.g. 9.9.9-test), without the OpenClaw prefix. But when a commit hash is available the output is OpenClaw 9.9.9-test (abc1234) — a completely different shape.

This means the --version output format is environment-dependent in a way that breaks the stated goal of keeping existing behaviour for environments without commit metadata. In those environments users will continue to see only the raw version number, while everyone else sees the branded, parenthesised form. The OpenClaw label (and any tooling that parses the new output) should appear consistently regardless of whether a commit hash exists.

The fallback should keep the OpenClaw prefix:

Suggested change
console.log(commit ? `OpenClaw ${ctx.programVersion} (${commit})` : ctx.programVersion);
console.log(commit ? `OpenClaw ${ctx.programVersion} (${commit})` : `OpenClaw ${ctx.programVersion}`);
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/program/help.ts
Line: 114

Comment:
**Inconsistent output format in fallback case**

When `resolveCommitHash()` returns a falsy value the output falls back to the bare `ctx.programVersion` string (e.g. `9.9.9-test`), without the `OpenClaw` prefix. But when a commit hash *is* available the output is `OpenClaw 9.9.9-test (abc1234)` — a completely different shape.

This means the `--version` output format is environment-dependent in a way that breaks the stated goal of keeping existing behaviour for environments without commit metadata. In those environments users will continue to see only the raw version number, while everyone else sees the branded, parenthesised form. The `OpenClaw` label (and any tooling that parses the new output) should appear consistently regardless of whether a commit hash exists.

The fallback should keep the `OpenClaw` prefix:

```suggestion
    console.log(commit ? `OpenClaw ${ctx.programVersion} (${commit})` : `OpenClaw ${ctx.programVersion}`);
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8c80fa3efe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

) {
console.log(ctx.programVersion);
const commit = resolveCommitHash();
console.log(commit ? `OpenClaw ${ctx.programVersion} (${commit})` : ctx.programVersion);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep --version output machine-readable semver

Changing the top-level version output to OpenClaw <version> (<sha>) breaks existing installer and verification flows that compare openclaw --version directly against an npm semver string; for example scripts/docker/install-sh-common/cli-verify.sh:30-37 and scripts/docker/install-sh-e2e/run.sh:71-77 both do exact string equality with npm view ... version. In any environment where resolveCommitHash() returns a value (git checkout or npm package with gitHead), these checks will now fail even when the correct version is installed.

Useful? React with 👍 / 👎.

@sourman
Copy link
Copy Markdown
Contributor

sourman commented Mar 5, 2026

hey @echoVic can you add to the discussion on #34972? the codex comments are correct

@echoVic
Copy link
Copy Markdown
Contributor Author

echoVic commented Mar 5, 2026

Thanks! I’ll follow up in #34972 to keep the discussion consolidated — agree the Codex comments there are pointing at the right shape. I’ll align this PR accordingly (and add the missing fallback test + harden commit formatting per the review notes).

@echoVic
Copy link
Copy Markdown
Contributor Author

echoVic commented Mar 5, 2026

Pushed an update to address the review notes:

  • Keep output shape consistent (always includes prefix; only append when available).
  • Add test coverage for the no-commit fallback branch.
  • Harden against crafted markers/HEAD refs (only allow , prevent path escape, bounded reads) and validate commit output as hex-only to avoid control-char/log injection.

Latest commit: b91602c.

@sourman
Copy link
Copy Markdown
Contributor

sourman commented Mar 5, 2026

@echoVic
can you fix up these two? no point in making it a separate PR imo

scripts/docker/install-sh-common/cli-verify.sh:30-37
scripts/docker/install-sh-e2e/run.sh:71-77

or did I miss something? #35713 (comment)

@echoVic
Copy link
Copy Markdown
Contributor Author

echoVic commented Mar 6, 2026

Following up here.

The review points about keeping the fallback output shape consistent, adding the no-commit test, and hardening commit-hash resolution were folded into this PR in the later update I pushed. If there are still two concrete snippets from the linked discussion that you want adjusted further, point me at the exact lines and I will patch them on this branch instead of splitting anything out.

@openclaw-barnacle openclaw-barnacle bot added scripts Repository scripts size: S and removed size: XS labels Mar 6, 2026
@echoVic
Copy link
Copy Markdown
Contributor Author

echoVic commented Mar 6, 2026

Follow-up pushed on this branch.

This folds in the two installer/verifier call sites called out in the review so they stay semver-compatible with openclaw --version now returning OpenClaw <version> (<sha>) when commit metadata is present.

Updated:

  • scripts/docker/install-sh-common/cli-verify.sh
  • scripts/docker/install-sh-e2e/run.sh

Both now normalize the CLI --version output back to the semver string before comparing against npm view ... version.

Local verification:

  • pnpm test src/cli/program/help.test.ts
  • bash -n scripts/docker/install-sh-common/cli-verify.sh
  • bash -n scripts/docker/install-sh-e2e/run.sh

@sourman
Copy link
Copy Markdown
Contributor

sourman commented Mar 6, 2026

🚢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes scripts Repository scripts size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement]: Include commit SHA in -v/--version output for local builds

4 participants