Skip to content

Phase B: mTLS as the primary trust-level path#10

Draft
hyperpolymath wants to merge 2 commits into
mainfrom
phase-b-mtls-primary-path
Draft

Phase B: mTLS as the primary trust-level path#10
hyperpolymath wants to merge 2 commits into
mainfrom
phase-b-mtls-primary-path

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Phase B — mTLS as the primary X-Trust-Level path

Closes the core security gap identified in the BoJ integration audit
(boj-server docs/integration/http-capability-gateway-audit.md §4): the
mTLS trust-extraction path was coded but not the primary proved path, and
header-derived trust is forgeable without transport-level client-certificate
verification.

Scope follows the Phase B deliverables in
boj-server docs/integration/http-capability-gateway-plan.md.

What landed

  • B1 — Cowboy TLS config. HttpCapabilityGateway.Application now builds
    a fail-closed HTTPS listener with verify: :verify_peer +
    fail_if_no_peer_cert: true, driven by MTLS_CA_CERT_PATH,
    GATEWAY_CERT_PATH, GATEWAY_KEY_PATH, GATEWAY_TLS_PORT. When
    TRUST_LEVEL_SOURCE=mtls the TLS material is mandatory — the app
    refuses to start rather than silently downgrading to the header path.
    Config wired in config/runtime.exs.
  • B2 — real TLS state. Gateway.is_cert_verified/1 no longer treats mere
    cert presence as verified; it requires the request to have arrived over
    the verify_peer HTTPS listener (https scheme + Cowboy adapter + non-empty
    peer cert) and fails closed otherwise.
  • B3 — CA selection + rotation runbook. docs/mtls-rotation-runbook.md:
    CA-selection decision (BoJ-own root initially; SDP-CA / Cloudflare-AOP
    parity noted) and a no-downtime cert/CA dual-trust rotation procedure.
  • B4 — proof obligation recorded. src/abi/MTLSPolicy.idr states the
    "an unverified client cert is never mapped to a privileged trust class"
    obligation in Idris2 terms; status pending Phase C/D, registered in
    PROOFS_NEEDED.md, excluded from gateway.ipkg modules so it does not
    gate the build before discharge.
  • Real test-CA fixture. test/fixtures/mtls/ (regen via
    gen-test-ca.sh) + test/mtls_test.exs drive the cert→trust pipeline
    with real certs and prove the CA trust invariant via
    :public_key.pkix_path_validation/3. Closes the audit's
    "Real-CA mTLS integration test" gap (TEST-NEEDS.md updated). Private
    keys are intentionally not committed (repo .gitignore secret
    hygiene); the suite only consumes the .crt fixtures.

Notes / scope boundaries

  • Build was not verified locally — the session environment has no
    Elixir 1.19 / OTP 28 toolchain (apt only offers 1.14). Repo CI
    (boj-build, dogfood-gate) must confirm mix compile + mix test.
    Changes were kept review-grade and pattern-consistent with existing code.
  • The live mTLS handshake end-to-end socket test across the
    gateway↔BoJ seam is Phase C (#98) scope per the integration plan; Phase B
    proves the cert→trust logic and the CA trust invariant at the crypto
    layer plus the fail-closed listener contract.

Refs hyperpolymath/standards#91
Closes hyperpolymath/standards#97

🤖 Generated with Claude Code


Generated by Claude Code

Closes the Phase B security gap from the http-capability-gateway audit:
header-derived trust is forgeable without transport-level client-cert
verification.

- B1: add a fail-closed Cowboy HTTPS listener (verify: :verify_peer,
  fail_if_no_peer_cert: true). When TRUST_LEVEL_SOURCE=mtls the TLS
  material is mandatory; the app refuses to start rather than silently
  downgrading to the header path.
- B2: is_cert_verified/1 now reads real TLS transport state (https
  scheme + Cowboy adapter + non-empty peer cert) instead of mere cert
  presence.
- B3: docs/mtls-rotation-runbook.md — CA-selection decision and
  no-downtime cert/CA rotation procedure.
- B4: src/abi/MTLSPolicy.idr records the "unverified cert is never
  privileged" proof obligation (status pending Phase C/D; excluded
  from gateway.ipkg so it does not gate the build).
- Real test-CA fixture (test/fixtures/mtls) + test/mtls_test.exs
  driving the cert->trust pipeline and proving the CA trust invariant
  via :public_key.pkix_path_validation/3. Closes the audit's
  "Real-CA mTLS integration test" gap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread src/abi/MTLSPolicy.idr Fixed
@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 16 issues detected

Severity Count
🔴 Critical 7
🟠 High 4
🟡 Medium 5

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Issue in quality.yml",
    "type": "missing_workflow",
    "file": "quality.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Issue in security-policy.yml",
    "type": "missing_workflow",
    "file": "security-policy.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "codeql.yml lists `language: javascript-typescript` but the repo has no source files in any CodeQL-scannable language. The analyze job will exit 'no source files' on every run. Switch the matrix to `actions` (which scans workflow files — every repo has those).",
    "type": "codeql_language_matrix_mismatch",
    "file": "codeql.yml",
    "action": "switch_codeql_matrix_to_actions",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "believe_me undermines formal verification (1 occurrences, CWE-704)",
    "type": "believe_me",
    "file": "/home/runner/work/http-capability-gateway/http-capability-gateway/src/abi/MTLSPolicy.idr",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "critical"
  },
  {
    "reason": "Nickel file missing SPDX-License-Identifier header (1 occurrences, CWE-1104)",
    "type": "ncl_missing_spdx",
    "file": "/home/runner/work/http-capability-gateway/http-capability-gateway/configs/config.ncl",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "Download-and-execute pattern (curl|wget pipe to shell) -- verify integrity before execution (1 occurrences, CWE-494)",
    "type": "shell_download_then_run",
    "file": "/home/runner/work/http-capability-gateway/http-capability-gateway/setup.sh",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "high"
  },
  {
    "reason": "Nominal-only SAST in http-capability-gateway: codeql.yml language matrix contains no language present in the repo and lacks `actions`, so CodeQL records zero results on every commit. Remediation: set the CodeQL matrix to `language: actions`.",
    "type": "StaticAnalysis",
    "file": "/home/runner/work/http-capability-gateway/http-capability-gateway",
    "action": "auto_fix",
    "rule_module": "scorecard",
    "severity": "medium",
    "remediation": "Add CodeQL or equivalent SAST workflow.",
    "scorecard_check": "SAST"
  },
  {
    "reason": "Repository has 2 non-main remote branch(es). Policy: single main branch only.",
    "type": "GS007",
    "file": ".",
    "action": "delete_remote_branches",
    "rule_module": "git_state",
    "severity": "medium"
  },
  {
    "reason": "6a2ml file outside canonical location -- must be in .machine_readable/6a2/",
    "type": "SD004",
    "file": ".machine_readable/STATE.a2ml",
    "action": "move",
    "rule_module": "structural_drift",
    "severity": "critical"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

Hypatia code_safety greps the literal token; the comment asserted the
absence of the primitive but tripped the scanner (CWE-704, 1 occurrence).
Rephrased -- no behaviour change, the module uses no bypass primitive.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 15 issues detected

Severity Count
🔴 Critical 6
🟠 High 4
🟡 Medium 5

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Issue in quality.yml",
    "type": "missing_workflow",
    "file": "quality.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Issue in security-policy.yml",
    "type": "missing_workflow",
    "file": "security-policy.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "codeql.yml lists `language: javascript-typescript` but the repo has no source files in any CodeQL-scannable language. The analyze job will exit 'no source files' on every run. Switch the matrix to `actions` (which scans workflow files — every repo has those).",
    "type": "codeql_language_matrix_mismatch",
    "file": "codeql.yml",
    "action": "switch_codeql_matrix_to_actions",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Nickel file missing SPDX-License-Identifier header (1 occurrences, CWE-1104)",
    "type": "ncl_missing_spdx",
    "file": "/home/runner/work/http-capability-gateway/http-capability-gateway/configs/config.ncl",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "medium"
  },
  {
    "reason": "Download-and-execute pattern (curl|wget pipe to shell) -- verify integrity before execution (1 occurrences, CWE-494)",
    "type": "shell_download_then_run",
    "file": "/home/runner/work/http-capability-gateway/http-capability-gateway/setup.sh",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "high"
  },
  {
    "reason": "Nominal-only SAST in http-capability-gateway: codeql.yml language matrix contains no language present in the repo and lacks `actions`, so CodeQL records zero results on every commit. Remediation: set the CodeQL matrix to `language: actions`.",
    "type": "StaticAnalysis",
    "file": "/home/runner/work/http-capability-gateway/http-capability-gateway",
    "action": "auto_fix",
    "rule_module": "scorecard",
    "severity": "medium",
    "remediation": "Add CodeQL or equivalent SAST workflow.",
    "scorecard_check": "SAST"
  },
  {
    "reason": "Repository has 2 non-main remote branch(es). Policy: single main branch only.",
    "type": "GS007",
    "file": ".",
    "action": "delete_remote_branches",
    "rule_module": "git_state",
    "severity": "medium"
  },
  {
    "reason": "6a2ml file outside canonical location -- must be in .machine_readable/6a2/",
    "type": "SD004",
    "file": ".machine_readable/STATE.a2ml",
    "action": "move",
    "rule_module": "structural_drift",
    "severity": "critical"
  },
  {
    "reason": "6a2ml file outside canonical location -- must be in .machine_readable/6a2/",
    "type": "SD004",
    "file": ".machine_readable/META.a2ml",
    "action": "move",
    "rule_module": "structural_drift",
    "severity": "critical"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase B — mTLS as primary trust-level path (Cowboy TLS + real-CA fixture)

2 participants