Skip to content

ci(fork): add pytest-run job with Postgres service container#2

Closed
HaraldeRoessler wants to merge 3 commits into
mainfrom
fix/fork-ci-run-tests
Closed

ci(fork): add pytest-run job with Postgres service container#2
HaraldeRoessler wants to merge 3 commits into
mainfrom
fix/fork-ci-run-tests

Conversation

@HaraldeRoessler

Copy link
Copy Markdown
Owner

Summary

The fork CI previously only ran pytest --collect-only — tests were never executed. This meant regressions like the PR#17 credit-middleware 402-block loss were invisible to CI.

The concurrency test test_concurrent_deducts_respect_balance would have caught it immediately (expects 3×200 + 7×402, got 10×200) — but it never ran.

What's added

pytest-run job — spins up a Postgres 16 service container, runs init_db.sql for the schema, then executes tests/test_credit_middleware.py with proper env vars:

- name: run credit middleware tests
  env:
    CREDITS_ENABLED: 'true'
    MOLTSTACK_DB_PW: 'ci-pw'
    MOLTRUST_API_KEYS: 'mt_ci_placeholder_key'
    NONCE_SECRET: 'ci-placeholder-nonce-secret'
  run: |
    python -m pytest tests/test_credit_middleware.py -v --tb=short

Scoped to credit middleware tests for now. Other test files (test_caep.py, test_protocol_compliance.py) need additional infrastructure and can follow separately.

Also adds httpx to pip install in the collect job (needed for imports to succeed).

Why this matters

The entire PR#17 regression was discovered by Kersten running tests manually on the production server. With this CI job, the red test blocks the merge automatically.

Previously the fork CI only ran pytest --collect-only, never executing
any tests. This meant regressions like the PR#17 credit-middleware
402-block loss were invisible to CI — the concurrency test
(test_concurrent_deducts_respect_balance) would have caught it
immediately, but it never ran.

New job:
- pytest-run: spins up a Postgres 16 service container, runs
  init_db.sql for the schema, executes tests/test_credit_middleware.py
  with proper env vars (CREDITS_ENABLED=true, MOLTSTACK_DB_PW, etc.)

This is intentionally scoped to credit middleware tests only — the
test_caep.py and protocol compliance tests need additional
infrastructure (sandbox, admin keys) and can be added separately.

Also adds httpx to the collect job's pip install (needed for import
to succeed).
@HaraldeRoessler

Copy link
Copy Markdown
Owner Author

Superseded — CI changes moved to PR#58 (MoltyCel#58)

@HaraldeRoessler
HaraldeRoessler deleted the fix/fork-ci-run-tests branch May 22, 2026 03:22
HaraldeRoessler pushed a commit that referenced this pull request Jul 3, 2026
…iew fixes

Erstes §12-Review (run 2026-05-28, label openclaw-plugin-v2.0.0-alpha.0)
fand 6 Blocker. Dieser PR adressiert 5 davon im Code + 1 als ADR-deferral.

Code-Fixes:
  - #1 Fail-Open Default → Neuer Config-Param `failOpen` (default false).
        before_tool_call + inbound_claim blocken jetzt bei API-Lookup-
        Fehlern, statt durchzulassen. Opt-in via failOpen=true.
  - #2 inbound_claim DENY-Ignorierung → Review-Befund war überwiegend
        Halluzination (Score-Check existierte schon), aber der Fail-Open-
        Pfad ist via #1 jetzt auch hier fail-closed.
  - MoltyCel#4 Cache TTL Default 300s → 10s (Zero-Trust Revocation-Latency).
  - MoltyCel#5 Vendored OpenClaw-Types ohne Versions-Anchor → Header-Kommentar
        in openclaw-types.ts pinnt jetzt explizit Range 0.9.x – 1.0.x
        + Anweisung „minor bump on upstream-breaking change". README
        zeigt Range in Architektur-Section.
  - MoltyCel#6 GDPR/Privacy → Neue README-Section „Privacy & Data Handling"
        listet explizit alle Endpoints, gesendete Datenfelder, Endpoint-
        URL, Retention-Pointer, Disable-Mechanismus.

ADR-Deferral:
  - MoltyCel#3 JWS Response-Signatur-Verifikation → ADR 0001 (docs/decisions/)
        erklärt warum v2.1 statt v2.0.0-alpha.1: braucht Bootstrap-/
        Rotations-/Failure-Mode-Design, eigener Sprint. Risiko-
        Mitigation in v2.0.0-alpha.1 durch #1 (kein Fall-Through bei
        API-unreachable). README-Section „Security Posture & Roadmap"
        kommuniziert v2.1-Plan + verbleibende MITM-Surface.

Publish-Prep (re-applied, since v2.0.0-alpha.0 origin/main state hat
diese noch nicht):
  - Paketname @moltrust/openclaw → @moltrust/openclaw-plugin
  - prepublishOnly, engines (node>=20), publishConfig.access: public
  - author, bugs, repository.url (Standardformat)
  - .gitignore + .npmignore

Tests: 27 → 31 (4 neue, fail-closed + opt-in-fail-open je hook).
py-equivalent: tsc + vitest grün, package size ~12 kB.

Re-§12-Review läuft nach Merge gegen das gleiche Briefing.
HaraldeRoessler pushed a commit that referenced this pull request Jul 3, 2026
Re-§12-Review für alpha.1 (run 2026-05-28 17:27 UTC, output
~/moltstack/reviews/20260528_172832_openclaw-plugin-v2.0.0-alpha.1_review.md)
votierte ÜBERARBEITEN. Drei neue Blocker für alpha.2 — alle in
diesem Commit adressiert.

Blocker #1 (Test-Lücke): kombinatorischer Pfad "own DID OK +
counterparty lookup fail + failOpen=true → ALLOW" war nicht getestet.
Test ergänzt in before-tool-call.test.ts.

Blocker #2 (Performance): sequenzielle Counterparty-Lookups in
before-tool-call.ts waren O(N × API-latency) — 4 DIDs × 50ms = 200ms+
Event-Loop-Block. Fix: Promise.allSettled + post-evaluation. Block-
Priority ist deterministisch (erste Counterparty im array order wins).
Own-DID-Check bleibt VOR counterparties (early-exit bei eigener
Insufficienz).

Blocker MoltyCel#3 (Air-Gap Lücke): die moltrust_verify / moltrust_trust_score /
moltrust_endorse Agent-Tools blieben auch bei minTrustScore=0 +
verifyOnStart=false beim Agent-Runtime registriert — LLM-Halluzinationen
konnten ungewollte DID-Lookups triggern. Neue Config-Option
`registerMoltrustTools` (default true). Wenn false: die 3 registerTool-
Calls werden geskipped, Tools sind für die LLM nicht aufrufbar. Slash-
Commands + RPC + Lifecycle-Hooks bleiben unverändert (explizite
Operator/User-Aktionen, nicht LLM-aufrufbar).

README: Privacy-Section schärft Disable-Anweisung — "Disabling
automatic outbound calls" (alt) vs neue "True air-gap mode" mit
registerMoltrustTools=false. Configuration-Beispiel zeigt neuen
Default-Wert.

Tests: 31 → 32 (1 neuer für kombinatorischen Pfad). Alle 32 grün,
tsc grün.

Re-Re-§12-Review läuft mit alpha.2-Briefing-Append vor npm publish.
HaraldeRoessler pushed a commit that referenced this pull request Jul 3, 2026
…ltyCel#128)

* docs(specs): D-1 Acceptance-Gate architecture brief (design-only)

Scope: AAE draft-04 §5 Step 1 (signature verify + signing-authority) + Step 2 (payload/schema/cty). Decisions: #1 JWS-wrapped VC submit-contract (extract blocks from verified payload; component-1 API/raw_canonical impact named); #2 did:web + did:moltrust launch (did:key follow-on); MoltyCel#3 resolve-and-verify with trust-tiering (trusted vs unverified_issuer, no hard-allowlist); MoltyCel#4 scope = steps 1+2 only (step 4 subject-binding + step 9 delegation = follow-ons). PyJWT 2.12.1 (no new dep). Canonicalization clarity: D-1 verifies JOSE-JWS bytes, not JCS raw_canonical. Open sign-off: DID-resolution depth/SSRF/caching, raw_canonical redefinition, trust-tier persistence.

* docs(specs): resolve 4 D-1 sign-off points (design-only)

1) DID-resolution SSRF/DoS = same egress-proxy as revocation_check (no new mitigation); did:web gated on proxy, D-1 LAUNCHES did:moltrust-only (no outbound, not proxy-gated). 2) raw_canonical = JWS-payload (trigger structurally unchanged); breaking submit-contract change, only smoke-rows affected. 3) trust-tier = new additive column issuer_trust_tier (trusted/unverified_issuer, analog value_source). 4) did:web VM-dereferencing = new layer (resolver gives raw DID-doc only). Phased launch: A did:moltrust-only now, B did:web when egress-proxy live.

* docs(specs): D-1 review-hardening — 4 criticals + 2 mediums resolved (design-only)

alg-confusion (explicit algorithms=[EdDSA] allowlist, never trust header alg); kid strict DID-URL validation + path-traversal/look-alike protection; canonicalization = exact b64url-decoded payload bytes (never re-serialize); submit rate-limit + per-issuer quota (PK already blocks exact replays); did:moltrust registry SPOF -> key rotation; JSON duplicate-keys reject via object_pairs_hook. Implementation contract, not architecture change.

---------

Co-authored-by: Lars Kroehl <kersten.kroehl@cryptokri.ch>
HaraldeRoessler added a commit that referenced this pull request Jul 3, 2026
The 3-model review blocker #1 (downgrade/stripping) was not fully solved
by AND-logic alone: my first implementation stripped the proof field
before signing, so an attacker who removed the Dilithium leg left the
Ed25519 signature valid over unchanged bytes — the exact OR-downgrade the
review flagged. A prior test even asserted the wrong behaviour (stripped
single verifies standalone).

Fix per the review explicit requirement (proof field must be included in
the signed message):

- dual_sign now builds a proof SKELETON (every intended proof dict with
  proofValue blanked) and signs the credential body PLUS that skeleton.
  Both Ed25519 and Dilithium sign the same canonicalized skeleton bytes.
- verify_proof reconstructs the same skeleton from the presented proofs.
  If a leg is missing, the skeleton differs from what the issuer signed,
  so the surviving Ed25519 signature fails too. Stripping a leg now
  breaks every remaining signature — composite-signature semantics
  (IETF composite-sigs, BSI TR-02102-1).
- dual_sign refuses to silently fall back to Ed25519-only when Dilithium
  is configured but signing fails (that would re-open the downgrade path:
  an attacker cannot distinguish a deliberate Ed25519-only cred from a
  failed-dual one). It raises instead.
- Legacy sort_keys credentials (no canonicalizationAlgorithm) still verify
  over the body only — backward compat unchanged, and they only ever had
  one leg so there was nothing to strip.

Also fix blocker #2 fully: scripts/generate_dilithium_keys.py --kms mode
previously printed base64(secret_key) to stdout. It now writes the
KMS-ready base64 to a chmod 600 file (dilithium_secret_key.kms.b64)
instead. Runtime proof: script stdout contains only the PUBLIC key; no
secret (hex or base64) reaches stdout/stderr. Secret files are mode 0600.

Verified:
- Downgrade attack (PQC-enabled issuer -> strip Dilithium -> verify) now
  FAILS: Ed25519 signature invalid because skeleton changed.
- Legit Ed25519-only (non-PQC issuer) still verifies.
- Legacy sort_keys credentials still verify.
- Dual both-legs-valid -> valid; one bad leg -> whole cred invalid.
- JCS fail-closed raises when jcs missing.
- py_compile clean on all 8 files.
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.

1 participant