From 5f6154e7d3a5ee914bd49f1200e49c231311a903 Mon Sep 17 00:00:00 2001 From: blessdyb Date: Mon, 12 Oct 2026 00:21:54 -0700 Subject: [PATCH] Add plugin provenance review Co-Authored-By: Claude Code --- .../Localization/de.lproj/Localizable.strings | 230 +++++++++++++++++- .../Localization/en.lproj/Localizable.strings | 229 ++++++++++++++++- .../Localization/es.lproj/Localizable.strings | 230 +++++++++++++++++- .../Localization/fr.lproj/Localizable.strings | 230 +++++++++++++++++- .../Localization/it.lproj/Localizable.strings | 230 +++++++++++++++++- .../Localization/ja.lproj/Localizable.strings | 230 +++++++++++++++++- .../Localization/ko.lproj/Localizable.strings | 230 +++++++++++++++++- .../pt-PT.lproj/Localizable.strings | 230 +++++++++++++++++- .../zh-Hans.lproj/Localizable.strings | 230 +++++++++++++++++- .../zh-Hant.lproj/Localizable.strings | 230 +++++++++++++++++- Flowlight/Plugins/PluginModels.swift | 175 ++++++++++++- Flowlight/Plugins/PluginPackageVerifier.swift | 178 ++++++++++++++ Flowlight/Plugins/PluginScriptRunner.swift | 68 ++++-- Flowlight/Plugins/PluginStore.swift | 51 ++-- Flowlight/UI/PluginSettingsView.swift | 120 ++++++++- FlowlightTests/PluginTests.swift | 78 ++++++ docs/llms-full.txt | 50 ++-- docs/plugins/index.html | 49 ++-- docs/sitemap.xml | 2 +- site/pages/plugins.html | 47 ++-- templates/inspection-plugin/README.md | 14 +- templates/inspection-plugin/manifest.json | 13 + templates/inspection-plugin/validate.js | 30 +++ 23 files changed, 2990 insertions(+), 184 deletions(-) create mode 100644 Flowlight/Plugins/PluginPackageVerifier.swift diff --git a/Flowlight/Localization/de.lproj/Localizable.strings b/Flowlight/Localization/de.lproj/Localizable.strings index 1ea142f..60d6e23 100644 --- a/Flowlight/Localization/de.lproj/Localizable.strings +++ b/Flowlight/Localization/de.lproj/Localizable.strings @@ -1023,8 +1023,6 @@ "Method" = "Methode"; "Delay" = "Verzögerung"; "seconds" = "Sekunden"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "Ein „Name: Wert“ pro Zeile. Content-Length und Connection setzt Flowlight selbst."; -"Body" = "Body"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "Die Anfrage wartet %@, bevor sie beantwortet wird. Clients mit eigenem Timeout geben vorher auf – meist ist genau das der Zweck."; "Add Rule" = "Regel hinzufügen"; "Save" = "Sichern"; @@ -1378,9 +1376,6 @@ "Version %@ is the latest release." = "Version %@ ist die neueste Veröffentlichung."; /* Common words kept for menus macOS builds itself. */ -"Edit" = "Bearbeiten"; -"On" = "Ein"; -"Settings" = "Einstellungen"; /* 0.9.5 export disclosure */ @@ -1430,3 +1425,228 @@ "Restart %@ through the proxy?" = "%@ über den Proxy neu starten?"; "Restart and End Its Sessions" = "Neu starten & Sitzungen beenden"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@ startet %@, deshalb müssen die Proxy-Einstellungen %@ erreichen und nicht ein neues Fenster – eine jetzt geöffnete Shell wäre niemals der Elternprozess eines Agenten. Ein Neustart beendet jede Sitzung, die gerade darin läuft, auch diese hier. Es wird mit demselben Befehl erneut gestartet, mit seinem HTTPS auf Flowlight gerichtet."; + +"No concerns found" = "No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "%@ sent %@ in one inspected %@ request."; +"Request size" = "Request size"; +"Highlights destructive-looking HTTP requests." = "Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "Reads header names only; values are never exposed."; +"Credential-like headers present" = "Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "This request included credential-like header names. Values remain redacted."; +"Header names" = "Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "Review %@"; +"High-risk tool available" = "High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "agent tool"; +"Traffic metadata" = "Traffic metadata"; +"App identity" = "App identity"; +"LLM metadata" = "LLM metadata"; +"MCP metadata" = "MCP metadata"; +"Suggest guardrails" = "Suggest guardrails"; +"Metadata only" = "Metadata only"; +"Tool names" = "Tool names"; +"Guardrail suggestions" = "Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "Unsigned local"; +"Verified official" = "Verified official"; +"Verified community" = "Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "Stop Monitoring %@"; +"Always Monitor %@" = "Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "Reinstall Filter"; +"Potential harm" = "Potential harm"; +"Show only medium and high local risk assessments" = "Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "Showing only requests with medium or high potential harm"; +"Response modified" = "Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "Modify This Response…"; +"Modify requests" = "Modify requests"; +"Modify responses" = "Modify responses"; +"Add Guardrail" = "Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "Local risk analysis"; +"Analyze potential harm after capture" = "Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "Clear analyses…"; +"Delete only retained local risk scores and explanations" = "Delete only retained local risk scores and explanations"; +"Not assessed" = "Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "No captured request headers."; +"Request payload" = "Request payload"; +"No response headers yet." = "No response headers yet."; +"Response body" = "Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "Import Package…"; +"Backfill Findings" = "Backfill Findings"; +"Clear plugin findings…" = "Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "No third-party plugin packages are installed."; +"Clear plugin findings?" = "Clear plugin findings?"; +"Clear findings" = "Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "Saved configuration for %@."; +"Installed %@." = "Installed %@."; +"Import failed: %@" = "Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "Exported %@."; +"Backfilled %@: %lld findings." = "Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "Package %@ · Script %@"; +"Configured: %@" = "Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "Review Plugin Package"; +"Plugin ID" = "Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "Package digest"; +"Script digest" = "Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "Not declared"; +"Configure %@" = "Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "Remove this setting"; +"Add Setting" = "Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "No response rules."; +"New response transform" = "New response transform"; +"Edit response transform" = "Edit response transform"; +"Transform response" = "Transform response"; +"Captured request context" = "Captured request context"; +"Upstream status: %lld" = "Upstream status: %lld"; +"No captured upstream status." = "No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "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."; +"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." = "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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "No request rules."; +"Every matching rule is applied, in order." = "Every matching rule is applied, in order."; +"%lld edit(s)" = "%lld edit(s)"; +"New request rule" = "New request rule"; +"Edit request rule" = "Edit request rule"; +"Optional, e.g. “Force temperature”" = "Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "Header name"; +"Add header edit" = "Add header edit"; +"Request body" = "Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "value (JSON or text)"; +"Add body edit" = "Add body edit"; +"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." = "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."; +"Clear local analyses?" = "Clear local analyses?"; +"Clear analyses" = "Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Localization/en.lproj/Localizable.strings b/Flowlight/Localization/en.lproj/Localizable.strings index df3b837..169c1d7 100644 --- a/Flowlight/Localization/en.lproj/Localizable.strings +++ b/Flowlight/Localization/en.lproj/Localizable.strings @@ -972,8 +972,6 @@ "Method" = "Method"; "Delay" = "Delay"; "seconds" = "seconds"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "One Name: value per line. Content-Length and Connection are written by Flowlight."; -"Body" = "Body"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point."; "Add Rule" = "Add Rule"; "Save" = "Save"; @@ -1310,9 +1308,6 @@ "Version %@ is the latest release." = "Version %@ is the latest release."; /* Common words kept for menus macOS builds itself. */ -"Edit" = "Edit"; -"On" = "On"; -"Settings" = "Settings"; /* 0.9.3 inspection budget, 0.9.4 rule simulation */ "Keep only headers I allow" = "Keep only headers I allow"; @@ -1430,3 +1425,227 @@ "Restart and End Its Sessions" = "Restart and End Its Sessions"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight."; +"No concerns found" = "No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "%@ sent %@ in one inspected %@ request."; +"Request size" = "Request size"; +"Highlights destructive-looking HTTP requests." = "Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "Reads header names only; values are never exposed."; +"Credential-like headers present" = "Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "This request included credential-like header names. Values remain redacted."; +"Header names" = "Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "Review %@"; +"High-risk tool available" = "High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "agent tool"; +"Traffic metadata" = "Traffic metadata"; +"App identity" = "App identity"; +"LLM metadata" = "LLM metadata"; +"MCP metadata" = "MCP metadata"; +"Suggest guardrails" = "Suggest guardrails"; +"Metadata only" = "Metadata only"; +"Tool names" = "Tool names"; +"Guardrail suggestions" = "Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "Unsigned local"; +"Verified official" = "Verified official"; +"Verified community" = "Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "Stop Monitoring %@"; +"Always Monitor %@" = "Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "Reinstall Filter"; +"Potential harm" = "Potential harm"; +"Show only medium and high local risk assessments" = "Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "Showing only requests with medium or high potential harm"; +"Response modified" = "Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "Modify This Response…"; +"Modify requests" = "Modify requests"; +"Modify responses" = "Modify responses"; +"Add Guardrail" = "Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "Local risk analysis"; +"Analyze potential harm after capture" = "Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "Clear analyses…"; +"Delete only retained local risk scores and explanations" = "Delete only retained local risk scores and explanations"; +"Not assessed" = "Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "No captured request headers."; +"Request payload" = "Request payload"; +"No response headers yet." = "No response headers yet."; +"Response body" = "Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "Import Package…"; +"Backfill Findings" = "Backfill Findings"; +"Clear plugin findings…" = "Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "No third-party plugin packages are installed."; +"Clear plugin findings?" = "Clear plugin findings?"; +"Clear findings" = "Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "Saved configuration for %@."; +"Installed %@." = "Installed %@."; +"Import failed: %@" = "Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "Exported %@."; +"Backfilled %@: %lld findings." = "Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "Package %@ · Script %@"; +"Configured: %@" = "Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "Review Plugin Package"; +"Plugin ID" = "Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "Package digest"; +"Script digest" = "Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "Not declared"; +"Configure %@" = "Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "Remove this setting"; +"Add Setting" = "Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "No response rules."; +"New response transform" = "New response transform"; +"Edit response transform" = "Edit response transform"; +"Transform response" = "Transform response"; +"Captured request context" = "Captured request context"; +"Upstream status: %lld" = "Upstream status: %lld"; +"No captured upstream status." = "No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "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."; +"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." = "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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "No request rules."; +"Every matching rule is applied, in order." = "Every matching rule is applied, in order."; +"%lld edit(s)" = "%lld edit(s)"; +"New request rule" = "New request rule"; +"Edit request rule" = "Edit request rule"; +"Optional, e.g. “Force temperature”" = "Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "Header name"; +"Add header edit" = "Add header edit"; +"Request body" = "Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "value (JSON or text)"; +"Add body edit" = "Add body edit"; +"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." = "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."; +"Clear local analyses?" = "Clear local analyses?"; +"Clear analyses" = "Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Localization/es.lproj/Localizable.strings b/Flowlight/Localization/es.lproj/Localizable.strings index 5cef857..3d83d68 100644 --- a/Flowlight/Localization/es.lproj/Localizable.strings +++ b/Flowlight/Localization/es.lproj/Localizable.strings @@ -1023,8 +1023,6 @@ "Method" = "Método"; "Delay" = "Retardo"; "seconds" = "segundos"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "Un «Nombre: valor» por línea. Content-Length y Connection los escribe Flowlight."; -"Body" = "Cuerpo"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "La petición espera %@ antes de responderse. Los clientes con su propio tiempo de espera se rendirán antes, que suele ser la idea."; "Add Rule" = "Añadir regla"; "Save" = "Guardar"; @@ -1378,9 +1376,6 @@ "Version %@ is the latest release." = "La versión %@ es la más reciente."; /* Common words kept for menus macOS builds itself. */ -"Edit" = "Editar"; -"On" = "Activado"; -"Settings" = "Ajustes"; /* 0.9.5 export disclosure */ @@ -1430,3 +1425,228 @@ "Restart %@ through the proxy?" = "¿Reiniciar %@ por el proxy?"; "Restart and End Its Sessions" = "Reiniciar y terminar sus sesiones"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@ inicia %@, así que los ajustes del proxy tienen que llegar a %@ y no a una ventana nueva — un shell que abras ahora nunca sería el padre de ningún agente. Reiniciarlo termina todas las sesiones que tiene en marcha, incluida esta. Se vuelve a iniciar con el mismo comando y con su HTTPS apuntando a Flowlight."; + +"No concerns found" = "No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "%@ sent %@ in one inspected %@ request."; +"Request size" = "Request size"; +"Highlights destructive-looking HTTP requests." = "Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "Reads header names only; values are never exposed."; +"Credential-like headers present" = "Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "This request included credential-like header names. Values remain redacted."; +"Header names" = "Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "Review %@"; +"High-risk tool available" = "High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "agent tool"; +"Traffic metadata" = "Traffic metadata"; +"App identity" = "App identity"; +"LLM metadata" = "LLM metadata"; +"MCP metadata" = "MCP metadata"; +"Suggest guardrails" = "Suggest guardrails"; +"Metadata only" = "Metadata only"; +"Tool names" = "Tool names"; +"Guardrail suggestions" = "Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "Unsigned local"; +"Verified official" = "Verified official"; +"Verified community" = "Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "Stop Monitoring %@"; +"Always Monitor %@" = "Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "Reinstall Filter"; +"Potential harm" = "Potential harm"; +"Show only medium and high local risk assessments" = "Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "Showing only requests with medium or high potential harm"; +"Response modified" = "Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "Modify This Response…"; +"Modify requests" = "Modify requests"; +"Modify responses" = "Modify responses"; +"Add Guardrail" = "Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "Local risk analysis"; +"Analyze potential harm after capture" = "Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "Clear analyses…"; +"Delete only retained local risk scores and explanations" = "Delete only retained local risk scores and explanations"; +"Not assessed" = "Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "No captured request headers."; +"Request payload" = "Request payload"; +"No response headers yet." = "No response headers yet."; +"Response body" = "Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "Import Package…"; +"Backfill Findings" = "Backfill Findings"; +"Clear plugin findings…" = "Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "No third-party plugin packages are installed."; +"Clear plugin findings?" = "Clear plugin findings?"; +"Clear findings" = "Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "Saved configuration for %@."; +"Installed %@." = "Installed %@."; +"Import failed: %@" = "Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "Exported %@."; +"Backfilled %@: %lld findings." = "Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "Package %@ · Script %@"; +"Configured: %@" = "Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "Review Plugin Package"; +"Plugin ID" = "Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "Package digest"; +"Script digest" = "Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "Not declared"; +"Configure %@" = "Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "Remove this setting"; +"Add Setting" = "Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "No response rules."; +"New response transform" = "New response transform"; +"Edit response transform" = "Edit response transform"; +"Transform response" = "Transform response"; +"Captured request context" = "Captured request context"; +"Upstream status: %lld" = "Upstream status: %lld"; +"No captured upstream status." = "No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "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."; +"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." = "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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "No request rules."; +"Every matching rule is applied, in order." = "Every matching rule is applied, in order."; +"%lld edit(s)" = "%lld edit(s)"; +"New request rule" = "New request rule"; +"Edit request rule" = "Edit request rule"; +"Optional, e.g. “Force temperature”" = "Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "Header name"; +"Add header edit" = "Add header edit"; +"Request body" = "Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "value (JSON or text)"; +"Add body edit" = "Add body edit"; +"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." = "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."; +"Clear local analyses?" = "Clear local analyses?"; +"Clear analyses" = "Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Localization/fr.lproj/Localizable.strings b/Flowlight/Localization/fr.lproj/Localizable.strings index b224244..f564706 100644 --- a/Flowlight/Localization/fr.lproj/Localizable.strings +++ b/Flowlight/Localization/fr.lproj/Localizable.strings @@ -1023,8 +1023,6 @@ "Method" = "Méthode"; "Delay" = "Délai"; "seconds" = "secondes"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "Un en-tête « Nom: valeur » par ligne. Content-Length et Connection sont écrits par Flowlight."; -"Body" = "Corps"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "La requête attend %@ avant de recevoir une réponse. Les clients qui ont leur propre délai d'expiration abandonneront avant, ce qui est généralement le but."; "Add Rule" = "Ajouter une règle"; "Save" = "Enregistrer"; @@ -1378,9 +1376,6 @@ "Version %@ is the latest release." = "La version %@ est la dernière publiée."; /* Common words kept for menus macOS builds itself. */ -"Edit" = "Modifier"; -"On" = "Activé"; -"Settings" = "Réglages"; /* 0.9.5 export disclosure */ @@ -1430,3 +1425,228 @@ "Restart %@ through the proxy?" = "Redémarrer %@ par le proxy ?"; "Restart and End Its Sessions" = "Redémarrer et mettre fin à ses sessions"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@ lance %@ : les réglages du proxy doivent donc atteindre %@ plutôt qu'une nouvelle fenêtre — un shell ouvert maintenant ne serait jamais le parent d'un agent. Le redémarrer met fin à toutes les sessions qu'il exécute actuellement, y compris celle-ci. Il est relancé avec la même commande, son HTTPS pointé vers Flowlight."; + +"No concerns found" = "No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "%@ sent %@ in one inspected %@ request."; +"Request size" = "Request size"; +"Highlights destructive-looking HTTP requests." = "Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "Reads header names only; values are never exposed."; +"Credential-like headers present" = "Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "This request included credential-like header names. Values remain redacted."; +"Header names" = "Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "Review %@"; +"High-risk tool available" = "High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "agent tool"; +"Traffic metadata" = "Traffic metadata"; +"App identity" = "App identity"; +"LLM metadata" = "LLM metadata"; +"MCP metadata" = "MCP metadata"; +"Suggest guardrails" = "Suggest guardrails"; +"Metadata only" = "Metadata only"; +"Tool names" = "Tool names"; +"Guardrail suggestions" = "Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "Unsigned local"; +"Verified official" = "Verified official"; +"Verified community" = "Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "Stop Monitoring %@"; +"Always Monitor %@" = "Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "Reinstall Filter"; +"Potential harm" = "Potential harm"; +"Show only medium and high local risk assessments" = "Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "Showing only requests with medium or high potential harm"; +"Response modified" = "Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "Modify This Response…"; +"Modify requests" = "Modify requests"; +"Modify responses" = "Modify responses"; +"Add Guardrail" = "Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "Local risk analysis"; +"Analyze potential harm after capture" = "Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "Clear analyses…"; +"Delete only retained local risk scores and explanations" = "Delete only retained local risk scores and explanations"; +"Not assessed" = "Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "No captured request headers."; +"Request payload" = "Request payload"; +"No response headers yet." = "No response headers yet."; +"Response body" = "Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "Import Package…"; +"Backfill Findings" = "Backfill Findings"; +"Clear plugin findings…" = "Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "No third-party plugin packages are installed."; +"Clear plugin findings?" = "Clear plugin findings?"; +"Clear findings" = "Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "Saved configuration for %@."; +"Installed %@." = "Installed %@."; +"Import failed: %@" = "Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "Exported %@."; +"Backfilled %@: %lld findings." = "Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "Package %@ · Script %@"; +"Configured: %@" = "Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "Review Plugin Package"; +"Plugin ID" = "Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "Package digest"; +"Script digest" = "Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "Not declared"; +"Configure %@" = "Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "Remove this setting"; +"Add Setting" = "Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "No response rules."; +"New response transform" = "New response transform"; +"Edit response transform" = "Edit response transform"; +"Transform response" = "Transform response"; +"Captured request context" = "Captured request context"; +"Upstream status: %lld" = "Upstream status: %lld"; +"No captured upstream status." = "No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "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."; +"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." = "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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "No request rules."; +"Every matching rule is applied, in order." = "Every matching rule is applied, in order."; +"%lld edit(s)" = "%lld edit(s)"; +"New request rule" = "New request rule"; +"Edit request rule" = "Edit request rule"; +"Optional, e.g. “Force temperature”" = "Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "Header name"; +"Add header edit" = "Add header edit"; +"Request body" = "Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "value (JSON or text)"; +"Add body edit" = "Add body edit"; +"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." = "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."; +"Clear local analyses?" = "Clear local analyses?"; +"Clear analyses" = "Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Localization/it.lproj/Localizable.strings b/Flowlight/Localization/it.lproj/Localizable.strings index 86a9ac2..373cdeb 100644 --- a/Flowlight/Localization/it.lproj/Localizable.strings +++ b/Flowlight/Localization/it.lproj/Localizable.strings @@ -1023,8 +1023,6 @@ "Method" = "Metodo"; "Delay" = "Ritardo"; "seconds" = "secondi"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "Una coppia Nome: valore per riga. Content-Length e Connection sono scritti da Flowlight."; -"Body" = "Corpo"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "La richiesta attende %@ prima di ricevere risposta. I client con un timeout proprio si arrenderanno prima, che di solito è lo scopo."; "Add Rule" = "Aggiungi regola"; "Save" = "Salva"; @@ -1378,9 +1376,6 @@ "Version %@ is the latest release." = "La versione %@ è l'ultima disponibile."; /* Common words kept for menus macOS builds itself. */ -"Edit" = "Modifica"; -"On" = "Attivato"; -"Settings" = "Impostazioni"; /* 0.9.5 export disclosure */ @@ -1430,3 +1425,228 @@ "Restart %@ through the proxy?" = "Riavviare %@ attraverso il proxy?"; "Restart and End Its Sessions" = "Riavvia e chiudi le sue sessioni"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@ avvia %@, quindi le impostazioni proxy devono raggiungere %@ e non una nuova finestra — una shell aperta adesso non sarebbe mai il padre di un agente. Riavviarlo chiude tutte le sessioni che sta eseguendo, compresa questa. Viene riavviato con lo stesso comando e con il suo HTTPS puntato su Flowlight."; + +"No concerns found" = "No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "%@ sent %@ in one inspected %@ request."; +"Request size" = "Request size"; +"Highlights destructive-looking HTTP requests." = "Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "Reads header names only; values are never exposed."; +"Credential-like headers present" = "Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "This request included credential-like header names. Values remain redacted."; +"Header names" = "Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "Review %@"; +"High-risk tool available" = "High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "agent tool"; +"Traffic metadata" = "Traffic metadata"; +"App identity" = "App identity"; +"LLM metadata" = "LLM metadata"; +"MCP metadata" = "MCP metadata"; +"Suggest guardrails" = "Suggest guardrails"; +"Metadata only" = "Metadata only"; +"Tool names" = "Tool names"; +"Guardrail suggestions" = "Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "Unsigned local"; +"Verified official" = "Verified official"; +"Verified community" = "Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "Stop Monitoring %@"; +"Always Monitor %@" = "Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "Reinstall Filter"; +"Potential harm" = "Potential harm"; +"Show only medium and high local risk assessments" = "Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "Showing only requests with medium or high potential harm"; +"Response modified" = "Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "Modify This Response…"; +"Modify requests" = "Modify requests"; +"Modify responses" = "Modify responses"; +"Add Guardrail" = "Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "Local risk analysis"; +"Analyze potential harm after capture" = "Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "Clear analyses…"; +"Delete only retained local risk scores and explanations" = "Delete only retained local risk scores and explanations"; +"Not assessed" = "Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "No captured request headers."; +"Request payload" = "Request payload"; +"No response headers yet." = "No response headers yet."; +"Response body" = "Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "Import Package…"; +"Backfill Findings" = "Backfill Findings"; +"Clear plugin findings…" = "Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "No third-party plugin packages are installed."; +"Clear plugin findings?" = "Clear plugin findings?"; +"Clear findings" = "Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "Saved configuration for %@."; +"Installed %@." = "Installed %@."; +"Import failed: %@" = "Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "Exported %@."; +"Backfilled %@: %lld findings." = "Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "Package %@ · Script %@"; +"Configured: %@" = "Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "Review Plugin Package"; +"Plugin ID" = "Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "Package digest"; +"Script digest" = "Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "Not declared"; +"Configure %@" = "Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "Remove this setting"; +"Add Setting" = "Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "No response rules."; +"New response transform" = "New response transform"; +"Edit response transform" = "Edit response transform"; +"Transform response" = "Transform response"; +"Captured request context" = "Captured request context"; +"Upstream status: %lld" = "Upstream status: %lld"; +"No captured upstream status." = "No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "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."; +"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." = "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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "No request rules."; +"Every matching rule is applied, in order." = "Every matching rule is applied, in order."; +"%lld edit(s)" = "%lld edit(s)"; +"New request rule" = "New request rule"; +"Edit request rule" = "Edit request rule"; +"Optional, e.g. “Force temperature”" = "Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "Header name"; +"Add header edit" = "Add header edit"; +"Request body" = "Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "value (JSON or text)"; +"Add body edit" = "Add body edit"; +"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." = "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."; +"Clear local analyses?" = "Clear local analyses?"; +"Clear analyses" = "Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Localization/ja.lproj/Localizable.strings b/Flowlight/Localization/ja.lproj/Localizable.strings index b15f138..65883b9 100644 --- a/Flowlight/Localization/ja.lproj/Localizable.strings +++ b/Flowlight/Localization/ja.lproj/Localizable.strings @@ -1023,8 +1023,6 @@ "Method" = "メソッド"; "Delay" = "遅延"; "seconds" = "秒"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "1 行に「名前: 値」を 1 つ書きます。Content-Length と Connection は Flowlight が付けます。"; -"Body" = "本文"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "リクエストは %@ 待ってから応答されます。独自のタイムアウトを持つクライアントは、その前にあきらめます。ふつうはそれが狙いです。"; "Add Rule" = "ルールを追加"; "Save" = "保存"; @@ -1378,9 +1376,6 @@ "Version %@ is the latest release." = "バージョン %@ が最新のリリースです。"; /* Common words kept for menus macOS builds itself. */ -"Edit" = "編集"; -"On" = "オン"; -"Settings" = "設定"; /* 0.9.5 export disclosure */ @@ -1430,3 +1425,228 @@ "Restart %@ through the proxy?" = "%@ をプロキシ経由で再起動しますか?"; "Restart and End Its Sessions" = "再起動してセッションをすべて終了"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@ が %@ を起動しているため、プロキシ設定は新しいウインドウではなく %@ に届く必要があります。いま開いたシェルは、どのエージェントの親プロセスにもなりません。再起動すると、いま実行中のセッションはこのセッションも含めてすべて終了します。同じコマンドで起動し直し、HTTPS は Flowlight に向けられます。"; + +"No concerns found" = "No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "%@ sent %@ in one inspected %@ request."; +"Request size" = "Request size"; +"Highlights destructive-looking HTTP requests." = "Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "Reads header names only; values are never exposed."; +"Credential-like headers present" = "Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "This request included credential-like header names. Values remain redacted."; +"Header names" = "Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "Review %@"; +"High-risk tool available" = "High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "agent tool"; +"Traffic metadata" = "Traffic metadata"; +"App identity" = "App identity"; +"LLM metadata" = "LLM metadata"; +"MCP metadata" = "MCP metadata"; +"Suggest guardrails" = "Suggest guardrails"; +"Metadata only" = "Metadata only"; +"Tool names" = "Tool names"; +"Guardrail suggestions" = "Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "Unsigned local"; +"Verified official" = "Verified official"; +"Verified community" = "Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "Stop Monitoring %@"; +"Always Monitor %@" = "Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "Reinstall Filter"; +"Potential harm" = "Potential harm"; +"Show only medium and high local risk assessments" = "Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "Showing only requests with medium or high potential harm"; +"Response modified" = "Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "Modify This Response…"; +"Modify requests" = "Modify requests"; +"Modify responses" = "Modify responses"; +"Add Guardrail" = "Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "Local risk analysis"; +"Analyze potential harm after capture" = "Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "Clear analyses…"; +"Delete only retained local risk scores and explanations" = "Delete only retained local risk scores and explanations"; +"Not assessed" = "Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "No captured request headers."; +"Request payload" = "Request payload"; +"No response headers yet." = "No response headers yet."; +"Response body" = "Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "Import Package…"; +"Backfill Findings" = "Backfill Findings"; +"Clear plugin findings…" = "Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "No third-party plugin packages are installed."; +"Clear plugin findings?" = "Clear plugin findings?"; +"Clear findings" = "Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "Saved configuration for %@."; +"Installed %@." = "Installed %@."; +"Import failed: %@" = "Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "Exported %@."; +"Backfilled %@: %lld findings." = "Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "Package %@ · Script %@"; +"Configured: %@" = "Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "Review Plugin Package"; +"Plugin ID" = "Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "Package digest"; +"Script digest" = "Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "Not declared"; +"Configure %@" = "Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "Remove this setting"; +"Add Setting" = "Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "No response rules."; +"New response transform" = "New response transform"; +"Edit response transform" = "Edit response transform"; +"Transform response" = "Transform response"; +"Captured request context" = "Captured request context"; +"Upstream status: %lld" = "Upstream status: %lld"; +"No captured upstream status." = "No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "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."; +"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." = "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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "No request rules."; +"Every matching rule is applied, in order." = "Every matching rule is applied, in order."; +"%lld edit(s)" = "%lld edit(s)"; +"New request rule" = "New request rule"; +"Edit request rule" = "Edit request rule"; +"Optional, e.g. “Force temperature”" = "Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "Header name"; +"Add header edit" = "Add header edit"; +"Request body" = "Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "value (JSON or text)"; +"Add body edit" = "Add body edit"; +"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." = "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."; +"Clear local analyses?" = "Clear local analyses?"; +"Clear analyses" = "Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Localization/ko.lproj/Localizable.strings b/Flowlight/Localization/ko.lproj/Localizable.strings index 373cdc2..5bd9d2b 100644 --- a/Flowlight/Localization/ko.lproj/Localizable.strings +++ b/Flowlight/Localization/ko.lproj/Localizable.strings @@ -1046,8 +1046,6 @@ "Method" = "메서드"; "Delay" = "지연"; "seconds" = "초"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "한 줄에 하나씩 Name: value 형식으로 씁니다. Content-Length와 Connection은 Flowlight가 채웁니다."; -"Body" = "본문"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "요청은 %@ 동안 기다린 뒤 응답을 받습니다. 자체 시간 제한이 있는 클라이언트는 그전에 포기하는데, 보통 그것이 목적입니다."; "Add Rule" = "규칙 추가"; "Save" = "저장"; @@ -1401,9 +1399,6 @@ "Version %@ is the latest release." = "최신 릴리스는 버전 %@입니다."; /* Common words kept for menus macOS builds itself. */ -"Edit" = "편집"; -"On" = "켬"; -"Settings" = "설정"; /* 0.9.6 MCP domains */ "in a config file" = "설정 파일에 있음"; @@ -1429,3 +1424,228 @@ "Restart %@ through the proxy?" = "%@를 프록시 경유로 다시 실행할까요?"; "Restart and End Its Sessions" = "다시 실행하고 세션 모두 종료"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@가 %@를 실행하므로, 프록시 설정은 새 창이 아니라 %@에 닿아야 합니다. 지금 여는 셸은 어느 에이전트의 부모 프로세스도 되지 않습니다. 다시 실행하면 지금 돌아가고 있는 세션이 이 세션까지 모두 끝납니다. 같은 명령으로 다시 실행되며, HTTPS는 Flowlight를 향하게 됩니다."; + +"No concerns found" = "No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "%@ sent %@ in one inspected %@ request."; +"Request size" = "Request size"; +"Highlights destructive-looking HTTP requests." = "Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "Reads header names only; values are never exposed."; +"Credential-like headers present" = "Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "This request included credential-like header names. Values remain redacted."; +"Header names" = "Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "Review %@"; +"High-risk tool available" = "High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "agent tool"; +"Traffic metadata" = "Traffic metadata"; +"App identity" = "App identity"; +"LLM metadata" = "LLM metadata"; +"MCP metadata" = "MCP metadata"; +"Suggest guardrails" = "Suggest guardrails"; +"Metadata only" = "Metadata only"; +"Tool names" = "Tool names"; +"Guardrail suggestions" = "Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "Unsigned local"; +"Verified official" = "Verified official"; +"Verified community" = "Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "Stop Monitoring %@"; +"Always Monitor %@" = "Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "Reinstall Filter"; +"Potential harm" = "Potential harm"; +"Show only medium and high local risk assessments" = "Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "Showing only requests with medium or high potential harm"; +"Response modified" = "Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "Modify This Response…"; +"Modify requests" = "Modify requests"; +"Modify responses" = "Modify responses"; +"Add Guardrail" = "Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "Local risk analysis"; +"Analyze potential harm after capture" = "Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "Clear analyses…"; +"Delete only retained local risk scores and explanations" = "Delete only retained local risk scores and explanations"; +"Not assessed" = "Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "No captured request headers."; +"Request payload" = "Request payload"; +"No response headers yet." = "No response headers yet."; +"Response body" = "Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "Import Package…"; +"Backfill Findings" = "Backfill Findings"; +"Clear plugin findings…" = "Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "No third-party plugin packages are installed."; +"Clear plugin findings?" = "Clear plugin findings?"; +"Clear findings" = "Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "Saved configuration for %@."; +"Installed %@." = "Installed %@."; +"Import failed: %@" = "Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "Exported %@."; +"Backfilled %@: %lld findings." = "Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "Package %@ · Script %@"; +"Configured: %@" = "Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "Review Plugin Package"; +"Plugin ID" = "Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "Package digest"; +"Script digest" = "Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "Not declared"; +"Configure %@" = "Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "Remove this setting"; +"Add Setting" = "Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "No response rules."; +"New response transform" = "New response transform"; +"Edit response transform" = "Edit response transform"; +"Transform response" = "Transform response"; +"Captured request context" = "Captured request context"; +"Upstream status: %lld" = "Upstream status: %lld"; +"No captured upstream status." = "No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "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."; +"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." = "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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "No request rules."; +"Every matching rule is applied, in order." = "Every matching rule is applied, in order."; +"%lld edit(s)" = "%lld edit(s)"; +"New request rule" = "New request rule"; +"Edit request rule" = "Edit request rule"; +"Optional, e.g. “Force temperature”" = "Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "Header name"; +"Add header edit" = "Add header edit"; +"Request body" = "Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "value (JSON or text)"; +"Add body edit" = "Add body edit"; +"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." = "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."; +"Clear local analyses?" = "Clear local analyses?"; +"Clear analyses" = "Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Localization/pt-PT.lproj/Localizable.strings b/Flowlight/Localization/pt-PT.lproj/Localizable.strings index bf663af..d187657 100644 --- a/Flowlight/Localization/pt-PT.lproj/Localizable.strings +++ b/Flowlight/Localization/pt-PT.lproj/Localizable.strings @@ -1046,8 +1046,6 @@ "Method" = "Método"; "Delay" = "Atraso"; "seconds" = "segundos"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "Um «Nome: valor» por linha. O Content-Length e o Connection são escritos pelo Flowlight."; -"Body" = "Corpo"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "O pedido espera %@ antes de ser respondido. Os clientes com o seu próprio tempo-limite desistem primeiro, o que é normalmente o objetivo."; "Add Rule" = "Adicionar regra"; "Save" = "Guardar"; @@ -1401,9 +1399,6 @@ "Version %@ is the latest release." = "A versão %@ é o lançamento mais recente."; /* Common words kept for menus macOS builds itself. */ -"Edit" = "Editar"; -"On" = "Ativado"; -"Settings" = "Definições"; /* 0.9.6 MCP domains */ "in a config file" = "em ficheiro de configuração"; @@ -1429,3 +1424,228 @@ "Restart %@ through the proxy?" = "Reiniciar %@ pelo proxy?"; "Restart and End Its Sessions" = "Reiniciar e terminar as suas sessões"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@ inicia %@, por isso as definições de proxy têm de chegar a %@ e não a uma janela nova — uma shell aberta agora nunca seria o pai de nenhum agente. Reiniciá-lo termina todas as sessões que está a executar, incluindo esta. É iniciado de novo com o mesmo comando e com o HTTPS apontado ao Flowlight."; + +"No concerns found" = "No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "%@ sent %@ in one inspected %@ request."; +"Request size" = "Request size"; +"Highlights destructive-looking HTTP requests." = "Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "Reads header names only; values are never exposed."; +"Credential-like headers present" = "Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "This request included credential-like header names. Values remain redacted."; +"Header names" = "Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "Review %@"; +"High-risk tool available" = "High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "agent tool"; +"Traffic metadata" = "Traffic metadata"; +"App identity" = "App identity"; +"LLM metadata" = "LLM metadata"; +"MCP metadata" = "MCP metadata"; +"Suggest guardrails" = "Suggest guardrails"; +"Metadata only" = "Metadata only"; +"Tool names" = "Tool names"; +"Guardrail suggestions" = "Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "Unsigned local"; +"Verified official" = "Verified official"; +"Verified community" = "Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "Stop Monitoring %@"; +"Always Monitor %@" = "Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "Reinstall Filter"; +"Potential harm" = "Potential harm"; +"Show only medium and high local risk assessments" = "Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "Showing only requests with medium or high potential harm"; +"Response modified" = "Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "Modify This Response…"; +"Modify requests" = "Modify requests"; +"Modify responses" = "Modify responses"; +"Add Guardrail" = "Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "Local risk analysis"; +"Analyze potential harm after capture" = "Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "Clear analyses…"; +"Delete only retained local risk scores and explanations" = "Delete only retained local risk scores and explanations"; +"Not assessed" = "Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "No captured request headers."; +"Request payload" = "Request payload"; +"No response headers yet." = "No response headers yet."; +"Response body" = "Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "Import Package…"; +"Backfill Findings" = "Backfill Findings"; +"Clear plugin findings…" = "Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "No third-party plugin packages are installed."; +"Clear plugin findings?" = "Clear plugin findings?"; +"Clear findings" = "Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "Saved configuration for %@."; +"Installed %@." = "Installed %@."; +"Import failed: %@" = "Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "Exported %@."; +"Backfilled %@: %lld findings." = "Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "Package %@ · Script %@"; +"Configured: %@" = "Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "Review Plugin Package"; +"Plugin ID" = "Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "Package digest"; +"Script digest" = "Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "Not declared"; +"Configure %@" = "Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "Remove this setting"; +"Add Setting" = "Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "No response rules."; +"New response transform" = "New response transform"; +"Edit response transform" = "Edit response transform"; +"Transform response" = "Transform response"; +"Captured request context" = "Captured request context"; +"Upstream status: %lld" = "Upstream status: %lld"; +"No captured upstream status." = "No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "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."; +"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." = "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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "No request rules."; +"Every matching rule is applied, in order." = "Every matching rule is applied, in order."; +"%lld edit(s)" = "%lld edit(s)"; +"New request rule" = "New request rule"; +"Edit request rule" = "Edit request rule"; +"Optional, e.g. “Force temperature”" = "Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "Header name"; +"Add header edit" = "Add header edit"; +"Request body" = "Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "value (JSON or text)"; +"Add body edit" = "Add body edit"; +"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." = "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."; +"Clear local analyses?" = "Clear local analyses?"; +"Clear analyses" = "Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Localization/zh-Hans.lproj/Localizable.strings b/Flowlight/Localization/zh-Hans.lproj/Localizable.strings index 53105f0..e1dad86 100644 --- a/Flowlight/Localization/zh-Hans.lproj/Localizable.strings +++ b/Flowlight/Localization/zh-Hans.lproj/Localizable.strings @@ -1046,8 +1046,6 @@ "Method" = "方法"; "Delay" = "延迟"; "seconds" = "秒"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "每行一个 Name: value。Content-Length 和 Connection 由 Flowlight 写入。"; -"Body" = "正文"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "请求会等待 %@ 之后才得到响应。有自己超时设置的客户端会先放弃,而这通常正是重点。"; "Add Rule" = "添加规则"; "Save" = "保存"; @@ -1401,9 +1399,6 @@ "Version %@ is the latest release." = "版本 %@ 是最新发布的版本。"; /* Common words kept for menus macOS builds itself. */ -"Edit" = "编辑"; -"On" = "开启"; -"Settings" = "设置"; /* 0.9.6 MCP domains */ "in a config file" = "在配置文件中"; @@ -1429,3 +1424,228 @@ "Restart %@ through the proxy?" = "经由代理服务器重新启动 %@?"; "Restart and End Its Sessions" = "重新启动并结束其会话"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@ 负责启动 %@,所以代理服务器设置必须送达 %@,而不是送进一个新窗口——现在打开的 shell 永远不会是任何代理的父进程。重新启动它会结束它当前运行的每一个会话,包括这一个。它会以同样的命令重新启动,并把 HTTPS 指向 Flowlight。"; + +"No concerns found" = "待翻译:No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "待翻译:The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "待翻译:The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "待翻译:The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "待翻译:The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "待翻译:The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "待翻译:Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "待翻译:Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "待翻译:The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "待翻译:Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "待翻译:The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "待翻译:The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "待翻译:The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "待翻译:Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "待翻译:This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "待翻译:This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "待翻译:This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "待翻译:This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "待翻译:The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "待翻译:The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "待翻译:Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "待翻译:Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "待翻译:Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "待翻译:%@ sent %@ in one inspected %@ request."; +"Request size" = "待翻译:Request size"; +"Highlights destructive-looking HTTP requests." = "待翻译:Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "待翻译:Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "待翻译:Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "待翻译:%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "待翻译:Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "待翻译:Reads header names only; values are never exposed."; +"Credential-like headers present" = "待翻译:Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "待翻译:This request included credential-like header names. Values remain redacted."; +"Header names" = "待翻译:Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "待翻译:Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "待翻译:Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "待翻译:MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "待翻译:%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "待翻译:Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "待翻译:Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "待翻译:Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "待翻译:%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "待翻译:Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "待翻译:Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "待翻译:Review %@"; +"High-risk tool available" = "待翻译:High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "待翻译:%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "待翻译:agent tool"; +"Traffic metadata" = "待翻译:Traffic metadata"; +"App identity" = "待翻译:App identity"; +"LLM metadata" = "待翻译:LLM metadata"; +"MCP metadata" = "待翻译:MCP metadata"; +"Suggest guardrails" = "待翻译:Suggest guardrails"; +"Metadata only" = "待翻译:Metadata only"; +"Tool names" = "待翻译:Tool names"; +"Guardrail suggestions" = "待翻译:Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "待翻译:Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "待翻译:Unsigned local"; +"Verified official" = "待翻译:Verified official"; +"Verified community" = "待翻译:Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "待翻译:Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "待翻译:Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "待翻译:Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "待翻译:This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "待翻译:The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "待翻译:The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "待翻译:The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "待翻译:The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "待翻译:Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "待翻译:The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "待翻译:The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "待翻译:Stop Monitoring %@"; +"Always Monitor %@" = "待翻译:Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "待翻译:Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "待翻译:Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "待翻译:· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "待翻译:Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "待翻译:%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "待翻译:Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "待翻译:The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "待翻译:Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "待翻译:Reinstall Filter"; +"Potential harm" = "待翻译:Potential harm"; +"Show only medium and high local risk assessments" = "待翻译:Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "待翻译:Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "待翻译:Showing only requests with medium or high potential harm"; +"Response modified" = "待翻译:Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "待翻译:%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "待翻译:Modify This Response…"; +"Modify requests" = "待翻译:Modify requests"; +"Modify responses" = "待翻译:Modify responses"; +"Add Guardrail" = "待翻译:Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "待翻译:Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "待翻译:Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "待翻译:Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "待翻译:%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "待翻译:Local risk analysis"; +"Analyze potential harm after capture" = "待翻译:Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "待翻译:Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "待翻译:Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "待翻译:Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "待翻译:Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "待翻译:Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "待翻译:Clear analyses…"; +"Delete only retained local risk scores and explanations" = "待翻译:Delete only retained local risk scores and explanations"; +"Not assessed" = "待翻译:Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "待翻译:No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "待翻译:Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "待翻译:No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "待翻译:This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "待翻译:Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "待翻译:No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "待翻译:Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "待翻译:No captured request headers."; +"Request payload" = "待翻译:Request payload"; +"No response headers yet." = "待翻译:No response headers yet."; +"Response body" = "待翻译:Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "待翻译:Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "待翻译:Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "待翻译:Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "待翻译:Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "待翻译:Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "待翻译:Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "待翻译:Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "待翻译:Import Package…"; +"Backfill Findings" = "待翻译:Backfill Findings"; +"Clear plugin findings…" = "待翻译:Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "待翻译:Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "待翻译:No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "待翻译:No third-party plugin packages are installed."; +"Clear plugin findings?" = "待翻译:Clear plugin findings?"; +"Clear findings" = "待翻译:Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "待翻译:Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "待翻译:Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "待翻译:%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "待翻译:Saved configuration for %@."; +"Installed %@." = "待翻译:Installed %@."; +"Import failed: %@" = "待翻译:Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "待翻译:Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "待翻译:Exported %@."; +"Backfilled %@: %lld findings." = "待翻译:Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "待翻译:Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "待翻译:Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "待翻译:Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "待翻译:%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "待翻译:Package %@ · Script %@"; +"Configured: %@" = "待翻译:Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "待翻译:Review Plugin Package"; +"Plugin ID" = "待翻译:Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "待翻译:Package digest"; +"Script digest" = "待翻译:Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "待翻译:Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "待翻译:Not declared"; +"Configure %@" = "待翻译:Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "待翻译:Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "待翻译:Remove this setting"; +"Add Setting" = "待翻译:Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "待翻译:%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "待翻译:No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "待翻译:%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "待翻译:Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "待翻译:Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "待翻译:On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "待翻译:Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "待翻译:Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "待翻译:HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "待翻译:No response rules."; +"New response transform" = "待翻译:New response transform"; +"Edit response transform" = "待翻译:Edit response transform"; +"Transform response" = "待翻译:Transform response"; +"Captured request context" = "待翻译:Captured request context"; +"Upstream status: %lld" = "待翻译:Upstream status: %lld"; +"No captured upstream status." = "待翻译:No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "待翻译:Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "待翻译: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."; +"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." = "待翻译: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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "待翻译:HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "待翻译:No request rules."; +"Every matching rule is applied, in order." = "待翻译:Every matching rule is applied, in order."; +"%lld edit(s)" = "待翻译:%lld edit(s)"; +"New request rule" = "待翻译:New request rule"; +"Edit request rule" = "待翻译:Edit request rule"; +"Optional, e.g. “Force temperature”" = "待翻译:Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "待翻译:Header name"; +"Add header edit" = "待翻译:Add header edit"; +"Request body" = "待翻译:Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "待翻译:value (JSON or text)"; +"Add body edit" = "待翻译:Add body edit"; +"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." = "待翻译: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."; +"Clear local analyses?" = "待翻译:Clear local analyses?"; +"Clear analyses" = "待翻译:Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "待翻译:This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Localization/zh-Hant.lproj/Localizable.strings b/Flowlight/Localization/zh-Hant.lproj/Localizable.strings index bb88451..d745d39 100644 --- a/Flowlight/Localization/zh-Hant.lproj/Localizable.strings +++ b/Flowlight/Localization/zh-Hant.lproj/Localizable.strings @@ -1046,8 +1046,6 @@ "Method" = "方法"; "Delay" = "延遲"; "seconds" = "秒"; -"One Name: value per line. Content-Length and Connection are written by Flowlight." = "每行一組 Name: value。Content-Length 和 Connection 由 Flowlight 自行填寫。"; -"Body" = "內文"; "The request waits %@ before it's answered. Clients with their own timeout will give up first, which is usually the point." = "請求會等 %@ 之後才得到回應。有自己逾時設定的用戶端會先放棄,而這通常正是目的。"; "Add Rule" = "加入規則"; "Save" = "儲存"; @@ -1401,9 +1399,6 @@ "Version %@ is the latest release." = "%@ 版是最新的發布版本。"; /* Common words kept for menus macOS builds itself. */ -"Edit" = "編輯"; -"On" = "開啟"; -"Settings" = "設定"; /* 0.9.6 MCP domains */ "in a config file" = "在設定檔中"; @@ -1429,3 +1424,228 @@ "Restart %@ through the proxy?" = "經過代理伺服器重新啟動 %@?"; "Restart and End Its Sessions" = "重新啟動並結束其工作階段"; "%@ starts %@, so the proxy settings have to reach %@ rather than a new window — a shell opened now would never be any agent's parent. Restarting it ends every session it is currently running, including this one. It is started again with the same command and its HTTPS pointed at Flowlight." = "%@ 負責啟動 %@,所以代理伺服器設定必須送達 %@,而不是送進一個新視窗 — 現在開啟的 shell 永遠不會是任何代理的父程序。重新啟動它會結束它目前正在執行的每一個工作階段,包括這一個。它會用同樣的指令重新啟動,並把 HTTPS 指向 Flowlight。"; + +"No concerns found" = "No concerns found"; +"Low" = "Low"; +"Medium" = "Medium"; +"High" = "High"; +"The request targets a private or local service through an administrative-looking route." = "The request targets a private or local service through an administrative-looking route."; +"The request targets a private or local service." = "The request targets a private or local service."; +"The request uploads substantially more data than it receives." = "The request uploads substantially more data than it receives."; +"The request is associated with a tool that can act on files, commands, credentials, or remote services." = "The request is associated with a tool that can act on files, commands, credentials, or remote services."; +"The request is part of an MCP tool call." = "The request is part of an MCP tool call."; +"Flowlight changed this request with a guardrail before it was sent." = "Flowlight changed this request with a guardrail before it was sent."; +"Flowlight answered this request locally with a mock rule." = "Flowlight answered this request locally with a mock rule."; +"The on-device model needs macOS 26 or later." = "The on-device model needs macOS 26 or later."; +"Local analysis was paused." = "Local analysis was paused."; +"The on-device model couldn't finish this analysis." = "The on-device model couldn't finish this analysis."; +"The captured %@ body was truncated, so Flowlight cannot show it as a complete value." = "The captured %@ body was truncated, so Flowlight cannot show it as a complete value."; +"The captured %@ body is unavailable." = "The captured %@ body is unavailable."; +"response" = "response"; +"request" = "request"; +"Only a real upstream response can be modified." = "Only a real upstream response can be modified."; +"This response was only partially retained, so Flowlight cannot safely prepare a transform." = "This response was only partially retained, so Flowlight cannot safely prepare a transform."; +"This response was streamed or chunked, so Flowlight cannot safely transform it." = "This response was streamed or chunked, so Flowlight cannot safely transform it."; +"This response was compressed, so Flowlight cannot safely transform it." = "This response was compressed, so Flowlight cannot safely transform it."; +"This response is not a complete JSON or UTF-8 text response." = "This response is not a complete JSON or UTF-8 text response."; +"The captured response body is unavailable." = "The captured response body is unavailable."; +"The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone." = "The settings file's environment block isn't in a shape Flowlight can edit safely, so it was left alone."; +"Couldn't read %@ as JSON, so it was left alone." = "Couldn't read %@ as JSON, so it was left alone."; +"Flags inspected requests that upload unusually large payloads." = "Flags inspected requests that upload unusually large payloads."; +"Uses request size, method, app and host metadata only." = "Uses request size, method, app and host metadata only."; +"%@ sent %@ in one inspected %@ request." = "%@ sent %@ in one inspected %@ request."; +"Request size" = "Request size"; +"Highlights destructive-looking HTTP requests." = "Highlights destructive-looking HTTP requests."; +"Uses method, redacted path shape and host metadata only." = "Uses method, redacted path shape and host metadata only."; +"Destructive-looking request" = "Destructive-looking request"; +"%@ made a %@ request to a sensitive-looking path on %@." = "%@ made a %@ request to a sensitive-looking path on %@."; +"Notes requests carrying credential-like header names." = "Notes requests carrying credential-like header names."; +"Reads header names only; values are never exposed." = "Reads header names only; values are never exposed."; +"Credential-like headers present" = "Credential-like headers present"; +"This request included credential-like header names. Values remain redacted." = "This request included credential-like header names. Values remain redacted."; +"Header names" = "Header names"; +"Surfaces direct MCP activity observed over inspected HTTP." = "Surfaces direct MCP activity observed over inspected HTTP."; +"Uses MCP method, server and tool names only." = "Uses MCP method, server and tool names only."; +"MCP over HTTP observed" = "MCP over HTTP observed"; +"%@ used MCP over inspected HTTP with %@." = "%@ used MCP over inspected HTTP with %@."; +"Methods" = "Methods"; +"Explains MCP connectors that run on the model provider side." = "Explains MCP connectors that run on the model provider side."; +"Uses connector labels, provider names and approval metadata only." = "Uses connector labels, provider names and approval metadata only."; +"Connector" = "Connector"; +"Approval" = "Approval"; +"configured" = "configured"; +"Provider-run MCP connector" = "Provider-run MCP connector"; +"%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them." = "%@ offered %@ through %@. Its network calls run outside this Mac, so local network rules may not see them."; +"Suggests guardrails for declared or used tools with write, shell or delete capability." = "Suggests guardrails for declared or used tools with write, shell or delete capability."; +"Uses tool and MCP server names only, not tool arguments." = "Uses tool and MCP server names only, not tool arguments."; +"Review %@" = "Review %@"; +"High-risk tool available" = "High-risk tool available"; +"%@ declared or used %@. Add a guardrail if this tool should be refused." = "%@ declared or used %@. Add a guardrail if this tool should be refused."; +"Tool" = "Tool"; +"Server" = "Server"; +"agent tool" = "agent tool"; +"Traffic metadata" = "Traffic metadata"; +"App identity" = "App identity"; +"LLM metadata" = "LLM metadata"; +"MCP metadata" = "MCP metadata"; +"Suggest guardrails" = "Suggest guardrails"; +"Metadata only" = "Metadata only"; +"Tool names" = "Tool names"; +"Guardrail suggestions" = "Guardrail suggestions"; +"Built-in" = "Built-in"; +"Local file" = "Local file"; +"Community" = "Community"; +"Official" = "Official"; +"Unsigned local" = "Unsigned local"; +"Verified official" = "Verified official"; +"Verified community" = "Verified community"; +"Rejected" = "Rejected"; +"LLM/MCP" = "LLM/MCP"; +"Installed plugin IDs cannot reuse a built-in plugin ID." = "Installed plugin IDs cannot reuse a built-in plugin ID."; +"Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens." = "Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens."; +"Plugin scripts must be 128 KB or smaller." = "Plugin scripts must be 128 KB or smaller."; +"This plugin package format is not supported." = "This plugin package format is not supported."; +"The plugin package was signed by an unknown key: %@." = "The plugin package was signed by an unknown key: %@."; +"The plugin package signature does not match the declared publisher." = "The plugin package signature does not match the declared publisher."; +"The plugin package signature did not verify." = "The plugin package signature did not verify."; +"The plugin package contents do not match their signed digest." = "The plugin package contents do not match their signed digest."; +"Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance." = "Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance."; +"The local model returned an invalid analysis." = "The local model returned an invalid analysis."; +"The local model cited evidence that was not supplied." = "The local model cited evidence that was not supplied."; +"Stop Monitoring %@" = "Stop Monitoring %@"; +"Always Monitor %@" = "Always Monitor %@"; +"Flowlight is keeping these agents routed through it on every launch." = "Flowlight is keeping these agents routed through it on every launch."; +"Hide this until the monitored agents change" = "Hide this until the monitored agents change"; +"· not routed yet — it may pin its certificates, or a shell setting may override the file" = "· not routed yet — it may pin its certificates, or a shell setting may override the file"; +"Stop Monitoring" = "Stop Monitoring"; +"%@ is reaching its model provider directly, so Flowlight can't see what it sends." = "%@ is reaching its model provider directly, so Flowlight can't see what it sends."; +"Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off." = "Always monitor it to add the proxy to %@'s own settings, so it routes through Flowlight on every launch. Flowlight removes this again when inspection is off."; +"The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs." = "The update installed, but macOS is still running the previous filter — it keeps the earlier copies until they're cleared, and one of them holds the slot the new filter needs."; +"Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler." = "Reinstall the filter to clear them now without restarting, or restart your Mac (Apple menu › Restart) to finish the update. Until then, Flowlight keeps capturing with the sampler."; +"Reinstall Filter" = "Reinstall Filter"; +"Potential harm" = "Potential harm"; +"Show only medium and high local risk assessments" = "Show only medium and high local risk assessments"; +"Turn on Local risk analysis in Reports or Settings to use this filter" = "Turn on Local risk analysis in Reports or Settings to use this filter"; +"Showing only requests with medium or high potential harm" = "Showing only requests with medium or high potential harm"; +"Response modified" = "Response modified"; +"%@ answered, then Flowlight changed the response using %@ before the app received it" = "%@ answered, then Flowlight changed the response using %@ before the app received it"; +"Plugins" = "Plugins"; +"Modify This Response…" = "Modify This Response…"; +"Modify requests" = "Modify requests"; +"Modify responses" = "Modify responses"; +"Add Guardrail" = "Add Guardrail"; +"Save this as a normal Flowlight guardrail" = "Save this as a normal Flowlight guardrail"; +"Plugin findings are advisory. They did not block or change this request." = "Plugin findings are advisory. They did not block or change this request."; +"Let the request reach %@, then change its eligible response before this app receives it" = "Let the request reach %@, then change its eligible response before this app receives it"; +"%@ answered this request. Flowlight then changed the response with “%@” before the app received it." = "%@ answered this request. Flowlight then changed the response with “%@” before the app received it."; +"Local risk analysis" = "Local risk analysis"; +"Analyze potential harm after capture" = "Analyze potential harm after capture"; +"Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac." = "Uses Apple's on-device model only after an inspected request is recorded. It never blocks, changes, or sends traffic off this Mac."; +"Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model." = "Deterministic capture provenance remains available, but contextual local-model assessment needs the on-device model."; +"Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe." = "Only meaningful decrypted HTTP request candidates with independent evidence are assessed. Requests marked Not assessed are not being called safe."; +"Analyze recent inspected requests" = "Analyze recent inspected requests"; +"Queues at most 100 recent eligible requests; captured traffic stays available immediately." = "Queues at most 100 recent eligible requests; captured traffic stays available immediately."; +"Clear analyses…" = "Clear analyses…"; +"Delete only retained local risk scores and explanations" = "Delete only retained local risk scores and explanations"; +"Not assessed" = "Not assessed"; +"No contextual local-model verdict was produced. This does not mean the request is safe." = "No contextual local-model verdict was produced. This does not mean the request is safe."; +"Confidence: %@" = "Confidence: %@"; +"No contextual local-model verdict was produced for this request. That is not a statement that it is safe." = "No contextual local-model verdict was produced for this request. That is not a statement that it is safe."; +"This request did not produce a local assessment. That is not a statement that it is safe." = "This request did not produce a local assessment. That is not a statement that it is safe."; +"Analysis is local and advisory; this request was not blocked or changed." = "Analysis is local and advisory; this request was not blocked or changed."; +"No captured request body." = "No captured request body."; +"Captured request details are reference only. They do not change what this mock matches and are not saved with the rule." = "Captured request details are reference only. They do not change what this mock matches and are not saved with the rule."; +"No captured request headers." = "No captured request headers."; +"Request payload" = "Request payload"; +"No response headers yet." = "No response headers yet."; +"Response body" = "Response body"; +"Binary mock bodies must contain complete hexadecimal byte pairs." = "Binary mock bodies must contain complete hexadecimal byte pairs."; +"Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared." = "Saving keeps this response as a mock rule until you remove it, even if captured traffic is later cleared."; +"Remove header" = "Remove header"; +"Content-Length, Connection and Transfer-Encoding are written by Flowlight." = "Content-Length, Connection and Transfer-Encoding are written by Flowlight."; +"Binary · hexadecimal" = "Binary · hexadecimal"; +"Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes." = "Each pair of hexadecimal characters is one byte. Flowlight writes the original bytes."; +"Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves." = "Plugins add advisory findings to inspected exchanges. They do not block, rewrite, or send traffic anywhere by themselves."; +"Import Package…" = "Import Package…"; +"Backfill Findings" = "Backfill Findings"; +"Clear plugin findings…" = "Clear plugin findings…"; +"Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete." = "Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete."; +"No official plugins are installed." = "No official plugins are installed."; +"Third-party" = "Third-party"; +"No third-party plugin packages are installed." = "No third-party plugin packages are installed."; +"Clear plugin findings?" = "Clear plugin findings?"; +"Clear findings" = "Clear findings"; +"Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place." = "Recorded requests, response bodies, rules, guardrails, and plugin enable settings stay in place."; +"Remove installed plugin?" = "Remove installed plugin?"; +"%@ and its retained findings will be removed. Built-in plugins cannot be removed." = "%@ and its retained findings will be removed. Built-in plugins cannot be removed."; +"Saved configuration for %@." = "Saved configuration for %@."; +"Installed %@." = "Installed %@."; +"Import failed: %@" = "Import failed: %@"; +"Review" = "Review"; +"Choose a Flowlight inspection plugin manifest or package JSON file." = "Choose a Flowlight inspection plugin manifest or package JSON file."; +"Exported %@." = "Exported %@."; +"Backfilled %@: %lld findings." = "Backfilled %@: %lld findings."; +"Backfilled plugin findings: %lld findings." = "Backfilled plugin findings: %lld findings."; +"Backfill failed: %@" = "Backfill failed: %@"; +"JavaScript" = "JavaScript"; +"Suggests guardrails" = "Suggests guardrails"; +"%@ · v%@ · rule v%lld" = "%@ · v%@ · rule v%lld"; +"Package %@ · Script %@" = "Package %@ · Script %@"; +"Configured: %@" = "Configured: %@"; +"Configure…" = "Configure…"; +"Export…" = "Export…"; +"Backfill" = "Backfill"; +"Review Plugin Package" = "Review Plugin Package"; +"Plugin ID" = "Plugin ID"; +"Version" = "Version"; +"Publisher" = "Publisher"; +"Channel" = "Channel"; +"Package digest" = "Package digest"; +"Script digest" = "Script digest"; +"Permissions" = "Permissions"; +"Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic." = "Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic."; +"Install" = "Install"; +"Not declared" = "Not declared"; +"Configure %@" = "Configure %@"; +"Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access." = "Configuration is stored locally as string key/value metadata. It does not grant a plugin network, file, or traffic mutation access."; +"Key" = "Key"; +"Remove this setting" = "Remove this setting"; +"Add Setting" = "Add Setting"; +"Installed" = "Installed"; +"%lld medium or high local assessment%@ in this window." = "%lld medium or high local assessment%@ in this window."; +"No medium or high local assessments in this window. Requests without an assessment are not being called safe." = "No medium or high local assessments in this window. Requests without an assessment are not being called safe."; +"%lld eligible request%@ did not receive a contextual verdict." = "%lld eligible request%@ did not receive a contextual verdict."; +"Local risk analysis is off. Retained local findings are hidden until you turn it back on." = "Local risk analysis is off. Retained local findings are hidden until you turn it back on."; +"Show in Inspect" = "Show in Inspect"; +"On-device-only, advisory scoring after capture. It does not block or change traffic." = "On-device-only, advisory scoring after capture. It does not block or change traffic."; +"Let the request reach its server, then change an eligible JSON or text response before the app receives it." = "Let the request reach its server, then change an eligible JSON or text response before the app receives it."; +"Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged." = "Only complete, fixed-length, uncompressed HTTP/1.1 responses Flowlight decrypts can be changed. Streaming, chunked and compressed replies pass through unchanged."; +"HTTPS inspection is off, so no response can be read or changed yet." = "HTTPS inspection is off, so no response can be read or changed yet."; +"No response rules." = "No response rules."; +"New response transform" = "New response transform"; +"Edit response transform" = "Edit response transform"; +"Transform response" = "Transform response"; +"Captured request context" = "Captured request context"; +"Upstream status: %lld" = "Upstream status: %lld"; +"No captured upstream status." = "No captured upstream status."; +"Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied." = "Define synchronous modifyResponse(args). responseJSON is available for JSON; responseText is available for UTF-8 text. Credential headers are never supplied."; +"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." = "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."; +"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." = "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."; +"HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet." = "HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."; +"No request rules." = "No request rules."; +"Every matching rule is applied, in order." = "Every matching rule is applied, in order."; +"%lld edit(s)" = "%lld edit(s)"; +"New request rule" = "New request rule"; +"Edit request rule" = "Edit request rule"; +"Optional, e.g. “Force temperature”" = "Optional, e.g. “Force temperature”"; +"Set" = "Set"; +"Header name" = "Header name"; +"Add header edit" = "Add header edit"; +"Request body" = "Request body"; +"key.path" = "key.path"; +"value (JSON or text)" = "value (JSON or text)"; +"Add body edit" = "Add body edit"; +"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." = "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."; +"Clear local analyses?" = "Clear local analyses?"; +"Clear analyses" = "Clear analyses"; +"This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place." = "This removes only local potential-harm scores and explanations. Recorded requests, response bodies, alerts, rules, and inspection settings stay in place."; diff --git a/Flowlight/Plugins/PluginModels.swift b/Flowlight/Plugins/PluginModels.swift index 94fb36a..61e4030 100644 --- a/Flowlight/Plugins/PluginModels.swift +++ b/Flowlight/Plugins/PluginModels.swift @@ -1,5 +1,15 @@ import Foundation +struct PluginImportResult: Sendable { + var manifest: PluginManifest + var installed: Bool +} + +struct PluginBackfillResult: Sendable { + var pluginID: String? + var findingCount: Int +} + /// Advisory plugin definitions and findings. Plugins do not alter traffic; they add derived context to inspected exchanges. struct PluginManifest: Codable, Identifiable, Equatable, Sendable { var id: String @@ -16,6 +26,10 @@ struct PluginManifest: Codable, Identifiable, Equatable, Sendable { var capabilities: [Capability] = [.annotate] var configuration: [String: String] = [:] var script: String = "" + var schemaVersion: Int = 1 + var permissions: [PluginPermission] = PluginPermission.defaultMetadata + var privacy: PluginPrivacyDisclosure = PluginPrivacyDisclosure() + var provenance: PluginProvenance = PluginProvenance() enum Kind: String, Codable, Sendable { case traffic, llmMCP } enum Source: String, Codable, Sendable { case builtIn, installed } @@ -41,7 +55,9 @@ struct PluginManifest: Codable, Identifiable, Equatable, Sendable { init(id: String, name: String, version: String, kind: Kind, enabled: Bool = true, source: Source = .builtIn, publisher: Publisher = .official, guardrailProvider: GuardrailProvider = .flowlight, description: String, ruleVersion: Int = 1, privacySummary: String, capabilities: [Capability] = [.annotate], - configuration: [String: String] = [:], script: String = "") { + configuration: [String: String] = [:], script: String = "", schemaVersion: Int = 1, + permissions: [PluginPermission] = PluginPermission.defaultMetadata, + privacy: PluginPrivacyDisclosure = PluginPrivacyDisclosure(), provenance: PluginProvenance = PluginProvenance()) { self.id = id self.name = name self.version = version @@ -56,10 +72,14 @@ struct PluginManifest: Codable, Identifiable, Equatable, Sendable { self.capabilities = capabilities self.configuration = configuration self.script = script + self.schemaVersion = schemaVersion + self.permissions = permissions + self.privacy = privacy + self.provenance = provenance } private enum CodingKeys: String, CodingKey { - case id, name, version, kind, enabled, source, publisher, guardrailProvider, description, ruleVersion, privacySummary, capabilities, configuration, script + case id, name, version, kind, enabled, source, publisher, guardrailProvider, description, ruleVersion, privacySummary, capabilities, configuration, script, schemaVersion, permissions, privacy, provenance } init(from decoder: Decoder) throws { @@ -78,25 +98,172 @@ struct PluginManifest: Codable, Identifiable, Equatable, Sendable { capabilities = try values.decodeIfPresent([Capability].self, forKey: .capabilities) ?? [.annotate] configuration = try values.decodeIfPresent([String: String].self, forKey: .configuration) ?? [:] script = try values.decodeIfPresent(String.self, forKey: .script) ?? "" + schemaVersion = try values.decodeIfPresent(Int.self, forKey: .schemaVersion) ?? 1 + permissions = try values.decodeIfPresent([PluginPermission].self, forKey: .permissions) ?? PluginPermission.defaultMetadata + privacy = try values.decodeIfPresent(PluginPrivacyDisclosure.self, forKey: .privacy) ?? PluginPrivacyDisclosure() + provenance = try values.decodeIfPresent(PluginProvenance.self, forKey: .provenance) ?? PluginProvenance() + } +} + +/// What sanitized metadata a plugin declares it uses. These are not mutation powers. +enum PluginPermission: String, Codable, CaseIterable, Sendable { + case trafficMetadata + case headerNames + case appIdentity + case llmMetadata + case mcpMetadata + case suggestGuardrail + + static let defaultMetadata: [PluginPermission] = [.trafficMetadata, .headerNames, .appIdentity, .llmMetadata, .mcpMetadata] + + var title: String { + switch self { + case .trafficMetadata: return L("Traffic metadata") + case .headerNames: return L("Header names") + case .appIdentity: return L("App identity") + case .llmMetadata: return L("LLM metadata") + case .mcpMetadata: return L("MCP metadata") + case .suggestGuardrail: return L("Suggest guardrails") + } + } +} + +struct PluginPrivacyDisclosure: Codable, Equatable, Sendable { + var categories: [Category] + var summary: String + + init(categories: [Category] = [.metadataOnly], summary: String = "") { + self.categories = categories + self.summary = summary + } + + enum Category: String, Codable, CaseIterable, Sendable { + case metadataOnly + case headerNames + case appIdentity + case toolNames + case mcpMetadata + case guardrailSuggestions + + var title: String { + switch self { + case .metadataOnly: return L("Metadata only") + case .headerNames: return L("Header names") + case .appIdentity: return L("App identity") + case .toolNames: return L("Tool names") + case .mcpMetadata: return L("MCP metadata") + case .guardrailSuggestions: return L("Guardrail suggestions") + } + } + } +} + +struct PluginProvenance: Codable, Equatable, Sendable { + var packageDigest: String + var scriptDigest: String + var sourceURL: String + var repositoryURL: String + var distributionChannel: DistributionChannel + var publisherName: String + var signingKeyID: String + var signedAt: String + var reviewStatus: ReviewStatus + var reviewerNote: String + var approvalFingerprint: String + + init(packageDigest: String = "", scriptDigest: String = "", sourceURL: String = "", repositoryURL: String = "", + distributionChannel: DistributionChannel = .local, publisherName: String = "", signingKeyID: String = "", + signedAt: String = "", reviewStatus: ReviewStatus = .unsigned, reviewerNote: String = "", approvalFingerprint: String = "") { + self.packageDigest = packageDigest + self.scriptDigest = scriptDigest + self.sourceURL = sourceURL + self.repositoryURL = repositoryURL + self.distributionChannel = distributionChannel + self.publisherName = publisherName + self.signingKeyID = signingKeyID + self.signedAt = signedAt + self.reviewStatus = reviewStatus + self.reviewerNote = reviewerNote + self.approvalFingerprint = approvalFingerprint + } + + enum DistributionChannel: String, Codable, Sendable { + case builtIn, local, community, official + + var title: String { + switch self { + case .builtIn: return L("Built-in") + case .local: return L("Local file") + case .community: return L("Community") + case .official: return L("Official") + } + } + } + + enum ReviewStatus: String, Codable, Sendable { + case builtIn, unsigned, verifiedOfficial, verifiedCommunity, rejected + + var title: String { + switch self { + case .builtIn: return L("Built-in") + case .unsigned: return L("Unsigned local") + case .verifiedOfficial: return L("Verified official") + case .verifiedCommunity: return L("Verified community") + case .rejected: return L("Rejected") + } + } + } +} + +struct PluginSignature: Codable, Equatable, Sendable { + var algorithm: String + var keyID: String + var signedAt: String + var value: String + + init(algorithm: String = "p256-sha256", keyID: String, signedAt: String = "", value: String) { + self.algorithm = algorithm + self.keyID = keyID + self.signedAt = signedAt + self.value = value } } struct PluginPackage: Codable, Equatable, Sendable { var manifest: PluginManifest + var signature: PluginSignature? + var packageVersion: Int - init(manifest: PluginManifest) { + init(manifest: PluginManifest, signature: PluginSignature? = nil, packageVersion: Int = 1) { self.manifest = manifest + self.signature = signature + self.packageVersion = packageVersion } init(from decoder: Decoder) throws { if let package = try? decoder.container(keyedBy: CodingKeys.self), package.contains(.manifest) { manifest = try package.decode(PluginManifest.self, forKey: .manifest) + signature = try package.decodeIfPresent(PluginSignature.self, forKey: .signature) + packageVersion = try package.decodeIfPresent(Int.self, forKey: .packageVersion) ?? 1 } else { manifest = try PluginManifest(from: decoder) + signature = nil + packageVersion = 1 } } - private enum CodingKeys: String, CodingKey { case manifest } + private enum CodingKeys: String, CodingKey { case manifest, signature, packageVersion } +} + +struct PluginPackageReview: Identifiable, Equatable, Sendable { + var id: String { approvalFingerprint } + var manifest: PluginManifest + var signatureStatus: PluginProvenance.ReviewStatus + var packageDigest: String + var scriptDigest: String + var approvalFingerprint: String + var warnings: [String] + var signature: PluginSignature? } struct PluginEvidence: Codable, Equatable, Sendable { diff --git a/Flowlight/Plugins/PluginPackageVerifier.swift b/Flowlight/Plugins/PluginPackageVerifier.swift new file mode 100644 index 0000000..aea134d --- /dev/null +++ b/Flowlight/Plugins/PluginPackageVerifier.swift @@ -0,0 +1,178 @@ +import CryptoKit +import Foundation + +enum PluginPackageError: LocalizedError, Equatable { + case reservedBuiltInID + case invalidID + case scriptTooLarge + case unsupportedPackage + case unknownSigner(String) + case wrongPublisher + case invalidSignature + case digestMismatch + + var errorDescription: String? { + switch self { + case .reservedBuiltInID: return L("Installed plugin IDs cannot reuse a built-in plugin ID.") + case .invalidID: return L("Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens.") + case .scriptTooLarge: return L("Plugin scripts must be 128 KB or smaller.") + case .unsupportedPackage: return L("This plugin package format is not supported.") + case .unknownSigner(let key): return L("The plugin package was signed by an unknown key: %@.", key) + case .wrongPublisher: return L("The plugin package signature does not match the declared publisher.") + case .invalidSignature: return L("The plugin package signature did not verify.") + case .digestMismatch: return L("The plugin package contents do not match their signed digest.") + } + } +} + +struct PluginTrustedSigner: Equatable, Sendable { + var keyID: String + var publisher: PluginManifest.Publisher + var publisherName: String + var channel: PluginProvenance.DistributionChannel + var publicKeyPEM: String +} + +enum PluginPackageVerifier { + private static let builtInSigners: [PluginTrustedSigner] = [] + + static func review(data: Data, trustedSigners: [PluginTrustedSigner] = builtInSigners) throws -> PluginPackageReview { + let package = try JSONDecoder().decode(PluginPackage.self, from: data) + guard package.packageVersion == 1 else { throw PluginPackageError.unsupportedPackage } + var manifest = package.manifest + try validateBase(manifest) + manifest.source = .installed + if manifest.capabilities.isEmpty { manifest.capabilities = [.annotate] } + manifest.permissions = normalizedPermissions(manifest) + if manifest.privacy.categories.isEmpty { manifest.privacy.categories = [.metadataOnly] } + if manifest.privacy.summary.isEmpty { manifest.privacy.summary = manifest.privacySummary } + + let scriptDigest = sha256Hex(Data(manifest.script.utf8)) + let packageDigest = digest(for: manifest) + let signatureStatus: PluginProvenance.ReviewStatus + var warnings: [String] = [] + if let signature = package.signature { + guard signature.algorithm == "p256-sha256" else { throw PluginPackageError.unsupportedPackage } + guard let signer = trustedSigners.first(where: { $0.keyID == signature.keyID }) else { throw PluginPackageError.unknownSigner(signature.keyID) } + try verify(signature, digest: packageDigest, signer: signer) + if signer.publisher == .official, manifest.publisher != .official { throw PluginPackageError.wrongPublisher } + if manifest.publisher == .official, signer.publisher != .official { throw PluginPackageError.wrongPublisher } + manifest.publisher = signer.publisher + signatureStatus = signer.publisher == .official ? .verifiedOfficial : .verifiedCommunity + manifest.provenance.publisherName = signer.publisherName + manifest.provenance.signingKeyID = signer.keyID + manifest.provenance.signedAt = signature.signedAt + manifest.provenance.distributionChannel = signer.channel + } else { + if manifest.publisher == .official { manifest.publisher = .thirdParty } + signatureStatus = .unsigned + manifest.provenance.distributionChannel = .local + warnings.append(L("Unsigned local packages can run advisory JavaScript, but they cannot claim official provenance.")) + } + manifest.provenance.packageDigest = packageDigest + manifest.provenance.scriptDigest = scriptDigest + manifest.provenance.reviewStatus = signatureStatus + let approval = approvalFingerprint(for: manifest) + manifest.provenance.approvalFingerprint = approval + return PluginPackageReview(manifest: manifest, signatureStatus: signatureStatus, packageDigest: packageDigest, + scriptDigest: scriptDigest, approvalFingerprint: approval, warnings: warnings, + signature: package.signature) + } + + static func signedPackage(manifest: PluginManifest, keyID: String, privateKey: P256.Signing.PrivateKey, + signedAt: String = ISO8601DateFormatter().string(from: Date())) throws -> PluginPackage { + var copy = manifest + copy.enabled = true + copy.configuration = [:] + let digest = digest(for: copy) + let signature = try privateKey.signature(for: signatureMessage(digest: digest, keyID: keyID)) + return PluginPackage(manifest: copy, + signature: PluginSignature(keyID: keyID, signedAt: signedAt, + value: Data(signature.derRepresentation).base64EncodedString()), + packageVersion: 1) + } + + static func digest(for manifest: PluginManifest) -> String { + let payload = signedPayload(for: manifest) + return sha256Hex(payload) + } + + static func approvalFingerprint(for manifest: PluginManifest) -> String { + let parts = [ + manifest.id, + manifest.version, + String(manifest.ruleVersion), + manifest.publisher.rawValue, + manifest.provenance.signingKeyID, + manifest.capabilities.map(\.rawValue).sorted().joined(separator: ","), + manifest.permissions.map(\.rawValue).sorted().joined(separator: ","), + manifest.privacy.categories.map(\.rawValue).sorted().joined(separator: ","), + manifest.provenance.packageDigest, + manifest.provenance.scriptDigest, + ] + return sha256Hex(Data(parts.joined(separator: "\u{1}").utf8)) + } + + private static func validateBase(_ manifest: PluginManifest) throws { + guard manifest.id.range(of: #"^[A-Za-z0-9_.-]+$"#, options: .regularExpression) != nil else { throw PluginPackageError.invalidID } + guard !PluginEngine.builtInManifests.contains(where: { $0.id == manifest.id }) else { throw PluginPackageError.reservedBuiltInID } + guard manifest.script.lengthOfBytes(using: .utf8) <= PluginScriptRunner.maxScriptBytes else { throw PluginPackageError.scriptTooLarge } + } + + private static func normalizedPermissions(_ manifest: PluginManifest) -> [PluginPermission] { + var set = Set(manifest.permissions.isEmpty ? PluginPermission.defaultMetadata : manifest.permissions) + if manifest.capabilities.contains(.suggestGuardrail) { set.insert(.suggestGuardrail) } + return PluginPermission.allCases.filter { set.contains($0) } + } + + private static func signedPayload(for manifest: PluginManifest) -> Data { + var object: [String: Any] = [ + "id": manifest.id, + "name": manifest.name, + "version": manifest.version, + "kind": manifest.kind.rawValue, + "publisher": manifest.publisher.rawValue, + "guardrailProvider": manifest.guardrailProvider.rawValue, + "description": manifest.description, + "ruleVersion": manifest.ruleVersion, + "privacySummary": manifest.privacySummary, + "capabilities": manifest.capabilities.map(\.rawValue).sorted(), + "schemaVersion": manifest.schemaVersion, + "permissions": manifest.permissions.map(\.rawValue).sorted(), + "privacy": [ + "categories": manifest.privacy.categories.map(\.rawValue).sorted(), + "summary": manifest.privacy.summary, + ], + "scriptDigest": sha256Hex(Data(manifest.script.utf8)), + ] + if !manifest.provenance.sourceURL.isEmpty { object["sourceURL"] = manifest.provenance.sourceURL } + if !manifest.provenance.repositoryURL.isEmpty { object["repositoryURL"] = manifest.provenance.repositoryURL } + let data = try? JSONSerialization.data(withJSONObject: object, options: [.sortedKeys]) + return data ?? Data() + } + + private static func verify(_ signature: PluginSignature, digest: String, signer: PluginTrustedSigner) throws { + guard let signatureData = Data(base64Encoded: signature.value), + let publicKey = publicKey(fromPEM: signer.publicKeyPEM), + let parsed = try? P256.Signing.ECDSASignature(derRepresentation: signatureData) else { + throw PluginPackageError.invalidSignature + } + guard publicKey.isValidSignature(parsed, for: signatureMessage(digest: digest, keyID: signature.keyID)) else { + throw PluginPackageError.invalidSignature + } + } + + private static func publicKey(fromPEM pem: String) -> P256.Signing.PublicKey? { + let lines = pem.split(whereSeparator: \.isNewline).map(String.init).filter { !$0.hasPrefix("-----") } + guard let data = Data(base64Encoded: lines.joined()) else { return nil } + return try? P256.Signing.PublicKey(pemRepresentation: pem) ?? (try? P256.Signing.PublicKey(derRepresentation: data)) + } + + private static func signatureMessage(digest: String, keyID: String) -> Data { + Data("flowlight-plugin-signature-v1\u{1}\(keyID)\u{1}\(digest)".utf8) + } + + private static func sha256Hex(_ data: Data) -> String { + SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined() + } +} diff --git a/Flowlight/Plugins/PluginScriptRunner.swift b/Flowlight/Plugins/PluginScriptRunner.swift index f16a473..cf573f1 100644 --- a/Flowlight/Plugins/PluginScriptRunner.swift +++ b/Flowlight/Plugins/PluginScriptRunner.swift @@ -29,7 +29,7 @@ final class PluginScriptRunner: @unchecked Sendable { "mode": "plugin", "script": manifest.script, "manifest": manifestPayload(manifest), - "context": contextPayload(exchange), + "context": contextPayload(exchange, permissions: Set(manifest.permissions)), ] guard JSONSerialization.isValidJSONObject(input), let encoded = try? JSONSerialization.data(withJSONObject: input), encoded.count <= Self.maxInputBytes, @@ -107,41 +107,55 @@ final class PluginScriptRunner: @unchecked Sendable { "publisher": manifest.publisher.rawValue, "guardrailProvider": manifest.guardrailProvider.rawValue, "capabilities": manifest.capabilities.map(\.rawValue), + "permissions": manifest.permissions.map(\.rawValue), + "privacy": [ + "categories": manifest.privacy.categories.map(\.rawValue), + "summary": manifest.privacy.summary, + ], "configuration": manifest.configuration, ] } - private func contextPayload(_ exchange: HTTPExchange) -> [String: Any] { + private func contextPayload(_ exchange: HTTPExchange, permissions: Set) -> [String: Any] { func value(_ optional: T?) -> Any { optional ?? NSNull() } - var context: [String: Any] = [ - "method": exchange.method, - "scheme": exchange.scheme, - "host": exchange.host, - "port": exchange.port, - "path": exchange.path.split(separator: "?", maxSplits: 1).first.map(String.init) ?? exchange.path, - "status": value(exchange.status), - "requestHeaderNames": exchange.requestHeaders.map { $0.name }, - "responseHeaderNames": exchange.responseHeaders.map { $0.name }, - "requestSize": exchange.requestSize, - "responseSize": exchange.responseSize, - "requestTruncated": exchange.requestTruncated, - "responseTruncated": exchange.responseTruncated, - "contentType": exchange.contentType, - "bundleID": exchange.bundleID, - "appName": exchange.appName, - "agent": value(exchange.agent), - "agentName": value(exchange.agentName), - "mcpServer": value(exchange.mcpServer), - "toolCalls": exchange.toolCalls.map { tool in + var context: [String: Any] = [:] + if permissions.contains(.trafficMetadata) { + context.merge([ + "method": exchange.method, + "scheme": exchange.scheme, + "host": exchange.host, + "port": exchange.port, + "path": exchange.path.split(separator: "?", maxSplits: 1).first.map(String.init) ?? exchange.path, + "status": value(exchange.status), + "requestSize": exchange.requestSize, + "responseSize": exchange.responseSize, + "requestTruncated": exchange.requestTruncated, + "responseTruncated": exchange.responseTruncated, + "contentType": exchange.contentType, + ]) { current, _ in current } + } + if permissions.contains(.headerNames) { + context["requestHeaderNames"] = exchange.requestHeaders.map { $0.name } + context["responseHeaderNames"] = exchange.responseHeaders.map { $0.name } + } + if permissions.contains(.appIdentity) { + context["bundleID"] = exchange.bundleID + context["appName"] = exchange.appName + context["agent"] = value(exchange.agent) + context["agentName"] = value(exchange.agentName) + context["mcpServer"] = value(exchange.mcpServer) + } + if permissions.contains(.mcpMetadata) { + context["toolCalls"] = exchange.toolCalls.map { tool in ["source": tool.source.rawValue, "name": tool.name, "mcpServer": value(tool.mcpServer)] - }, - "mcp": exchange.mcp.map { activity in + } + context["mcp"] = exchange.mcp.map { activity in ["server": activity.server, "endpoint": activity.endpoint, "method": activity.method, "tool": value(activity.tool), "tools": value(activity.tools), "version": value(activity.version), "isError": activity.isError] - }, - ] - if let llm = exchange.llm { + } + } + if permissions.contains(.llmMetadata), let llm = exchange.llm { context["llm"] = [ "provider": llm.provider.rawValue, "model": value(llm.model), diff --git a/Flowlight/Plugins/PluginStore.swift b/Flowlight/Plugins/PluginStore.swift index 826d2a3..267fd1c 100644 --- a/Flowlight/Plugins/PluginStore.swift +++ b/Flowlight/Plugins/PluginStore.swift @@ -1,29 +1,5 @@ import Foundation -struct PluginImportResult: Sendable { - var manifest: PluginManifest - var installed: Bool -} - -struct PluginBackfillResult: Sendable { - var pluginID: String? - var findingCount: Int -} - -enum PluginPackageError: LocalizedError, Equatable { - case reservedBuiltInID - case invalidID - case scriptTooLarge - - var errorDescription: String? { - switch self { - case .reservedBuiltInID: return L("Installed plugin IDs cannot reuse a built-in plugin ID.") - case .invalidID: return L("Plugin IDs must contain only letters, numbers, dots, underscores, and hyphens.") - case .scriptTooLarge: return L("Plugin scripts must be 128 KB or smaller.") - } - } -} - @MainActor final class PluginStore: ObservableObject { @Published private(set) var manifests: [PluginManifest] = [] @@ -67,17 +43,17 @@ final class PluginStore: ObservableObject { version += 1 } - func importPackage(data: Data, completion: @escaping @MainActor (Result) -> Void) { + func reviewPackage(data: Data) throws -> PluginPackageReview { + try PluginPackageVerifier.review(data: data) + } + + func installReviewedPackage(_ review: PluginPackageReview, completion: @escaping @MainActor (Result) -> Void) { guard let db else { return } db.async { [weak self] db in do { - var manifest = try JSONDecoder().decode(PluginPackage.self, from: data).manifest - guard manifest.id.range(of: #"^[A-Za-z0-9_.-]+$"#, options: .regularExpression) != nil else { throw PluginPackageError.invalidID } - guard !PluginEngine.builtInManifests.contains(where: { $0.id == manifest.id }) else { throw PluginPackageError.reservedBuiltInID } - guard manifest.script.lengthOfBytes(using: .utf8) <= PluginScriptRunner.maxScriptBytes else { throw PluginPackageError.scriptTooLarge } + var manifest = review.manifest manifest.source = .installed - if manifest.publisher != .official { manifest.publisher = .thirdParty } - if manifest.capabilities.isEmpty { manifest.capabilities = [.annotate] } + manifest.enabled = true try db.savePluginManifest(manifest) let merged = PluginEngine.mergedManifests(try db.loadPluginManifests()) Task { @MainActor in @@ -91,10 +67,21 @@ final class PluginStore: ObservableObject { } } + func importPackage(data: Data, completion: @escaping @MainActor (Result) -> Void) { + do { + installReviewedPackage(try reviewPackage(data: data), completion: completion) + } catch { + completion(.failure(error)) + } + } + func exportPackage(_ manifest: PluginManifest) throws -> Data { + var copy = manifest + copy.enabled = true + copy.configuration = [:] let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] - return try encoder.encode(PluginPackage(manifest: manifest)) + return try encoder.encode(PluginPackage(manifest: copy)) } func removeInstalled(_ manifest: PluginManifest) { diff --git a/Flowlight/UI/PluginSettingsView.swift b/Flowlight/UI/PluginSettingsView.swift index 6afbb9b..c5c7bf8 100644 --- a/Flowlight/UI/PluginSettingsView.swift +++ b/Flowlight/UI/PluginSettingsView.swift @@ -8,6 +8,7 @@ struct PluginSettingsView: View { @State private var message: String? @State private var configuring: PluginManifest? @State private var removing: PluginManifest? + @State private var pendingReview: PluginPackageReview? var body: some View { Form { @@ -23,7 +24,7 @@ struct PluginSettingsView: View { Text(message).font(.caption).foregroundStyle(.secondary).fixedSize(horizontal: false, vertical: true) } } footer: { - Text(L("Import reads a local plugin manifest package. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete.")) + Text(L("Import reviews provenance, permissions, privacy summary, and script digests before installation. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only; failures are ignored so capture stays complete.")) .font(.caption).foregroundStyle(.secondary) } pluginSection(L("Official"), plugins: monitor.plugins.officialPlugins, @@ -56,6 +57,19 @@ struct PluginSettingsView: View { message = L("Saved configuration for %@.", plugin.name) } } + .sheet(item: $pendingReview) { review in + PluginPackageReviewSheet(review: review) { accepted in + if accepted { + monitor.plugins.installReviewedPackage(review) { result in + switch result { + case .success(let imported): message = L("Installed %@.", imported.manifest.name) + case .failure(let error): message = L("Import failed: %@", error.localizedDescription) + } + } + } + pendingReview = nil + } + } } private func pluginSection(_ title: String, plugins: [PluginManifest], empty: String) -> some View { @@ -85,17 +99,12 @@ struct PluginSettingsView: View { panel.allowedContentTypes = [.json] panel.allowsMultipleSelection = false panel.canChooseDirectories = false - panel.prompt = L("Import") + panel.prompt = L("Review") panel.message = L("Choose a Flowlight inspection plugin manifest or package JSON file.") guard panel.runModal() == .OK, let url = panel.url else { return } do { let data = try Data(contentsOf: url) - monitor.plugins.importPackage(data: data) { result in - switch result { - case .success(let imported): message = L("Installed %@.", imported.manifest.name) - case .failure(let error): message = L("Import failed: %@", error.localizedDescription) - } - } + pendingReview = try monitor.plugins.reviewPackage(data: data) } catch { message = L("Import failed: %@", error.localizedDescription) } @@ -121,7 +130,7 @@ struct PluginSettingsView: View { switch result { case .success(let result): message = plugin.map { L("Backfilled %@: %lld findings.", $0.name, result.findingCount) } - ?? L("Backfilled built-in plugins: %lld findings.", result.findingCount) + ?? L("Backfilled plugin findings: %lld findings.", result.findingCount) case .failure(let error): message = L("Backfill failed: %@", error.localizedDescription) } } @@ -145,6 +154,7 @@ private struct PluginSettingsRow: View { PluginPill(plugin.kind.title) PluginPill(plugin.guardrailProvider.title) PluginPill(plugin.source.title) + PluginPill(plugin.provenance.reviewStatus.title) if !plugin.script.isEmpty { PluginPill(L("JavaScript")) } if plugin.capabilities.contains(.suggestGuardrail) { PluginPill(L("Suggests guardrails")) } } @@ -152,6 +162,10 @@ private struct PluginSettingsRow: View { Text(plugin.privacySummary).font(.caption2).foregroundStyle(.tertiary).fixedSize(horizontal: false, vertical: true) Text(L("%@ · v%@ · rule v%lld", plugin.publisher.title, plugin.version, plugin.ruleVersion)) .font(.caption2).foregroundStyle(.tertiary) + if !plugin.provenance.packageDigest.isEmpty { + Text(L("Package %@ · Script %@", plugin.provenance.packageDigest.shortDigest, plugin.provenance.scriptDigest.shortDigest)) + .font(.caption2).foregroundStyle(.tertiary) + } if !plugin.configuration.isEmpty { Text(L("Configured: %@", plugin.configuration.keys.sorted().joined(separator: ", "))) .font(.caption2).foregroundStyle(.tertiary) @@ -171,6 +185,87 @@ private struct PluginSettingsRow: View { } } +private struct PluginPackageReviewSheet: View { + var review: PluginPackageReview + var close: (Bool) -> Void + + var body: some View { + VStack(alignment: .leading, spacing: 14) { + Text(L("Review Plugin Package")).font(.headline) + VStack(alignment: .leading, spacing: 6) { + HStack(spacing: 6) { + Text(review.manifest.name).font(.callout.bold()) + PluginPill(review.signatureStatus.title) + PluginPill(review.manifest.kind.title) + if !review.manifest.script.isEmpty { PluginPill(L("JavaScript")) } + } + Text(review.manifest.description).font(.caption).foregroundStyle(.secondary).fixedSize(horizontal: false, vertical: true) + Text(review.manifest.privacySummary).font(.caption).foregroundStyle(.secondary).fixedSize(horizontal: false, vertical: true) + } + Divider() + Grid(alignment: .leading, horizontalSpacing: 16, verticalSpacing: 6) { + reviewRow(L("Plugin ID"), review.manifest.id) + reviewRow(L("Version"), review.manifest.version) + reviewRow(L("Publisher"), review.manifest.publisher.title) + reviewRow(L("Provider"), review.manifest.guardrailProvider.title) + reviewRow(L("Channel"), review.manifest.provenance.distributionChannel.title) + reviewRow(L("Package digest"), review.packageDigest.shortDigest) + reviewRow(L("Script digest"), review.scriptDigest.shortDigest) + } + GroupBox(L("Permissions")) { + FlowPillLine(items: review.manifest.permissions.map(\.title)) + .frame(maxWidth: .infinity, alignment: .leading) + } + GroupBox(L("Privacy")) { + FlowPillLine(items: review.manifest.privacy.categories.map(\.title)) + .frame(maxWidth: .infinity, alignment: .leading) + } + if !review.warnings.isEmpty { + VStack(alignment: .leading, spacing: 4) { + ForEach(review.warnings, id: \.self) { warning in + Text(warning).font(.caption).foregroundStyle(.orange).fixedSize(horizontal: false, vertical: true) + } + } + } + Text(L("Plugins are advisory. Installing this package does not let it block, rewrite, upload, or export traffic.")) + .font(.caption).foregroundStyle(.secondary).fixedSize(horizontal: false, vertical: true) + HStack { + Spacer() + Button(L("Cancel")) { close(false) } + Button(L("Install")) { close(true) }.keyboardShortcut(.defaultAction) + } + } + .padding(20) + .frame(width: 620) + } + + private func reviewRow(_ key: String, _ value: String) -> some View { + GridRow { + Text(key).font(.caption).foregroundStyle(.secondary) + Text(value.isEmpty ? L("Not declared") : value).font(.caption).textSelection(.enabled) + } + } +} + +private struct FlowPillLine: View { + var items: [String] + + var body: some View { + WrappingHStack(items: items) { item in PluginPill(item) } + } +} + +private struct WrappingHStack: View { + var items: [Item] + var content: (Item) -> Content + + var body: some View { + HStack(spacing: 6) { + ForEach(items, id: \.self) { item in content(item) } + } + } +} + private struct PluginConfigurationSheet: View { @Environment(\.dismiss) private var dismiss var plugin: PluginManifest @@ -255,3 +350,10 @@ private extension PluginManifest.Source { } } } + +private extension String { + var shortDigest: String { + guard count > 12 else { return self } + return String(prefix(12)) + } +} diff --git a/FlowlightTests/PluginTests.swift b/FlowlightTests/PluginTests.swift index 90b763d..ad85f27 100644 --- a/FlowlightTests/PluginTests.swift +++ b/FlowlightTests/PluginTests.swift @@ -1,3 +1,4 @@ +import CryptoKit import XCTest @testable import Flowlight @@ -100,12 +101,89 @@ final class PluginTests: XCTestCase { let bare = Data(#"{"id":"thirdparty.test","name":"Test","version":"0.1.0","kind":"traffic","source":"installed","publisher":"thirdParty","guardrailProvider":"custom","description":"Test plugin.","privacySummary":"Uses metadata only.","capabilities":["annotate"]}"#.utf8) let decodedBare = try JSONDecoder().decode(PluginPackage.self, from: bare) XCTAssertEqual(decodedBare.manifest.id, "thirdparty.test") + XCTAssertNil(decodedBare.signature) + XCTAssertEqual(decodedBare.packageVersion, 1) let wrapped = try JSONEncoder().encode(PluginPackage(manifest: decodedBare.manifest)) let decodedWrapped = try JSONDecoder().decode(PluginPackage.self, from: wrapped) XCTAssertEqual(decodedWrapped.manifest.id, "thirdparty.test") } + func testUnsignedPackageReviewNormalizesOfficialClaimToThirdParty() throws { + var manifest = PluginManifest(id: "thirdparty.unsigned", name: "Unsigned", version: "0.1.0", kind: .traffic, + source: .installed, publisher: .official, guardrailProvider: .custom, + description: "Unsigned plugin.", privacySummary: "Uses metadata only.") + manifest.script = "function evaluate(context) { return []; }" + let data = try JSONEncoder().encode(PluginPackage(manifest: manifest)) + let review = try PluginPackageVerifier.review(data: data) + XCTAssertEqual(review.signatureStatus, .unsigned) + XCTAssertEqual(review.manifest.publisher, .thirdParty) + XCTAssertEqual(review.manifest.provenance.reviewStatus, .unsigned) + XCTAssertFalse(review.packageDigest.isEmpty) + XCTAssertFalse(review.scriptDigest.isEmpty) + XCTAssertFalse(review.approvalFingerprint.isEmpty) + XCTAssertTrue(review.warnings.joined(separator: "\n").contains("cannot claim official")) + } + + func testSignedCommunityPackageReviewBindsDigestAndApproval() throws { + var manifest = PluginManifest(id: "thirdparty.signed", name: "Signed", version: "0.1.0", kind: .llmMCP, + source: .installed, publisher: .thirdParty, guardrailProvider: .custom, + description: "Signed plugin.", privacySummary: "Uses metadata only.", + capabilities: [.annotate, .suggestGuardrail], + permissions: [.trafficMetadata, .llmMetadata, .suggestGuardrail], + privacy: PluginPrivacyDisclosure(categories: [.metadataOnly, .toolNames, .guardrailSuggestions], summary: "Metadata only.")) + manifest.script = "function evaluate(context) { return []; }" + let privateKey = P256.Signing.PrivateKey() + let signer = PluginTrustedSigner(keyID: "test-community-v1", publisher: .thirdParty, publisherName: "Test Community", + channel: .community, publicKeyPEM: privateKey.publicKey.pemRepresentation) + let package = try PluginPackageVerifier.signedPackage(manifest: manifest, keyID: signer.keyID, privateKey: privateKey) + let review = try PluginPackageVerifier.review(data: JSONEncoder().encode(package), trustedSigners: [signer]) + XCTAssertEqual(review.signatureStatus, .verifiedCommunity) + XCTAssertEqual(review.manifest.publisher, .thirdParty) + XCTAssertEqual(review.manifest.provenance.signingKeyID, "test-community-v1") + XCTAssertTrue(review.manifest.permissions.contains(.suggestGuardrail)) + XCTAssertEqual(review.manifest.provenance.packageDigest, review.packageDigest) + XCTAssertEqual(review.manifest.provenance.approvalFingerprint, review.approvalFingerprint) + } + + func testSignedPackageRejectsTamperedManifest() throws { + var manifest = PluginManifest(id: "thirdparty.tamper", name: "Tamper", version: "0.1.0", kind: .traffic, + source: .installed, publisher: .thirdParty, guardrailProvider: .custom, + description: "Signed plugin.", privacySummary: "Uses metadata only.") + manifest.script = "function evaluate(context) { return []; }" + let privateKey = P256.Signing.PrivateKey() + let signer = PluginTrustedSigner(keyID: "test-community-v1", publisher: .thirdParty, publisherName: "Test Community", + channel: .community, publicKeyPEM: privateKey.publicKey.pemRepresentation) + var package = try PluginPackageVerifier.signedPackage(manifest: manifest, keyID: signer.keyID, privateKey: privateKey) + package.manifest.script = "function evaluate(context) { return [{ severity: 'high', title: 'Changed', summary: 'Changed', evidence: [] }]; }" + XCTAssertThrowsError(try PluginPackageVerifier.review(data: JSONEncoder().encode(package), trustedSigners: [signer])) { error in + XCTAssertEqual(error as? PluginPackageError, .invalidSignature) + } + } + + func testPermissionFilteringRemovesHeaderNamesFromJavaScriptContext() throws { + let db = try database() + var manifest = PluginManifest(id: "thirdparty.permissions", name: "Permissions", version: "0.1.0", kind: .traffic, + source: .installed, publisher: .thirdParty, guardrailProvider: .custom, + description: "JS plugin.", privacySummary: "Uses traffic metadata only.", + permissions: [.trafficMetadata], privacy: PluginPrivacyDisclosure(categories: [.metadataOnly], summary: "Metadata only.")) + manifest.script = """ + function evaluate(context) { + return [{ + severity: 'low', + title: 'Permission check', + summary: context.exchange.requestHeaderNames === undefined ? 'headers hidden' : 'headers visible', + evidence: [{ label: 'Headers', value: String(context.exchange.requestHeaderNames) }] + }]; + } + """ + try db.savePluginManifest(manifest) + let id = try db.insertExchange(exchange()) + let finding = try XCTUnwrap(try db.pluginFindings(exchangeIDs: [id])[id]?.first { $0.pluginID == "thirdparty.permissions" }) + XCTAssertEqual(finding.summary, "headers hidden") + XCTAssertFalse(finding.evidence.map(\.value).joined().contains("Authorization")) + } + @MainActor func testPluginStoreRejectsReservedBuiltInPackageID() { let store = PluginStore() diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 7521ae9..11a2bad 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -1309,12 +1309,12 @@ Plugins Write inspection plugins - Plugins turn inspected exchanges into local advisory findings. Official Flowlight plugins cover common traffic and LLM/MCP guardrail patterns; third-party plugins use the same manifest shape. + Plugins turn inspected exchanges into local advisory findings. Official Flowlight plugins cover common traffic and LLM/MCP guardrail patterns; community plugins use reviewable manifests with provenance, permissions and privacy disclosures. On this page Model - Trust boundaryManifestFindings + Trust boundaryManifestProvenanceFindings Guardrails ProvidersTemplate packageReview checklist @@ -1323,24 +1323,29 @@ Plugins Flowlight plugins are scoped to HTTPS inspection. They run after a request and response have already been captured, redacted, clipped, attributed to an app or agent, and written to the local database. A plugin can add findings and suggest ordinary Flowlight guardrails, but it does not block, rewrite, mock, or transform traffic by itself. - Flowlight can import and export local plugin manifest packages from Settings. Installed JavaScript plugins run in a short-lived sandbox with sanitized metadata only, then return advisory findings through the same local findings surface as official built-ins. + Flowlight reviews a plugin package before installation. The review shows provenance, signature status, permissions, privacy categories, package digest and script digest. Package import fails closed when a signed package is malformed or tampered with; runtime JavaScript still fails open so capture remains complete. Manifest - A manifest tells Flowlight what the plugin is, who publishes it, what kind of inspected data it uses, whether it can suggest guardrails, and optionally the JavaScript evaluate(context) script to run. + A manifest tells Flowlight what the plugin is, who publishes it, what sanitized metadata it uses, whether it can suggest guardrails, and optionally the JavaScript evaluate(context) script to run. { - "id": "official.presidio-pii", - "name": "Presidio PII", - "version": "1.0.0", + "id": "thirdparty.example-guardrails", + "name": "Example Guardrails", + "version": "0.1.0", "kind": "llmMCP", - "enabled": true, "source": "installed", - "publisher": "official", - "guardrailProvider": "presidio", - "description": "Flags requests that should be reviewed by a PII masking policy.", + "publisher": "thirdParty", + "guardrailProvider": "custom", + "description": "Example advisory findings for inspected LLM and MCP activity.", "ruleVersion": 1, - "privacySummary": "Uses header names, sizes, tool names and bounded redacted evidence only.", + "schemaVersion": 1, + "privacySummary": "Uses provider names, tool names, MCP server names, request sizes and redacted evidence only.", + "privacy": { + "categories": ["metadataOnly", "toolNames", "mcpMetadata", "guardrailSuggestions"], + "summary": "Uses sanitized metadata only." + }, + "permissions": ["trafficMetadata", "appIdentity", "llmMetadata", "mcpMetadata", "suggestGuardrail"], "capabilities": ["annotate", "suggestGuardrail"], "script": "function evaluate(context) { return []; }" } @@ -1349,15 +1354,21 @@ Plugins kindtraffic, llmMCPWhether the plugin describes ordinary inspected HTTP or model/tool/MCP activity. - publisherofficial, thirdPartyOfficial plugins are released by Flowlight; third-party plugins are authored outside the project. + publisherofficial, thirdPartyOfficial status is trusted only when package provenance verifies; unsigned packages are treated as third-party. - sourcebuiltIn, installedBuilt-in plugins ship in the app. Installed plugins come from a package. + permissionstrafficMetadata, headerNames, appIdentity, llmMetadata, mcpMetadata, suggestGuardrailWhich sanitized metadata families the plugin expects. These are not mutation powers. capabilitiesannotate, suggestGuardrailCapabilities are advisory; enforcement still uses Flowlight rules and guardrails. + Provenance and package review + + Installed packages may include a detached P-256 signature envelope next to the manifest. Flowlight binds the plugin ID, version, rule version, publisher identity, permissions, privacy categories and script digest to the reviewed package. If those values change, the package needs review again. + + Unsigned local packages are allowed for development and internal sharing, but the review sheet labels them as unsigned local packages and they cannot claim official provenance. Local settings such as enabled and configuration are not part of the signed payload, so toggling a plugin or changing local metadata does not invalidate package identity. + Findings - A JavaScript plugin defines a synchronous evaluate(context) function. Flowlight passes context.manifest and a sanitized context.exchange containing method, host, path without query, header names, byte counts, app and agent labels, declared tools, MCP activity and provider connector metadata. Scripts run out of process with an empty environment, a 128 KB script limit and a short timeout; failures produce no findings. + A JavaScript plugin defines a synchronous evaluate(context) function. Flowlight passes context.manifest and a sanitized context.exchange containing only the metadata families declared in permissions. Scripts run out of process with an empty environment, a 128 KB script limit and a short timeout; failures produce no findings. A finding is a bounded record attached to one inspected exchange. Evidence should name facts, not leak content: method, host, redacted path shape, byte counts, header names, model provider, tool names, MCP server names, approval mode and policy labels. Do not store header values, raw bodies, credentials, full tool inputs, prompts, completions or unredacted query strings. @@ -1399,14 +1410,13 @@ Plugins Template package - The starter package lives at templates/inspection-plugin. It includes a manifest, plugin.js, sample findings, a policy checklist and fixtures for ordinary traffic and LLM/MCP guardrail plugins. Import the manifest JSON from Settings › Plugins, then export it again to share the stored package shape. + The starter package lives at templates/inspection-plugin. It includes a manifest, plugin.js, sample findings and a validator for ordinary traffic and LLM/MCP guardrail plugins. Import the manifest JSON from Settings › Plugins and Flowlight will show the review sheet before installation. cp -R templates/inspection-plugin my-flowlight-plugin cd my-flowlight-plugin -node -e "JSON.parse(require('fs').readFileSync('manifest.json'))" node validate.js - The JavaScript validator is deliberately local and boring: it checks manifest fields, provider names, capability names, evidence size limits and the absence of common secret-looking values in sample findings. + The JavaScript validator is deliberately local and boring: it checks manifest fields, provider names, capability names, permissions, privacy categories, evidence size limits and the absence of common secret-looking values in sample findings. Review checklist @@ -1414,11 +1424,13 @@ node validate.js Evidence is bounded and redacted; no raw bodies, prompts, completions, header values or tool inputs. + Permissions match the sanitized metadata the script actually reads. + Provider-side MCP connector findings say local network rules cannot reach provider-run calls. Failures are fail-open: capture remains complete and no finding is better than blocking capture. - The privacy summary is specific enough that a user can tell what data the plugin needs. + The privacy summary and categories are specific enough that a user can tell what data the plugin needs. --- diff --git a/docs/plugins/index.html b/docs/plugins/index.html index d8e8eaf..d8a1f1b 100644 --- a/docs/plugins/index.html +++ b/docs/plugins/index.html @@ -28,7 +28,7 @@ - + @@ -56,7 +56,7 @@

Plugins

Write inspection plugins

-

Plugins turn inspected exchanges into local advisory findings. Official Flowlight plugins cover common traffic and LLM/MCP guardrail patterns; third-party plugins use the same manifest shape.

+

Plugins turn inspected exchanges into local advisory findings. Official Flowlight plugins cover common traffic and LLM/MCP guardrail patterns; community plugins use reviewable manifests with provenance, permissions and privacy disclosures.