Security¶
Reporting a vulnerability¶
Please open a private security advisory on GitHub: https://github.com/ThoTischner/observability-mcp/security/advisories/new
Continuous security automation¶
The repo runs a self-driving security pipeline so issues get caught and patched without manual sweeping.
| Mechanism | What it does | Cadence |
|---|---|---|
| Dependabot | Grouped PRs for npm (mcp-server, agent), GitHub Actions, and Docker base images | Weekly, Monday |
| CodeQL | Static analysis with security-extended + quality queries; results in the Security tab |
PR + weekly |
| Trivy | Docker image and filesystem scans for CRITICAL/HIGH CVEs (SARIF upload) | PR + daily |
| npm audit | Fails CI on --audit-level=high |
PR + daily |
| OSSF Scorecard | Repo posture analysis published to the Security tab | Weekly |
| Auto-merge sweeper | Merges Dependabot PRs ≥ 72 h old when checks pass; majors stay manual | Daily |
| Auto-release | Patch-bumps + tags if commits landed since the last release; triggers npm + GHCR + GitHub Release | Weekly, Sunday |
Built-in protections¶
- Input validation for durations, metric names, and service identifiers (length-bounded, character-allowlisted).
- PromQL/LogQL injection guarded by per-language escape helpers around quoted label values.
- SSRF mitigated for source URLs: cloud metadata endpoints and non-HTTP schemes are rejected.
- Security headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy) on Web UI responses.
- Body size limits on JSON request bodies.
- Session TTL of 30 minutes with periodic cleanup.
- Non-root user in the Docker image (
USER node). - npm provenance on every published version (SLSA build attestation).
Connector signing¶
Hub-distributed connectors (e.g. Datadog) ship a detached signature of
their manifest.json inside the tarball. The server and omcp plugin
install/verify check it fail-closed against a trust root — fully
offline, no transparency log (airgapped-safe).
- Public key (trust root):
docs/plugin-signing.pub.pem(Ed25519). Operators pass it asPLUGIN_TRUST_ROOT/--trust-root. - Private key: only the
PLUGIN_SIGNING_KEY_B64repo secret (base64 PKCS#8). CI-only, scoped to connector signing. Never in git. - Pipeline:
.github/workflows/connector-publish.ymlrunsconnectors/pack.mjs(validatesmanifest.integrity== sha256(entry), signsmanifest.json, tars the dir) and uploads<name>-<version>.tgzto theconnector-<name>-<version>release — the URL the hub catalog points at. Signing is fail-open (missing key → unsigned tarball; install then needs--insecure). - Verify manually:
bash omcp plugin verify ./plugins/datadog --trust-root docs/plugin-signing.pub.pem
Rotation / revocation¶
The key has no expiry; rotate if exposure is suspected.
node -e 'c=require("crypto");k=c.generateKeyPairSync("ed25519"); require("fs").writeFileSync("docs/plugin-signing.pub.pem", k.publicKey.export({type:"spki",format:"pem"})); process.stdout.write(Buffer.from(k.privateKey.export({type:"pkcs8", format:"pem"})).toString("base64"))'→ set the output asPLUGIN_SIGNING_KEY_B64.- Commit the regenerated
docs/plugin-signing.pub.pem, bump connector versions, re-run the publish workflow. Already-published tarballs stay verifiable with the previous public key from git history.
Helm chart signing¶
Released Helm charts are GPG-signed. Each observability-mcp-X.Y.Z.tgz ships
with a matching .tgz.prov provenance file, and ArtifactHub shows the
"signed" badge.
- Public key:
docs/helm-signing.pub.asc(also referenced byartifacthub.io/signKeyinhelm/observability-mcp/Chart.yaml). - Private key: held only as the
HELM_SIGNING_KEY_B64/HELM_SIGNING_KEY_PASSPHRASErepository secrets. It is a dedicated, CI-only key — it signs nothing but charts and is never used locally. - Verify a release:
bash helm pull observability-mcp/observability-mcp --prov gpg --import docs/helm-signing.pub.asc helm verify observability-mcp-*.tgz
Key rotation / revocation¶
The signing key has no expiry, so rotation is manual. Rotate immediately if the private key (or its passphrase) is suspected exposed; otherwise rotate on a routine cadence.
- Generate a fresh key (RSA 4096, empty or stored passphrase):
gpg --batch --gen-keywith aName-Real: observability-mcp helm signing. - Export and update the secrets:
gpg --export-secret-keys <fpr> | base64 -w0→HELM_SIGNING_KEY_B64; updateHELM_SIGNING_KEY_PASSPHRASEto match (empty if none). - Export the public key over the old one:
gpg --armor --export <fpr> > docs/helm-signing.pub.asc. - Update the fingerprint in
helm/observability-mcp/Chart.yaml(artifacthub.io/signKey.fingerprint), bump the chartversion, and open a PR. The next publish signs with the new key; older releases stay verifiable with the previous public key from git history. - Revocation: generate and publish a revocation certificate for the
retired key (
gpg --gen-revoke <fpr>), and note the retirement date inCHANGELOG.md. Already-published.provfiles remain valid against the archived public key; only future releases use the new key.
The CI signing step is best-effort and fail-open: if the key is missing or unimportable the chart still publishes (unsigned), and gpg error detail is kept out of the public Actions log.
Token / secret handling¶
- Do not bake secrets into
sources.yaml. Use${VAR}substitution and supply them via env or a.envfile. - The container reads
sources.yamlfrom a mounted volume — nothing about credentials lives in the image layer. - GitHub repository secrets used by CI:
NPM_TOKEN(npm publish),RELEASE_PAT(lets the auto-release tag push trigger downstream workflows). Both are injected only into the workflows that need them.