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:
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
GqF/jfDJVQqPMNY=
|
||||||
|
=sjIH
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
+33
-11
@@ -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.")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user