Add an open-issues register, and correct what port 8443 actually is

status/OPEN-ISSUES.md is a fourth register, so it opens by saying what
does NOT belong in it. Work blocked on other people stays in REQUESTS.md;
shortcuts we consciously accepted stay in BUILD-AI-CONTAINERS.md 14 and
are closed by decision, not to be re-raised here; what is running stays
in current-state.html. What had no home until now is a defect we own and
have not fixed - those were living in commit messages. Closed issues move
to the bottom rather than being deleted: this repo is an as-built record,
and an issue with no trace of how it closed is worth less than one that
was never raised.

OI-01: the OpenPLC Editor is not installed anywhere. It is a desktop tool
published by OpenPLC, and it is what authors and compiles the IEC 61131-3
program openplc-runtime executes. Without it there is no reviewable
source for the control logic in or beside this repository - the program
exists only inside the container.

Raising it exposed two wrong statements in the build spec, both calling
port 8443 the "OpenPLC Runtime web UI". Probed read-only on the host:

  Server: Werkzeug/3.1.8 Python/3.11.2
  / /login /index.html /programs /status /runtime  -> 404
  /api/v1                                          -> 401 Unauthorized

It is an authenticated REST API with no browser interface at all. Section
4 now says so with the evidence, and 14's entry is restated: 8443 is
contained by the same 10.0.0.17 binding as 502, but unlike 502 it is not
anonymous, which is a better position than that section recorded. The
binding is still the control that matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-09-01 17:09:55 +10:00
parent 0b8ff4b0c3
commit 7d6d90f486
3 changed files with 87 additions and 3 deletions

View file

@ -44,6 +44,7 @@ Then, depending on what you came to do:
| Understand *why* it is built this way | [`spec/BUILD-AI-CONTAINERS.md`](spec/BUILD-AI-CONTAINERS.md) | | Understand *why* it is built this way | [`spec/BUILD-AI-CONTAINERS.md`](spec/BUILD-AI-CONTAINERS.md) |
| Understand the host it runs on | [`spec/YAU_Linux_Host_Onboarding.md`](spec/YAU_Linux_Host_Onboarding.md) | | Understand the host it runs on | [`spec/YAU_Linux_Host_Onboarding.md`](spec/YAU_Linux_Host_Onboarding.md) |
| Know what is still blocked, and on whom | [`status/REQUESTS.md`](status/REQUESTS.md) | | Know what is still blocked, and on whom | [`status/REQUESTS.md`](status/REQUESTS.md) |
| Know what is broken or unfinished, and ours to fix | [`status/OPEN-ISSUES.md`](status/OPEN-ISSUES.md) |
| Change code | `pytest api/tests` first — it needs no network, no API key and no database | | Change code | `pytest api/tests` first — it needs no network, no API key and no database |
**You will need**, and should confirm before starting: SSH to `lin001`, an AD **You will need**, and should confirm before starting: SSH to `lin001`, an AD
@ -227,6 +228,7 @@ status/ where it got to — snapshots, go stale, edited oft
an answer, and what is built. Open it in a browser an answer, and what is built. Open it in a browser
current-state.html what is actually running right now. Open it too current-state.html what is actually running right now. Open it too
REQUESTS.md what is still needed from other people REQUESTS.md what is still needed from other people
OPEN-ISSUES.md what is broken or unfinished, and ours to fix
compose/ deployed to ~/ai-compose.yml and ~/langfuse-compose.yml compose/ deployed to ~/ai-compose.yml and ~/langfuse-compose.yml
caddy/ai-routes.caddy blocks to paste into ~/Caddyfile caddy/ai-routes.caddy blocks to paste into ~/Caddyfile

View file

@ -86,8 +86,14 @@ Consequences worth knowing:
So it is published on the VNet interface only and is not internet-reachable at the So it is published on the VNet interface only and is not internet-reachable at the
Docker level, whatever the NSG says. That is a stronger position than this document Docker level, whatever the NSG says. That is a stronger position than this document
originally assumed, and it is the reason the NSG item in §15 is now a confirmation originally assumed, and it is the reason the NSG item in §15 is now a confirmation
rather than an open risk. **`openplc-runtime` also publishes 8443** — the OpenPLC rather than an open risk. **`openplc-runtime` also publishes 8443** on the same
Runtime web UI — on the same private address; earlier drafts did not mention it. private address; earlier drafts did not mention it. **It is a REST API, not a web
UI** — earlier drafts of this document said web UI and were wrong. Probed read-only
on the host 2026-09-01: `Server: Werkzeug/3.1.8 Python/3.11.2`, `/`, `/login`,
`/index.html`, `/programs`, `/status` and `/runtime` all `404`, and `/api/v1`
returns `401`. There is no browser interface on that port. The OpenPLC **Editor**
a separate desktop tool published by OpenPLC, installed on a workstation — is what
talks to it, and it is not installed anywhere yet (`status/OPEN-ISSUES.md` OI-01).
- **Port 502 still has no authentication and no encryption.** Modbus never has. The - **Port 502 still has no authentication and no encryption.** Modbus never has. The
binding above is what contains it, so anything that changes the binding to `0.0.0.0`, binding above is what contains it, so anything that changes the binding to `0.0.0.0`,
or any NSG rule that exposes the VNet address, removes the only control on it. or any NSG rule that exposes the VNet address, removes the only control on it.
@ -802,7 +808,7 @@ it is the same derivation that will run against `imh`.
- **`ai.yokogawa.tech` is unauthenticated from `cicore1`.** Applied 2026-08-28 at the customer's direction: an on-site operator should not complete a Duo push to ask a question, and nobody outside the plant should reach the assistant at all. The Caddy block admits `remote_ip 10.0.0.21` only and returns 403 to everything else, `import authelia` removed from that branch. This is an IP allowlist on a flat network with no OT/IT boundary — anything that can take `10.0.0.21`, or ARP-spoof it, inherits unauthenticated access to every answer the assistant can give. It is a demo affordance, **not a security control**, and it is the first thing network segmentation closes. Two consequences worth stating separately: Langfuse traces are now anonymous, so there is no record of who asked what; and the assistant is unreachable by browser from the VPN, so engineers need an SSH tunnel. **A third, found 2026-08-31: there is no usage record at the edge either.** The Caddy block has no `log` directive — no site on this host does — so the front door logs nothing, and the only evidence that the console has ever been used is `ai-web`'s container log, which `docker compose up -d --force-recreate` wipes. That is how a working operator path sat unnoticed for three days while the repo recorded it as unproven. **Access logging was considered and declined on 2026-09-01.** The ephemeral evidence is accepted: usage is confirmed as at 31 August and recorded in Phase 7's gate, and if the container is recreated that confirmation stands on the record here rather than in a log. Do not re-raise this as a task. `api.yokogawa.tech` is unchanged and still fully gated — Phase 9 document publishing depends on that and must stay there - **`ai.yokogawa.tech` is unauthenticated from `cicore1`.** Applied 2026-08-28 at the customer's direction: an on-site operator should not complete a Duo push to ask a question, and nobody outside the plant should reach the assistant at all. The Caddy block admits `remote_ip 10.0.0.21` only and returns 403 to everything else, `import authelia` removed from that branch. This is an IP allowlist on a flat network with no OT/IT boundary — anything that can take `10.0.0.21`, or ARP-spoof it, inherits unauthenticated access to every answer the assistant can give. It is a demo affordance, **not a security control**, and it is the first thing network segmentation closes. Two consequences worth stating separately: Langfuse traces are now anonymous, so there is no record of who asked what; and the assistant is unreachable by browser from the VPN, so engineers need an SSH tunnel. **A third, found 2026-08-31: there is no usage record at the edge either.** The Caddy block has no `log` directive — no site on this host does — so the front door logs nothing, and the only evidence that the console has ever been used is `ai-web`'s container log, which `docker compose up -d --force-recreate` wipes. That is how a working operator path sat unnoticed for three days while the repo recorded it as unproven. **Access logging was considered and declined on 2026-09-01.** The ephemeral evidence is accepted: usage is confirmed as at 31 August and recorded in Phase 7's gate, and if the container is recreated that confirmation stands on the record here rather than in a log. Do not re-raise this as a task. `api.yokogawa.tech` is unchanged and still fully gated — Phase 9 document publishing depends on that and must stay there
- Public egress to Azure OpenAI, no private endpoint - Public egress to Azure OpenAI, no private endpoint
- Chromium running on the SCADA VM itself - Chromium running on the SCADA VM itself
- Modbus TCP on port 502 with no authentication or encryption — inherent to the protocol; contained by its bind to `10.0.0.17` plus NSG/VPN scope. The OpenPLC Runtime web UI on 8443 is contained the same way and nothing else - Modbus TCP on port 502 with no authentication or encryption — inherent to the protocol; contained by its bind to `10.0.0.17` plus NSG/VPN scope. Port 8443 on the same container is contained the same way and nothing else — but unlike 502 it is **not** anonymous: it serves a REST API under `/api/v1` that answers `401` unauthenticated, and there is no web UI on it (verified 2026-09-01, §4). That is a better position than earlier drafts of this document recorded, and the binding is still the control that matters: nothing on this container should ever move to `0.0.0.0`
- Single host, no HA — `lin001` is now a single point of failure for **both** the demo estate and the simulated plant's PLC - Single host, no HA — `lin001` is now a single point of failure for **both** the demo estate and the simulated plant's PLC
- Shared `azureuser` login; no per-person audit trail on the host - Shared `azureuser` login; no per-person audit trail on the host
- Shared service account to `imh`; no per-operator row-level security - Shared service account to `imh`; no per-operator row-level security

76
status/OPEN-ISSUES.md Normal file
View file

@ -0,0 +1,76 @@
# Open issues — WRPS Plant Assistant
**Work we own, know about, and intend to do.** One entry per issue, newest first.
Nothing here is assigned yet; owners are set at handover.
## What belongs here, and what does not
This repository has three other registers. Putting an item in the wrong one is how
a repository ends up saying two different things about the same fact.
| If it is... | It goes in | Not here |
|---|---|---|
| Waiting on somebody outside this project | [`REQUESTS.md`](REQUESTS.md) | ✗ |
| A shortcut we **consciously accepted** | [`BUILD-AI-CONTAINERS.md`](../spec/BUILD-AI-CONTAINERS.md) §14 | ✗ |
| Something already running, and its state | [`current-state.html`](current-state.html) | ✗ |
| **A defect or gap we own and have not fixed** | **here** | ✓ |
Three rules:
1. **Anything in §14 is closed by decision.** It was weighed and accepted. Do not
re-open it here — `caddy/ai-routes.caddy` carries one such decision explicitly
marked *"do not re-raise this as a task"*.
2. **If an issue is a defect in something that already passed a phase gate**, fixing
it means re-running that gate. `CLAUDE.md` requires it; say so in the issue.
3. **Closed issues move to the bottom, they are not deleted.** The register is part
of the as-built record, and an issue with no trace of how it closed is worth less
than one that was never raised.
---
## Open
### OI-01 · OpenPLC Editor is not installed
**Raised** 2026-09-01 · **Owner** unassigned · **Affects** the demo plant, not the assistant
The **OpenPLC Editor** — a desktop tool published by the OpenPLC project, installed on a
workstation — is the application used to author and compile the IEC 61131-3 program that
`openplc-runtime` executes. It is not installed anywhere. It talks to the running container
over its REST API on port `8443`, bound to `10.0.0.17`.
**Why it matters.** `openplc-runtime` is live control for this demo: `CLAUDE.md` and
[`BUILD-AI-CONTAINERS.md`](../spec/BUILD-AI-CONTAINERS.md) §4 both forbid reconfiguring it
as a side effect of other work. Without the Editor there is no way to author, review or
compile the control logic — and no reviewable source for it in or beside this repository.
The program exists only inside the container. If the container is lost, so is the logic.
**What port 8443 is** — probed read-only on the host 2026-09-01, because two earlier
statements in the build spec called it a web UI and were wrong:
```
Server: Werkzeug/3.1.8 Python/3.11.2
/ /login /index.html /programs /status /runtime -> 404
/api/v1 -> 401 Unauthorized
```
An authenticated REST API. No browser interface. Both build-spec statements were corrected
in the commit that raised this issue.
**Open questions to settle when this is picked up.**
- **Which workstation.** Not `lin001` — the Editor is a desktop application. Not `cicore1`
`CLAUDE.md` forbids installing anything on it. That leaves an engineering workstation with
VPN or LAN reach to `10.0.0.17:8443`.
- **Credentials for `/api/v1`**, which currently answers `401`. Not held by this project.
- **Whether the PLC program goes under version control**, and where. This is the part that
closes the "logic exists only in the container" gap, and it is the reason this issue is
worth more than "install a tool".
**Blocked by** nothing. **Blocking** nothing today — the demo runs.
---
## Closed
*None yet. Closed issues move here with the commit that closed them.*