# How Nojam is secured

Nojam sits on the path to your Vault, so it assumes that a pull request, a runner or the network may be hostile. This page covers what Nojam does about each, and how to report a problem.

## Where the trust boundaries are

Repository code runs on a runner host, in a Podman container, and reaches Vault only with a short-lived credential. The Nojam server talks to GitHub, keeps its own records and the audit log in PostgreSQL, and keeps plans and logs in a blob store. Nojam holds no Terraform state; that stays in your PostgreSQL or S3 backend.

The threat model works through each boundary: what an attacker on that side could try, the control that stops it, and the items still open.

## Credentials that end with the job

Every job gets its own Vault credential for its own phase: read-only for plans, and an apply role bound to `main`. It is revoked when Terraform finishes, and again when its lease expires.

The token never crosses the wire in the clear. Each runner sends a fresh key, and Vault's answer is sealed to it with HPKE, using X25519 together with ML-KEM-768 so that recorded traffic stays safe from a future quantum computer.

Nojam's own JWT signing key is sealed at rest. Where Vault Transit is approved, the key can live in Vault instead, and every signature then shows in Vault's audit log.

## Approval that proves a person

A production approval needs a signed-in session, MFA and a recent step-up. API tokens and GitHub comments can't approve production, and the author of a change can't approve it.

At the moment of apply, Nojam checks again that the approver still exists, still has access to the repository and is still in the approver group. The approval is bound to a keyed digest of the values in the plan, so a merge only inherits it if it makes exactly the same changes.

## A pull request can't bring its own provider

Providers and modules run with the job's credentials, which makes them the easiest way to steal them. Nojam installs providers only from a mirror you control and an allow-list on the server, requires each stack's committed lock file, and shows any new provider or version on the pull request and to your policy.

The Vault provider's address, namespace and login come from the target, never from the repository. A pull request can't define a production target.

## Secrets in plans and logs

Sensitive values are masked in stored plans and in logs, on the runner and again on the server. Plan files and logs are encrypted at rest, and live logs are encrypted in the database.

The support bundle you send when something is stuck holds the version, the settings and the request IDs. Secrets, plans and logs are never in it.

## A production profile that won't start insecurely

With `NOJAM_PROFILE=production`, the server and every runner refuse to start until the protections are on, naming each one that is missing and how to fix it.

- Runners use mTLS and the sealed hand-over.
- The Vault and provider allow-lists are set.
- Approval requires MFA, and Nojam is served over HTTPS.
- The protection baseline is on, and no development token is set.
- Runners use Podman with the provider mirror, verified images and a restricted network.

## Testing on every change

Every pull request to Nojam runs these checks, and a gate fails the build if any test is skipped without a reason on file.

| Tool | Looks for |
| --- | --- |
| govulncheck | Known vulnerabilities the code can actually reach |
| staticcheck, gosec | Bugs and insecure Go patterns, including path, redirect and SSRF taint |
| Semgrep | Insecure patterns and hard-coded secrets |
| gitleaks | Secrets anywhere in the commit history |
| Trivy | Vulnerable dependencies, and the runner image's packages |
| Go fuzzing | Crashes in every parser of outside input |
| OWASP ZAP, testssl.sh | A running server, scanned from outside |

On top of that, manual testing with Burp Suite Professional runs in-house against a dedicated target. Every finding is recorded with its verdict, and a test is added so it can't come back.

## Fixing vulnerable dependencies

When a vulnerability is found in released code, these are the time frames to ship a fixed release, counted from the day it is known.

| Severity (CVSS) | Fixed within |
| --- | --- |
| Critical, 9.0 and above, or known to be exploited | 3 days |
| High, 7.0 to 8.9 | 14 days |
| Medium, 4.0 to 6.9 | 45 days |
| Low | The next planned release |

## Releases you can verify

Every release binary carries a cosign keyless signature, an SBOM and SLSA level 3 provenance. The runner image is signed too. The release guide shows how to check all three before you install.

The threat model, the cryptographic inventory and our OWASP ASVS 5.0 level 2 self-assessment go to every pilot customer, and are published with the first public release.

## Reporting a vulnerability

Email [hello@nojam.io](mailto:hello@nojam.io) with what you found and how to reproduce it. Please give us time to ship a fix before you publish.

The same contact is in our [security.txt](https://nojam.io/.well-known/security.txt).

## Put it in front of your security team

A pilot comes with the threat model and the self-assessment, so your reviewers can check our claims before anything runs.

[Request a pilot](https://nojam.io/pilot.md) [Join early access](https://nojam.io/early-access.md)

---

Canonical page: https://nojam.io/security/  
Site map for AI agents: https://nojam.io/llms.txt
