up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,563 @@
|
||||
Here’s a crisp, ready‑to‑use rule for VEX hygiene that will save you pain in audits and customer reviews—and make Stella Ops look rock‑solid.
|
||||
|
||||
# Adopt a strict “`not_affected` only with proof” policy
|
||||
|
||||
**What it means (plain English):**
|
||||
Only mark a vulnerability as `not_affected` if you can *prove* the vulnerable code can’t run in your product under defined conditions—then record that proof (scope, entry points, limits) inside a VEX bundle.
|
||||
|
||||
## The non‑negotiables
|
||||
|
||||
* **Audit coverage:**
|
||||
You must enumerate the reachable entry points you audited (e.g., exported handlers, CLI verbs, HTTP routes, scheduled jobs, init hooks). State their *limits* (versions, build flags, feature toggles, container args, config profiles).
|
||||
* **VEX justification required:**
|
||||
Use a concrete justification (OpenVEX/CISA style), e.g.:
|
||||
|
||||
* `vulnerable_code_not_in_execute_path`
|
||||
* `component_not_present`
|
||||
* `vulnerable_code_cannot_be_controlled_by_adversary`
|
||||
* `inline_mitigation_already_in_place`
|
||||
* **Impact or constraint statement:**
|
||||
Explain *why* it’s safe given your product’s execution model: sandboxing, dead code elimination, policy blocks, feature gates, OS hardening, container seccomp/AppArmor, etc.
|
||||
* **VEX proof bundle:**
|
||||
Store the evidence alongside the VEX: call‑graph slices, reachability reports, config snapshots, build args, lattice/policy decisions, test traces, and hashes of the exact artifacts (SBOM + attestation refs). This is what makes the claim stand up in an audit six months later.
|
||||
|
||||
## Minimal OpenVEX example (drop‑in)
|
||||
|
||||
```json
|
||||
{
|
||||
"document": {
|
||||
"id": "urn:stellaops:vex:2025-11-25:svc-api:log4j:2.14.1",
|
||||
"author": "Stella Ops Authority",
|
||||
"role": "vex"
|
||||
},
|
||||
"statements": [
|
||||
{
|
||||
"vulnerability": "CVE-2021-44228",
|
||||
"products": ["pkg:maven/com.acme/svc-api@1.7.3?type=jar"],
|
||||
"status": "not_affected",
|
||||
"justification": "vulnerable_code_not_in_execute_path",
|
||||
"impact_statement": "Log4j JNDI classes excluded at build; no logger bridge; JVM flags `-Dlog4j2.formatMsgNoLookups=true` enforced by container entrypoint.",
|
||||
"analysis": {
|
||||
"entry_points_audited": [
|
||||
"com.acme.api.HttpServer#routes",
|
||||
"com.acme.jobs.Cron#run",
|
||||
"Main#init"
|
||||
],
|
||||
"limits": {
|
||||
"image_digest": "sha256:…",
|
||||
"config_profile": "prod",
|
||||
"args": ["--no-dynamic-plugins"],
|
||||
"seccomp": "stellaops-baseline-v3"
|
||||
},
|
||||
"evidence_refs": [
|
||||
"dsse:sha256:…/reachability.json",
|
||||
"dsse:sha256:…/build-args.att",
|
||||
"dsse:sha256:…/policy-lattice.proof"
|
||||
]
|
||||
},
|
||||
"timestamp": "2025-11-25T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Fast checklist (use this on every `not_affected`)
|
||||
|
||||
* [ ] Define product + artifact by immutable IDs (PURL + digest).
|
||||
* [ ] List **audited entry points** and **execution limits**.
|
||||
* [ ] Declare **status** = `not_affected` with a **justification** from the allowed set.
|
||||
* [ ] Add a short **impact/why‑safe** sentence.
|
||||
* [ ] Attach **evidence**: call graph, configs, policies, build args, test traces.
|
||||
* [ ] Sign the VEX (DSSE/In‑Toto), link it to the SBOM attestation.
|
||||
* [ ] Version and keep the proof bundle with your release.
|
||||
|
||||
## When to use an exception (temporary VEX)
|
||||
|
||||
If you can prove non‑reachability **only under a temporary constraint** (e.g., feature flag off while a permanent fix lands), emit a **time‑boxed exception** VEX:
|
||||
|
||||
* Add `constraints.expires` and the required control (e.g., `feature_flag=Off`, `policy=BlockJNDI`).
|
||||
* Schedule an auto‑recheck on expiry; flip to `affected` if the constraint lapses.
|
||||
|
||||
---
|
||||
|
||||
If you want, I can generate a Stella Ops‑flavored VEX template and a tiny “proof bundle” schema (JSON) so your devs can drop it into the pipeline and your documentators can copy‑paste the rationale blocks.
|
||||
Cool, let’s turn that policy into something your devs can actually follow day‑to‑day.
|
||||
|
||||
Below is a concrete implementation plan you can drop into an internal RFC / Notion page and wire into your pipelines.
|
||||
|
||||
---
|
||||
|
||||
## 0. What we’re implementing (for context)
|
||||
|
||||
**Goal:** At Stella Ops, you can only mark a vulnerability as `not_affected` if:
|
||||
|
||||
1. You’ve **audited specific entry points** under clearly documented limits (version, build flags, config, container image).
|
||||
2. You’ve captured **evidence** and **rationale** in a VEX statement + proof bundle.
|
||||
3. The VEX is **validated, signed, and shipped** with the artifact.
|
||||
|
||||
We’ll standardize on **OpenVEX** with a small extension (`analysis` section) for developer‑friendly evidence.
|
||||
|
||||
---
|
||||
|
||||
## 1. Repo & artifact layout (week 1)
|
||||
|
||||
### 1.1. Create a standard security layout
|
||||
|
||||
In each service repo:
|
||||
|
||||
```text
|
||||
/security/
|
||||
vex/
|
||||
openvex.json # aggregate VEX doc (generated/curated)
|
||||
statements/ # one file per CVE (optional, if you like)
|
||||
proofs/
|
||||
CVE-YYYY-NNNN/
|
||||
reachability.json
|
||||
configs/
|
||||
tests/
|
||||
notes.md
|
||||
schemas/
|
||||
openvex.schema.json # JSON schema with Stella extensions
|
||||
```
|
||||
|
||||
**Developer guidance:**
|
||||
|
||||
* If you touch anything related to a vulnerability decision, you **edit `security/vex/` and `security/proofs/` in the same PR**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Define the VEX schema & allowed justifications (week 1)
|
||||
|
||||
### 2.1. Fix the format & fields
|
||||
|
||||
You’ve already chosen OpenVEX, so formalize the required extras:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"vulnerability": "CVE-2021-44228",
|
||||
"products": ["pkg:maven/com.acme/svc-api@1.7.3?type=jar"],
|
||||
"status": "not_affected",
|
||||
"justification": "vulnerable_code_not_in_execute_path",
|
||||
"impact_statement": "…",
|
||||
"analysis": {
|
||||
"entry_points_audited": [
|
||||
"com.acme.api.HttpServer#routes",
|
||||
"com.acme.jobs.Cron#run",
|
||||
"Main#init"
|
||||
],
|
||||
"limits": {
|
||||
"image_digest": "sha256:…",
|
||||
"config_profile": "prod",
|
||||
"args": ["--no-dynamic-plugins"],
|
||||
"seccomp": "stellaops-baseline-v3"
|
||||
},
|
||||
"evidence_refs": [
|
||||
"dsse:sha256:…/reachability.json",
|
||||
"dsse:sha256:…/build-args.att",
|
||||
"dsse:sha256:…/policy-lattice.proof"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Action items:**
|
||||
|
||||
* Write a **JSON schema** for the `analysis` block (required for `not_affected`):
|
||||
|
||||
* `entry_points_audited`: non‑empty array of strings.
|
||||
* `limits`: object with at least one of `image_digest`, `config_profile`, `args`, `seccomp`, `feature_flags`.
|
||||
* `evidence_refs`: non‑empty array of strings.
|
||||
* Commit this as `security/schemas/openvex.schema.json`.
|
||||
|
||||
### 2.2. Fix the allowed `justification` values
|
||||
|
||||
Publish an internal list, e.g.:
|
||||
|
||||
* `vulnerable_code_not_in_execute_path`
|
||||
* `component_not_present`
|
||||
* `vulnerable_code_cannot_be_controlled_by_adversary`
|
||||
* `inline_mitigation_already_in_place`
|
||||
* `protected_by_environment` (e.g., mandatory sandbox, read‑only FS)
|
||||
|
||||
**Rule:** any `not_affected` must pick one of these. Any new justification needs security team approval.
|
||||
|
||||
---
|
||||
|
||||
## 3. Developer process for handling a new vuln (week 2)
|
||||
|
||||
This is the **“how to act”** guide devs follow when a CVE pops up in scanners or customer reports.
|
||||
|
||||
### 3.1. Decision flow
|
||||
|
||||
1. **Is the vulnerable component actually present?**
|
||||
|
||||
* If no → `status: not_affected`, `justification: component_not_present`.
|
||||
Still fill out `products`, `impact_statement` (explain why it’s not present: different version, module excluded, etc.).
|
||||
2. **If present: analyze reachability.**
|
||||
|
||||
* Identify **entry points** of the service:
|
||||
|
||||
* HTTP routes, gRPC methods, message consumers, CLI commands, cron jobs, startup hooks.
|
||||
* Check:
|
||||
|
||||
* Is the vulnerable path reachable from any of these?
|
||||
* Is it blocked by configuration / feature flags / sandboxing?
|
||||
3. **If reachable or unclear → treat as `affected`.**
|
||||
|
||||
* Plan a patch, workaround, or runtime mitigation.
|
||||
4. **If not reachable & you can argue that clearly → `not_affected` with proof.**
|
||||
|
||||
* Fill in:
|
||||
|
||||
* `entry_points_audited`
|
||||
* `limits`
|
||||
* `evidence_refs`
|
||||
* `impact_statement` (“why safe”)
|
||||
|
||||
### 3.2. Developer checklist (drop this into your docs)
|
||||
|
||||
> **Stella Ops `not_affected` checklist**
|
||||
>
|
||||
> For any CVE you mark as `not_affected`:
|
||||
>
|
||||
> 1. **Identify product + artifact**
|
||||
>
|
||||
> * [ ] PURL (package URL)
|
||||
> * [ ] Image digest / binary hash
|
||||
> 2. **Audit execution**
|
||||
>
|
||||
> * [ ] List entry points you reviewed
|
||||
> * [ ] Note the limits (config profile, feature flags, container args, sandbox)
|
||||
> 3. **Collect evidence**
|
||||
>
|
||||
> * [ ] Reachability analysis (manual or tool report)
|
||||
> * [ ] Config snapshot (YAML, env vars, Helm values)
|
||||
> * [ ] Tests or traces (if applicable)
|
||||
> 4. **Write VEX statement**
|
||||
>
|
||||
> * [ ] `status = not_affected`
|
||||
> * [ ] `justification` from allowed list
|
||||
> * [ ] `impact_statement` explains “why safe”
|
||||
> * [ ] `analysis.entry_points_audited`, `analysis.limits`, `analysis.evidence_refs`
|
||||
> 5. **Wire into repo**
|
||||
>
|
||||
> * [ ] Proofs stored under `security/proofs/CVE-…/`
|
||||
> * [ ] VEX updated under `security/vex/`
|
||||
> 6. **Request review**
|
||||
>
|
||||
> * [ ] Security reviewer approved in PR
|
||||
|
||||
---
|
||||
|
||||
## 4. Automation & tooling for devs (week 2–3)
|
||||
|
||||
Make it easy to “do the right thing” with a small CLI and CI jobs.
|
||||
|
||||
### 4.1. Add a small `vexctl` helper
|
||||
|
||||
Language doesn’t matter—Python is fine. Rough sketch:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
VEX_PATH = Path("security/vex/openvex.json")
|
||||
|
||||
def load_vex():
|
||||
if VEX_PATH.exists():
|
||||
return json.loads(VEX_PATH.read_text())
|
||||
return {"document": {}, "statements": []}
|
||||
|
||||
def save_vex(data):
|
||||
VEX_PATH.write_text(json.dumps(data, indent=2, sort_keys=True))
|
||||
|
||||
def add_statement():
|
||||
cve = input("CVE ID (e.g. CVE-2025-1234): ").strip()
|
||||
product = input("Product PURL: ").strip()
|
||||
status = input("Status [affected/not_affected/fixed]: ").strip()
|
||||
justification = None
|
||||
analysis = None
|
||||
|
||||
if status == "not_affected":
|
||||
justification = input("Justification (from allowed list): ").strip()
|
||||
entry_points = input("Entry points (comma-separated): ").split(",")
|
||||
limits_profile = input("Config profile (e.g. prod/stage): ").strip()
|
||||
image_digest = input("Image digest (optional): ").strip()
|
||||
evidence = input("Evidence refs (comma-separated): ").split(",")
|
||||
|
||||
analysis = {
|
||||
"entry_points_audited": [e.strip() for e in entry_points if e.strip()],
|
||||
"limits": {
|
||||
"config_profile": limits_profile or None,
|
||||
"image_digest": image_digest or None
|
||||
},
|
||||
"evidence_refs": [e.strip() for e in evidence if e.strip()]
|
||||
}
|
||||
|
||||
impact = input("Impact / why safe (short text): ").strip()
|
||||
|
||||
vex = load_vex()
|
||||
vex.setdefault("document", {})
|
||||
vex.setdefault("statements", [])
|
||||
stmt = {
|
||||
"vulnerability": cve,
|
||||
"products": [product],
|
||||
"status": status,
|
||||
"impact_statement": impact,
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z"
|
||||
}
|
||||
if justification:
|
||||
stmt["justification"] = justification
|
||||
if analysis:
|
||||
stmt["analysis"] = analysis
|
||||
|
||||
vex["statements"].append(stmt)
|
||||
save_vex(vex)
|
||||
print(f"Added VEX statement for {cve}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
add_statement()
|
||||
```
|
||||
|
||||
**Dev UX:** run:
|
||||
|
||||
```bash
|
||||
./tools/vexctl add
|
||||
```
|
||||
|
||||
and follow prompts instead of hand‑editing JSON.
|
||||
|
||||
### 4.2. Schema validation in CI
|
||||
|
||||
Add a CI job (GitHub Actions example) that:
|
||||
|
||||
1. Installs `jsonschema`.
|
||||
2. Validates `security/vex/openvex.json` against `security/schemas/openvex.schema.json`.
|
||||
3. Fails if:
|
||||
|
||||
* any `not_affected` statement lacks `analysis.*` fields, or
|
||||
* `justification` is not in the allowed list.
|
||||
|
||||
```yaml
|
||||
name: VEX validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "security/vex/**"
|
||||
- "security/schemas/**"
|
||||
|
||||
jobs:
|
||||
validate-vex:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install deps
|
||||
run: pip install jsonschema
|
||||
|
||||
- name: Validate OpenVEX
|
||||
run: |
|
||||
python tools/validate_vex.py
|
||||
```
|
||||
|
||||
Example `validate_vex.py` core logic:
|
||||
|
||||
```python
|
||||
import json
|
||||
from jsonschema import validate, ValidationError
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
schema = json.loads(Path("security/schemas/openvex.schema.json").read_text())
|
||||
vex = json.loads(Path("security/vex/openvex.json").read_text())
|
||||
|
||||
try:
|
||||
validate(instance=vex, schema=schema)
|
||||
except ValidationError as e:
|
||||
print("VEX schema validation failed:", e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
ALLOWED_JUSTIFICATIONS = {
|
||||
"vulnerable_code_not_in_execute_path",
|
||||
"component_not_present",
|
||||
"vulnerable_code_cannot_be_controlled_by_adversary",
|
||||
"inline_mitigation_already_in_place",
|
||||
"protected_by_environment",
|
||||
}
|
||||
|
||||
for stmt in vex.get("statements", []):
|
||||
if stmt.get("status") == "not_affected":
|
||||
just = stmt.get("justification")
|
||||
if just not in ALLOWED_JUSTIFICATIONS:
|
||||
print(f"Invalid justification '{just}' in statement {stmt.get('vulnerability')}")
|
||||
sys.exit(1)
|
||||
|
||||
analysis = stmt.get("analysis") or {}
|
||||
missing = []
|
||||
if not analysis.get("entry_points_audited"):
|
||||
missing.append("analysis.entry_points_audited")
|
||||
if not analysis.get("limits"):
|
||||
missing.append("analysis.limits")
|
||||
if not analysis.get("evidence_refs"):
|
||||
missing.append("analysis.evidence_refs")
|
||||
|
||||
if missing:
|
||||
print(
|
||||
f"'not_affected' for {stmt.get('vulnerability')} missing fields: {', '.join(missing)}"
|
||||
)
|
||||
sys.exit(1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Signing & publishing VEX + proof bundles (week 3)
|
||||
|
||||
### 5.1. Signing
|
||||
|
||||
Pick a signing mechanism (e.g., DSSE + cosign/in‑toto), but keep the dev‑visible rules simple:
|
||||
|
||||
* CI step:
|
||||
|
||||
1. Build artifact (image/binary).
|
||||
2. Generate/update SBOM.
|
||||
3. Validate VEX.
|
||||
4. **Sign**:
|
||||
|
||||
* The artifact.
|
||||
* The SBOM.
|
||||
* The VEX document.
|
||||
|
||||
Enforce **KMS‑backed keys** controlled by the security team.
|
||||
|
||||
### 5.2. Publishing layout
|
||||
|
||||
Decide a canonical layout in your artifact registry / S3:
|
||||
|
||||
```text
|
||||
artifacts/
|
||||
svc-api/
|
||||
1.7.3/
|
||||
image.tar
|
||||
sbom.spdx.json
|
||||
vex.openvex.json
|
||||
proofs/
|
||||
CVE-2025-1234/
|
||||
reachability.json
|
||||
configs/
|
||||
tests/
|
||||
```
|
||||
|
||||
Link evidence by digest (`evidence_refs`) so you can prove exactly what you audited.
|
||||
|
||||
---
|
||||
|
||||
## 6. PR / review policy (week 3–4)
|
||||
|
||||
### 6.1. Add a PR checklist item
|
||||
|
||||
In your PR template:
|
||||
|
||||
```md
|
||||
### Security / VEX
|
||||
|
||||
- [ ] If this PR **changes how we handle a known CVE** or marks one as `not_affected`, I have:
|
||||
- [ ] Updated `security/vex/openvex.json`
|
||||
- [ ] Added/updated proof bundle under `security/proofs/`
|
||||
- [ ] Ran `./tools/vexctl` and CI VEX validation locally
|
||||
```
|
||||
|
||||
### 6.2. Require security reviewer for `not_affected` changes
|
||||
|
||||
Add a CODEOWNERS entry:
|
||||
|
||||
```text
|
||||
/security/vex/* @stellaops-security-team
|
||||
/security/proofs/* @stellaops-security-team
|
||||
```
|
||||
|
||||
* Any PR touching these paths must be approved by security.
|
||||
|
||||
---
|
||||
|
||||
## 7. Handling temporary exceptions (time‑boxed VEX)
|
||||
|
||||
Sometimes you’re only safe because of a **temporary constraint** (e.g., feature flag off until patch). For those:
|
||||
|
||||
1. Add a `constraints` block:
|
||||
|
||||
```json
|
||||
"constraints": {
|
||||
"control": "feature_flag",
|
||||
"name": "ENABLE_UNSAFE_PLUGIN_API",
|
||||
"required_value": "false",
|
||||
"expires": "2025-12-31T23:59:59Z"
|
||||
}
|
||||
```
|
||||
|
||||
2. Add a scheduled job (e.g., weekly) that:
|
||||
|
||||
* Parses VEX.
|
||||
* Finds any `constraints.expires < now()`.
|
||||
* Opens an issue or fails a synthetic CI job: “Constraint expired: reevaluate CVE‑2025‑1234”.
|
||||
|
||||
Dev guidance: **do not** treat time‑boxed exceptions as permanent; they must be re‑reviewed or turned into `affected` + mitigation.
|
||||
|
||||
---
|
||||
|
||||
## 8. Rollout plan by week
|
||||
|
||||
You can present this timeline internally:
|
||||
|
||||
* **Week 1**
|
||||
|
||||
* Finalize OpenVEX + `analysis` schema.
|
||||
* Create `security/` layout in 1–2 key services.
|
||||
* Publish allowed `justification` list + written policy.
|
||||
|
||||
* **Week 2**
|
||||
|
||||
* Implement `vexctl` helper.
|
||||
* Add CI validation job.
|
||||
* Pilot with one real CVE decision; walk through full proof bundle creation.
|
||||
|
||||
* **Week 3**
|
||||
|
||||
* Add signing + publishing steps for SBOM and VEX.
|
||||
* Wire artifact registry layout, link VEX + proofs per release.
|
||||
|
||||
* **Week 4**
|
||||
|
||||
* Enforce CODEOWNERS + PR checklist across all services.
|
||||
* Enable scheduled checks for expiring constraints.
|
||||
* Run internal training (30–45 min) walking through:
|
||||
|
||||
* “Bad VEX” (hand‑wavy, no entry points) vs
|
||||
* “Good VEX” (clear scope, evidence, limits).
|
||||
|
||||
---
|
||||
|
||||
## 9. What you can hand to devs right now
|
||||
|
||||
If you want, you can literally paste these as separate internal docs:
|
||||
|
||||
* **“How to mark a CVE as not_affected at Stella Ops”**
|
||||
|
||||
* Copy section 3 (decision flow + checklist) and the VEX snippet.
|
||||
* **“VEX technical reference for developers”**
|
||||
|
||||
* Copy sections 1–2–4 (structure, schema, CLI, CI validation).
|
||||
* **“VEX operations runbook”**
|
||||
|
||||
* Copy sections 5–7 (signing, publishing, exceptions).
|
||||
|
||||
---
|
||||
|
||||
If you tell me which CI system you use (GitHub Actions, GitLab CI, Circle, etc.) and your primary stack (Java, Go, Node, etc.), I can turn this into exact job configs and maybe a more tailored `vexctl` CLI for your environment.
|
||||
Reference in New Issue
Block a user