srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/internal/scope
AgeCommit message (Collapse)AuthorFilesLines
2026-08-25Wire-format JSON tag consistency fix, and the first real pluginsrdusr1-4/+4
Writing PLUGINS.md as an authoritative external spec surfaced a real, pre-existing bug: store.Summary/Entry/EntryTag/WSMessage, rules.Rule, scope.Rule, and clientcert.Cert had no JSON struct tags at all, so Go's default marshaling serialized them PascalCase ("ID", "StartedAt") while the rest of the protocol (EntryDetail's own fields, every Request/Response wrapper field) uses snake_case. Confirmed live against a real daemon before touching anything: a raw socket "list" request came back with "ID"/"StartedAt"/"StatusCode", exactly the mismatch suspected. Nothing outside this repo's own Go code consumes this wire format yet, so this was a free, purely additive fix rather than something to work around - every affected struct now tags snake_case consistently. plugins/authcheck is the first real plugin: an Autorize-style authorization checker. For every proxied request carrying an Authorization or Cookie header, resends it with that header stripped and compares status classes - a resend that still succeeds where the original did too is a likely missing-function-level-access-control bug, tagged authcheck:bypass with structured detail. Deliberately speaks the wire protocol directly (its own local request/response/ summary/entryDetail structs mirroring the real ones field-for-field, not imported from internal/ipc) rather than taking the shortcut a Go plugin could - proof the documented protocol is actually sufficient on its own, since that's all a non-Go plugin author has to work with. Verified live end to end: a real daemon, a real Python origin with one endpoint that looks like it enforces auth but doesn't (vulnerable by design) and one that actually does (the control case) - the broken endpoint was correctly tagged, the secure one correctly left alone, no false positive, confirmed both via the stored tag data directly and visually in the TUI (tmux, real keystrokes): the Tags column badge, T's tag list, and the tag detail view's JSON-colorized data (ANSI-verified, not eyeballed) all showing the plugin's actual findings.
2026-05-20Target scope: filter what gets recorded, not what gets proxiedsrdusr2-0/+157
The proxy captured and stored literally everything with no way to exclude unrelated traffic - every CDN asset, analytics beacon, and third-party tracker request on a real engagement pollutes history and search right alongside the traffic that actually matters. internal/scope: Rule{Enabled, Pattern, IsRegex} and InScope(rules, host). A non-regex pattern matches by case-insensitive substring against the host - "example.com" matches "example.com", "www.example.com", and "api.example.com" alike, covering "this domain and its subdomains" without inventing a separate wildcard syntax. IsRegex mirrors the same toggle match-and-replace rules already use, for one consistent mental model across both rule types in this tool. An empty or all-disabled rule set means everything is in scope - the behavior before scope existed at all, unchanged, so a fresh install or a user who never opens the scope view keeps recording everything rather than silently nothing. Deliberately a recording filter, not access control: out-of-scope traffic still proxies completely normally, reaching its destination and the client exactly as before. internal/proxy's forward() already writes the response to the client before record() ever runs, so the scope check (new in record()) can only affect whether the exchange gets stored, never whether it happens. Blocking out-of-scope traffic outright would be a materially different, much riskier feature - a wrong scope pattern could silently break the very traffic someone's trying to test, which is a far worse failure mode than a noisier history. Repeat/Intrude (recordRaw, a separate function from record()) deliberately don't go through the scope check at all: a user explicitly resending or fuzzing a specific request wants to see the result regardless of scope, which exists to cut passive-capture noise, not second-guess a deliberate action. internal/store: new scope_rules table (CREATE TABLE IF NOT EXISTS, no migration needed - it's a new table, not a new column on an existing one) plus List/Add/SetEnabled/Delete, mirroring the existing match-and-replace rules CRUD exactly. internal/ipc: scope_list/ scope_add/scope_delete/scope_toggle request types and matching Client methods; scope_add validates a regex pattern compiles before persisting, same reasoning and same fix as the earlier rules_save validation (an invalid regex should be rejected up front, not silently never match at apply time with zero feedback). TUI: 's' from the history list opens scope management, mirroring the Rules view's own list+form pattern but simpler (add-only, no edit-in-place - a pattern and a regex toggle don't need a five-field form, delete-and-re-add covers changing one). Verified live in tmux against a running daemon: added a substring scope rule for one host, sent requests to both a matching and a non-matching host - the non-matching one proxied successfully (client got its 200) but was never recorded, the matching one was recorded normally; confirmed a Repeater resend of the excluded host WAS recorded despite being out of scope; toggled the rule off and confirmed recording resumed for everything; added and confirmed a regex-mode rule saves and displays correctly; deleted a rule and confirmed the list returns to empty ("no rules means everything is recorded"). go build/vet/gofmt/test/mod tidy all clean.