Skip to content

Self-audit: prioritised weakness list (2026-06-02) #31

@hyperpolymath

Description

@hyperpolymath

Self-audit (2026-06-02)

A read-through of README.adoc, lib/, examples/, and test/ against the
five concerns supplied by the estate-level auditor. Findings are ordered by
priority (worst gap first); each item has an associated audit branch
(audit/<slug>) carrying a small DRAFT PR.


Priority 1 — OUTBOUND (egress) mode is entirely absent

The repository is exclusively an inbound governance layer. Every call path
in lib/http_capability_gateway/{gateway,proxy}.ex assumes the gateway is a
reverse proxy in front of a single backend. There is no mode in which an
estate application can route an outbound HTTP/HTTPS call (e.g. to
api.anthropic.com, api.openai.com, the LLM provider du jour) through
the gateway so that the same declarative policy bounds what leaves the box.

This blocks hyperpolymath/neurophone from meeting its data-egress obligation
(#84-3.1: sensor-derived data must be classified and rate-limited before
leaving the device on a cloud/LLM call). Without an egress mode the device
either lets every outbound call through unchecked or builds its own ad-hoc
allowlist — both of which duplicate work the gateway is otherwise the right
home for.

Branch: audit/egress-mode-scaffold — adds a draft EgressPolicy schema,
a :forward_proxy capability shape, and a Proxy.egress/2 seam (no live
forwarder yet; that is a follow-up). Includes one test fixture covering
deny-by-default + allow on a host allowlist entry.

Priority 2 — Policy schema is allowlist-shaped but not deny-by-default in spirit

policy_validator.ex requires a non-empty governance.global_verbs list and
gateway.ex's handle_request/1 does default-deny on no-match
({:error, :no_match} -> stealth/403). That part is correct. But there are
two structural weaknesses:

  1. No "capability" field. The DSL has path, verbs, optional exposure
    — but no first-class capability name (the boj-server cartridge vocabulary
    uses cartridge / capability strings). Today the only capability
    binding is via the implicit trust-level total order in SafeTrust. This
    limits how chimichanga-style attenuation can be plugged in later (Priority
    3).
  2. parse_exposure is fail-open. parse_exposure("typo") -> :public is
    documented as intentional, but combined with the
    Map.get(route, "exposure", "public") default in policy_compiler.ex it
    means a route that omits exposure is silently treated as public.
    Combined with a missing capability field this means the policy DSL is
    shallower than the README's "principled, schema-driven approach" claim
    suggests.

Branch: audit/policy-schema-capability — adds an optional capability
field at the route level (with validator support + property test), plus a
documented opt-in policy.defaults.exposure_fail_closed: true flag that
switches parse_exposure from fail-open to fail-closed. Backwards compatible.

Priority 3 — Estate capability + discovery integration

The gateway already understands BoJ cartridges (PolicyLoader.load_from_boj_catalog/1)
which is good. What is missing:

  • Chimichanga capability attenuation. There is no place in the request flow
    where a parent capability token can be attenuated (narrowed) before being
    passed to the backend. The X-Trust-Level header is set by the gateway
    authoritatively (good), but no capability vocabulary travels with it.
  • Service discovery via groove-protocol. Backend URLs are static
    (config :http_capability_gateway, :backend_url). There is no hook into
    groove-protocol service discovery, so estate apps that move between
    hosts can't be tracked.

Both are out-of-scope for a small PR but the audit branch documents the
contract surface where they would attach.

Branch: audit/capability-discovery-contract-doc — adds
docs/CAPABILITY-INTEGRATION.md and a PROOFS_NEEDED.md entry. Pure docs,
no code change.

Priority 4 — Provenance/audit logging coverage

gateway.ex already calls VeriSimDB.audit_allow/6 and VeriSimDB.audit_deny/4
on the allow/deny paths, and log_decision/7 emits a structured Logger
message. Two gaps:

  • No audit on the no-match path{:error, :no_match} emits a structured
    log entry but does NOT call VeriSimDB.audit_deny/4. The no-match path is
    the most security-relevant one (someone probing for an undeclared route),
    yet it is the one missing from the audit stream.
  • No audit on the unknown-method (405) path — same problem. A client
    sending PROPFIND/MKCOL/random strings is dropped with a Logger.warning
    but not persisted to the audit ledger.

Branch: audit/audit-no-match-and-unknown-method — calls VeriSimDB.audit_deny/4
on both paths, with a thin reason-string discriminator. Plus tests asserting
the audit cast was sent.

Priority 5 — Auth/bypass weaknesses and test coverage

Generally strong (the strip_untrusted_headers plug, the safe_verb/1
allowlist, the verify_peer mTLS listener, the is_cert_verified/1
scheme: :https-only gate). Two open items:

  • make_backend_request/4 uses String.to_existing_atom/1 on conn.method
    (line 189 of proxy.ex) without an allowlist. By the time this is reached
    safe_verb/1 has already filtered for the seven supported methods, so
    this is defence-in-depth only — but the comment in gateway.ex claims the
    gateway never uses to_existing_atom on user input, which is half-true.
  • No fuzz test asserting that :authenticated cannot reach an :internal
    route over the plaintext HTTP listener even if a forged header is present
    .
    security_test.exs covers header stripping at the plug layer; an explicit
    end-to-end "plaintext listener + forged header" test would close the loop.

Branch: audit/plaintext-forged-header-test — adds a single regression test
plus an Atom.to_string |> Map.fetch replacement in proxy.ex.


Echo-types audit

Per estate convention (feedback_proofs_must_check_and_cross_doc_echo_types.md)
this audit checked hyperpolymath/echo-types for relevant existing proofs.
Findings: no echo-types obligation is currently load-bearing on this repo —
the trust hierarchy is mechanised in proven/SafeTrust.idr (already
referenced) and no L3 echo obligation is in scope. Recorded as
record-as-not-relevant in PROOF-NEEDS.md per estate convention.


Surprises noticed during the meander

  • The repo advertises multi-protocol (MULTI-PROTOCOL.md, grpc_handler.ex,
    graphql_handler.ex) but the README is honest that handlers are stubs.
    Worth a status sync.
  • examples/policy-dev.yaml and 15+ workflow/config files still carry
    SPDX-License-Identifier: PMPL-1.0-or-later headers, which violates the
    2026-06-02 estate license directive (PMPL only for the palimpsest-license
    repo itself). A local working-tree sweep to MPL-2.0 is in progress but
    not yet committed. Filing this here for visibility; not fixing in this
    audit run to avoid clashing with that sweep.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions