Second demo fixture (public-sample shape) to complement the all-rules
pump_station.st: a timed pedestrian-crossing state machine adapted from
the OpenPLC traffic-light example, extended with a SCADA/Modbus uplink and
a maintenance override. Mostly sound control logic with three planted,
field-realistic defects (hardcoded SCADA password, cleartext Modbus master,
maintenance mode that drops the pedestrian safety permit).
The regression test asserts the scanner surfaces those defects while staying
quiet on the guarded duty-cycle division and the JMP-free CASE machine —
demonstrating low false positives on real-world-shaped code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI clippy (rust 1.94.0, overly_complex_bool_expr) flagged the disabling
check as a logic bug: the `watchdog && matches!(value, Int(0))` term is
fully subsumed by the preceding `matches!(value, Int(0))`. Simplify to
`Bool(false) || Int(0)` — behavior is unchanged (a safety/watchdog signal
driven to FALSE or 0 is still a bypass), and `watchdog` stays used in the
outer guard. All 5 PLC tests still pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements ScanType::PlcControlLogic — the missing piece for PlcSps targets,
which previously classified but ran no scan.
New `pipeline::plc`:
- A real IEC 61131-3 Structured Text front end: lexer + recursive-descent parser
→ AST (POUs, typed VAR sections, statements, expressions). Tolerant recovery so
odd constructs never sink a file.
- PLCopen XML extractor: pulls each ST POU's interface vars + `<ST>` body and
reconstructs equivalent ST, so raw `.st` files and PLCopen projects share one
analysis path.
- Eight semantic, guard-aware rules over the AST → findings: hardcoded
credentials, default/weak passwords, safety-interlock/watchdog bypass, array
indexed by unvalidated input, division without a zero-guard (suppressed when an
enclosing `IF <d> <> 0` proves it), insecure comm (auth/encryption disabled),
and cleartext OT protocol ports, plus unstructured JMP. Each carries CWE +
remediation.
- `PlcControlLogicScanner` (Scanner impl) walks the project tree and emits
`Finding`s (dedup fingerprint, file, line, severity).
Wired into `run_target_pipeline`: when the scan plan includes PlcControlLogic,
`run_plc_scan` ingests the PlcProject artifact, analyzes it, and persists the
findings (findings_count handled by run_target).
Demo fixtures under examples/plc-demo/ (a vulnerable pump-station `.st` + a
PLCopen `conveyor.xml`). Tests: parser, all-rules-fire, guarded-clean-is-quiet,
and an end-to-end tree scan — 5 passing.
Adds `roxmltree` (read-only XML) for PLCopen parsing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PR `check` job authenticates the private tramiton-core git fetch, but the main-branch **`deploy-agent`** job builds `Dockerfile.agent` where cargo also fetches tramiton-core — inside the image build, with no credentials — so **agent image builds on main fail**.
Fix: inject the PAT as a **BuildKit secret** (never baked into an image layer).
- `Dockerfile.agent`: `RUN --mount=type=secret,id=tramiton_token …` applies the same `https`-insteadOf rewrite + `CARGO_NET_GIT_FETCH_WITH_CLI=true` before `cargo build`.
- `deploy-agent`: `DOCKER_BUILDKIT=1 docker build --secret id=tramiton_token,env=TRAMITON_FETCH_TOKEN …`, reusing the existing `TRAMITON_FETCH_TOKEN` secret.
Only the agent image depends on tramiton-core (dashboard/mcp/docs unaffected). **Self-tests on merge** — changing `Dockerfile.agent` makes `detect-changes` run `deploy-agent`.
Note: couldn't fully run the image build locally (no PAT value on hand + no `.dockerignore` so the context is large), but this mirrors the working PR-stage auth and uses standard BuildKit secret injection. Assumes the deploy runner's Docker daemon supports BuildKit (docker:27-cli → yes).
---------
Co-authored-by: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com>
Reviewed-on: #143
Adds /mcp-tokens page so a logged-in user can mint, list, and revoke bearer tokens for the MCP server without curl. Pairs with #92's tenant-scoped MCP middleware — copy a token from the dashboard straight into an LLM client config.
Replaces M7.2-C static SCHEDULER_TENANT_IDS with a live query to the tenant-registry at every tick. New tenants picked up without an agent restart; env stays as fallback so a registry outage never silences the scheduler. Resolution order: registry -> SCHEDULER_TENANT_IDS env -> DEFAULT_SCHEDULER_TENANT_ID. Logs the active source on startup.
RUSTSEC-2026-0185 (quinn-proto 0.11.14): patch-bump to 0.11.15. RUSTSEC-2026-0189 (rmcp 0.16 DNS rebinding): added to ignore with public-hostname + bearer-auth threat-model justification; rmcp 0.16->2.x migration tracked as a separate multi-hour PR.
MCP server validates per-tenant bearer tokens on incoming calls and routes each tool to the caller's tenant DB. Closes the cross-tenant data leak in the MCP path identified in M7.3.
GET /api/admin/tenants lists tenant DBs; DELETE /api/admin/tenants/{tenant_id} drops them (GDPR). Behind a separate auth path that rejects customer realm tokens.
Webhook routes live on the separate webhook server (port 3002). M7.2-C URL form is /webhook/{tenant_id}/{platform}/{repo_id}; mounting unscoped variants on the API router would mismatch handler signatures.
## Summary
- **Scan produces no results in Orca** — semgrep (`--config=auto`, unbounded memory) and syft (remote license network calls) were getting OOM-killed or hanging in resource-constrained Orca containers. Scan would "complete" with 0 findings/SBOMs silently because each scanner failure is caught and logged as a warning.
- **Dashboard Script error spam** — `document::Script` in Dioxus 0.7 needs a single text node child for inline scripts; `dangerous_inner_html` was invalid and spammed the error log on every unauthenticated page load.
## Changes
| File | Change |
|------|--------|
| `semgrep.rs` | Add `--max-memory 500 --jobs 1`; 10-minute timeout |
| `syft.rs` | Remove remote license lookup env vars; 5-minute timeout |
| `gitleaks.rs` | 5-minute timeout |
| `app_shell.rs` | Fix `dangerous_inner_html` → text child in `document::Script` |
## Test plan
- [ ] Trigger a scan on a repo in Orca — findings and SBOM entries should now appear
- [ ] Agent logs should show timeout/error warnings rather than silent empty results when tools are killed
- [ ] Navigate to dashboard unauthenticated — Script error gone from logs
- [ ] Verify scans work end-to-end with `docker compose up`
---------
Co-authored-by: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com>
Reviewed-on: #78
Each deploy job now builds the per-service image, pushes to the
private registry as :latest and :sha, then triggers an HMAC-signed
orca redeploy webhook. Coolify webhooks are no longer used.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>