Skip to content

Commit d8ebc5f

Browse files
pluskymimi1vx
authored andcommitted
chore(ci): add CodeQL, dependency-review and Scorecard; harden release build
Bring the security tooling in line with openSUSE/repose and make the release build reproducible from the committed lockfile. - codeql.yml scans rust + actions, weekly as well as per-PR, so a newly published query still reaches code that no PR happens to touch. - dependency-review.yml blocks a PR introducing a high-severity advisory; Dependabot only alerts once the code is already on main. - scorecard.yml runs OpenSSF Scorecard and publishes results. - release.yml builds with --locked, so a release is compiled from exactly the versions in Cargo.lock rather than whatever resolves at tag time, and drops the checkout credential it never needs (the release is published with gh release create using github.token). SECURITY.md was stale from the Python era: it asked reporters for their paramiko/openqa-client versions and scoped out ruamel.yaml, pyxdg and osc. deny-licenses is deliberately left unset in dependency-review. mtui is GPL-2.0-only while russh, rmcp and ring are Apache-2.0-only, which is generally read as GPLv2-incompatible. That predates this change and wants a real decision rather than a CI gate that is red from day one.
1 parent 19684c0 commit d8ebc5f

6 files changed

Lines changed: 195 additions & 14 deletions

File tree

.github/workflows/codeql.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
name: codeql
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
schedule:
9+
# Weekly, so a newly published query still finds old code that no PR touches.
10+
- cron: '31 5 * * 3'
11+
12+
permissions:
13+
contents: read
14+
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.ref }}
17+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
18+
19+
jobs:
20+
analyze:
21+
name: Analyze (${{ matrix.language }})
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 30
24+
permissions:
25+
security-events: write
26+
contents: read
27+
strategy:
28+
fail-fast: false
29+
matrix:
30+
include:
31+
# Rust is source-extracted, so no build orchestration is needed;
32+
# the workflows themselves are scanned as `actions`.
33+
- language: rust
34+
build-mode: none
35+
- language: actions
36+
build-mode: none
37+
steps:
38+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
39+
with:
40+
persist-credentials: false
41+
- name: Initialize CodeQL
42+
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4
43+
with:
44+
languages: ${{ matrix.language }}
45+
build-mode: ${{ matrix.build-mode }}
46+
- name: Analyze
47+
uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4
48+
with:
49+
category: "/language:${{ matrix.language }}"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
name: dependency-review
3+
on:
4+
pull_request:
5+
branches: [main]
6+
7+
permissions:
8+
contents: read
9+
# Write only so the action can post its summary comment on the PR.
10+
pull-requests: write
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
15+
16+
jobs:
17+
dependency-review:
18+
# Block PRs that add a vulnerable dependency before merge. Complements
19+
# Dependabot, which only alerts once the code is already on main.
20+
runs-on: ubuntu-latest
21+
timeout-minutes: 10
22+
steps:
23+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24+
with:
25+
persist-credentials: false
26+
- name: Dependency review
27+
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
28+
with:
29+
fail-on-severity: high
30+
comment-summary-in-pr: on-failure
31+
# No `deny-licenses` here, deliberately. mtui is GPL-2.0-only while
32+
# russh, rmcp and ring are Apache-2.0-*only*, which is generally read
33+
# as GPLv2-incompatible. That question predates this workflow and
34+
# needs a real decision (relicense to -or-later, or replace those
35+
# stacks); gating every PR on it would just paint CI red without
36+
# moving it forward.

.github/workflows/release.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ jobs:
2525
env:
2626
TARGET: x86_64-unknown-linux-musl
2727
steps:
28-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
28+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29+
with:
30+
# The release is published with `gh release create` using
31+
# ${{ github.token }}; no git push happens, so the checkout does not
32+
# need a credential left behind in .git/config.
33+
persist-credentials: false
2934

3035
- name: Verify tag matches workspace version
3136
run: |
@@ -42,10 +47,13 @@ jobs:
4247
- name: Install musl tools
4348
run: sudo apt-get update && sudo apt-get install -y musl-tools
4449

50+
# `--locked` so a release is built from the dependency versions committed
51+
# in Cargo.lock. Without it a newly published (or yanked-and-replaced)
52+
# transitive crate could differ from anything CI ever tested.
4553
- name: Build release binaries
4654
run: |
47-
cargo build --release --target "$TARGET" -p mtui-cli
48-
cargo build --release --target "$TARGET" -p mtui-mcp --features mcp
55+
cargo build --release --locked --target "$TARGET" -p mtui-cli
56+
cargo build --release --locked --target "$TARGET" -p mtui-mcp --features mcp
4957
5058
- name: Package release tarball
5159
run: cargo xtask package --version "$GITHUB_REF_NAME" --target "$TARGET"

.github/workflows/scorecard.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
name: scorecard
3+
4+
on:
5+
branch_protection_rule:
6+
schedule:
7+
- cron: "42 3 * * 1"
8+
push:
9+
branches:
10+
- main
11+
12+
# Declare default permissions as read only.
13+
permissions: read-all
14+
15+
jobs:
16+
analysis:
17+
name: Scorecards analysis
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 20
20+
permissions:
21+
# Needed to upload the results to the code-scanning dashboard.
22+
security-events: write
23+
# Needed to publish results and get a badge (see publish_results below).
24+
id-token: write
25+
26+
steps:
27+
- name: "Checkout code"
28+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29+
with:
30+
persist-credentials: false
31+
32+
- name: "Run analysis"
33+
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
34+
with:
35+
results_file: results.sarif
36+
results_format: sarif
37+
# Publish to the OpenSSF REST API so consumers can see the score and
38+
# the repository can carry a Scorecard badge.
39+
publish_results: true
40+
41+
- name: "Upload artifact"
42+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
43+
with:
44+
name: SARIF file
45+
path: results.sarif
46+
retention-days: 5
47+
48+
- name: "Upload to code-scanning"
49+
uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4
50+
with:
51+
sarif_file: results.sarif

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ _The entries below document the final Python releases, retained for history._
7676
gate is turned off by configuration (`slack_enabled = false`), which is
7777
explicit and auditable. `reject` is never gated. With Slack disabled (the
7878
default) `approve` behaves exactly as before.
79+
### Security
80+
81+
- Release binaries are now built with `cargo build --locked`, so a published
82+
release is compiled from exactly the dependency versions recorded in
83+
`Cargo.lock` rather than whatever resolved at tag time.
84+
- Pull requests are now gated by `dependency-review`, which blocks a change that
85+
introduces a dependency with a known high-severity advisory. The code and the
86+
workflows are additionally scanned by CodeQL and OpenSSF Scorecard, and
87+
Dependabot now opens security updates for known-vulnerable crates.
88+
- `SECURITY.md` has been rewritten for the Rust tool; it previously asked
89+
reporters for their Python and paramiko versions and scoped out dependencies
90+
the project no longer has.
7991

8092
### Fixed
8193

SECURITY.md

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ maintained release branches.
77

88
## Reporting a vulnerability
99

10-
Please report suspected security issues privately to
11-
**qa-maintenance@suse.de**.
10+
Please report suspected security issues privately, either via GitHub's
11+
[**Report a vulnerability**](https://github.com/openSUSE/mtui/security/advisories/new)
12+
(Security → Advisories) or by mail to **qa-maintenance@suse.de**.
1213

1314
Do not open a public GitHub issue or pull request for a suspected
1415
vulnerability before it has been triaged.
@@ -17,8 +18,7 @@ When reporting, include as much of the following as you can:
1718

1819
- A description of the issue and its potential impact.
1920
- Steps to reproduce or a proof of concept.
20-
- The mtui version (`mtui -V`) and the Python / paramiko / openqa-client
21-
versions.
21+
- The mtui version (`mtui -V`).
2222
- Any relevant configuration (with secrets redacted).
2323

2424
## Response expectations
@@ -29,10 +29,35 @@ team; there is no committed response time or fix-time SLA.
2929

3030
## Scope
3131

32-
In scope: defects in mtui itself (the CLI, the SSH connection layer,
33-
the configuration loader, the OBS / openQA / Gitea connectors).
34-
35-
Out of scope: vulnerabilities in upstream dependencies (`paramiko`,
36-
`openqa-client`, `requests`, `ruamel.yaml`, `pyxdg`, `osc`, …). Please
37-
report those directly to the respective upstream projects; we will
38-
update the dependency once an upstream fix is available.
32+
In scope: defects in mtui itself. The security-relevant areas are:
33+
34+
- **SSH.** Host-key verification against `~/.ssh/known_hosts` (including
35+
hashed entries) and remote command construction (shell quoting). mtui is
36+
**pubkey-only by design** — password authentication is deliberately
37+
absent and should not be added.
38+
- **Credential handling.** Service credentials (Gitea token, `oscrc` for
39+
OBS, openQA API secrets, and any chat integration token) must never reach
40+
terminal output, logs, or MCP responses. Secret config fields are
41+
classified by `is_secret_attr` and masked as `<set>` by both
42+
`config show` and `config set`; a change adding a credential field must
43+
add it there in the same commit.
44+
- **URLs in logs.** URLs may carry credentials in userinfo or query
45+
parameters, so they are passed through `mtui_datasources::sanitize_url`
46+
before being logged.
47+
- **The MCP server.** `mtui-mcp --transport http` binds loopback
48+
(`127.0.0.1`) by default and relies on rmcp's DNS-rebinding guard. It
49+
exposes mtui commands — including ones that mutate remote hosts — to
50+
whatever can reach that port.
51+
- The configuration loader and the OBS / openQA / Gitea connectors.
52+
53+
Out of scope: vulnerabilities in upstream crates (`russh`, `rmcp`,
54+
`reqwest`, `tokio`, …). Please report those to the respective upstream
55+
projects; we will bump the dependency once an upstream fix is available.
56+
57+
## Dependency and code scanning
58+
59+
Pull requests are checked by `dependency-review`, which blocks a change
60+
introducing a dependency with a known high-severity advisory. The code and
61+
the workflows themselves are scanned by CodeQL, the repository is assessed
62+
by OpenSSF Scorecard, and Dependabot keeps Cargo and Actions dependencies
63+
current and opens security updates for known-vulnerable crates.

0 commit comments

Comments
 (0)