The stubbed `GET .../code` answered with `{ code }` while the real endpoint
is a PlainTextResponse returning the source, which is what crashed the demo
service page, and the save route was registered as POST where the real API
and the frontend both use PUT.
The seeded sample also carried a `strip_debug_header` filter that mutated
`HttpRequest.headers`. That silently does nothing: `headers` is a read-only
property over the parsed message, and nothing re-serialises it onto the wire
(a mangle forwards `current_pkt.raw_packet`, which the dict never touches).
Dropped it, leaving the one filter that behaves as shown, and brought the
filter counts and the packet counters back in line with it.
`genericapi` ran every response body through `JSON.parse`, falling back to
the raw text only when that threw. The nfproxy filter source is served as a
PlainTextResponse, so a filter file that happens to be valid JSON came back
as a number/array/object. Mantine's CodeHighlight then calls `code.trim()`
on it, which throws and takes the whole page down with it. `42`, `"abc"`,
`[1,2]` and `{"a":1}` are all both valid JSON and valid Python, so they
pass the upload validation and leave the service page unreachable.
Responses declaring text/plain are no longer parsed, and the render site
type-checks the code before handing it to CodeHighlight.
The Card carried Mantine's `w="100%"` prop, whose style props land in the
style attribute after anything passed via `style`, so it overrode the pixel
width dnd measures at lift time. On a `position: fixed` element `100%`
resolves against the viewport rather than the list, so grabbing a rule
snapped the card from 734px to 798px and pushed it past the right edge.
Moving the width into `style`, ahead of the dnd spread, leaves normal flow
untouched and lets the measured width win while dragging.
Three separate defects made the rule list impossible to reorder:
- The Card's own `style` was placed after `{...provided.draggableProps}`,
clobbering the `position`/`transform` dnd sets while dragging. The drag
did start, but nothing moved on screen. Styles are now merged with dnd's
winning, and the dragged card is highlighted.
- `Draggable index` used the position in the full `state` array and the
rules of the other table were filtered out afterwards, leaving gaps.
dnd requires contiguous indexes from 0 inside a Droppable, so a single
rule in the other table broke the visible tab entirely. Visible rules
are now re-indexed and mapped back to their `state` position on drop.
- `destination?.index || 0` reordered to the top instead of doing nothing
when a rule was dropped outside the list.
The real frontend, built against an in-memory backend, so the UI can be tried
without a Linux host, NET_ADMIN or a running firewall. A visitor can create,
edit, start, stop and delete services across all five modules and watch the UI
react as it would against a real instance; a reload restores the seed.
- frontend/src/js/demo.ts holds the mock. genericapi() and the socket.io client
are the frontend's only two ways out to the network, so routing both through
it was enough - no component knows the difference. It also emits the same
"update" events the backend does, and ticks the packet counters of running
services so the page looks alive.
- The demo lives behind IS_DEMO, which vite inlines as a literal, so a normal
`bun run build` tree-shakes the module and its seed data away. Verified: no
demo string survives in the production bundle.
- The workflow rebuilds on every push touching frontend/ or docs/ and deploys
to demo.firegex.pwnzer0tt1.it, copying index.html to 404.html so deep links
resolve on Pages.
Rename the flag to --unsafe-disable-auth so the risk is visible at the call
site, and close the ways the mode could outlive itself:
- Keep an explicit -P/--startup-psw instead of dropping it. Without a password
in the database, --no-unsafe-disable-auth brought firegex up in its
initial-setup state, where anyone able to reach it could choose the password.
- Refuse /api/login, /api/set-password and /api/change-password while
authentication is disabled: an anonymous caller could otherwise plant a
credential that kept working once authentication was turned back on.
run.py config --password still works, writing to the database from the host.
- Print a banner on every start/restart with authentication disabled. The flag
is persisted in the config file, so a later bare `run.py start` inherits it.
- Report the change in `run.py config --unsafe-disable-auth`, which claimed
"No configuration changes specified" while persisting it, and stop that
command from nulling the stored port and host on the way.
- Expose the state as auth_disabled in /api/status and hide the logout button
and the change-password entry, which were silent no-ops in this mode.
Tests cover both directions of the toggle, the socket.io handler, the password
endpoints and the -P handling.