feat(phase02): searxng — wave 3 tailnet audit #37

Merged
pdurlej merged 1 commit from codex/orders/phase-02-searx into main 2026-05-03 20:56:52 +02:00
Collaborator

Summary

Phase 02 wave 3 audit for the SearxNG module (modules/searxng, requested as searx shorthand). Runtime audit found live Traefik labels are Tailnet-gated (ts-allowlist@file,security-headers@file) while the manifest still claimed public/no auth. This PR fixes desired documentation to match runtime and does not mutate runtime.

Change size: Medium. Per platform policy, trigger 3+3 ensemble review after PR open.

Changes

  • modules/searxng/module.yaml
    • Adds v2 audit fields: image_observed, digest pin flag, audit timestamp, image build, statefulness.
    • Changes exposure from public/no-auth to tailnet-only/Tailscale.
    • Records home-platform_searxng_data and statefulness as stateful.
    • Adds SEARXNG_SECRET ref and acknowledged risks for root runtime + plaintext stack.env secret.
  • modules/searxng/runbook.md
    • Replaces TODO runbook with live audit table, data classification, import plan, release checklist, canonical recovery, break-glass path, digest verification via yq, acknowledged risks, and known issues.

Verification

  • Schema validates: SCHEMA: OK
  • Token budget: 3478B ~869 tokens (<1500)
  • Digest cross-check: live image searxng/searxng:2026.2.22-5054e6984@sha256:1106e118f6876bf54b99d1d066c6c834d15b60729523f254f6aca46c4a5d5dc9
  • Middleware cross-check: ts-allowlist@file,security-headers@file
  • Runtime cross-check: user root-default, network hp_proxy, named volume home-platform_searxng_data, healthcheck /healthz
  • git diff --check

Test plan

  • Read-only runtime audit only (ssh rs2000 docker inspect, compose grep, volume list).
  • python3 -c "...jsonschema.validate..." against schema/module.schema.json.
  • No runtime mutation, no env file edits, no raw secret values committed.
## Summary Phase 02 wave 3 audit for the SearxNG module (`modules/searxng`, requested as `searx` shorthand). Runtime audit found live Traefik labels are Tailnet-gated (`ts-allowlist@file,security-headers@file`) while the manifest still claimed public/no auth. This PR fixes desired documentation to match runtime and does not mutate runtime. Change size: Medium. Per platform policy, trigger 3+3 ensemble review after PR open. ## Changes - `modules/searxng/module.yaml` - Adds v2 audit fields: `image_observed`, digest pin flag, audit timestamp, image build, statefulness. - Changes exposure from public/no-auth to tailnet-only/Tailscale. - Records `home-platform_searxng_data` and statefulness as stateful. - Adds `SEARXNG_SECRET` ref and acknowledged risks for root runtime + plaintext stack.env secret. - `modules/searxng/runbook.md` - Replaces TODO runbook with live audit table, data classification, import plan, release checklist, canonical recovery, break-glass path, digest verification via `yq`, acknowledged risks, and known issues. ## Verification - [x] Schema validates: `SCHEMA: OK` - [x] Token budget: `3478B ~869 tokens` (<1500) - [x] Digest cross-check: live image `searxng/searxng:2026.2.22-5054e6984@sha256:1106e118f6876bf54b99d1d066c6c834d15b60729523f254f6aca46c4a5d5dc9` - [x] Middleware cross-check: `ts-allowlist@file,security-headers@file` - [x] Runtime cross-check: user root-default, network `hp_proxy`, named volume `home-platform_searxng_data`, healthcheck `/healthz` - [x] `git diff --check` ## Test plan - Read-only runtime audit only (`ssh rs2000 docker inspect`, compose grep, volume list). - `python3 -c "...jsonschema.validate..."` against `schema/module.schema.json`. - No runtime mutation, no env file edits, no raw secret values committed.
Wave 3 extends the canary-validated PR #29/#30/#34 Phase 02 patterns to the actual SearxNG module (requested as searx shorthand). Runtime audit found Traefik labels are Tailnet-gated with ts-allowlist@file while the manifest still claimed public/no auth. This PR fixes the manifest and runbook to match runtime; no runtime mutation was performed.

Schema v2 fields per PR #29 pattern: user_facing_outcome, acceptance_criteria, image_observed, image_digest_pinned_in_compose, image_audit_ts, image_build, statefulness, acknowledged_risks.

Drift fixes: exposure public->tailnet-only; internet_exposed true->false; health path /health->/healthz; Docker volume name corrected to home-platform_searxng_data; module marked stateful; SEARXNG_SECRET documented as a ref.

Acknowledged risks: container-runs-as-root (root default bounded by Tailnet exposure); secret-in-plaintext-stack-env (Phase 02 secrets migration debt).

Schema: validates OK. Token budget: 869/1500.
Collaborator

3+3 ensemble review by claude — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-37/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.75)

Risks

  • medium — secrets.provider claims 'infisical' while runtime + acknowledged risk both say stack.env
    • Evidence: modules/searxng/module.yaml:55-58 sets secrets.provider: infisicalwithrefs: [SEARXNG_SECRET], but the same file lines 75-83 acknowledges secret-in-plaintext-stack-env stating 'SEARXNG_SECRET is injected from stack.env today ... migration path toward Infisical-backed refs'. The PR descripti
    • Recommendation: Either (a) revert provider to a value that reflects current runtime (e.g. env-only / stack-env) and add a TODO/open-loop for the Infisical migration, or (b) document the convention explicitly in the runbook that secrets.provider tracks desired post-migration state and acknowledged_risks tracks the current-state gap. Otherwise downstream tooling that reads the manifest as truth (e.g. plan/a

Opportunities

  • Surface the smoke test gap as a tracked open loop, not just a runbook bulletmodule.yaml.spec.health.smoke_test: tests/smoke.sh references a file the runbook itself notes does not exist ('declared but file not yet written'). If a future audit job reads smoke_test and tries to execute it, it will fail at apply-time. Consider either omitting the field until written, or adding a one-line entry to L3/OPEN_LOOPS.md so it is not lost in a runbook checkbox.

Product hat: OK (confidence 0.82)

Risks

  • low — Manifest claims secrets.provider: infisical but reality is stack.env
    • Evidence: module.yaml secrets.provider: infisical(line 56-ish in diff) vs acknowledged risksecret-in-plaintext-stack-env rationale: "SEARXNG_SECRET is injected from stack.env today."
    • Recommendation: Pick one convention for v2 manifests and stick to it: either (a) provider records current runtime state — then this should be env-only-non-secret until migration, OR (b) it records desired/target state — then add a one-line README note in modules/ explaining that. Right now an automated audit reading just secrets.provider would falsely conclude this module is on Infisical. The risk acknowl

Opportunities

  • Capture acceptance criterion #4 as a one-line smoke check now — Acceptance criterion says "Non-Tailnet access is blocked by ts-allowlist@file" but this was never actually tested — verification only confirmed the label string is present, not that the middleware blocks. A 2-line curl --interface <non-tailnet> from outside Tailnet would close the loop. Could go into the still-unwritten tests/smoke.sh when that lands; doesn't need to block this PR, but worth
  • Runbook recovery section is now genuinely usable for tired-operator mode — Strong upgrade from the TODO stub: canonical path is labelled, break-glass path is labelled, digest verify uses yq to source from manifest (avoiding copy-paste errors of 64-char hashes). This is exactly the ADHD-compatible shape — no remembering required, scannable headings, explicit "only if platformctl down" gating on the dangerous path. Worth using as a template for the remaining wave 3 modul
<!-- platform-review:claude:pdurlej/platform:PR-37:7dcfce5e --> # 3+3 ensemble review by `claude` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-37/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.75) ### Risks - **`medium`** — secrets.provider claims 'infisical' while runtime + acknowledged risk both say stack.env - Evidence: `modules/searxng/module.yaml:55-58 sets `secrets.provider: infisical` with `refs: [SEARXNG_SECRET]`, but the same file lines 75-83 acknowledges `secret-in-plaintext-stack-env` stating '`SEARXNG_SECRET` is injected from stack.env today ... migration path toward Infisical-backed refs'. The PR descripti` - Recommendation: Either (a) revert provider to a value that reflects current runtime (e.g. `env-only` / `stack-env`) and add a TODO/open-loop for the Infisical migration, or (b) document the convention explicitly in the runbook that `secrets.provider` tracks desired post-migration state and `acknowledged_risks` tracks the current-state gap. Otherwise downstream tooling that reads the manifest as truth (e.g. plan/a ### Opportunities - **Surface the smoke test gap as a tracked open loop, not just a runbook bullet** — `module.yaml.spec.health.smoke_test: tests/smoke.sh` references a file the runbook itself notes does not exist ('declared but file not yet written'). If a future audit job reads `smoke_test` and tries to execute it, it will fail at apply-time. Consider either omitting the field until written, or adding a one-line entry to L3/OPEN_LOOPS.md so it is not lost in a runbook checkbox. ## Product hat: ✅ **OK** (confidence 0.82) ### Risks - **`low`** — Manifest claims `secrets.provider: infisical` but reality is stack.env - Evidence: `module.yaml `secrets.provider: infisical` (line 56-ish in diff) vs acknowledged risk `secret-in-plaintext-stack-env` rationale: "`SEARXNG_SECRET` is injected from stack.env today."` - Recommendation: Pick one convention for v2 manifests and stick to it: either (a) `provider` records current runtime state — then this should be `env-only-non-secret` until migration, OR (b) it records desired/target state — then add a one-line README note in `modules/` explaining that. Right now an automated audit reading just `secrets.provider` would falsely conclude this module is on Infisical. The risk acknowl ### Opportunities - **Capture acceptance criterion #4 as a one-line smoke check now** — Acceptance criterion says "Non-Tailnet access is blocked by `ts-allowlist@file`" but this was never actually tested — verification only confirmed the label string is present, not that the middleware blocks. A 2-line `curl --interface <non-tailnet>` from outside Tailnet would close the loop. Could go into the still-unwritten `tests/smoke.sh` when that lands; doesn't need to block this PR, but worth - **Runbook recovery section is now genuinely usable for tired-operator mode** — Strong upgrade from the TODO stub: canonical path is labelled, break-glass path is labelled, digest verify uses `yq` to source from manifest (avoiding copy-paste errors of 64-char hashes). This is exactly the ADHD-compatible shape — no remembering required, scannable headings, explicit "only if platformctl down" gating on the dangerous path. Worth using as a template for the remaining wave 3 modul
Author
Collaborator

3+3 ensemble review by codex — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-37/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.82)

Opportunities

  • Secrets provider wording may confuse later import work — module.yaml now declares provider=infisical while the same PR documents SEARXNG_SECRET as currently injected from stack.env. This is not a merge blocker because the risk is explicitly acknowledged and the PR is documentation-only, but the next migration/import PR should make clear whether provider means desired target or current runtime source.

Product hat: NOT_OK (confidence 0.78)

Risks

  • medium — Secret source is represented two different ways
    • Evidence: modules/searxng/module.yaml changes spec.secrets.providertoinfisical, while modules/searxng/runbook.mdand the acknowledged risk saySEARXNG_SECRETis still plaintextstack.env-backed pending migration.
    • Recommendation: Before merge, make the manifest reflect the current operational truth: either keep the provider as the actual stack/env source and record Infisical as a future migration, or add an explicit transitional field so the operator does not read this as 'secret migration is done'.

Opportunities

  • Keep the decision comment brutally simple — The useful operator-facing outcome is: public exposure claim was wrong, runtime is Tailnet-gated, and this PR fixes the docs only. After the secret-source mismatch is fixed, that should be the approval surface.
<!-- platform-review:codex:pdurlej/platform:PR-37:7dcfce5e --> # 3+3 ensemble review by `codex` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-37/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.82) ### Opportunities - **Secrets provider wording may confuse later import work** — module.yaml now declares provider=infisical while the same PR documents SEARXNG_SECRET as currently injected from stack.env. This is not a merge blocker because the risk is explicitly acknowledged and the PR is documentation-only, but the next migration/import PR should make clear whether provider means desired target or current runtime source. ## Product hat: ❌ **NOT_OK** (confidence 0.78) ### Risks - **`medium`** — Secret source is represented two different ways - Evidence: `modules/searxng/module.yaml changes `spec.secrets.provider` to `infisical`, while `modules/searxng/runbook.md` and the acknowledged risk say `SEARXNG_SECRET` is still plaintext `stack.env`-backed pending migration.` - Recommendation: Before merge, make the manifest reflect the current operational truth: either keep the provider as the actual stack/env source and record Infisical as a future migration, or add an explicit transitional field so the operator does not read this as 'secret migration is done'. ### Opportunities - **Keep the decision comment brutally simple** — The useful operator-facing outcome is: public exposure claim was wrong, runtime is Tailnet-gated, and this PR fixes the docs only. After the secret-source mismatch is fixed, that should be the approval surface.
First-time contributor

3+3 ensemble review by glm — tech + product hats

One of three independent AI reviewers (claude / codex / glm). See state/reviews/PR-37/decision_packet.md for consolidated risk across all 6 outputs.

Tech hat: OK (confidence 0.95)

Opportunities

  • Excellent documentation artifact for Phase 02 — This PR serves as a strong template for wave 3 audits: observed image with full digest, acknowledged risks with rationale + timestamps + acknowledger, and a runbook that actually helps recovery. The 'canonical vs break-glass' recovery path distinction is operator-friendly for mobile approvals.
  • Exposure drift fix prevents future confusion — Correcting manifest from public to tailnet-only (with middleware evidence) closes a security documentation gap. Future reviews won't mistakenly think this is internet-exposed, which aligns with §6 cognition (value first, but truth in docs).

Product hat: NOT_OK (confidence 0.95)

Risks

  • blocker — Secret provider contradicts acknowledged reality
    • Evidence: modules/searxng/module.yaml:55 (provider: infisical) vs modules/searxng/module.yaml:67 (acknowledged risk: secret-in-plaintext-stack-env)
    • Recommendation: Revert provider to env-only-non-secret (or plaintext). The PR description confirms 'no env file edits' and the risk acknowledges the secret is still in stack.env. The manifest must mirror runtime reality to avoid confusion during future migrations.
  • medium — Schema drift in 'intent' section creates ambiguity
    • Evidence: modules/searxng/module.yaml:25-30 (user_facing_outcome vs acceptance_criteria)
    • Recommendation: Consolidate 'intent' fields for mobile readability. 'user_facing_outcome' is great (product-fit), but 'acceptance_criteria' duplicates technical healthchecks already defined in the 'health' section. Keep the product-outcome, remove the checklist to reduce surface noise.

Opportunities

  • Live audit table is high-value pattern — The 'Live state audit' table in runbook.md provides instant 'ground truth' without SSH. Recommend making this a standard section for all module audits.
  • Break-glass path clarity — Explicitly providing the docker-compose fallback path in the runbook reduces operator anxiety during platformctl outages. Good fit for §3 cognition rules.
<!-- platform-review:glm:pdurlej/platform:PR-37:7dcfce5e --> # 3+3 ensemble review by `glm` — tech + product hats > One of three independent AI reviewers (claude / codex / glm). See `state/reviews/PR-37/decision_packet.md` for consolidated risk across all 6 outputs. ## Tech hat: ✅ **OK** (confidence 0.95) ### Opportunities - **Excellent documentation artifact for Phase 02** — This PR serves as a strong template for wave 3 audits: observed image with full digest, acknowledged risks with rationale + timestamps + acknowledger, and a runbook that actually helps recovery. The 'canonical vs break-glass' recovery path distinction is operator-friendly for mobile approvals. - **Exposure drift fix prevents future confusion** — Correcting manifest from public to tailnet-only (with middleware evidence) closes a security documentation gap. Future reviews won't mistakenly think this is internet-exposed, which aligns with §6 cognition (value first, but truth in docs). ## Product hat: ❌ **NOT_OK** (confidence 0.95) ### Risks - **`blocker`** — Secret provider contradicts acknowledged reality - Evidence: `modules/searxng/module.yaml:55 (provider: infisical) vs modules/searxng/module.yaml:67 (acknowledged risk: secret-in-plaintext-stack-env)` - Recommendation: Revert `provider` to `env-only-non-secret` (or `plaintext`). The PR description confirms 'no env file edits' and the risk acknowledges the secret is still in `stack.env`. The manifest must mirror runtime reality to avoid confusion during future migrations. - **`medium`** — Schema drift in 'intent' section creates ambiguity - Evidence: `modules/searxng/module.yaml:25-30 (user_facing_outcome vs acceptance_criteria)` - Recommendation: Consolidate 'intent' fields for mobile readability. 'user_facing_outcome' is great (product-fit), but 'acceptance_criteria' duplicates technical healthchecks already defined in the 'health' section. Keep the product-outcome, remove the checklist to reduce surface noise. ### Opportunities - **Live audit table is high-value pattern** — The 'Live state audit' table in runbook.md provides instant 'ground truth' without SSH. Recommend making this a standard section for all module audits. - **Break-glass path clarity** — Explicitly providing the docker-compose fallback path in the runbook reduces operator anxiety during platformctl outages. Good fit for §3 cognition rules.
Collaborator

Review decision

Status: BLOCKER — recommended action: defer

Single-reviewer blockers

  • Secret provider contradicts acknowledged reality
    • Seen by: product-glm
    • Decision: Revert provider to env-only-non-secret (or plaintext). The PR description confirms 'no env file edits' and the risk acknowledges the secret is still in stack.env. The manifest must mirror runtime reality to avoid confusion during future migrations.

Reviewer dissents

  • product-gpt voted NOT_OK (confidence 0.78)
  • product-glm voted NOT_OK (confidence 0.95)

Operator decisions (yes/no)

  1. Risk 'Secret provider contradicts acknowledged reality' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated?
  2. Risk 'Secret source is represented two different ways': mitigated, accepted, or convert to open_loop?
  3. Risk 'secrets.provider claims 'infisical' while runtime + acknowledged risk both say stack.env': mitigated, accepted, or convert to open_loop?

Per-actor evidence: see comments by claude, codex, glm above. Tech: 3/3 OK · Product: 1/3 OK.

<!-- platform-review-decision:pdurlej/platform:PR-37:7dcfce5e --> # Review decision **Status:** BLOCKER — recommended action: `defer` ### Single-reviewer blockers - **Secret provider contradicts acknowledged reality** - Seen by: product-glm - Decision: Revert `provider` to `env-only-non-secret` (or `plaintext`). The PR description confirms 'no env file edits' and the risk acknowledges the secret is still in `stack.env`. The manifest must mirror runtime reality to avoid confusion during future migrations. ### Reviewer dissents - `product-gpt` voted **NOT_OK** (confidence 0.78) - `product-glm` voted **NOT_OK** (confidence 0.95) ### Operator decisions (yes/no) 1. Risk 'Secret provider contradicts acknowledged reality' raised by 1/6: do you accept this risk, or should this PR be deferred until it's mitigated? 2. Risk 'Secret source is represented two different ways': mitigated, accepted, or convert to open_loop? 3. Risk 'secrets.provider claims 'infisical' while runtime + acknowledged risk both say stack.env': mitigated, accepted, or convert to open_loop? --- _Per-actor evidence: see comments by `claude`, `codex`, `glm` above. Tech: 3/3 OK · Product: 1/3 OK._
Sign in to join this conversation.
No reviewers
No labels
W6d-automerge-calibration
agent/claude-code
agent/codex
agent/hermes
agent/iskra
agent/ollama
agent/patchwarden
automerge-candidate
class/security-sensitive
cutover-gate
dependency/blocked
dependency/blocks-others
dependency/cross-repo
dependency/needs-confirmation
domain:agents
domain:ci
domain:docs
domain:forgejo
domain:infra
domain:memory
domain:runtime
domain:signal
domain:ux
flow/architecture
flow/blocked
flow/deployed
flow/done
flow/implementation
flow/intake
flow/maintained
flow/observed
flow/ready
flow/refining
flow/retired
flow/review
iterating
judge/codex-candidate
judge/hermes-candidate
judge/low-confidence
judge/needs-refinement
judge/operator-needed
judge/p0
judge/p1
judge/p2
judge/p3
judge/park
judge/patchwarden-candidate
judge/stale-priority
kind/adr
kind/bug
kind/chore
kind/feature
kind/infra
kind/ops
kind/refactor
kind/research
large-impact
merge/auto
merge/manual
merge/manual-dependency-conflict
merge/manual-failing-tests
merge/manual-merge-conflict
merge/manual-missing-review
merge/manual-operator-preference
merge/manual-red-zone
merge/manual-security-sensitive
merge/manual-unclear-scope
merge/manual-unknown
meta
mode:operator-only
mode:patchwarden-iskra-approved
mode:safe-auto
needs-operator-decision
needs-triage
not-ready
observed/erroring
observed/needs-followup
observed/pending
observed/retire-candidate
observed/unused
observed/used
operator-emotional
owner-attention
phase/02
phase/03
priority:p0
priority:p1
priority:p2
priority:p3
proposed
ready-for-agent
ready-for-operator
recovery
review:claude-reviewed
review:codex-reviewed
review:dziadek-reviewed
review:needs-human
risk/exposure
risk/process
risk/product
risk/runtime
safety:external-write
safety:no-prod-mutation
safety:prod-impact
safety:secret-touch
size/large
size/medium
size/small
size/tiny
size/unknown
source/adr
source/agent-generated
source/manual
source/operator-chat
source/voice-note
status:blocked
status:codex-ready
status:merged:pending-evidence
status:needs-evidence
status:operator-needed
status:parked
tier/full
tier/lite
tier/stacked
tier:0-platform-substrate
tier:1-iskra-value-layer
tier:2-tools-products-modules
type:bug
type:chore
type:docs
type:feat
type:policy
type:research
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pdurlej/platform!37
No description provided.