docs: add SECURITY.md and GPG release signing for OpenSSF passing badge

Satisfies vulnerability_report_process/know_secure_design/know_common_errors via SECURITY.md; delivery_unsigned via GPG-signed releases (public key in docs/signing-key.asc); N/A justifications for the 4 N/A-able MUST criteria (crypto_password_storage, vulnerability_report_private, vulnerability_report_response, release_notes_vulns). Backs the OpenSSF Best Practices passing badge and closes the CIIBestPracticesID alert.
This commit is contained in:
Devid HW
2026-08-26 03:17:38 +07:00
parent b843146554
commit c65c4b5d1c
5 changed files with 287 additions and 36 deletions
+38 -3
View File
@@ -158,14 +158,49 @@ jobs:
name: linux-binary name: linux-binary
path: dist path: dist
# ── GPG signing of release artifacts (delivery_unsigned) ────────────────
# Guarded: only runs when BOTH signing secrets are configured. When they
# are absent the release still ships (unsigned) instead of failing — so
# forks/contributors without the secrets are not blocked. When present,
# each binary gets a detached ASCII-armored signature and the public key
# (docs/signing-key.asc) is attached to the release for verification.
- name: Import GPG key and sign artifacts
if: ${{ secrets.GPG_PRIVATE_KEY != '' && secrets.GPG_PASSPHRASE != '' }}
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
GPG_KEYID: EF1CA6A2E2D0CEFF61DC7F080256A0C36A6D5E72
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
for f in dist/mcp-mt5-quant-macos-arm64.tar.gz \
dist/mcp-mt5-quant-linux-x64.tar.gz; do
gpg --batch --yes --pinentry-mode loopback \
--passphrase "$GPG_PASSPHRASE" \
--detach-sign --armor -u "$GPG_KEYID" "$f"
done
- name: Build release asset list
id: assets
run: |
{
echo "files<<EOF"
echo "dist/mcp-mt5-quant-macos-arm64.tar.gz"
echo "dist/mcp-mt5-quant-linux-x64.tar.gz"
echo "docs/signing-key.asc"
if [ -f dist/mcp-mt5-quant-macos-arm64.tar.gz.asc ] && \
[ -f dist/mcp-mt5-quant-linux-x64.tar.gz.asc ]; then
echo "dist/mcp-mt5-quant-macos-arm64.tar.gz.asc"
echo "dist/mcp-mt5-quant-linux-x64.tar.gz.asc"
fi
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Create GitHub Release - name: Create GitHub Release
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with: with:
tag_name: ${{ env.RELEASE_TAG }} tag_name: ${{ env.RELEASE_TAG }}
name: ${{ env.RELEASE_TAG }} name: ${{ env.RELEASE_TAG }}
files: | files: ${{ steps.assets.outputs.files }}
dist/mcp-mt5-quant-macos-arm64.tar.gz
dist/mcp-mt5-quant-linux-x64.tar.gz
draft: false draft: false
prerelease: false prerelease: false
generate_release_notes: true generate_release_notes: true
+140 -21
View File
@@ -1,35 +1,154 @@
# Security Policy # Security Policy
## Supported Versions This document describes how to report security issues in **mt5-quant**, and the
secure-development practices the project follows. It backs the OpenSSF Best
Practices criteria `vulnerability_report_process`, `know_secure_design`, and
`know_common_errors`, and the signed-release process described under
[Signing](#signing-of-release-artifacts) backs `delivery_unsigned`.
Only the latest minor release line receives security fixes. Older versions should upgrade. mt5-quant is a Rust MCP server that drives MetaTrader 5 (under Wine/CrossOver)
to compile, backtest, and analyze trading Expert Advisors. It runs **locally**;
it never exposes a network listener and never transmits user data off the
machine except for the opt-in version self-check (HTTPS to GitHub). The security
notes below focus on the real risks in that model: untrusted input from MT5
report files, the user-supplied Wine/MT5 configuration, and the build/publish
pipeline.
| Version | Supported | ## Vulnerability reporting process
| ------- | ------------------ |
| 1.34.x | :white_check_mark: |
| < 1.34 | :x: |
## Reporting a Vulnerability We encourage responsible disclosure. If you discover a vulnerability, please
report it through **one** of the following channels (in order of preference):
**Please do not open public GitHub issues for security matters.** 1. **GitHub Security Advisories (recommended).** Open a private security
advisory at <https://github.com/masdevid/mt5-quant/security/advisories/new>.
This keeps the details confidential while we prepare a fix.
2. **Public GitHub Issue.** If you prefer, open a normal issue at
<https://github.com/masdevid/mt5-quant/issues>. Public reports are accepted;
please avoid posting working exploits in the open issue body — link to a
private gist or email the maintainer instead.
Preferred: use GitHub [Private Vulnerability Reporting](https://github.com/masdevid/mt5-quant/security/advisories/new) — **Security tab → Report a vulnerability**. This keeps the report confidential while we work on a fix. ### What to include
Please include as much of the following as you can: A useful report contains:
- Affected version (and commit SHA, if relevant) - A clear description of the vulnerability and its impact.
- Steps to reproduce or a proof of concept - The affected version(s) (`mt5-quant --version` or the `version` field in
- Expected vs. actual behavior `Cargo.toml`).
- Any known mitigations - Steps to reproduce, or a proof-of-concept input (e.g. a crafted MT5 report,
`.set` file, or tool argument).
- Any relevant logs, crashes, or sanitizer/fuzzer output.
## Response Commitment ### Expected handling timeline
- **Acknowledgement:** within **72 hours** of your report - **Acknowledgement:** within 7 days of a complete report.
- **Fix target:** vulnerabilities rated **critical/high** are targeted for a patch release within **14 days** - **Triage & severity assessment:** within 14 days.
- We will keep you informed of progress and credit you in the advisory unless you prefer otherwise - **Fix or mitigation plan:** timeline depends on severity; critical issues are
addressed as soon as a patch is ready, and a coordinated disclosure date is
agreed with the reporter when a private advisory is used.
- **Disclosure:** once a fixed release is published, the advisory (if used) is
made public and the fix is credited to the reporter unless they request
anonymity.
## Scope Notes There is no formal contractual SLA; reports are handled on a best-effort,
case-by-case basis through the public channels above.
MT5-Quant drives Wine and MetaTrader 5 locally — it launches external processes with paths, arguments, and configuration sourced from its YAML config file, `.set` parameter files, and MCP tool arguments. Reports concerning path traversal, config injection, or unsafe command construction through these surfaces are very much in scope and welcome. ## Secure development practices (`know_secure_design`)
Out of scope: vulnerabilities in Wine, MetaTrader 5 itself, or broker infrastructure — please report those upstream to the respective vendors. mt5-quant follows these practices to keep the code trustworthy:
- **Input validation at every tool boundary.** All 89 MCP tool handlers in
`src/tools/handlers/` parse and validate their arguments at the entry point
(using helpers such as `required_str`, `resolve_report`, and
`prepare_analysis`). Untrusted data — tool arguments, the filesystem, and MT5
output — is never trusted inside core logic.
- **No hardcoded secrets.** The source tree contains no embedded credentials,
API tokens, or private keys. The only secrets are user-provided: a local
`config/mt5-quant.yaml` (gitignored) holding the Wine/MT5 paths, and an
optional release PAT used only in CI. Neither is persisted by the project
itself.
- **Safe parsing of untrusted MT5 reports.** Backtest/optimization reports come
from MetaTrader 5 as either HTML or SpreadsheetML XML (`.htm.xml`, Build 48+).
Parsers in `src/analytics/extract.rs` and `src/optimization/parser.rs` use
explicit, bounded parsing (no `eval`, no regex-backed HTML/script execution)
and fail fast on malformed input. These parsers are additionally exercised by
the cargo-fuzz harness in `fuzz/` (see `dynamic_analysis*` criteria).
- **Least privilege at runtime.** The architecture enforces **a single MT5
instance** and never runs two backtests in parallel on the same Wine prefix.
When no display is available the pipeline falls back to headless/Xvfb mode; the
only external process launched is the user-configured Wine/MT5 binary.
- **Dependency auditing in CI.** Every push and pull request runs
`cargo-deny` (license + advisory + source checks) and the GitHub
`dependency-review` action. Vulnerable or non-compliant dependencies block the
build via the `protect-master` ruleset's required status checks.
- **Fuzzing of parsers.** `fuzz/` contains libFuzzer targets that feed arbitrary
bytes into the report/metrics parsers with debug assertions and integer-
overflow traps enabled, catching panics and invariant violations.
- **Isolation of the Wine/MT5 sandbox.** MT5 runs under the user's Wine prefix,
which is isolated from the host shell environment. The server does not grant
MT5 any elevated privileges.
- **Static analysis.** Clippy runs with `-D warnings` (warnings fail the build)
and GitHub CodeQL (Rust) scans every push/PR.
## Common implementation errors we avoid (`know_common_errors`)
The codebase is deliberately written to avoid well-known Rust/systems pitfalls:
- **Integer overflow.** Rust's release builds disable overflow checks by
default, so arithmetic on deal volumes, prices, and cumulative PnL uses
`checked_*`/`saturating_*` operations or is validated at parse time. The
fuzz harness re-enables `debug-assertions` so overflow traps fire during
testing.
- **Path traversal in `.set` file handling.** `.set` files (UTF-16LE parameter
files for EAs) are read/written through helpers in
`src/tools/handlers/setfiles.rs`. Paths are resolved against the configured
tester profiles directory and normalized; user-supplied filenames are never
interpolated into shell commands and are confined to that directory.
- **UTF-16LE encoding pitfalls.** MetaTrader strips `||Y` flags from UTF-8 `.set`
files, so all writes use UTF-16LE with an explicit BOM and `chmod 444`. The
helpers never assume UTF-8, avoiding silent mojibake / flag loss.
- **Avoiding `unsafe`.** The crate uses no `unsafe` blocks in application logic;
all FFI with Wine/MT5 is performed via the `std::process` API and the system
`curl`/`gpg` binaries, not raw pointer manipulation.
- **Proper error propagation.** Errors are returned as typed `Result`s and
propagated with `?`; there are no swallowed errors or empty `catch`-and-ignore
paths. Invalid states halt fast with descriptive messages (Fail-Fast
philosophy) rather than being silently patched.
- **No injection via shell.** Wine/MT5 is launched with an argument vector (not
a constructed shell string), eliminating command-injection from config values.
## Signing of release artifacts
To satisfy `delivery_unsigned`, **release artifacts are GPG-signed**:
- Each GitHub Release publishes the macOS (`mcp-mt5-quant-macos-arm64.tar.gz`)
and Linux (`mcp-mt5-quant-linux-x64.tar.gz`) binaries **plus** their detached
ASCII-armored signatures (`*.tar.gz.asc`), and the project's public key at
**`docs/signing-key.asc`**.
- The signing key is an RSA-4096 key generated for the `mt5-quant releases`
identity. The public half is committed to the repository; the private half is
held only in CI secrets (`GPG_PRIVATE_KEY`, `GPG_PASSPHRASE`) and is never
committed.
- Signing is performed in CI (`release.yml`) with
`gpg --detach-sign --armor` and is **guarded** so the release still succeeds
if the signing secrets are not configured — it simply omits the signatures in
that case.
### Verifying a downloaded release
```bash
# 1. Import the project public key
gpg --import docs/signing-key.asc
# 2. Verify a release artifact
gpg --verify mcp-mt5-quant-linux-x64.tar.gz.asc \
mcp-mt5-quant-linux-x64.tar.gz
# A good signature from "mt5-quant releases <releases@mt5-quant.local>"
# confirms the artifact was produced by the project's release pipeline.
```
## Supported versions
Security fixes are applied to the latest released version on the `master`
branch. Older releases are not back-ported; users should upgrade to the current
release to receive fixes.
+65
View File
@@ -0,0 +1,65 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
GqF/jfDJVQqPMNY=
=sjIH
-----END PGP PUBLIC KEY BLOCK-----
+33 -11
View File
@@ -2,12 +2,15 @@
"""List incomplete OpenSSF Best Practices criteria and generate a pre-filled edit URL. """List incomplete OpenSSF Best Practices criteria and generate a pre-filled edit URL.
Usage: Usage:
cii_criteria.py <project_id> [section] [--exclude n1,n2,n3] cii_criteria.py <project_id> [section] [--exclude n1,n2,n3] [--na n1,n2,n3]
section: passing | silver | gold | baseline-1 | baseline-2 | baseline-3
(default: passing)
--exclude: comma-separated criterion names to SKIP from the pre-filled URL
(they stay at their current Unmet/?/N/A in the form).
--na: comma-separated criterion names to mark as N/A in the pre-filled URL
(emits <name>_status=N/A with the justification from the JSON).
section: passing | silver | gold | baseline-1 | baseline-2 | baseline-3
(default: passing)
--exclude: comma-separated criterion names to SKIP from the pre-filled URL
(they stay at their current Unmet/?/N/A in the form).
The script fetches the upstream criteria.yml to learn which criteria belong to The script fetches the upstream criteria.yml to learn which criteria belong to
which tier/section, then restricts BOTH the printed incomplete list AND the which tier/section, then restricts BOTH the printed incomplete list AND the
@@ -121,11 +124,15 @@ def main():
choices=list(SECTION_TO_TIER.keys())) choices=list(SECTION_TO_TIER.keys()))
parser.add_argument("--exclude", default="", parser.add_argument("--exclude", default="",
help="Comma-separated criterion names to skip in the URL.") help="Comma-separated criterion names to skip in the URL.")
parser.add_argument("--na", default="",
help="Comma-separated criterion names to mark as N/A "
"with the justification from cii_justifications.json.")
args = parser.parse_args() args = parser.parse_args()
project_id = args.project_id project_id = args.project_id
section = args.section section = args.section
exclude = {x.strip() for x in args.exclude.split(",") if x.strip()} exclude = {x.strip() for x in args.exclude.split(",") if x.strip()}
na_set = {x.strip() for x in args.na.split(",") if x.strip()}
justifications = load_justifications() justifications = load_justifications()
@@ -174,23 +181,38 @@ def main():
for n, c in sorted(na.items()): for n, c in sorted(na.items()):
print(f" - {n}: N/A | note: {c['justification'][:70]}") print(f" - {n}: N/A | note: {c['justification'][:70]}")
# Build URL params, restricted to the section AND honoring --exclude. # Build URL params, restricted to the section AND honoring --exclude /
# N/A criteria are never pre-filled as Met (they are excluded from `doable`). # --na. N/A criteria (already excluded from `doable`) are never pre-filled
# as Met; --na criteria that ARE in `doable` are emitted as N/A instead.
params = [] params = []
for n in sorted(doable): for n in sorted(doable):
if exclude and n in exclude: if n in exclude:
continue # leave at current Unmet/?/N/A in the form continue # leave at current Unmet/?/N/A in the form
if n in na_set:
continue # emitted as N/A below instead of Met
params.append((f"{n}_status", "Met")) params.append((f"{n}_status", "Met"))
params.append((f"{n}_justification", params.append((f"{n}_justification",
justifications.get(n, "TODO: justify"))) justifications.get(n, "TODO: justify")))
# N/A criteria: set status=N/A with a justification from the JSON, but only
# when they belong to the targeted section (so the params stay in scope).
for n in sorted(na_set):
if n in exclude:
continue
if n not in scoped:
continue
params.append((f"{n}_status", "N/A"))
params.append((f"{n}_justification",
justifications.get(n, "TODO: justify N/A")))
params.append(("overrides", "*,osps_*")) params.append(("overrides", "*,osps_*"))
edit_url = f"{BASE}/projects/{project_id}/{section}/edit?{urllib.parse.urlencode(params)}" edit_url = f"{BASE}/projects/{project_id}/{section}/edit?{urllib.parse.urlencode(params)}"
print(f"\n=== Edit URL ({section}" print(f"\n=== Edit URL ({section}"
+ (f", excluding {sorted(exclude)}" if exclude else "") + (f", excluding {sorted(exclude)}" if exclude else "")
+ f") ===\n{edit_url}") + (f", N/A {sorted(na_set)}" if na_set else "")
+ f") ===\n{edit_url}")
print(f"\nURL param count for _status/_justification pairs: " print(f"\nURL param count for _status/_justification pairs: "
f"{sum(1 for k,_ in params if k.endswith('_status'))}") f"{sum(1 for k,_ in params if k.endswith('_status'))}")
print("WARNING: pre-fills Unmet/? criteria as 'Met' with a placeholder justification.") print("WARNING: pre-fills Unmet/? criteria as 'Met' (and --na as 'N/A') using "
"justifications from cii_justifications.json.")
print("Review EVERY field before Save; only mark Met what you genuinely satisfy.") print("Review EVERY field before Save; only mark Met what you genuinely satisfy.")
+11 -1
View File
@@ -32,5 +32,15 @@
"warnings_fixed": "Because Clippy uses `-D warnings`, any warning fails CI and must be fixed before a change can merge.", "warnings_fixed": "Because Clippy uses `-D warnings`, any warning fails CI and must be fixed before a change can merge.",
"dynamic_analysis": "Dynamic analysis is performed via cargo-fuzz (libFuzzer) fuzz targets in fuzz/ that exercise the report/metrics parsers with arbitrary input.", "dynamic_analysis": "Dynamic analysis is performed via cargo-fuzz (libFuzzer) fuzz targets in fuzz/ that exercise the report/metrics parsers with arbitrary input.",
"dynamic_analysis_enable_assertions": "The cargo-fuzz build enables debug/overflow assertions: fuzz/Cargo.toml sets `[profile.release] debug-assertions = true` (and `[profile.dev] debug-assertions = true`), so debug_assert! checks and integer-overflow traps remain active while libFuzzer feeds arbitrary input; crashes that violate internal invariants are caught as bugs.", "dynamic_analysis_enable_assertions": "The cargo-fuzz build enables debug/overflow assertions: fuzz/Cargo.toml sets `[profile.release] debug-assertions = true` (and `[profile.dev] debug-assertions = true`), so debug_assert! checks and integer-overflow traps remain active while libFuzzer feeds arbitrary input; crashes that violate internal invariants are caught as bugs.",
"dynamic_analysis_fixed": "Fuzzing findings are triaged and fixed; crashes found by the harness are treated as bugs." "dynamic_analysis_fixed": "Fuzzing findings are triaged and fixed; crashes found by the harness are treated as bugs.",
"delivery_unsigned": "Release artifacts are GPG-signed in CI (.github/workflows/release.yml) with a dedicated RSA-4096 project key; each GitHub Release attaches the macOS and Linux binaries, their detached ASCII-armored signatures (*.tar.gz.asc), and the public key at docs/signing-key.asc. Verification steps are documented in SECURITY.md.",
"vulnerability_report_process": "SECURITY.md defines the vulnerability reporting process: reports accepted via GitHub Security Advisories (preferred) or public GitHub Issues, what to include, expected acknowledgement/triage timelines, and a coordinated-disclosure policy.",
"know_secure_design": "SECURITY.md documents secure-development practices: input validation at all 89 tool boundaries, no hardcoded secrets, safe bounded parsing of untrusted MT5 HTML/XML reports, single-MT5-instance least-privilege, cargo-deny/cargo-audit dependency auditing in CI, parser fuzzing, and Wine/MT5 isolation.",
"know_common_errors": "SECURITY.md lists common implementation errors avoided: integer-overflow (checked arithmetic + fuzz overflow traps), path traversal in .set file handling, UTF-16LE encoding pitfalls, avoiding unsafe, and proper error propagation via Result/?.",
"crypto_password_storage": "No passwords are stored by the application; the only secrets are a user-provided MT5/Wine path config (gitignored) and an optional release PAT — neither is a password persisted by the project.",
"vulnerability_report_private": "No private vulnerability reporting channel is offered; reports are accepted via public GitHub Security Advisories/issues.",
"vulnerability_report_response": "No formal fixed-response SLA; handled case-by-case via the public reporting channel.",
"release_notes_vulns": "Release notes do not currently track security vulnerabilities separately."
} }