fix(02): stop short ids rewriting pagination and IPv4 (CR-01)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-09-17 14:39:45 +02:00
parent d27897c7e0
commit 5994e671b3
2 changed files with 59 additions and 21 deletions

View File

@@ -444,10 +444,12 @@ func ScrubStep(store *Store, step *Step) error {
return nil
}
pairs := store.replacements()
step.Request.Path = replaceAll(step.Request.Path, pairs)
step.Request.Query = replaceAll(step.Request.Query, pairs)
step.Request.Headers = scrubMap(step.Request.Headers, pairs)
step.Request.Body = Body(replaceAll(string(step.Request.Body), pairs))
// Short numeric IDs belong in path/query/headers (e.g. /albums/1). JSON
// bodies keep literal counts and IPv4; normalizeJSON already masks id/*_id.
step.Request.Path = replaceAll(step.Request.Path, pairs, true)
step.Request.Query = replaceAll(step.Request.Query, pairs, true)
step.Request.Headers = scrubMap(step.Request.Headers, pairs, true)
step.Request.Body = Body(replaceAll(string(step.Request.Body), pairs, false))
for _, rule := range step.Capture {
if strings.TrimSpace(rule.From) != "request.form" {
continue
@@ -457,8 +459,8 @@ func ScrubStep(store *Store, step *Step) error {
}
step.Request.Body = Body(scrubFormField(string(step.Request.Body), rule.Name, rule.As))
}
step.Response.Headers = scrubMap(step.Response.Headers, pairs)
step.Response.Body = Body(replaceAll(string(step.Response.Body), pairs))
step.Response.Headers = scrubMap(step.Response.Headers, pairs, true)
step.Response.Body = Body(replaceAll(string(step.Response.Body), pairs, false))
return rejectUnclassifiedCredentials(*step)
}
@@ -484,22 +486,25 @@ func (s *Store) replacements() [][2]string {
return out
}
func scrubMap(in map[string]string, pairs [][2]string) map[string]string {
func scrubMap(in map[string]string, pairs [][2]string, allowShortNumeric bool) map[string]string {
if in == nil {
return nil
}
out := make(map[string]string, len(in))
for k, v := range in {
out[k] = replaceAll(v, pairs)
out[k] = replaceAll(v, pairs, allowShortNumeric)
}
return out
}
func replaceAll(s string, pairs [][2]string) string {
func replaceAll(s string, pairs [][2]string, allowShortNumeric bool) string {
for _, p := range pairs {
if p[0] == "" {
continue
}
if !allowShortNumeric && isAllDigits(p[0]) && len(p[0]) < 8 {
continue
}
olds := []string{p[0]}
if esc := phpJSONEscape(p[0]); esc != p[0] {
olds = append(olds, esc)
@@ -515,6 +520,18 @@ func replaceAll(s string, pairs [][2]string) string {
return s
}
func isAllDigits(s string) bool {
if s == "" {
return false
}
for i := 0; i < len(s); i++ {
if s[i] < '0' || s[i] > '9' {
return false
}
}
return true
}
func phpJSONEscape(s string) string {
return strings.ReplaceAll(s, "/", `\/`)
}
@@ -556,7 +573,8 @@ func replaceIsolated(s, old, neu string) string {
}
func isIdentByte(c byte) bool {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') || c == '_' || c == '.'
}
func rejectUnclassifiedCredentials(step Step) error {