From 93c8a782fc456a939acfa5fe23df5f8898569c06 Mon Sep 17 00:00:00 2001 From: blessdyb Date: Fri, 2 Oct 2026 02:35:03 -0700 Subject: [PATCH 1/2] Rewrite form bodies, not only JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A request rule's body edit did nothing against a form POST. Reported with a "Set stockApi = http://localhost/admin" rule that never fired: the request Chrome made was application/x-www-form-urlencoded — stockApi=http%3A%2F%2Fstock.example.net%3A8080%2Fcheck and the body edit was gated on the body parsing as a JSON object, so a form body fell through untouched. The dialog said as much ("Only requests with a JSON body are changed"), which made it correct behaviour and a useless feature for the most common kind of body there is. So a body edit now also applies to a form body. When the body is not JSON and the request carries a Content-Type of application/x-www-form-urlencoded, the path is a field name taken whole — a form is flat, so `a.b` is a field literally named that, not a nested one — and the value is used as typed, because a form has no types. Fields are parsed into ordered pairs that may repeat (set edits the first, remove drops all), and re-encoded the way a browser does: + for space, everything else percent-encoded, so http://localhost/admin goes on the wire as http%3A%2F%2Flocalhost%2Fadmin. Content-Length is reframed to match, as for JSON. The Content-Type header is the signal: a body that merely looks like a form, with no such header, is still left alone. JSON is tried first and is unchanged. Co-Authored-By: Claude Opus 4.8 --- Flowlight/Inspection/RewriteRule.swift | 127 ++++++++++++++++++++++--- Flowlight/UI/RewriteRulesView.swift | 8 +- FlowlightTests/RewriteRuleTests.swift | 77 +++++++++++++++ 3 files changed, 193 insertions(+), 19 deletions(-) diff --git a/Flowlight/Inspection/RewriteRule.swift b/Flowlight/Inspection/RewriteRule.swift index e0ee6055..054dda30 100644 --- a/Flowlight/Inspection/RewriteRule.swift +++ b/Flowlight/Inspection/RewriteRule.swift @@ -22,9 +22,10 @@ struct HeaderEdit: Codable, Equatable, Identifiable, Sendable { } } -/// An edit to the request's JSON body, addressed by a dotted key path into objects (`metadata.user`). `set` creates -/// the path if needed; `remove` deletes the leaf. The value is parsed as JSON when it can be (`0.7`, `true`, -/// `{"a":1}`) and taken as a plain string otherwise. +/// An edit to the request's body. For a JSON body, `path` is a dotted key path into objects (`metadata.user`): +/// `set` creates the path if needed, `remove` deletes the leaf, and the value is parsed as JSON when it can be +/// (`0.7`, `true`, `{"a":1}`) and taken as a plain string otherwise. For a form body +/// (`application/x-www-form-urlencoded`), `path` is a field name taken whole and the value is used as typed. struct BodyEdit: Codable, Equatable, Identifiable, Sendable { enum Op: String, Codable, Sendable, CaseIterable { case set, remove } var id = UUID() @@ -46,8 +47,7 @@ struct BodyEdit: Codable, Equatable, Identifiable, Sendable { } } -/// A rule that rewrites a matching outgoing request before it is forwarded upstream — changing headers or the JSON -/// body. Like a mock, but it edits the request and lets it through rather than answering it: add an `Authorization` +/// A rule that rewrites a matching outgoing request before it is forwarded upstream — changing headers or the body (JSON or form). Like a mock, but it edits the request and lets it through rather than answering it: add an `Authorization` /// header, pin `model`, strip a tracking field. Matched like a mock (host / path glob / method), and applied by the /// same intervention path the guardrails use. Only requests Flowlight decrypts and can buffer (bodies up to a few MB, /// not chunked or streamed) can be rewritten. @@ -139,18 +139,35 @@ enum RewriteRules { } let bodyEdits = applicable.flatMap(\.body) - if !bodyEdits.isEmpty, !bodyData.isEmpty, - var json = (try? JSONSerialization.jsonObject(with: bodyData)) as? [String: Any] { - var changed = false - for edit in bodyEdits { - let comps = edit.path.split(separator: ".").map(String.init) - guard !comps.isEmpty else { continue } - switch edit.op { - case .set: setJSON(&json, path: comps, value: parseValue(edit.value)); changed = true; notes.append("set \(edit.path)") - case .remove: removeJSON(&json, path: comps); changed = true; notes.append("removed \(edit.path)") + if !bodyEdits.isEmpty, !bodyData.isEmpty { + if var json = (try? JSONSerialization.jsonObject(with: bodyData)) as? [String: Any] { + var changed = false + for edit in bodyEdits { + let comps = edit.path.split(separator: ".").map(String.init) + guard !comps.isEmpty else { continue } + switch edit.op { + case .set: setJSON(&json, path: comps, value: parseValue(edit.value)); changed = true; notes.append("set \(edit.path)") + case .remove: removeJSON(&json, path: comps); changed = true; notes.append("removed \(edit.path)") + } } + if changed, let out = try? JSONSerialization.data(withJSONObject: json) { bodyData = out } + } else if isFormEncoded(headerLines), var form = FormBody(bodyData) { + // A form body is flat, so a path is a field name taken whole — `a.b` means a field literally + // named `a.b`, not a nested one. The value is always text; forms have no types, so it is used + // verbatim rather than through `parseValue`. + var changed = false + for edit in bodyEdits { + let field = edit.path + guard !field.isEmpty else { continue } + switch edit.op { + case .set: + form.set(field, to: edit.value); changed = true; notes.append("set \(field)") + case .remove: + if form.remove(field) { changed = true; notes.append("removed \(field)") } + } + } + if changed { bodyData = form.encoded() } } - if changed, let out = try? JSONSerialization.data(withJSONObject: json) { bodyData = out } } guard !notes.isEmpty else { return nil } @@ -174,6 +191,19 @@ enum RewriteRules { return s } + /// Whether the request carries an `application/x-www-form-urlencoded` body, from its Content-Type header. + /// + /// A `charset` or other parameter may follow (`...; charset=utf-8`), so this matches the media type as a + /// prefix rather than the whole value. + static func isFormEncoded(_ headerLines: [String]) -> Bool { + for line in headerLines where line.lowercased().hasPrefix("content-type:") { + let value = line.drop(while: { $0 != ":" }).dropFirst() + .trimmingCharacters(in: .whitespaces).lowercased() + return value.hasPrefix("application/x-www-form-urlencoded") + } + return false + } + private static func setJSON(_ object: inout [String: Any], path: [String], value: Any) { guard let key = path.first else { return } if path.count == 1 { object[key] = value; return } @@ -190,3 +220,70 @@ enum RewriteRules { object[key] = child } } + +/// An `application/x-www-form-urlencoded` body as its ordered `name=value` pairs. +/// +/// Ordered rather than a dictionary, and able to hold the same name twice, because a form can — and a rewrite +/// that silently collapsed `tag=a&tag=b` into one field would change a request in a way nobody asked for. Set +/// edits the first pair of that name in place (or appends when there is none); remove drops every pair of that +/// name. Decoding and re-encoding follow the form rules: `+` is a space, everything else is percent-encoded. +struct FormBody { + private var pairs: [(name: String, value: String)] + + /// Parses a form body. Fails only if the bytes are not UTF-8; an empty or shapeless body parses to no pairs, + /// which is a body a `set` can still add a field to. + init?(_ data: Data) { + guard let text = String(data: data, encoding: .utf8) else { return nil } + pairs = [] + guard !text.isEmpty else { return } + for part in text.components(separatedBy: "&") where !part.isEmpty { + if let eq = part.firstIndex(of: "=") { + let name = Self.decode(String(part[part.startIndex.. Bool { + let before = pairs.count + pairs.removeAll { $0.name == name } + return pairs.count != before + } + + /// Re-encodes the pairs. Field order is preserved so a diff reads as the one change that was made. + func encoded() -> Data { + let body = pairs.map { "\(Self.encode($0.name))=\(Self.encode($0.value))" }.joined(separator: "&") + return Data(body.utf8) + } + + /// Form-decode one component: `+` is a space, then `%XX` is a byte. A malformed `%` is left as written + /// rather than dropped, so a value is never silently truncated. + private static func decode(_ s: String) -> String { + s.replacingOccurrences(of: "+", with: " ").removingPercentEncoding + ?? s.replacingOccurrences(of: "+", with: " ") + } + + /// Form-encode one component. Everything outside the unreserved set is percent-encoded and space becomes + /// `+`, which is what a browser emits — so `http://localhost/admin` becomes `http%3A%2F%2Flocalhost%2Fadmin`. + private static func encode(_ s: String) -> String { + let unreserved = CharacterSet(charactersIn: + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*-._ ") + let percent = s.addingPercentEncoding(withAllowedCharacters: unreserved) ?? s + return percent.replacingOccurrences(of: " ", with: "+") + } +} diff --git a/Flowlight/UI/RewriteRulesView.swift b/Flowlight/UI/RewriteRulesView.swift index f3a7f02f..4a11a43d 100644 --- a/Flowlight/UI/RewriteRulesView.swift +++ b/Flowlight/UI/RewriteRulesView.swift @@ -1,6 +1,6 @@ import SwiftUI -/// Modify requests: rules that edit an outgoing request's headers or JSON body before it's forwarded. Sits with the +/// Modify requests: rules that edit an outgoing request's headers or body (JSON or form) before it's forwarded. Sits with the /// rest of inspection setup — a rule can only change a request Flowlight decrypts. struct RewriteRulesSection: View { @ObservedObject var inspection: InspectionController @@ -9,7 +9,7 @@ struct RewriteRulesSection: View { var body: some View { VStack(alignment: .leading, spacing: 10) { - Text(L("Change a request on its way out — add or replace a header, pin a field in the JSON body, or strip one — and let it continue to the server. Like a mock, but it edits the request instead of answering it.")) + Text(L("Change a request on its way out — add or replace a header, pin a field in the JSON or form body, or strip one — and let it continue to the server. Like a mock, but it edits the request instead of answering it.")) .font(.caption).foregroundStyle(.secondary).fixedSize(horizontal: false, vertical: true) Label(L("Only requests Flowlight decrypts can be rewritten, and only buffered ones — bodies up to a few megabytes. Tunnelled, pinned, chunked or streamed uploads pass through untouched."), systemImage: "info.circle") @@ -155,7 +155,7 @@ struct RewriteRuleEditor: View { // Body edits VStack(alignment: .leading, spacing: 6) { - Text(L("JSON body")).font(.caption.bold()).foregroundStyle(.secondary) + Text(L("Request body")).font(.caption.bold()).foregroundStyle(.secondary) ForEach($rule.body) { $edit in HStack(spacing: 6) { Picker("", selection: $edit.op) { @@ -171,7 +171,7 @@ struct RewriteRuleEditor: View { } } Button(L("Add body edit")) { rule.body.append(BodyEdit()) }.controlSize(.small) - Text(L("Dotted path into the JSON object (metadata.user). A value that is valid JSON (0.7, true, {\"a\":1}) is used as-is; anything else is a string. Only requests with a JSON body are changed.")) + Text(L("For a JSON body, a dotted path into the object (metadata.user); a value that is valid JSON (0.7, true, {\"a\":1}) is used as-is, anything else is a string. For a form body (application/x-www-form-urlencoded), the field name, taken whole, set to the text as typed. Only requests with one of those two bodies are changed.")) .font(.caption).foregroundStyle(.secondary).fixedSize(horizontal: false, vertical: true) } diff --git a/FlowlightTests/RewriteRuleTests.swift b/FlowlightTests/RewriteRuleTests.swift index bf28c02e..0e869a5f 100644 --- a/FlowlightTests/RewriteRuleTests.swift +++ b/FlowlightTests/RewriteRuleTests.swift @@ -103,6 +103,83 @@ final class RewriteRuleTests: XCTestCase { XCTAssertTrue(parts(out).head.contains("X-Tag: 1")) } + // MARK: Form bodies + + private let form = "Content-Type: application/x-www-form-urlencoded" + + private func bodyForm(_ data: Data) -> [String: String] { + guard let sep = data.range(of: Data("\r\n\r\n".utf8)) else { return [:] } + let body = String(decoding: data[sep.upperBound...], as: UTF8.self) + var out: [String: String] = [:] + for part in body.components(separatedBy: "&") where !part.isEmpty { + let halves = part.components(separatedBy: "=") + let name = (halves.first ?? "").removingPercentEncoding ?? "" + let value = halves.dropFirst().joined(separator: "=") + .replacingOccurrences(of: "+", with: " ").removingPercentEncoding ?? "" + out[name] = value + } + return out + } + + /// The reported case: a form field set to a URL, the value percent-encoded, Content-Length reframed. This is + /// what the "Set stockApi = http://localhost/admin" rule did nothing for, because the body is a form, not JSON. + func testFormSetEncodesValue() throws { + let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .set, path: "stockApi", value: "http://localhost/admin")]) + let out = try XCTUnwrap(apply(rule, request("POST", "/product/stock", headers: [form], + body: "stockApi=http%3A%2F%2Fstock.example.net%3A8080%2Fcheck"), + path: "/product/stock")) + XCTAssertEqual(bodyForm(out)["stockApi"], "http://localhost/admin") + // The value is percent-encoded on the wire, and Content-Length agrees with the final body. + XCTAssertTrue(parts(out).body.contains("http%3A%2F%2Flocalhost%2Fadmin")) + let declared = parts(out).head.first { $0.lowercased().hasPrefix("content-length:") }? + .components(separatedBy: ": ").last.flatMap { Int($0) } + let sep = try XCTUnwrap(out.range(of: Data("\r\n\r\n".utf8))) + XCTAssertEqual(declared, out.distance(from: sep.upperBound, to: out.endIndex)) + } + + /// Set adds a field that was not there, leaving the others in place and in order. + func testFormSetAppendsNewField() throws { + let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .set, path: "role", value: "admin")]) + let out = try XCTUnwrap(apply(rule, request("POST", "/login", headers: [form], body: "user=alice&pass=x"), path: "/login")) + XCTAssertEqual(bodyForm(out)["user"], "alice") + XCTAssertEqual(bodyForm(out)["pass"], "x") + XCTAssertEqual(bodyForm(out)["role"], "admin") + } + + /// Remove drops a field; removing one that is not there is no change. + func testFormRemove() throws { + let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .remove, path: "csrf")]) + let out = try XCTUnwrap(apply(rule, request("POST", "/login", headers: [form], body: "user=alice&csrf=tok"), path: "/login")) + XCTAssertNil(bodyForm(out)["csrf"]) + XCTAssertEqual(bodyForm(out)["user"], "alice") + + let miss = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .remove, path: "nope")]) + XCTAssertNil(apply(miss, request("POST", "/login", headers: [form], body: "user=alice"), path: "/login")) + } + + /// Set replaces the first pair of a repeated field and leaves the rest, rather than collapsing them. + func testFormSetReplacesInPlaceKeepingOrder() throws { + let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .set, path: "tag", value: "z")]) + let out = try XCTUnwrap(apply(rule, request("POST", "/x", headers: [form], body: "tag=a&tag=b&keep=1"), path: "/x")) + let body = String(decoding: out[(out.range(of: Data("\r\n\r\n".utf8))!.upperBound)...], as: UTF8.self) + XCTAssertEqual(body, "tag=z&tag=b&keep=1") + } + + /// Without the form Content-Type, a body shaped like a form is still left untouched — the header is the signal. + func testFormBodyWithoutContentTypeIsNotTouched() { + let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .set, path: "a", value: "2")]) + XCTAssertNil(apply(rule, request("POST", "/x", body: "a=1&b=2"), path: "/x")) + } + + /// A charset parameter after the media type does not stop it being recognised as a form. + func testFormContentTypeWithCharset() throws { + let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .set, path: "a", value: "2")]) + let out = try XCTUnwrap(apply(rule, request("POST", "/x", + headers: ["Content-Type: application/x-www-form-urlencoded; charset=utf-8"], + body: "a=1"), path: "/x")) + XCTAssertEqual(bodyForm(out)["a"], "2") + } + // MARK: Matching /// A rule for another host, method or path doesn't touch the request. From bbecd5c7d80db74280f7316578f9d4122ee61b84 Mon Sep 17 00:00:00 2001 From: blessdyb Date: Sat, 3 Oct 2026 21:30:06 -0700 Subject: [PATCH 2/2] Flowlight 0.13.1 Rewrite rules now support application/x-www-form-urlencoded request bodies, so form fields can be set or removed before the request is forwarded. Preserve repeated fields and field order, re-encode values like a browser, and reframe Content-Length. Add form-body coverage and release metadata for 0.13.1. Co-Authored-By: Claude Code --- docs/404.html | 2 +- docs/about/index.html | 2 +- docs/de/about/index.html | 2 +- docs/de/docs/index.html | 4 ++-- docs/de/index.html | 6 +++--- docs/de/privacy/index.html | 2 +- docs/de/threat-model/index.html | 2 +- docs/docs/index.html | 4 ++-- docs/es/about/index.html | 2 +- docs/es/docs/index.html | 4 ++-- docs/es/index.html | 6 +++--- docs/es/privacy/index.html | 2 +- docs/es/threat-model/index.html | 2 +- docs/fr/about/index.html | 2 +- docs/fr/docs/index.html | 4 ++-- docs/fr/index.html | 6 +++--- docs/fr/privacy/index.html | 2 +- docs/fr/threat-model/index.html | 2 +- docs/index.html | 6 +++--- docs/it/about/index.html | 2 +- docs/it/docs/index.html | 4 ++-- docs/it/index.html | 6 +++--- docs/it/privacy/index.html | 2 +- docs/it/threat-model/index.html | 2 +- docs/ja/about/index.html | 2 +- docs/ja/docs/index.html | 4 ++-- docs/ja/index.html | 6 +++--- docs/ja/privacy/index.html | 2 +- docs/ja/threat-model/index.html | 2 +- docs/ko/about/index.html | 2 +- docs/ko/docs/index.html | 4 ++-- docs/ko/index.html | 6 +++--- docs/ko/privacy/index.html | 2 +- docs/ko/threat-model/index.html | 2 +- docs/llms-full.txt | 14 +++++++++++--- docs/llms.txt | 2 +- docs/privacy/index.html | 2 +- docs/pt-PT/about/index.html | 2 +- docs/pt-PT/docs/index.html | 4 ++-- docs/pt-PT/index.html | 6 +++--- docs/pt-PT/privacy/index.html | 2 +- docs/pt-PT/threat-model/index.html | 2 +- docs/releases/index.html | 14 ++++++++++++-- docs/sitemap.xml | 2 +- docs/threat-model/index.html | 2 +- docs/zh-Hans/about/index.html | 2 +- docs/zh-Hans/docs/index.html | 4 ++-- docs/zh-Hans/index.html | 6 +++--- docs/zh-Hans/privacy/index.html | 2 +- docs/zh-Hans/threat-model/index.html | 2 +- docs/zh-Hant/about/index.html | 2 +- docs/zh-Hant/docs/index.html | 4 ++-- docs/zh-Hant/index.html | 6 +++--- docs/zh-Hant/privacy/index.html | 2 +- docs/zh-Hant/threat-model/index.html | 2 +- project.yml | 2 +- site/pages/releases.html | 12 +++++++++++- 57 files changed, 118 insertions(+), 90 deletions(-) diff --git a/docs/404.html b/docs/404.html index f871b1fc..fa38be00 100644 --- a/docs/404.html +++ b/docs/404.html @@ -80,7 +80,7 @@

That page isn't here

Try the home page,

diff --git a/docs/about/index.html b/docs/about/index.html index 7bd16466..0cd7817f 100644 --- a/docs/about/index.html +++ b/docs/about/index.html @@ -138,7 +138,7 @@

Thanks

diff --git a/docs/de/about/index.html b/docs/de/about/index.html index f231b8e5..fdd3c5d0 100644 --- a/docs/de/about/index.html +++ b/docs/de/about/index.html @@ -137,7 +137,7 @@

Dank

diff --git a/docs/de/docs/index.html b/docs/de/docs/index.html index 435ac392..c5521986 100644 --- a/docs/de/docs/index.html +++ b/docs/de/docs/index.html @@ -61,7 +61,7 @@

Dokumentation

Flowlight benutzen

-

Alles vom ersten Start bis zum Feinschliff an den Agentenregeln. Flowlight 0.13.0, macOS 15 oder neuer.

+

Alles vom ersten Start bis zum Feinschliff an den Agentenregeln. Flowlight 0.13.1, macOS 15 oder neuer.

diff --git a/docs/de/index.html b/docs/de/index.html index 9aca252a..e0d605d8 100644 --- a/docs/de/index.html +++ b/docs/de/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

Sieh, was deine Apps im Netz tun.
brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+UniversalGPL-3.0Keine Telemetrie

+

v0.13.1macOS 15+UniversalGPL-3.0Keine Telemetrie

@@ -616,7 +616,7 @@

Wisse, was deinen Mac verlässt.

diff --git a/docs/de/privacy/index.html b/docs/de/privacy/index.html index 822d7b21..328e10e1 100644 --- a/docs/de/privacy/index.html +++ b/docs/de/privacy/index.html @@ -173,7 +173,7 @@

Kontakt

diff --git a/docs/de/threat-model/index.html b/docs/de/threat-model/index.html index b01b8ab0..fb0175d5 100644 --- a/docs/de/threat-model/index.html +++ b/docs/de/threat-model/index.html @@ -213,7 +213,7 @@

Eine Schwachstelle melden

diff --git a/docs/docs/index.html b/docs/docs/index.html index c71c6e0f..00d53c83 100644 --- a/docs/docs/index.html +++ b/docs/docs/index.html @@ -61,7 +61,7 @@

Documentation

Using Flowlight

-

Everything from the first launch to tuning the agent rules. Flowlight 0.13.0, macOS 15 or later.

+

Everything from the first launch to tuning the agent rules. Flowlight 0.13.1, macOS 15 or later.

diff --git a/docs/es/about/index.html b/docs/es/about/index.html index d689032f..fd1d911d 100644 --- a/docs/es/about/index.html +++ b/docs/es/about/index.html @@ -137,7 +137,7 @@

Agradecimientos

diff --git a/docs/es/docs/index.html b/docs/es/docs/index.html index 0bf4de8e..03d929e6 100644 --- a/docs/es/docs/index.html +++ b/docs/es/docs/index.html @@ -61,7 +61,7 @@

Documentación

Usar Flowlight

-

Todo, desde el primer arranque hasta el ajuste de las reglas de agentes. Flowlight 0.13.0, macOS 15 o posterior.

+

Todo, desde el primer arranque hasta el ajuste de las reglas de agentes. Flowlight 0.13.1, macOS 15 o posterior.

diff --git a/docs/es/index.html b/docs/es/index.html index ac26d224..40a81f15 100644 --- a/docs/es/index.html +++ b/docs/es/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

Observa la actividad de red de tus apps.

brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+UniversalGPL-3.0Sin telemetría

+

v0.13.1macOS 15+UniversalGPL-3.0Sin telemetría

@@ -616,7 +616,7 @@

Ten claro qué sale de tu Mac.

diff --git a/docs/es/privacy/index.html b/docs/es/privacy/index.html index 8ebc5f07..a1d50be1 100644 --- a/docs/es/privacy/index.html +++ b/docs/es/privacy/index.html @@ -173,7 +173,7 @@

Contacto

diff --git a/docs/es/threat-model/index.html b/docs/es/threat-model/index.html index 69681702..3acca5ba 100644 --- a/docs/es/threat-model/index.html +++ b/docs/es/threat-model/index.html @@ -208,7 +208,7 @@

Informar de una vulnerabilidad

diff --git a/docs/fr/about/index.html b/docs/fr/about/index.html index 32bdea25..029349df 100644 --- a/docs/fr/about/index.html +++ b/docs/fr/about/index.html @@ -137,7 +137,7 @@

Remerciements

diff --git a/docs/fr/docs/index.html b/docs/fr/docs/index.html index fb2bb297..ac81a035 100644 --- a/docs/fr/docs/index.html +++ b/docs/fr/docs/index.html @@ -61,7 +61,7 @@

Documentation

Utiliser Flowlight

-

Tout, du premier lancement au réglage des règles des agents. Flowlight 0.13.0, macOS 15 ou version ultérieure.

+

Tout, du premier lancement au réglage des règles des agents. Flowlight 0.13.1, macOS 15 ou version ultérieure.

diff --git a/docs/fr/index.html b/docs/fr/index.html index 339245ef..33421858 100644 --- a/docs/fr/index.html +++ b/docs/fr/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

Voyez l’activité réseau de vos apps.

brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+UniverselGPL-3.0Sans télémétrie

+

v0.13.1macOS 15+UniverselGPL-3.0Sans télémétrie

@@ -616,7 +616,7 @@

Sachez ce qui quitte votre Mac.

diff --git a/docs/fr/privacy/index.html b/docs/fr/privacy/index.html index 611c048e..483a2320 100644 --- a/docs/fr/privacy/index.html +++ b/docs/fr/privacy/index.html @@ -173,7 +173,7 @@

Contact

diff --git a/docs/fr/threat-model/index.html b/docs/fr/threat-model/index.html index 7276be5a..83fef166 100644 --- a/docs/fr/threat-model/index.html +++ b/docs/fr/threat-model/index.html @@ -211,7 +211,7 @@

Signaler une vulnérabilité

diff --git a/docs/index.html b/docs/index.html index 53dd98b6..d3b44ea6 100644 --- a/docs/index.html +++ b/docs/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

See your apps' network activity.
brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+UniversalGPL-3.0No telemetry

+

v0.13.1macOS 15+UniversalGPL-3.0No telemetry

@@ -625,7 +625,7 @@

Know what leaves your Mac.

diff --git a/docs/it/about/index.html b/docs/it/about/index.html index a4d95f2a..b1b36ca3 100644 --- a/docs/it/about/index.html +++ b/docs/it/about/index.html @@ -137,7 +137,7 @@

Ringraziamenti

diff --git a/docs/it/docs/index.html b/docs/it/docs/index.html index bc81260d..c4e52e8a 100644 --- a/docs/it/docs/index.html +++ b/docs/it/docs/index.html @@ -61,7 +61,7 @@

Documentazione

Usare Flowlight

-

Tutto, dal primo avvio alla messa a punto delle regole per gli agenti. Flowlight 0.13.0, macOS 15 o successivo.

+

Tutto, dal primo avvio alla messa a punto delle regole per gli agenti. Flowlight 0.13.1, macOS 15 o successivo.

diff --git a/docs/it/index.html b/docs/it/index.html index 81876b2a..4faab092 100644 --- a/docs/it/index.html +++ b/docs/it/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

Vedi la rete delle tue app.
Ca Metti una stella su GitHub

brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+UniversaleGPL-3.0Nessuna telemetria

+

v0.13.1macOS 15+UniversaleGPL-3.0Nessuna telemetria

@@ -616,7 +616,7 @@

Sappi che cosa esce dal tuo Mac.

diff --git a/docs/it/privacy/index.html b/docs/it/privacy/index.html index e87a59d3..40586529 100644 --- a/docs/it/privacy/index.html +++ b/docs/it/privacy/index.html @@ -173,7 +173,7 @@

Contatti

diff --git a/docs/it/threat-model/index.html b/docs/it/threat-model/index.html index 5a581a6b..d46b031c 100644 --- a/docs/it/threat-model/index.html +++ b/docs/it/threat-model/index.html @@ -209,7 +209,7 @@

Segnalare una vulnerabilità

diff --git a/docs/ja/about/index.html b/docs/ja/about/index.html index bc4b631b..1cb96274 100644 --- a/docs/ja/about/index.html +++ b/docs/ja/about/index.html @@ -138,7 +138,7 @@

謝辞

diff --git a/docs/ja/docs/index.html b/docs/ja/docs/index.html index 7f2e472f..babe886d 100644 --- a/docs/ja/docs/index.html +++ b/docs/ja/docs/index.html @@ -61,7 +61,7 @@

ドキュメント

Flowlight の使い方

-

初回起動からエージェントのルールの調整まで、すべてここに。Flowlight 0.13.0、macOS 15 以降。

+

初回起動からエージェントのルールの調整まで、すべてここに。Flowlight 0.13.1、macOS 15 以降。

diff --git a/docs/ja/index.html b/docs/ja/index.html index 39177eca..cb0c13e9 100644 --- a/docs/ja/index.html +++ b/docs/ja/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

アプリの通信が見える。
brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+ユニバーサルGPL-3.0テレメトリなし

+

v0.13.1macOS 15+ユニバーサルGPL-3.0テレメトリなし

@@ -615,7 +615,7 @@

Mac から何が出ていくのかを知る。

diff --git a/docs/ja/privacy/index.html b/docs/ja/privacy/index.html index a5e9af2e..43abdb8c 100644 --- a/docs/ja/privacy/index.html +++ b/docs/ja/privacy/index.html @@ -171,7 +171,7 @@

連絡先

diff --git a/docs/ja/threat-model/index.html b/docs/ja/threat-model/index.html index 23a38aba..2b22c123 100644 --- a/docs/ja/threat-model/index.html +++ b/docs/ja/threat-model/index.html @@ -210,7 +210,7 @@

脆弱性を報告する

diff --git a/docs/ko/about/index.html b/docs/ko/about/index.html index 0ec3545f..f53c2154 100644 --- a/docs/ko/about/index.html +++ b/docs/ko/about/index.html @@ -138,7 +138,7 @@

감사

diff --git a/docs/ko/docs/index.html b/docs/ko/docs/index.html index c0f4ccb0..548104f3 100644 --- a/docs/ko/docs/index.html +++ b/docs/ko/docs/index.html @@ -61,7 +61,7 @@

문서

Flowlight 사용하기

-

첫 실행부터 에이전트 규칙 조정까지 전부. Flowlight 0.13.0, macOS 15 이상.

+

첫 실행부터 에이전트 규칙 조정까지 전부. Flowlight 0.13.1, macOS 15 이상.

diff --git a/docs/ko/index.html b/docs/ko/index.html index b62e2f17..ff693764 100644 --- a/docs/ko/index.html +++ b/docs/ko/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

앱이 무엇을 하는지 봅니다.

brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+유니버설GPL-3.0텔레메트리 없음

+

v0.13.1macOS 15+유니버설GPL-3.0텔레메트리 없음

@@ -616,7 +616,7 @@

내 Mac에서 무엇이 나가는지 아세요.

diff --git a/docs/ko/privacy/index.html b/docs/ko/privacy/index.html index 3127e950..6b42b321 100644 --- a/docs/ko/privacy/index.html +++ b/docs/ko/privacy/index.html @@ -174,7 +174,7 @@

문의

diff --git a/docs/ko/threat-model/index.html b/docs/ko/threat-model/index.html index 4eb0ce1d..35f29682 100644 --- a/docs/ko/threat-model/index.html +++ b/docs/ko/threat-model/index.html @@ -208,7 +208,7 @@

취약점 신고하기

diff --git a/docs/llms-full.txt b/docs/llms-full.txt index c5a5d294..db7c9b65 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -62,7 +62,7 @@ Documentation Using Flowlight - Everything from the first launch to tuning the agent rules. Flowlight 0.13.0, macOS 15 or later. + Everything from the first launch to tuning the agent rules. Flowlight 0.13.1, macOS 15 or later. On this page @@ -839,7 +839,7 @@ Understand your AI agents. brew install --cask xinbetween/tap/flowlightCopy - v0.13.0macOS 15+UniversalGPL-3.0No telemetry + v0.13.1macOS 15+UniversalGPL-3.0No telemetry connectionslive @@ -1398,8 +1398,16 @@ Releases Downloads, checksums and full notes for each version are on GitHub Releases. + 0.13.1 +October 3, 2026Latest + + Rewrite form requests too. Request-modification rules now change fields in + application/x-www-form-urlencoded bodies, not only JSON. Set a field to a URL, text or + value and Flowlight encodes it as a browser would; remove a field and it is omitted. The request's + Content-Length is recalculated before it goes upstream. + 0.13.0 -October 1, 2026Latest +October 1, 2026 Change a request on its way out. A new kind of rule edits a matching outgoing request before it reaches the server — add or replace a header, pin a field in the JSON body, or strip diff --git a/docs/llms.txt b/docs/llms.txt index 106f4362..b5b87804 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -1,6 +1,6 @@ # Flowlight -> Free, open-source (GPL-3.0) application-aware network monitor for macOS, with focused visibility into AI agents. It attributes observed TCP and UDP activity to the application that made it and records the destination, protocol and byte counts, keeping local history from second to year. Recognized AI agents are listed by name along with the tools and MCP servers they start, under per-agent allowlists. It can also refuse, once asked: a rule blocks an application, a destination or a URL for as long as you specify, and a guardrail withholds a tool from an agent before its model is offered it. Runs on macOS 15 or later; capture is by a sampler or a Network Extension. Current version: 0.13.0. +> Free, open-source (GPL-3.0) application-aware network monitor for macOS, with focused visibility into AI agents. It attributes observed TCP and UDP activity to the application that made it and records the destination, protocol and byte counts, keeping local history from second to year. Recognized AI agents are listed by name along with the tools and MCP servers they start, under per-agent allowlists. It can also refuse, once asked: a rule blocks an application, a destination or a URL for as long as you specify, and a guardrail withholds a tool from an agent before its model is offered it. Runs on macOS 15 or later; capture is by a sampler or a Network Extension. Current version: 0.13.1. - [Download Flowlight.dmg](https://github.com/xinbetween/flowlight/releases/latest/download/Flowlight.dmg) - [Source code](https://github.com/xinbetween/flowlight) diff --git a/docs/privacy/index.html b/docs/privacy/index.html index d057e20a..3b7b925d 100644 --- a/docs/privacy/index.html +++ b/docs/privacy/index.html @@ -174,7 +174,7 @@

Contact

diff --git a/docs/pt-PT/about/index.html b/docs/pt-PT/about/index.html index 53ae95f3..b78d9c5d 100644 --- a/docs/pt-PT/about/index.html +++ b/docs/pt-PT/about/index.html @@ -137,7 +137,7 @@

Agradecimentos

diff --git a/docs/pt-PT/docs/index.html b/docs/pt-PT/docs/index.html index 9ab38622..bbf97b78 100644 --- a/docs/pt-PT/docs/index.html +++ b/docs/pt-PT/docs/index.html @@ -61,7 +61,7 @@

Documentação

Usar o Flowlight

-

Tudo, da primeira abertura ao ajuste das regras dos agentes. Flowlight 0.13.0, macOS 15 ou posterior.

+

Tudo, da primeira abertura ao ajuste das regras dos agentes. Flowlight 0.13.1, macOS 15 ou posterior.

diff --git a/docs/pt-PT/index.html b/docs/pt-PT/index.html index beb2c2b6..f9c41f36 100644 --- a/docs/pt-PT/index.html +++ b/docs/pt-PT/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

Veja a atividade de rede das suas apps.

brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+UniversalGPL-3.0Sem telemetria

+

v0.13.1macOS 15+UniversalGPL-3.0Sem telemetria

@@ -616,7 +616,7 @@

Saiba o que sai do seu Mac.

diff --git a/docs/pt-PT/privacy/index.html b/docs/pt-PT/privacy/index.html index 8fd8b112..0873cef9 100644 --- a/docs/pt-PT/privacy/index.html +++ b/docs/pt-PT/privacy/index.html @@ -173,7 +173,7 @@

Contacto

diff --git a/docs/pt-PT/threat-model/index.html b/docs/pt-PT/threat-model/index.html index 87665077..a9ee0ed4 100644 --- a/docs/pt-PT/threat-model/index.html +++ b/docs/pt-PT/threat-model/index.html @@ -210,7 +210,7 @@

Comunicar uma vulnerabilidade

diff --git a/docs/releases/index.html b/docs/releases/index.html index 7ccfb5b0..4532b3ec 100644 --- a/docs/releases/index.html +++ b/docs/releases/index.html @@ -55,7 +55,17 @@

What's new

-

0.13.0

Latest
+

0.13.1

Latest
+
    +
  • Rewrite form requests too. Request-modification rules now change fields in + application/x-www-form-urlencoded bodies, not only JSON. Set a field to a URL, text or + value and Flowlight encodes it as a browser would; remove a field and it is omitted. The request's + Content-Length is recalculated before it goes upstream.
  • +
+
+ +
+

0.13.0

  • Change a request on its way out. A new kind of rule edits a matching outgoing request before it reaches the server — add or replace a header, pin a field in the JSON body, or strip @@ -1026,7 +1036,7 @@

    What's new

diff --git a/docs/sitemap.xml b/docs/sitemap.xml index adafc655..f6555c02 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -4,7 +4,7 @@ https://flowlight.xinbetween.com/docs/2026-09-28 https://flowlight.xinbetween.com/2026-10-01 https://flowlight.xinbetween.com/privacy/2026-09-27 - https://flowlight.xinbetween.com/releases/2026-10-01 + https://flowlight.xinbetween.com/releases/2026-10-03 https://flowlight.xinbetween.com/threat-model/2026-09-27 https://flowlight.xinbetween.com/de/about/2026-09-26 https://flowlight.xinbetween.com/de/docs/2026-09-28 diff --git a/docs/threat-model/index.html b/docs/threat-model/index.html index 937520ba..146028f5 100644 --- a/docs/threat-model/index.html +++ b/docs/threat-model/index.html @@ -209,7 +209,7 @@

Reporting a vulnerability

diff --git a/docs/zh-Hans/about/index.html b/docs/zh-Hans/about/index.html index 0d1f1919..719c9fd2 100644 --- a/docs/zh-Hans/about/index.html +++ b/docs/zh-Hans/about/index.html @@ -137,7 +137,7 @@

致谢

diff --git a/docs/zh-Hans/docs/index.html b/docs/zh-Hans/docs/index.html index ecfa416d..70461bce 100644 --- a/docs/zh-Hans/docs/index.html +++ b/docs/zh-Hans/docs/index.html @@ -61,7 +61,7 @@

文档

使用 Flowlight

-

从第一次启动到调整代理规则,需要的都在这里。Flowlight 0.13.0,macOS 15 或更高版本。

+

从第一次启动到调整代理规则,需要的都在这里。Flowlight 0.13.1,macOS 15 或更高版本。

diff --git a/docs/zh-Hans/index.html b/docs/zh-Hans/index.html index 5cbeb776..2099dd23 100644 --- a/docs/zh-Hans/index.html +++ b/docs/zh-Hans/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

看清应用的网络活动。

brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+通用架构GPL-3.0无遥测

+

v0.13.1macOS 15+通用架构GPL-3.0无遥测

@@ -613,7 +613,7 @@

知道什么离开了你的 Mac。

diff --git a/docs/zh-Hans/privacy/index.html b/docs/zh-Hans/privacy/index.html index 8b3ae140..781d049d 100644 --- a/docs/zh-Hans/privacy/index.html +++ b/docs/zh-Hans/privacy/index.html @@ -171,7 +171,7 @@

联系方式

diff --git a/docs/zh-Hans/threat-model/index.html b/docs/zh-Hans/threat-model/index.html index 7567a16e..309e7e99 100644 --- a/docs/zh-Hans/threat-model/index.html +++ b/docs/zh-Hans/threat-model/index.html @@ -197,7 +197,7 @@

报告漏洞

diff --git a/docs/zh-Hant/about/index.html b/docs/zh-Hant/about/index.html index ae950462..779c2ab6 100644 --- a/docs/zh-Hant/about/index.html +++ b/docs/zh-Hant/about/index.html @@ -137,7 +137,7 @@

致謝

diff --git a/docs/zh-Hant/docs/index.html b/docs/zh-Hant/docs/index.html index ff1f7dc8..6e094061 100644 --- a/docs/zh-Hant/docs/index.html +++ b/docs/zh-Hant/docs/index.html @@ -61,7 +61,7 @@

說明文件

使用 Flowlight

-

從第一次啟動到調整代理規則,全都在這裡。Flowlight 0.13.0,macOS 15 或以上版本。

+

從第一次啟動到調整代理規則,全都在這裡。Flowlight 0.13.1,macOS 15 或以上版本。

diff --git a/docs/zh-Hant/index.html b/docs/zh-Hant/index.html index 47574b34..0c46601d 100644 --- a/docs/zh-Hant/index.html +++ b/docs/zh-Hant/index.html @@ -34,7 +34,7 @@ - + @@ -75,7 +75,7 @@

看見每個 App 的網路活動。
brew install --cask xinbetween/tap/flowlight

-

v0.13.0macOS 15+通用架構GPL-3.0無遙測

+

v0.13.1macOS 15+通用架構GPL-3.0無遙測

@@ -614,7 +614,7 @@

知道有什麼離開了你的 Mac。

diff --git a/docs/zh-Hant/privacy/index.html b/docs/zh-Hant/privacy/index.html index ed46a274..37a00d0e 100644 --- a/docs/zh-Hant/privacy/index.html +++ b/docs/zh-Hant/privacy/index.html @@ -171,7 +171,7 @@

聯絡

diff --git a/docs/zh-Hant/threat-model/index.html b/docs/zh-Hant/threat-model/index.html index 7b1cfac5..b2b7ddaf 100644 --- a/docs/zh-Hant/threat-model/index.html +++ b/docs/zh-Hant/threat-model/index.html @@ -198,7 +198,7 @@

回報漏洞

diff --git a/project.yml b/project.yml index a57e66a7..3a004184 100644 --- a/project.yml +++ b/project.yml @@ -11,7 +11,7 @@ settings: DEVELOPMENT_TEAM: "" CODE_SIGN_STYLE: Automatic ENABLE_HARDENED_RUNTIME: YES - MARKETING_VERSION: "0.13.0" + MARKETING_VERSION: "0.13.1" CURRENT_PROJECT_VERSION: "22" targets: Flowlight: diff --git a/site/pages/releases.html b/site/pages/releases.html index 272b14c0..0823e7c5 100644 --- a/site/pages/releases.html +++ b/site/pages/releases.html @@ -13,7 +13,17 @@

What's new

-

0.13.0

Latest
+

0.13.1

Latest
+
    +
  • Rewrite form requests too. Request-modification rules now change fields in + application/x-www-form-urlencoded bodies, not only JSON. Set a field to a URL, text or + value and Flowlight encodes it as a browser would; remove a field and it is omitted. The request's + Content-Length is recalculated before it goes upstream.
  • +
+
+ +
+

0.13.0

  • Change a request on its way out. A new kind of rule edits a matching outgoing request before it reaches the server — add or replace a header, pin a field in the JSON body, or strip