Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/scripts/asvs-gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# OWASP ASVS V12 re-affirmation gate.
#
# Fails the PR if TLS-relevant code changed without an updated audit-log
# entry in docs/security/owasp-asvs.md.
#
# TLS-relevance predicate (OR):
# - changed file matches src/main/java/com/retailsvc/http/internal/.*(Ssl|Tls|Https).*\.java
# - any changed .java file under src/main/java/ adds or removes a line
# importing javax.net.ssl.* or com.sun.net.httpserver.Https*
#
# Local invocation:
# BASE_SHA=$(git merge-base origin/master HEAD) HEAD_SHA=HEAD .github/scripts/asvs-gate.sh

set -euo pipefail

: "${BASE_SHA:?BASE_SHA env var required}"
: "${HEAD_SHA:?HEAD_SHA env var required}"

CHECKLIST="docs/security/owasp-asvs.md"

changed_files=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")

tls_paths=$(printf '%s\n' "$changed_files" \
| grep -E '^src/main/java/com/retailsvc/http/internal/.*(Ssl|Tls|Https).*\.java$' || true)

import_diff=$(git diff -U0 "$BASE_SHA" "$HEAD_SHA" -- 'src/main/java/**/*.java' \
| grep -E '^[+-]import (javax\.net\.ssl\.|com\.sun\.net\.httpserver\.Https)' || true)

if [ -z "$tls_paths" ] && [ -z "$import_diff" ]; then
echo "ASVS gate: no TLS-relevant changes."
exit 0
fi

triggers=""
if [ -n "$tls_paths" ]; then
triggers+=" (path) $(echo "$tls_paths" | tr '\n' ' ')"$'\n'
fi
if [ -n "$import_diff" ]; then
triggers+=" (import) $(echo "$import_diff" | head -5)"$'\n'
fi

if ! printf '%s\n' "$changed_files" | grep -qx "$CHECKLIST"; then
cat >&2 <<EOF
::error title=ASVS V12 gate::TLS-relevant code changed but $CHECKLIST was not updated.

Triggered by:
$triggers
Required action:
1. Open $CHECKLIST
2. Confirm each ASVS 5.0 L2 control still holds (update Status / Evidence rows if not)
3. Append a dated line to ## Audit log, e.g.:
- **$(date -u +%Y-%m-%d)** — Re-affirmed after change to <file>.java (this PR); all controls hold

This gate exists so TLS changes can't silently drift away from the documented controls.
See $CHECKLIST for the policy.
EOF
exit 1
fi

added_audit_line=$(git diff "$BASE_SHA" "$HEAD_SHA" -- "$CHECKLIST" \
| grep -E '^\+- \*\*[0-9]{4}-[0-9]{2}-[0-9]{2}\*\* — ' || true)

if [ -z "$added_audit_line" ]; then
cat >&2 <<EOF
::error title=ASVS V12 gate::$CHECKLIST was updated but no new audit-log line was added.

Triggered by:
$triggers
The gate requires a new line in the ## Audit log section matching:
- **YYYY-MM-DD** — <free-text re-affirmation, e.g. "Re-affirmed after change to X.java (this PR); all controls hold">

Touch-only changes to the checklist do not satisfy the gate. The dated line must be added.
EOF
exit 1
fi

echo "ASVS gate: TLS-relevant changes re-affirmed in $CHECKLIST."
exit 0
24 changes: 24 additions & 0 deletions .github/workflows/asvs-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: OWASP ASVS gate

on:
pull_request:
branches: [master]
types: [opened, synchronize, reopened]

permissions:
contents: read
pull-requests: read

jobs:
asvs-checklist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Enforce ASVS re-affirmation on TLS code changes
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: .github/scripts/asvs-gate.sh
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=extenda_openapi-httpserver-java&metric=code_smells&token=c87f52089c6158081787f26e272d0a0e412c205b)](https://sonarcloud.io/dashboard?id=extenda_openapi-httpserver-java)
[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=extenda_openapi-httpserver-java&metric=duplicated_lines_density&token=c87f52089c6158081787f26e272d0a0e412c205b)](https://sonarcloud.io/dashboard?id=extenda_openapi-httpserver-java)
[![WorkFlow](https://github.com/extenda/openapi-httpserver-java/actions/workflows/commit.yaml/badge.svg)](https://github.com/extenda/openapi-httpserver-java/actions)
[![OWASP ASVS](https://img.shields.io/badge/OWASP_ASVS_5.0-Level_2_V12-blueviolet)](docs/security/owasp-asvs.md)

A lightweight Java library that wraps the JDK's `com.sun.net.httpserver.HttpServer` and serves
endpoints declared in an OpenAPI 3.1.x specification. Handlers are pure functions registered by
Expand Down
54 changes: 54 additions & 0 deletions docs/security/owasp-asvs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# OWASP ASVS 5.0 Level 2 — self-assessment

**Standard:** OWASP Application Security Verification Standard, version
5.0.0, chapter V12 (Secure Communication).
**Level:** 2 (typical baseline; consumers needing L3 must layer
additional controls).
**Scope:** Server-side TLS termination via `Builder.https(...)`.
Outbound / service-to-service controls (V12.3) are N/A — the library
makes no outbound TLS connections on the consumer's behalf.

**Wording rules.** This document is a self-assessment; it does NOT
claim certification or compliance. The README badge says "ASVS 5.0
Level 2" only — never "Certified", never "Compliant". OWASP does not
issue conformance certifications.

**Re-affirmation rule.** Any change to TLS-related code (see
`.github/scripts/asvs-gate.sh` for the exact predicate) MUST append a
dated entry to the [Audit log](#audit-log) in the same PR. CI
enforces this. The contributor writes the line — automated bumps are
not accepted.

**Status legend.**

- ✅ Implemented — the library satisfies this control end-to-end
- 🤝 Delegated — consumer must satisfy; see Evidence for guidance
- ⛔ N/A — out of scope for a server-side TLS termination library
- 📋 Future — accepted as a gap; tracked for a follow-up release

## Controls

| ID | Control (verbatim, ASVS 5.0) | Level | Status | Evidence |
|---|---|---|---|---|
| 12.1.1 | "Verify that only the latest recommended versions of the TLS protocol are enabled, such as TLS 1.2 and TLS 1.3." | L1 | ✅ Implemented | `TlsHttpsConfigurator` pins `setProtocols({"TLSv1.3","TLSv1.2"})`; verified end-to-end by `OpenApiServerHttpsIT#negotiatesTls13`. |
| 12.1.2 | "Verify that only recommended cipher suites are enabled, with the strongest cipher suites set as preferred." | L2 | 📋 Future | We rely on JDK 25 defaults (which exclude RC4, 3DES, EXPORT, NULL, anonymous suites by default). A curated allowlist with explicit preference order is tracked as a follow-up. |
| 12.1.3 | "Verify that the application validates that mTLS client certificates are trusted before using the certificate identity." | L2 | ⛔ N/A | mTLS is not supported in v1. `TlsHttpsConfigurator` explicitly sets `setNeedClientAuth(false)` and `setWantClientAuth(false)`. If mTLS lands later, this row flips to Implemented + new evidence. |
| 12.1.4 | "Verify that proper certification revocation, such as Online Certificate Status Protocol (OCSP) Stapling, is enabled." | L3 | 📋 Future | Out of scope for L2 baseline. Documented so L3-targeting consumers know the gap. |
| 12.1.5 | "Verify that Encrypted Client Hello (ECH) is enabled in the application's TLS settings to prevent exposure of sensitive metadata." | L3 | 📋 Future | Out of scope for L2 baseline. JDK 25 has no stable ECH API. |
| 12.2.1 | "Verify that TLS is used for all connectivity between a client and external facing, HTTP-based services, and does not fall back to insecure communications." | L1 | ✅ Implemented | When `.https(...)` is configured, the server binds `HttpsServer` only; no plaintext fallback listener is created. Mixed-mode (HTTP + HTTPS) is a documented non-goal — operators run two `OpenApiServer` instances if they need both. |
| 12.2.2 | "Verify that external facing services use publicly trusted TLS certificates." | L1 | 🤝 Delegated | The library accepts whatever cert chain the consumer supplies. For production deployments, consumers MUST point `.https(certChain, privateKey)` at a chain signed by a publicly-trusted CA. See satisfaction guidance below. |
| 12.3.1 – 12.3.5 | (Service-to-service / outbound) | L2 | ⛔ N/A | The library does not initiate outbound TLS on the consumer's behalf. Consumers' outbound HTTP clients are their own responsibility. |

### Delegated control: satisfaction guidance

**12.2.2.** Operators should point `.https(certChain, privateKey)` at
a chain issued by a publicly trusted CA. The recommended workflow is
certbot / Let's Encrypt with the PEM files mounted from a secret
manager (see README §HTTPS for the full deployment pattern). The
library performs no chain validation on its own certificate — the
server merely presents the chain — so it is on the operator to ensure
publicly-trusted issuance.

## Audit log

- **2026-05-21** — Initial ASVS 5.0 Level 2 mapping for V12 controls. All listed controls accepted as Implemented / Delegated / N/A / Future as tabulated above.
Loading
Loading