Connect policy-gated browser review and audit runtime
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
2cc32168ac
commit
83849b75d4
35 changed files with 2381 additions and 83 deletions
166
docs/protected-browser-review.md
Normal file
166
docs/protected-browser-review.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# Protected browser review
|
||||
|
||||
Implemented under `INFD-WP-0001-T08`, release `0.2.0`. The configured service
|
||||
now presents one named memo, records explicit highlight acknowledgments and
|
||||
accept/return/discuss/decline responses, and submits an accepted request to
|
||||
Approval Engine. This source has passed a disposable Chromium exercise; native
|
||||
policy, human login, custody and deployment admission remain open.
|
||||
|
||||
This first browser profile accepts English, organizational `approve` memos
|
||||
only. Other locales, weaker steps and AES/QES levels refuse before rendering,
|
||||
so an English interface cannot attest to a German presentation or imply a
|
||||
signature level it does not implement. German UI acceptance (PR-60), broader
|
||||
steps and awareness controls remain T08 product work; the domain model retains
|
||||
their vocabulary. The initial factory browser proof uses this bounded profile.
|
||||
|
||||
## Request and evidence path
|
||||
|
||||
1. KeyCape login verifies the human, paired token claims and MFA facts. The
|
||||
browser holds only an opaque session cookie. The human access token is used
|
||||
only for Approval Engine and never as the Flex Auth workload credential.
|
||||
2. `review.py` checks the named recipient and requests a fresh Flex Auth `read`
|
||||
decision before exposing memo/packet content or calling Approval Engine.
|
||||
The [consumer contract](flex-auth-review-contract.md) defines all six actions.
|
||||
3. The immutable policy observation is persisted before use. The returned
|
||||
package/version/digest, submitted request digest, core binding and lifetime
|
||||
must match. Unsupported effects/obligations and unavailable callers refuse.
|
||||
Registry enrichment is observed, never reproduced by this consumer.
|
||||
4. The live Approval Engine object must declare `human_control=true` and match
|
||||
the memo's carried act digest. The requesting party and named human approver
|
||||
remain distinct. The presentation privately retains the observed binding.
|
||||
5. Every acknowledgment or response obtains another applicable decision. Accept
|
||||
requires the current memo, required acknowledgments, a live human session,
|
||||
current audit readiness and an open matching approval. Immediately before
|
||||
the engine POST, the adapter rechecks the binding and the controller checks
|
||||
policy/session freshness and reserves the single durable attempt.
|
||||
6. Acceptance stores the original engine entry correlation. Reload does not
|
||||
POST again. A lost response, in-flight attempt or unknown duplicate remains
|
||||
visible and cannot acquire a new presentation link. Return, discussion and
|
||||
decline record local human responses and never create an engine entry.
|
||||
|
||||
The governed canonicalizer remains the sole writer of `view_hash`. No
|
||||
presentation/acknowledgment fact is submitted to its own permission decision.
|
||||
Policy observations stay in private content, outside the presentation claim.
|
||||
`decision_attributable=false` remains explicit: checking the current unsigned
|
||||
envelope is not GH-DEC-2026-010 decision-path validation (`FLEX-WP-0024`).
|
||||
|
||||
The renderer requires `memo.ui_release=informed-decision@0.2.0`. An older memo
|
||||
must receive a new immutable version; it is not silently relabelled or rendered
|
||||
as though the old interface produced the current view. Schema v1 databases
|
||||
migrate atomically to v2 by adding append-only policy observations, preserving
|
||||
old evidence and submission records.
|
||||
|
||||
## Browser surface
|
||||
|
||||
| Route | Behavior |
|
||||
| --- | --- |
|
||||
| `GET /` | Sign-in or named memo identifier form; no approvals inbox |
|
||||
| `GET /review?memo_id=...` | Entitled current memo; existing intent retrieves its original view |
|
||||
| `GET /presentations/<id>` | Entitled historical presentation and current submission result |
|
||||
| `GET /presentations/<id>/packet/<index>` | Fresh read permission and full attachment bytes |
|
||||
| `POST /presentations/<id>/ack` | Explicit acknowledgment of selected highlights |
|
||||
| `POST /presentations/<id>/act` | Accept, return, discuss or decline with an operation id |
|
||||
|
||||
Forms require the exact fixed origin and session CSRF token, with bounded bodies
|
||||
and rejection of duplicate/unknown fields. Session expiry and actor/version
|
||||
checks apply server-side, including when a user bypasses a disabled button.
|
||||
The page names the complete request and packet as the object of acceptance.
|
||||
Highlights do not narrow its scope. Untrusted memo text is escaped, previews
|
||||
are bounded text, and downloads are attachments with `nosniff`.
|
||||
|
||||
Pages use `Referrer-Policy: same-origin`, while authentication paths and
|
||||
downloads use `no-referrer`. The Chromium test caught that using `no-referrer`
|
||||
on a form page produces `Origin: null` and rejects legitimate submissions.
|
||||
Missing, foreign and null form origins remain refused. Cross-origin referrers,
|
||||
inline scripts, framing and caching remain prohibited. There is no telemetry
|
||||
or client-side token storage. The reverse proxy must still omit auth queries,
|
||||
bearer headers and cookies from its logs.
|
||||
|
||||
## Owner configuration and audit readiness
|
||||
|
||||
Set `INFD_REVIEW_CONFIG` to an absolute, owned, regular 0600 JSON file. Its
|
||||
parent evidence directory must already exist with mode 0700. Without this
|
||||
configuration, the entrypoint remains the login shell and `/readyz` stays 503.
|
||||
The following is a shape example, **not an admitted configuration**; the
|
||||
placeholders intentionally do not pass validation:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": "informed-decision.review-runtime.v1",
|
||||
"evidence_db": "/OWNER-PRIVATE-PATH/review.sqlite",
|
||||
"approval_origin": "https://APPROVAL-OWNER-ORIGIN",
|
||||
"policy": {
|
||||
"origin": "https://FLEX-OWNER-ORIGIN",
|
||||
"package": "OWNER-ADMITTED-PACKAGE",
|
||||
"version": "OWNER-ADMITTED-VERSION",
|
||||
"package_digest": "OWNER-ADMITTED-SHA256",
|
||||
"caller_token_file": "/OWNER-PROJECTED-PATH/flex-caller-token"
|
||||
},
|
||||
"audit": {
|
||||
"origin": "https://AUDIT-OWNER-ORIGIN",
|
||||
"sender_token_file": "/OWNER-PRIVATE-PATH/audit-sender-token"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`INFD_KEYCAPE_ISSUER` remains mandatory. Credential files are read afresh when
|
||||
needed, so projected rotation does not require storing a token in configuration
|
||||
or SQLite. Runtime loading provisions nothing. Fixed internal `.svc` origins
|
||||
are supported explicitly; public cleartext origins and redirects are refused.
|
||||
The server remains one Waitress process, loopback port 8080, four threads.
|
||||
Multi-replica sessions/storage and deployment packaging require separate work.
|
||||
|
||||
The audit thread ticks every 30 seconds, generates the declared per-class
|
||||
heartbeats, drains at most ten records per tick and writes a private
|
||||
`audit-reconciliation.json` at most every five minutes. A successful drain and
|
||||
reconciliation with no retained delivery debt make readiness current for 90
|
||||
seconds. Errors or blocked records clear readiness; accept refuses while it is
|
||||
closed. This is a process/delivery health signal, not an assertion of native
|
||||
policy admission or authorization. Dispositions still persist transactionally.
|
||||
|
||||
Credential/schema refusals need operator repair and explicit requeue; no failed
|
||||
event is discarded. Source `occurred_at` and receiver `accepted_at` are retained
|
||||
as different count windows, without an automatic loss or completeness claim.
|
||||
An independent auditor reads the receiver in component tests. Production
|
||||
sender/receiver custody, backup/restore and reconciliation procedures remain
|
||||
admission work under T08 and `AUDIT-WP-0009-T11`.
|
||||
|
||||
Uncertain entry recovery must preserve causation. Finding a later entry is
|
||||
insufficient because Approval Engine stores no caller presentation/operation
|
||||
id. Inspect the original immutable intent and independent evidence; never reset
|
||||
an unresolved attempt merely to make the button available. If causation cannot
|
||||
be established, keep it unresolved and obtain an owner-directed disposition of
|
||||
the approval. This UI offers no shortcut that fabricates the missing link.
|
||||
|
||||
## Reproduce the component and browser checks
|
||||
|
||||
Build the checked Flex Auth source with its own `go build ./cmd/flex-auth`, then
|
||||
set explicit source paths and the resulting binary:
|
||||
|
||||
```sh
|
||||
export INFD_APPROVAL_ENGINE_SOURCE=/path/to/approval-engine
|
||||
export INFD_AUDIT_CORE_SOURCE=/path/to/audit-core
|
||||
export INFD_FLEX_AUTH_BINARY=/absolute/path/to/flex-auth
|
||||
make check
|
||||
```
|
||||
|
||||
With the Python dev dependencies, Playwright and Chromium installed, start
|
||||
`PYTHONPATH=. python tests/browser_fixture.py /tmp/infd-browser-UNIQUE` and run
|
||||
`node tests/browser_review.mjs /tmp/infd-browser-UNIQUE` in another terminal.
|
||||
`INFD_PLAYWRIGHT_PACKAGE` can name an installed Playwright package, and
|
||||
`INFD_CHROMIUM_EXECUTABLE` can name its Chromium executable. The driver stops
|
||||
the fixture and writes the result plus desktop/mobile screenshots.
|
||||
|
||||
The harness uses real local HTTPS, browser redirects/forms/cookies, the actual
|
||||
Flex Auth server with enforced caller authentication, and the actual Approval
|
||||
Engine/Audit Core APIs with separate SQLite stores. Its issuer, TokenReview,
|
||||
policy assignments, identities, credentials and act are synthetic. Browser
|
||||
hostnames resolve to loopback; only the fixture certificate's public key is
|
||||
trusted by the override. The harness control endpoints live only in tests.
|
||||
Exact production callback/registration strings remain covered separately.
|
||||
|
||||
The result covers login, escaped content, acknowledgment bypass refusal,
|
||||
explicit acknowledgment, packet download, return, mobile fit, a single accepted
|
||||
entry/reload, receiver delivery, workload caller refusal, sign-out and absence
|
||||
of script execution. It proves no native human login, production policy/custody,
|
||||
deployed UI or factory execution. See the dated JSON evidence for exact sources.
|
||||
Loading…
Add table
Add a link
Reference in a new issue