Copilot commented on code in PR #13708:
URL: https://github.com/apache/trafficserver/pull/13708#discussion_r4075280201


##########
plugins/experimental/access_control/access_control.cc:
##########
@@ -23,12 +23,84 @@
 
 #include <iostream>
 #include <string>
+#include <charconv>
 
 #include "access_control.h"
 
 size_t calcMessageDigest(const StringView hf, const char *secret, const char 
*message, size_t messageLen, char *buffer, size_t len);
 const char *getSecretMap(const StringMap &map, const StringView &key, size_t 
&secretSize);
 
+static bool
+percentDecodePath(StringView in, String &out)
+{
+  out.clear();
+  out.reserve(in.size());
+  for (size_t i = 0; i < in.size();) {
+    if (in[i] == '%') {
+      unsigned char val = 0;
+      if (i + 2 < in.size() && std::from_chars(in.data() + i + 1, in.data() + 
i + 3, val, 16).ec == std::errc{}) {
+        out.push_back(static_cast<char>(val));
+        i += 3;
+      } else {
+        return false;
+      }
+    } else {
+      out.push_back(in[i]);
+      ++i;
+    }
+  }

Review Comment:
   The percent-decoder turns all `%xx` sequences into bytes, and normalization 
treats both `/` and `\\` as path separators. This means encoded separators 
(e.g., `%2F`, `%5C`) can alter the segment structure during authorization, 
which can lead to inconsistent authorization vs. routing behavior if upstream 
components treat encoded separators differently. Consider explicitly rejecting 
percent-encoded path separators (and possibly other problematic bytes like NUL) 
during decoding/normalization, or clearly documenting that encoded separators 
are treated as separators for scope enforcement.



##########
plugins/experimental/access_control/access_control.cc:
##########
@@ -23,12 +23,84 @@
 
 #include <iostream>
 #include <string>
+#include <charconv>
 
 #include "access_control.h"
 
 size_t calcMessageDigest(const StringView hf, const char *secret, const char 
*message, size_t messageLen, char *buffer, size_t len);
 const char *getSecretMap(const StringMap &map, const StringView &key, size_t 
&secretSize);
 
+static bool
+percentDecodePath(StringView in, String &out)
+{
+  out.clear();
+  out.reserve(in.size());
+  for (size_t i = 0; i < in.size();) {
+    if (in[i] == '%') {
+      unsigned char val = 0;
+      if (i + 2 < in.size() && std::from_chars(in.data() + i + 1, in.data() + 
i + 3, val, 16).ec == std::errc{}) {
+        out.push_back(static_cast<char>(val));
+        i += 3;
+      } else {
+        return false;
+      }
+    } else {
+      out.push_back(in[i]);
+      ++i;
+    }
+  }
+  return true;
+}
+
+static bool
+normalizePath(StringView path, String &normalized, bool isScope = false)
+{
+  normalized.clear();
+  if (path.empty()) {
+    normalized = "/";
+    return true;
+  }
+
+  String decoded;
+  if (!percentDecodePath(path, decoded)) {
+    return false;
+  }
+  StringVector segments;
+  size_t       start = 0;
+
+  while (start < decoded.size()) {
+    size_t end = decoded.find_first_of("/\\", start);

Review Comment:
   The percent-decoder turns all `%xx` sequences into bytes, and normalization 
treats both `/` and `\\` as path separators. This means encoded separators 
(e.g., `%2F`, `%5C`) can alter the segment structure during authorization, 
which can lead to inconsistent authorization vs. routing behavior if upstream 
components treat encoded separators differently. Consider explicitly rejecting 
percent-encoded path separators (and possibly other problematic bytes like NUL) 
during decoding/normalization, or clearly documenting that encoded separators 
are treated as separators for scope enforcement.



##########
plugins/experimental/access_control/access_control.cc:
##########
@@ -23,12 +23,84 @@
 
 #include <iostream>
 #include <string>
+#include <charconv>
 
 #include "access_control.h"
 
 size_t calcMessageDigest(const StringView hf, const char *secret, const char 
*message, size_t messageLen, char *buffer, size_t len);
 const char *getSecretMap(const StringMap &map, const StringView &key, size_t 
&secretSize);
 
+static bool
+percentDecodePath(StringView in, String &out)
+{
+  out.clear();
+  out.reserve(in.size());
+  for (size_t i = 0; i < in.size();) {
+    if (in[i] == '%') {
+      unsigned char val = 0;
+      if (i + 2 < in.size() && std::from_chars(in.data() + i + 1, in.data() + 
i + 3, val, 16).ec == std::errc{}) {
+        out.push_back(static_cast<char>(val));
+        i += 3;
+      } else {
+        return false;
+      }
+    } else {
+      out.push_back(in[i]);
+      ++i;
+    }
+  }
+  return true;
+}
+
+static bool
+normalizePath(StringView path, String &normalized, bool isScope = false)
+{
+  normalized.clear();
+  if (path.empty()) {
+    normalized = "/";
+    return true;
+  }
+
+  String decoded;
+  if (!percentDecodePath(path, decoded)) {
+    return false;
+  }
+  StringVector segments;
+  size_t       start = 0;
+
+  while (start < decoded.size()) {
+    size_t end = decoded.find_first_of("/\\", start);
+    if (end == String::npos) {
+      end = decoded.size();
+    }
+    if (end > start) {
+      StringView seg(decoded.data() + start, end - start);
+      if (seg == ".") {
+      } else if (seg == "..") {
+        if (isScope) {
+          return false;
+        }
+        if (!segments.empty()) {
+          segments.pop_back();
+        }
+      } else {
+        segments.emplace_back(seg);
+      }
+    }
+    start = end + 1;
+  }
+  if (segments.empty()) {
+    normalized = "/";
+    return true;
+  }
+
+  for (const auto &seg : segments) {
+    normalized.push_back('/');
+    normalized.append(seg);
+  }

Review Comment:
   This normalization allocates and stores all segments in a vector before 
rebuilding the normalized path, which adds per-request allocations/copies in 
the hot path (`enforceAccessControl`). Consider a single-pass approach that 
builds `normalized` incrementally (or tracks segment boundaries in-place) to 
avoid the `StringVector` allocation and repeated string materialization, while 
preserving the existing `.`/`..` semantics.



##########
plugins/experimental/access_control/access_control.h:
##########
@@ -107,6 +107,17 @@ enum AccessTokenStatus {
 
 const char *accessTokenStatusToString(const AccessTokenStatus &state);
 
+/**
+ * Validates whether a request path fails within the scope claim of an access 
token.

Review Comment:
   Typo in the public header comment: `fails within` should be `falls within`.



##########
doc/admin-guide/plugins/access_control.en.rst:
##########
@@ -237,7 +237,7 @@ Query-Param-Style Named Claim format
    * ``iat`` for `issued at time`_, `optional`
    * ``tid`` for `token id`_, `optional`
    * ``ver`` for `version`_, `optional`, defaults to ``ver=1`` if not 
specified.
-   * ``scope`` for `scope`_, `optional`, ignored by the current version of the 
plugin, still not finalized (more applications and their use cases need to be 
studied to finalize the format)
+   * ``scope`` for `scope`_, `optional`. an absent or empty scope is 
unrestricted. Otherwise, it is a path-prefix restriction matched against 
normalized path segments, including the segment boundary (for example, 
``/reports`` matches ``/reports/2026`` but not ``/reports2``); out-of-scope 
requests use the configured ``--invalid-scope-status-code``.

Review Comment:
   This doc line has a grammatical/capitalization issue (`. an absent...`) and 
is overly dense for admin docs. Consider capitalizing the sentence start and 
breaking it into multiple wrapped lines/bullets to improve readability (e.g., 
separate backward-compat behavior, matching rule, and status-code behavior).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to