Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/nuclei/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.CallbackVar(resetCallback, "reset", "reset removes all nuclei configuration and data files (including nuclei-templates)"),
flagSet.BoolVarP(&options.TlsImpersonate, "tls-impersonate", "tlsi", false, "enable experimental client hello (ja3) tls randomization"),
flagSet.StringVarP(&options.HttpApiEndpoint, "http-api-endpoint", "hae", "", "experimental http api endpoint"),
flagSet.StringVarP(&options.HttpApiToken, "http-api-token", "hat", "", "http api endpoint token (optional)"),
)

flagSet.CreateGroup("interactsh", "interactsh",
Expand Down
47 changes: 47 additions & 0 deletions docs/markdown-report-filenames.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Markdown report filenames

The Markdown exporter writes one result file per finding. Result filenames are
constructed from the template ID, target host, a random UUID, and—when
available—the operator name:

```text
<template-id>-<host>-<uuid>-<matcher-or-extractor>.md
```

The matcher name takes precedence when a result contains both a matcher name
and an extractor name. For extractor-only results, the extractor name is used.

## Safety properties

Filename components can contain values derived from templates or scan targets,
so the exporter applies the following rules before writing a report:

1. Path separators, parent-directory references, spaces, and characters that
are unsafe on common filesystems are replaced with underscores.
2. Filenames are limited to 255 bytes.
3. Truncation stops at a valid UTF-8 boundary.
4. The UUID and `.md` extension are always retained when the template ID or
host must be truncated.
5. The operator name is sanitized and truncated separately from the prefix.

Preserving the UUID prevents two findings with long, identical prefixes from
being truncated to the same filename and overwriting each other. Sanitizing
path separators and `..` prevents target-controlled values from escaping the
configured report directory.

## Verification

The Markdown exporter tests cover:

- matcher-only and extractor-only filenames;
- matcher precedence when both operator names are present;
- UUID and operator suffix preservation for long names;
- UTF-8-safe truncation; and
- containment of filenames and sort-mode subdirectories within the report
directory.

Run the tests with:

```console
go test ./pkg/reporting/exporters/markdown
```
88 changes: 88 additions & 0 deletions docs/markdown-security-change-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Markdown and security change report

This file is a standalone, downloadable summary of the Markdown report filename
hardening and Go vulnerability remediation work.

## Executive summary

The change set:

- prevents Markdown report paths from being influenced by directory traversal
sequences;
- prevents long template IDs and hosts from removing the per-finding UUID;
- includes the selected matcher or extractor name in result filenames;
- truncates filenames without producing invalid UTF-8;
- updates the Go toolchain and vulnerable dependencies; and
- reduces reachable `govulncheck` findings from 23 to one upstream advisory
with no fixed dependency version.

## Markdown filename hardening

Markdown finding files use this structure:

```text
<template-id>-<host>-<uuid>-<matcher-or-extractor>.md
```

The matcher name is selected when both a matcher and extractor name are
available. Extractor-only findings use the extractor name.

Before a file is written, unsafe filesystem characters, path separators, and
parent-directory references are replaced. The resulting filename is limited to
255 bytes and truncated only at a valid UTF-8 boundary. Prefix truncation does
not remove the UUID or `.md` extension, so findings cannot collide merely
because their long template and host prefixes are identical.

## Dependency remediation

| Component | Previous version | Updated version |
| --- | --- | --- |
| Go toolchain | 1.26.0 | 1.26.5 |
| `golang.org/x/text` | v0.38.0 | v0.39.0 |
| `github.com/yuin/goldmark` | v1.7.13 | v1.7.17 |
| Direct `github.com/google/go-github` use | v30.1.0 | v81.0.0 |

The direct GitHub client migration covers the custom-template downloader and
GitHub reporting tracker. Module metadata was refreshed after the upgrades.

## Vulnerability scan result

The initial source scan reported 23 reachable vulnerabilities. After the
updates, the scan reports only GO-2026-5932.

GO-2026-5932 remains reachable through the latest available
`github.com/projectdiscovery/utils/update` dependency, which transitively uses
`github.com/google/go-github/v30` and `golang.org/x/crypto/openpgp`. The advisory
does not identify a fixed `golang.org/x/crypto` release. Eliminating this final
result requires the upstream update package to migrate away from the older
GitHub client or a replacement of that update subsystem.

Run the vulnerability scan with:

```console
go run golang.org/x/vuln/cmd/govulncheck@latest \
-db=https://storage.googleapis.com/go-vulndb ./...
```

An exit status indicating GO-2026-5932 is expected until the upstream
dependency is migrated.

## Validation commands

```console
go mod verify
go test ./pkg/reporting/exporters/markdown
go test ./pkg/external/customtemplates -run '^$'
go test ./pkg/reporting/trackers/github
go vet ./pkg/external/customtemplates \
./pkg/reporting/trackers/github \
./pkg/reporting/exporters/markdown
```

The `-run '^$'` custom-template command checks compilation without running the
network-dependent GitHub download tests.

## Detailed documentation

- [Markdown report filenames](markdown-report-filenames.md)
- [Go vulnerability remediation](security-remediation.md)
65 changes: 65 additions & 0 deletions docs/security-remediation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Go vulnerability remediation

## Scope

A `govulncheck` source scan was performed for all Go packages. The initial scan
reported 23 reachable vulnerabilities in the Go standard library and direct or
transitive dependencies.

The remediation updates the following components:

| Component | Previous | Remediated | Purpose |
| --- | --- | --- | --- |
| Go toolchain | 1.26.0 | 1.26.5 | Includes standard-library security fixes |
| `golang.org/x/text` | v0.38.0 | v0.39.0 | Fixes GO-2026-5970 |
| `github.com/yuin/goldmark` | v1.7.13 | v1.7.17 | Fixes GO-2026-5320 |
| `github.com/google/go-github` (direct use) | v30.1.0 | v81.0.0 | Removes Nuclei's direct dependency path through the deprecated OpenPGP API |

The GitHub client migration applies to the custom-template downloader and the
GitHub reporting tracker.

## Verification command

The default Go vulnerability database endpoint may be unavailable in restricted
environments. The equivalent Google Cloud Storage database mirror can be used:

```console
go run golang.org/x/vuln/cmd/govulncheck@latest \
-db=https://storage.googleapis.com/go-vulndb ./...
```

After remediation, the scan reports one reachable advisory instead of 23.

## Remaining upstream advisory

GO-2026-5932 remains reachable through
`github.com/projectdiscovery/utils/update`, which transitively depends on
`github.com/google/go-github/v30` and its use of
`golang.org/x/crypto/openpgp`.

The advisory describes the OpenPGP package as unmaintained and unsafe by
design, and the Go vulnerability database does not provide a fixed
`golang.org/x/crypto` version. Nuclei already uses `go-github/v81` for its own
GitHub API integrations, but the older module remains in the dependency graph
until `projectdiscovery/utils` migrates its update package.

The remaining advisory therefore cannot be resolved by upgrading
`golang.org/x/crypto`. It requires an upstream dependency migration or a local
replacement of the update subsystem.

## Validation

Use the following checks when changing these dependencies:

```console
go mod verify
go vet ./pkg/external/customtemplates \
./pkg/reporting/trackers/github \
./pkg/reporting/exporters/markdown
go test ./pkg/external/customtemplates -run '^$'
go test ./pkg/reporting/trackers/github \
./pkg/reporting/exporters/markdown
```

The custom-template package has tests that access external GitHub resources.
Using `-run '^$'` performs a compilation check without requiring network access.
13 changes: 8 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module github.com/projectdiscovery/nuclei/v3

go 1.26

toolchain go1.26.5

require (
github.com/andygrunwald/go-jira v1.16.1
github.com/antchfx/htmlquery v1.3.5
Expand Down Expand Up @@ -34,7 +36,7 @@ require (
go.uber.org/multierr v1.11.0
golang.org/x/net v0.56.0
golang.org/x/oauth2 v0.36.0
golang.org/x/text v0.38.0
golang.org/x/text v0.39.0
)

require (
Expand Down Expand Up @@ -73,7 +75,7 @@ require (
github.com/go-pg/pg/v10 v10.15.0
github.com/go-sql-driver/mysql v1.10.0
github.com/goccy/go-json v0.10.5
github.com/google/go-github/v30 v30.1.0
github.com/google/go-github/v81 v81.0.0
Comment thread
prithvee07 marked this conversation as resolved.
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0
github.com/h2non/filetype v1.1.3
Expand Down Expand Up @@ -251,6 +253,7 @@ require (
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/certificate-transparency-go v1.3.2 // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gosimple/slug v1.15.0 // indirect
Expand Down Expand Up @@ -376,7 +379,7 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark v1.7.17 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
github.com/zcalusic/sysinfo v1.1.3 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
Expand Down Expand Up @@ -415,10 +418,10 @@ require (
goftp.io/server/v2 v2.0.1 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/exp v0.0.0-20260527015227-08cc5374adb3
golang.org/x/mod v0.36.0 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.45.0
golang.org/x/tools v0.47.0
google.golang.org/protobuf v1.36.11
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
Expand Down
18 changes: 10 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,8 @@ github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+u
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
github.com/google/go-github/v81 v81.0.0 h1:hTLugQRxSLD1Yei18fk4A5eYjOGLUBKAl/VCqOfFkZc=
github.com/google/go-github/v81 v81.0.0/go.mod h1:upyjaybucIbBIuxgJS7YLOZGziyvvJ92WX6WEBNE3sM=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
Expand Down Expand Up @@ -1129,8 +1131,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark v1.7.17 h1:p36OVWwRb246iHxA/U4p8OPEpOTESm4n+g+8t0EE5uA=
github.com/yuin/goldmark v1.7.17/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
Expand Down Expand Up @@ -1261,8 +1263,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -1443,8 +1445,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down Expand Up @@ -1496,8 +1498,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
45 changes: 43 additions & 2 deletions internal/httpapi/apiendpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"
"time"

"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
Expand All @@ -22,26 +23,66 @@ type Concurrency struct {
// Server represents the HTTP server that handles the concurrency settings endpoints.
type Server struct {
addr string
token string
config *types.Options
}

// New creates a new instance of Server.
func New(addr string, config *types.Options) *Server {
return &Server{
addr: addr,
token: config.HttpApiToken,
config: config,
}
}

// Start initializes the server and its routes, then starts listening on the specified address.
//
// A dedicated ServeMux is used (rather than http.DefaultServeMux via
// http.HandleFunc/http.ListenAndServe) so this experimental endpoint never
// accidentally exposes handlers registered on the default mux by other
// packages (e.g. net/http/pprof, which is blank-imported for the separate,
// opt-in -enable-pprof server).
func (s *Server) Start() error {
http.HandleFunc("/api/concurrency", s.handleConcurrency)
if err := http.ListenAndServe(s.addr, nil); err != nil {
if s.token == "" {
gologger.Warning().Msgf("http-api-endpoint is running without a token (-http-api-token); anyone able to reach %s can read and change scan settings", s.addr)
}

mux := http.NewServeMux()
mux.HandleFunc("/api/concurrency", s.handleConcurrency)

var handler http.Handler = mux
if s.token != "" {
handler = s.tokenAuthMiddleware(handler)
}

server := &http.Server{
Addr: s.addr,
Handler: handler,
ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
if err := server.ListenAndServe(); err != nil {
return err
}
return nil
}

// tokenAuthMiddleware requires a matching ?token= query parameter on every
// request when a token has been configured.
func (s *Server) tokenAuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.URL.Query().Get("token")
if token == "" || token != s.token {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
Comment on lines +73 to +84

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Harden tokenAuthMiddleware: use constant-time comparison and avoid a query-string-only token.

Two issues affect this authentication check:

  • Line 78 compares token != s.token with the standard != operator. This comparison is not constant-time and creates a timing side-channel that can help an attacker recover the token byte by byte.
  • The middleware only reads the token from r.URL.Query().Get("token") (line 77). URL query parameters commonly end up in server access logs, reverse-proxy logs, and browser history. A secret token in the query string risks unintended disclosure through these channels. Support an Authorization header as the primary transport, and treat the query parameter as a fallback only if still needed.

Use crypto/subtle.ConstantTimeCompare for the comparison, and read the token from the Authorization header.

🔒 Proposed fix for constant-time comparison and header-based token
+import (
+	"crypto/subtle"
+	"strings"
+)
+
 func (s *Server) tokenAuthMiddleware(next http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		token := r.URL.Query().Get("token")
-		if token == "" || token != s.token {
+		token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
+		if token == "" {
+			token = r.URL.Query().Get("token")
+		}
+		if token == "" || subtle.ConstantTimeCompare([]byte(token), []byte(s.token)) != 1 {
 			http.Error(w, "Unauthorized", http.StatusUnauthorized)
 			return
 		}
 		next.ServeHTTP(w, r)
 	})
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/httpapi/apiendpoint.go` around lines 73 - 84, Update
Server.tokenAuthMiddleware to read the token from the Authorization header
first, using the query parameter only as a fallback if required. Parse the
header according to the existing authentication contract, reject missing or
malformed credentials, and compare the supplied token with s.token via
crypto/subtle.ConstantTimeCompare rather than != while preserving the
unauthorized response.


// handleConcurrency routes the request based on its method to the appropriate handler.
func (s *Server) handleConcurrency(w http.ResponseWriter, r *http.Request) {
switch r.Method {
Expand Down
Loading