Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ jobs:
run: python scripts/outbound_scope_regression.py build/Src/App/Release/NullockApp.exe
- name: control API and Repeater responsiveness
run: python scripts/control_responsiveness_regression.py build/Src/App/Release/NullockApp.exe
- name: native Repeater Send and Stop controls
run: python scripts/native_repeater_controls_test.py
- name: workspace crash recovery
run: python scripts/workspace_recovery_regression.py build/Src/App/Release/NullockApp.exe
- name: authenticated scanner regression
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ developer-facing record.

## [Unreleased]

### Native Repeater responsiveness
- Run native-window sends on the existing background worker. Show Send/Stop
progress and disable Send/Clear while the request is active.
- Copy the visible target fields before sending, reject blank hosts and ports
outside 1–65535, and expose the pending cancellation state to QML.
- Stop prevents further redirects after the current response or transport
timeout. Immediate socket cancellation and simultaneous sends remain open work.

### Reflected HTML context handling
- Keep HTML template contents inert, including nested templates and apparent
closing tags inside raw text, comments or attributes.
Expand Down
28 changes: 26 additions & 2 deletions Src/App/app.qml
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ ApplicationWindow {
spacing: 8
HeaderCell { text: "Target" }
TextField {
id: repeaterHostField
Layout.preferredWidth: 280
placeholderText: "host"
text: repeater.host
Expand All @@ -686,8 +687,10 @@ ApplicationWindow {
}
HeaderCell { text: ":" }
TextField {
id: repeaterPortField
Layout.preferredWidth: 80
text: "" + repeater.port
validator: IntValidator { bottom: 1; top: 65535; locale: "C" }
color: root.text
font.family: "Consolas"
font.pixelSize: 12
Expand All @@ -701,11 +704,32 @@ ApplicationWindow {
Item { Layout.fillWidth: true }
Cell { text: repeater.statusLine; color: root.accent; Layout.preferredWidth: 200 }
AccentButton {
label: repeater.busy ? "..." : "Send"
onClicked: repeater.send()
label: repeater.cancelling ? "Stopping..." : "Stop"
visible: repeater.busy
enabled: !repeater.cancelling
opacity: enabled ? 1 : 0.5
onClicked: repeater.cancel()
ToolTip.visible: stopHover.hovered
ToolTip.text: "Stops after the current response or transport timeout; skips further redirects."
HoverHandler { id: stopHover }
}
AccentButton {
label: repeater.busy ? "Sending..." : "Send"
enabled: !repeater.busy && repeaterHostField.text.trim().length > 0
&& repeaterPortField.acceptableInput && repeater.requestText.length > 0
opacity: enabled ? 1 : 0.5
onClicked: {
// Rectangle buttons do not necessarily take focus from a field.
// Commit the visible target before copying it into the worker.
repeater.host = repeaterHostField.text.trim()
repeater.port = Number(repeaterPortField.text)
repeater.sendAsync()
}
}
AccentButton {
label: "Clear"
enabled: !repeater.busy
opacity: enabled ? 1 : 0.5
onClicked: repeater.clear()
}
}
Expand Down
1 change: 1 addition & 0 deletions Src/Core/Networking/repeater.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class Repeater : public QObject {
Q_PROPERTY(qint64 elapsedMs READ elapsedMs NOTIFY responseChanged)
Q_PROPERTY(int responseBytes READ responseBytes NOTIFY responseChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
Q_PROPERTY(bool cancelling READ cancelling NOTIFY busyChanged)
Q_PROPERTY(int activeTab READ activeTab NOTIFY tabsChanged)
Q_PROPERTY(int tabCount READ tabCount NOTIFY tabsChanged)
// Recompute Content-Length from the actual body before each send (Burp's
Expand Down
5 changes: 3 additions & 2 deletions docs/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ <h1 class="h1-page grad-text" style="margin:10px 0 0;">What shipped</h1>
<div class="rel-body">
<p class="rel-note">The development build includes corrected security checks, certificate-cache validation and Repeater CLI fixes; v3.7.0 remains the latest public release, and the v3.8.0 notes below are a draft.</p>
<ul>
<li><span class="tag fix">fix</span> <strong>Native Repeater stays responsive while sending.</strong> The native window now uses the same background worker as the browser and exposes Stop progress. Send uses the visible target fields and rejects blank hosts or invalid ports. Stop still takes effect after the current response or transport timeout; immediate socket cancellation remains on the roadmap.</li>
<li><span class="tag fix">fix</span> <strong>Reflected HTML respects templates and namespaces.</strong> Inert HTML templates no longer produce findings, while SVG and MathML elements follow their own parsing contexts. Nested templates, CDATA, escaped script text and malformed tag boundaries are covered by 1,255 Chromium comparisons and 13 new live API checks. The roadmap keeps the probe partial because framesets and full browser parsing still need work; a reflected marker does not prove script execution.</li>
<li><span class="tag fix">fix</span> <strong>Repeater preserves binary responses.</strong> Request Hex honors UTF-8 or Latin-1. Response Hex and <strong>Save bytes</strong> use either the body bytes or the original wire response, including compression and chunk framing. Downloads keep the full content beyond the 64 KiB preview. Current responses survive project reloads; past sends retain exact bytes for the current session. Empty compressed bodies and CRLF-separated Body views are also corrected.</li>
<li><span class="tag fix">fix</span> <strong>Build follow-up and roadmap corrections.</strong> The <a href="https://github.com/Bikebrainz/Nullock/blob/Nullock/docs/reviews/2026-09-29-build-follow-up.md">September 29 checklist</a> records remaining byte-fidelity, HTML-context, cancellation, fixture-isolation and certificate-lifecycle work. Scope enforcement evidence is refreshed, and the native window's blocking Send path is called out separately.</li>
<li><span class="tag fix">fix</span> <strong>Build follow-up and roadmap corrections.</strong> The <a href="https://github.com/Bikebrainz/Nullock/blob/Nullock/docs/reviews/2026-09-29-build-follow-up.md">September 29 checklist</a> now records completed byte-fidelity, HTML-context and native-window improvements alongside the remaining cancellation, fixture-isolation and certificate-lifecycle work. Scope enforcement evidence is refreshed.</li>
<li><span class="tag fix">fix</span> <strong>Request files keep their binary bodies.</strong> Repeater CLI file and stdin loading preserves NUL bytes, non-UTF-8 data and trailing newlines. Invalid input leaves the draft unchanged; an empty file explicitly clears it. Tab summaries distinguish fresh tabs from completed responses, and duplicated tabs clear their old response metadata. Empty drafts and blank targets are rejected instead of reporting an old response as a successful send.</li>
<li><span class="tag fix">fix</span> <strong>Repeater CLI loads the right request and waits for sends.</strong> History IDs select the complete captured request, including binary bodies. Send polling handles pending responses and reports refusals or unfinished waits. The new <code class="inline">repeater stop</code> command stops after the current response. The roadmap now distinguishes working views and responsive sends from the remaining byte-fidelity and immediate-cancellation gaps.</li>
<li><span class="tag fix">fix</span> <strong>Reflected-XSS checks respect HTML tag boundaries.</strong> Iframe fallback text stays inert; custom tag names and abruptly closed comments no longer hide reflections. The roadmap now marks this probe as partial because template and SVG/MathML context handling still needs work.</li>
<li><span class="tag fix">fix</span> <strong>Reflected-XSS checks respect HTML tag boundaries.</strong> Iframe fallback text stays inert; custom tag names and abruptly closed comments no longer hide reflections. The roadmap records the additional context fixes above and the remaining browser-parsing limits.</li>
<li><span class="tag fix">fix</span> <strong>Content-Type values drive reflected-XSS decisions.</strong> A plain-text parameter mentioning <code class="inline">text/html</code> no longer triggers an HTML finding. Repeated fields use the last usable media type, quoted commas stay inside parameters, and unknown types follow the browser's sniffing rules.</li>
<li><span class="tag fix">fix</span> <strong>Recover from stale or damaged certificate caches.</strong> After a restart, cached proxy certificates must match the current CA, private key and requested DNS/IP name. Invalid signatures and damaged or mismatched keys trigger replacement; valid cached certificates are retained.</li>
<li><span class="tag fix">fix</span> <strong>Proxy certificates work with strict TLS clients.</strong> Generated certificates explicitly include key identifiers on both OpenSSL and LibreSSL. Older cached leaves missing an authority key identifier are replaced when loaded after a restart.</li>
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ <h2 class="h2-section">Project notes (unreleased)</h2>

<section id="responsive-repeater" class="doc-sec">
<h2 class="h2-section">Repeater while a request runs (unreleased)</h2>
<p>Browser, API and CLI Repeater sends run in the background. Their busy state remains visible while you inspect other tools or edit the next request. The response and history belong to the request that was actually sent; later edits stay in the request pane. One Repeater request runs at a time, and project switching or closing tabs waits until it finishes. The native QML Send button still uses the synchronous path.</p>
<p>Browser, native-window, API and CLI Repeater sends run in the background. Their busy state remains visible while you inspect other tools or edit the next request. The response and history belong to the request that was actually sent; later edits stay in the request pane. One Repeater request runs at a time, and project switching or closing tabs waits until it finishes. The native window exposes Send/Stop progress, validates the visible host and port, and disables Send/Clear until the request completes.</p>
<p><strong>Hex / download</strong> selects the response bytes to inspect or save. <strong>Body bytes</strong> excludes HTTP chunk framing and decodes supported gzip/deflate compression; <strong>Wire response bytes</strong> includes the original headers, compressed data and framing. <strong>Save bytes</strong> downloads the full selected content even when Hex stops at its 64 KiB preview limit. Text views are readable UTF-8 representations; use Hex or the binary download for non-text content. Request Hex uses the request's UTF-8 or Latin-1 encoding.</p>
<p><strong>Stop</strong> takes effect after the current response or transport timeout. It prevents any further redirects; it cannot undo a request already sent to the server. A slow client uploading to the control API no longer stalls the UI. Some other synchronous scanner and extension handlers still share the UI thread.</p>
</section>
Expand Down
19 changes: 12 additions & 7 deletions docs/reviews/2026-09-29-build-follow-up.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,21 @@ and proof of arbitrary script execution remain outside this marker check.

### 3. Finish Repeater cancellation and native-window responsiveness

- [ ] Route the native QML Send button through the asynchronous send path and
- [x] Route the native QML Send button through the asynchronous send path and
expose the same Stop state as the browser.
- [ ] Add cancellation to the socket-owning worker during connection, TLS,
header and body waits.
- [ ] Keep concurrent sends from separate tabs as a separate design task.

**Evidence:** browser/API/CLI sends use `sendAsync`, but `Src/App/app.qml` still
calls `repeater.send()` directly. Current Stop sets an atomic flag checked
between responses and redirects; it does not interrupt a stalled socket.
`HttpClient` uses per-wait timeouts and a five-minute total read budget.
**Completed September 30:** [c94f5388](https://github.com/Bikebrainz/Nullock/commit/c94f5388)
routes native Send through `sendAsync`, exposes Stop/pending state, disables
Send/Clear during work and copies the visible host/port before sending. A QML
harness executes the actual controls with a test backend; the real application
passes 64 native-window runtime checks and 20 control responsiveness checks.

**Remaining evidence:** Stop sets an atomic flag checked between responses and
redirects; it does not interrupt a stalled socket. `HttpClient` uses per-wait
timeouts and a five-minute total read budget.

**Done when:** a controlled stalled connection, TLS handshake, header, fixed body
and chunked body can each be stopped promptly without cross-thread socket use.
Expand Down Expand Up @@ -155,8 +160,8 @@ key permissions and test both OpenSSL and LibreSSL certificate generation.
This pass corrects the obsolete Repeater scope-bypass claim: the shared
`HttpClient` checks outbound scope before connecting, including redirects, and
the API snapshots the request synchronously before scheduling its worker.
The 41-check outbound-scope regression passes. The native window's remaining
blocking Send behavior is a separate issue described above.
The 41-check outbound-scope regression passes. Native Send now uses the same
background worker; interrupting a stalled socket remains open as described above.

## Verification to carry forward

Expand Down
Loading
Loading