Class ::nsmcp::LogRedactor

::nsmcp::LogRedactor[i] create ... \
           [ -headerNames (default "authorization proxy-authorization cookie set-cookie x-api-key") ] \
           [ -maskPeerAddresses:boolean (default "true") ] \
           [ -maskUserIdentifiers:boolean (default "true") ] \
           [ -queryParameters (default "__csrf_token csrf_token token access_token refresh_token id_token password passwd secret api_key apikey") ] \
           [ -replacement (default "[REDACTED]") ]

Defined in /usr/local/ns/tcl/nsmcp/lib/log-redaction.tcl

Class Relations

  • class: ::nx::Class[i]
  • superclass: ::nx::Object[i]
::nx::Class create ::nsmcp::LogRedactor \
     -superclass ::nx::Object

Methods (to be applied on instances)

  • message (scripted, public)

     <instance of nsmcp::LogRedactor[i]> message text
    Parameters:
    text (required)

    Testcases:
    No testcase defined.
    set lines {}
    foreach line [split $text \n] {
        if {[regexp {^([ \t]*(?::[ \t]*)?)([^:\s]+)[ \t]*:[ \t]*(.*)$} $line -> prefix name value] &&
            [:sensitive $name ${:headerNames}]} {
            set line "${prefix}${name}: ${:replacement}"
        } else {set line [:queryValues $line]}
        if {${:maskPeerAddresses}} {
            if {[regexp -indices {^(\S+)[ \t]+\S+[ \t]+\S+[ \t]+\[[0-9]{2}/[A-Za-z]{3}/[0-9]{4}:} $line match peer]} {
                lassign $peer a b
                set line [string replace $line $a $b ${:replacement}]
            }
            set line [:identifiers $line {(peer(?:address|addr)?[ \t]*[:=]?[ \t]+)([0-9A-Fa-f:.]+)(?:[ \t,;]|$)}]
        }
        if {${:maskUserIdentifiers}} {
            set line [:identifiers $line {(user_id[ \t]*[:=]?[ \t]+)([0-9]+)(?:[ \t,;]|$)}]
            # The third field in the recognized access header is the user.
            if {[regexp -indices {^\S+[ \t]+\S+[ \t]+(\S+)[ \t]+\[[0-9]{2}/[A-Za-z]{3}/[0-9]{4}:} $line match user]} {
                lassign $user a b
                if {[string range $line $a $b] ne "-"} {
                    set line [string replace $line $a $b ${:replacement}]
                }
            }
        }
        lappend lines $line
    }
    return [join $lines \n]
  • page (scripted, public)

     <instance of nsmcp::LogRedactor[i]> page page
    Parameters:
    page (required)

    Testcases:
    No testcase defined.
    set records {}
    foreach entry [dict get $page records] {lappend records [:record $entry]}
    dict set page records $records
    return $page
  • queryValues (scripted, public)

     <instance of nsmcp::LogRedactor[i]> queryValues text
    Parameters:
    text (required)

    Testcases:
    No testcase defined.
    set start 0
    # Match literal query separators. Decode names, never values, for policy
    # comparison; retain the original URL spelling and nonsensitive values.
    while {[regexp -indices -start $start {[?&]([^=&#\s"<>]+)=([^&#\s"<>]*)} $text match key value]} {
        lassign $key a b
        set name [string range $text $a $b]
        if {[catch {ns_urldecode -charset utf-8 -- $name} decoded]} {
            set decoded $name
        }
        lassign $value a b
        if {[:sensitive $decoded ${:queryParameters}]} {
            set text "[string range $text 0 [expr {$a-1}]]${:replacement}[string range $text [expr {$b+1}] end]"
            set start [expr {$a+[string length ${:replacement}]}]
        } else {set start [expr {[lindex $match 1]+1}]}
    }
    return $text
  • record (scripted, public)

     <instance of nsmcp::LogRedactor[i]> record entry
    Parameters:
    entry (required)

    Testcases:
    No testcase defined.
    set original $entry
    if {[dict exists $entry message]} {
        dict set entry message [:message [dict get $entry message]]
    }
    if {[dict exists $entry url]} {
        dict set entry url [:queryValues [dict get $entry url]]
    }
    if {${:maskPeerAddresses} && [dict exists $entry peerAddress] &&
        [dict get $entry peerAddress] ne ""} {
        set peer [dict get $entry peerAddress]
        if {[dict exists $entry message]} {
            dict set entry message [string map [list $peer ${:replacement}] [dict get $entry message]]
        }
        dict set entry peerAddress ${:replacement}
    }
    dict set entry redacted [expr {$entry ne $original}]
    return $entry