think
16px
820px

Plan: security remediation + ISO 16175 conformance (2026-07-29)

Two independent tracks. Security is defect-fixing (do it first). 16175 is feature work.
Time estimates are for Claude executing with the usual deploy + e2e discipline.

Source: docs/audits/2026-07-29-security-audit-consolidated.md


Track A — Security (5 batches, ranked)

S1 · My own newly-shipped code · ~2h

Contained, low blast radius, squarely my responsibility. Do first.
- M1 AI base_url SSRF + key exfil — validate scheme/host, block private IPs + link-local, CheckRedirect deny. go/internal/ai/app/provider.go:232-271
- M2 Daily budget TOCTOU — atomic reserve-before-call; meter embeddings. go/internal/ai/app/service.go:161-184
- M15 Scrub gaps — embeddings adapter has no scrub (embed_openai.go:64); scrubKey exact-substring misses masked keys; SSE error event forwards upstream body + internal host (handlers_ai.go:603).

Gate: cd go && go build ./... && go vet ./... + live /test against a private IP must 400.

S2 · Signature verification (release-blocking) · ~1 day

C2 — the "Trusted" badge is forgeable and /ByteRange is never validated.
- Resolve the signer cert via IssuerAndSerialNumber, not Certificates[0] (attacker-orderable).
- Validate ByteRange covers the whole file (kills shadow-update forgery).
- go/internal/esign/adapters/pdfsign.go:305-329

Gate: craft a re-ordered-cert PDF + an appended-content PDF; both must verify as untrusted. Regression: every existing Peruri-sealed demo doc must still verify green.
Why its own batch: touches the product's core claim. Wants careful review, not a batch commit.

S3 · Authorization holes · ~1 day

  • H1 Any member rewrites/archives ANY workflow template → approval-chain hijack (owner guard is dead code). workflow/app/service.go:748-756,802,2127,2152
  • H2 Bulk move has no destination-folder gate → Contributor→Manager escalation. dms/app/service.go:2356-2376
  • H7 OnlyOffice callback forgeable + document save path skips save-time ACL recheck (letters do it right). handlers_office.go:593-601,697-731
  • M4 Any member writes folder attributes → silent DLP-class downgrade. server.go:757
  • M5 Copy + finalize-as-new bypass destination gate. handlers_dms.go:230-261, handlers_finalize.go:136-150

Gate: e2e each as a real second user on the demo (member token vs director token).

S4 · Deploy / infra · ~1 day my side + your rotation

Blocked on your host actions for the first two.
- C1 Hardcoded Postgres obscura:obscura + MinIO creds as literals → ${VAR:?} + generate in bootstrap-client.sh + add to requirements.go.
- D1/D2/D3 You: rotate exposed secrets, confirm prod DB password, verify valbox nginx resets X-Real-IP.
- H4 bootstrap-client.sh chmods the blob master key 644 / license 666 → chown 65532 + 400/600.
- H5 DOCKER-USER DROP rules + CI check that every base ports: has a prod !override.
- H6 Sidecar hardening: shared-secret header, USER nobody, mem_limit/cpus, read_only, cap_drop, docs_url=None, body cap, defusedxml.
- M10 CSP + frame-ancestors 'none' + nosniff on both nginx tiers.

S5 · Cleanup lows · ~half day

M3 (MCP keys carry full REST authority), M6 (/act collapses a definition chain), M7 (stego demo key passes preflight), M8/M9/M11/M12/M13/M14, plus the LOW batch. One commit per theme.


Track B — ISO 16175 (one workstream: "records leave the system intact")

Certification note: 16175 is a conformance checklist, not certifiable. ISO 30301 is the
certifiable records analogue of 27001 and reuses the same Annex SL clause structure — highest-leverage
add now that 27001 is in hand. 15489 is the vocabulary both are written in.

Already done (verified in code): HMAC audit chain + hourly verification + append-only triggers,
disposition certificates + reference-counted destruction, legal hold that wins inside the tx,
document types as file-plan rows, deny-wins ACLs, gapless doc-ID provenance, frozen version snapshots.

R1 · Per-record audit-trail extract · ~1 day

Surface one record's full history as a verifiable exportable artifact. Required on its own and a
prerequisite for R2. Nothing exists today (rg found no audit-export service method).

R2 · Transfer / export package · ~3 days

DispositionTransfer is currently a label — the code comment literally says
"no destination modelled" (dms/domain/retention_policy.go:16).
Build: an open self-describing package = document bytes + recordkeeping metadata + R1 audit trail +
manifest with hashes. Satisfies 16175 transfer requirements and kills lock-in objections in tenders.
Fold R6 (ISO 23081 recordkeeping metadata schema) in here — the schema must travel with the record,
so it is the same build, not a separate one.

R3 · PDF/A-2b preservation format · ~1 day

Zero PDF/A anywhere in the tree today. Official copies already render through gotenberg → emit PDF/A-2b
there. Small change, large conformance payoff (16175 long-term-readability requirement).

R4 · Restorable backup · ~2–3 days

RunBackup/OpenManifest produce a corpus manifest only — not restorable. This is the existing
queue #4 (incident-motivated after down -v wiped demo pgdata+miniodata). 16175 has explicit
system-administration requirements here. Design fork already scoped: distroless app can't shell to
pg_dump → dedicated compose backup sidecar.

R5 · Aggregation-level retention · design decision first, then ~2 days

Retention currently attaches per document via its type. 16175 expects schedules on aggregations
(file/series) inheriting to items. Wants a decision from you before building.

Dismissed deliberately: hybrid/physical records tracking (16175 covers it; almost certainly YAGNI
for these customers).


Suggested order

S1 → S2 → S3 → S4 → R1 → R2+R6 → R3 → R4 → S5 → R5

Rationale: contained self-inflicted bugs first, then the release-blocking crypto, then authz, then
infra (partly gated on you). 16175 starts at R1 because R2 depends on it and R2 carries the most
conformance weight.

Caveat: the standards clause content is working knowledge, not verified against published texts.
The code findings are verified directly. Confirm the mapping with an Indonesian records/compliance
advisor before customer-facing claims — especially the ANRI layer on top of the ISO family.