Repository: prebid/prebid-server
Pull request #1758 (2021-03-17 07:32:57)

Applogy: Fix Shared Memory Overwriting
Copy of reference field `Banner` needed to be made inside `adapters/applogy/applogy.go` in order to avoid a data race on shared memory. Authors of source code should be notified and encourage to make any changes to this PR if they disagree with the proposed approach.



Patch:
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
new file mode 100644
index 00000000..720e2f04
--- /dev/null
+++ b/.github/release-drafter.yml
@@ -0,0 +1,9 @@
+change-template: '* $TITLE (#$NUMBER)'
+template: |
+  ## Changes
+
+  $CHANGES
+
+  ## Contributors
+  
+  $CONTRIBUTORS
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..fb9b6592
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,26 @@
+name: Release
+
+on:
+  push:
+    tags:
+      - '[0-9]+.[0-9]+.[0-9]+'
+
+jobs:
+  release:
+    name: Create Release
+    if: github.event.base_ref == 'refs/heads/master'
+    runs-on: ubuntu-latest
+    steps:
+      - name: Get Version
+        id: get_version
+        run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\//}
+
+      - name: Create & Publish Release
+        uses: release-drafter/release-drafter@v5.12.1
+        with:
+          name: ${{ steps.get_version.outputs.version }}
+          tag: ${{ steps.get_version.outputs.version }}
+          version: ${{ steps.get_version.outputs.version }}
+          publish: true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 97d8cea4..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-language: go
-
-go:
-  - '1.14.2'
-  - '1.15'
-
-go_import_path: github.com/prebid/prebid-server
-
-env:
-- GO111MODULE=on
-
-script:
-  - "./validate.sh --nofmt --cov --race 10"
-
-before_deploy:
-  - go get github.com/mitchellh/gox
-  - gox -os="linux" -arch="386" -output="{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...;
-
-deploy:
-  provider: releases
-  skip_cleanup: true
-  api_key:
-    secure: TSJcbIpg2zTJuzUXwv0Un5DPztDTeIKQ2BuuO9KiWYY3Td/nKn0flTYE6B5O6iVqE96HKyj2j0W51rhnRTNDReRZv76L+YXLTJOTQEEQY/A+7XaUXRT0KIbr1EHaeU+4uPJe/8YXxq+nFNeqOjj+LY457WbvnQTIbraAmCgi4yNq4JR+J9BoCELkX0SnU7oq+brq9tJNL3V+7EHIVH6ZLa1lWOrapMnbrVils8gwzWR8XpbdaI+Sn30AGOFKZ0WE2ojZkZb8oZxyX0HKarIiykfZUUzRhlXlTJ0D81QOdc5AtPNR/2dqUXsUE8mRav9R3AJM2BCS2pnP29orCRQU/kxS/mRfx2oZhkr+OHPsNbJcGNSbqNKlM13bX2nL1ZJsJ6xL0VrkBFYlI01SWR12CT9DhZSqTmGPNEkt3fdzwuYtkJNfthb9e9obscnmJEHPSiZRv9dv/stP5LVJJHfFdrzM4+Qo3MCxLNOhmc+p93gsZPeuDGDlx8Tqv1KpN7sp0glbmOwyFAwbCVh5can/JPIAKsQi9VRyZAJvn+7sqqZCExN4TvFArq7pe0LjIVHUQZP9g/vS8HobQnPutmGxf8HqzVVEBnjMsXuiY4cVRecXVRM7crfJjLGr2e9ywIkUZMSD+bRkbRUZ0QQQPvWtcgRw5JmLKG9jDklj8BDkON8=
-  file:
-    - prebid-server_linux_386
-  on:
-    repo: prebid/prebid-server
-    tags: true
-    branch:
-      - master
diff --git a/README.md b/README.md
index 32bf4575..a619533e 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
-[![Build Status](https://travis-ci.org/prebid/prebid-server.svg?branch=master)](https://travis-ci.org/prebid/prebid-server)
+[![Build](https://img.shields.io/github/workflow/status/prebid/prebid-server/Validate/master?style=flat-square)](https://github.com/prebid/prebid-server/actions/workflows/validate.yml)
 [![Go Report Card](https://goreportcard.com/badge/github.com/prebid/prebid-server?style=flat-square)](https://goreportcard.com/report/github.com/prebid/prebid-server)
+![Go Version](https://img.shields.io/github/go-mod/go-version/prebid/prebid-server?style=flat-square)
 
 # Prebid Server
 
diff --git a/adapters/applogy/applogy.go b/adapters/applogy/applogy.go
index ebac0283..fe5fdc17 100644
--- a/adapters/applogy/applogy.go
+++ b/adapters/applogy/applogy.go
@@ -25,7 +25,7 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E
 	result := make([]*adapters.RequestData, 0, len(impressions))
 	errs := make([]error, 0, len(impressions))
 
-	for i, impression := range impressions {
+	for _, impression := range impressions {
 		if impression.Banner == nil && impression.Video == nil && impression.Native == nil {
 			errs = append(errs, &errortypes.BadInput{
 				Message: "Applogy only supports banner, video or native ads",
@@ -33,17 +33,17 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E
 			continue
 		}
 		if impression.Banner != nil {
-			banner := impression.Banner
-			if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 {
-				if len(banner.Format) == 0 {
+			if impression.Banner.W == nil || impression.Banner.H == nil || *impression.Banner.W == 0 || *impression.Banner.H == 0 {
+				if len(impression.Banner.Format) == 0 {
 					errs = append(errs, &errortypes.BadInput{
 						Message: "banner size information missing",
 					})
 					continue
 				}
-				format := banner.Format[0]
-				banner.W = &format.W
-				banner.H = &format.H
+				banner := *impression.Banner
+				banner.W = openrtb.Uint64Ptr(banner.Format[0].W)
+				banner.H = openrtb.Uint64Ptr(banner.Format[0].H)
+				impression.Banner = &banner
 			}
 		}
 		if len(impression.Ext) == 0 {
@@ -70,7 +70,7 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E
 			errs = append(errs, errors.New("Applogy token required"))
 			continue
 		}
-		request.Imp = impressions[i : i+1]
+		request.Imp = []openrtb.Imp{impression}
 		body, err := json.Marshal(request)
 		if err != nil {
 			errs = append(errs, err)
diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go
index 3a431c9c..58554577 100644
--- a/adapters/beachfront/beachfront.go
+++ b/adapters/beachfront/beachfront.go
@@ -76,6 +76,7 @@ type beachfrontBannerRequest struct {
 	AdapterVersion string           `json:"adapterVersion"`
 	IP             string           `json:"ip"`
 	RequestID      string           `json:"requestId"`
+	Real204        bool             `json:"real204"`
 }
 
 type beachfrontSlot struct {
@@ -367,6 +368,7 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e
 	if request.Imp[0].Secure != nil {
 		bfr.Secure = *request.Imp[0].Secure
 	}
+	bfr.Real204 = true
 
 	return bfr, errs
 }
@@ -465,7 +467,7 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []
 		}
 
 		bfReqs[i].Request.Imp = nil
-		bfReqs[i].Request.Imp = make([]openrtb.Imp, 1, 1)
+		bfReqs[i].Request.Imp = make([]openrtb.Imp, 1)
 		bfReqs[i].Request.Imp[0] = imp
 
 	}
@@ -481,18 +483,12 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []
 }
 
 func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
-	var bids []openrtb.Bid
-
-	// The case of response status == 200 and response body length == 2 below covers the case of the banner endpoint returning
-	// an empty JSON array ('[]'), which is functionally no content.
-	if response.StatusCode == http.StatusNoContent || (response.StatusCode == http.StatusOK && len(response.Body) <= 2) {
-		return nil, []error{&errortypes.BadInput{
-			Message: fmt.Sprintf("no content or truncated content received from server. status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri),
-		}}
+	if response.StatusCode == http.StatusNoContent {
+		return nil, nil
 	}
 
 	if response.StatusCode >= http.StatusInternalServerError {
-		return nil, []error{&errortypes.BadInput{
+		return nil, []error{&errortypes.BadServerResponse{
 			Message: fmt.Sprintf("server error status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri),
 		}}
 	}
@@ -507,8 +503,9 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern
 		return nil, []error{fmt.Errorf("unexpected status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri)}
 	}
 
-	var xtrnal openrtb.BidRequest
+	var bids []openrtb.Bid
 	var errs = make([]error, 0)
+	var xtrnal openrtb.BidRequest
 
 	// For video, which uses RTB for the external request, this will unmarshal as expected. For banner, it will
 	// only get the User struct and everything else will be nil
@@ -524,6 +521,7 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern
 
 	var dur beachfrontVideoBidExtension
 	bidResponse := adapters.NewBidderResponseWithBidsCapacity(BidCapacity)
+
 	for i := 0; i < len(bids); i++ {
 
 		// If we unmarshal without an error, this is an AdM video
diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go
index 91211a61..32bf971c 100644
--- a/adapters/rubicon/rubicon.go
+++ b/adapters/rubicon/rubicon.go
@@ -93,6 +93,10 @@ type rubiconExtUserTpID struct {
 	UID    string `json:"uid"`
 }
 
+type rubiconUserDataExt struct {
+	TaxonomyName string `json:"taxonomyname"`
+}
+
 type rubiconUserExt struct {
 	Consent     string                        `json:"consent,omitempty"`
 	DigiTrust   *openrtb_ext.ExtUserDigiTrust `json:"digitrust"`
@@ -752,6 +756,11 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap
 			userCopy := *request.User
 			userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}}
 
+			if err := updateUserExtWithIabAttribute(&userExtRP, userCopy.Data); err != nil {
+				errs = append(errs, err)
+				continue
+			}
+
 			if request.User.Ext != nil {
 				var userExt *openrtb_ext.ExtUser
 				if err = json.Unmarshal(userCopy.Ext, &userExt); err != nil {
@@ -886,6 +895,43 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap
 	return requestData, errs
 }
 
+func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb.Data) error {
+	var segmentIdsToCopy = make([]string, 0)
+
+	for _, dataRecord := range data {
+		if dataRecord.Ext != nil {
+			var dataExtObject rubiconUserDataExt
+			err := json.Unmarshal(dataRecord.Ext, &dataExtObject)
+			if err != nil {
+				continue
+			}
+			if strings.EqualFold(dataExtObject.TaxonomyName, "iab") {
+				for _, segment := range dataRecord.Segment {
+					segmentIdsToCopy = append(segmentIdsToCopy, segment.ID)
+				}
+			}
+		}
+	}
+
+	userExtRPTarget := make(map[string]interface{})
+
+	if userExtRP.RP.Target != nil {
+		if err := json.Unmarshal(userExtRP.RP.Target, &userExtRPTarget); err != nil {
+			return &errortypes.BadInput{Message: err.Error()}
+		}
+	}
+
+	userExtRPTarget["iab"] = segmentIdsToCopy
+
+	if target, err := json.Marshal(&userExtRPTarget); err != nil {
+		return &errortypes.BadInput{Message: err.Error()}
+	} else {
+		userExtRP.RP.Target = target
+	}
+
+	return nil
+}
+
 func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) (mappedRubiconUidsParam, []error) {
 	rubiconUidsParam := mappedRubiconUidsParam{
 		tpIds:    make([]rubiconExtUserTpID, 0),
diff --git a/docs/developers/contributing.md b/docs/developers/contributing.md
index 2dafa67f..2a6a574e 100644
--- a/docs/developers/contributing.md
+++ b/docs/developers/contributing.md
@@ -40,7 +40,7 @@ those updates must be submitted in the same Pull Request as the code changes.
 When you're ready, [submit a Pull Request](https://help.github.com/articles/creating-a-pull-request/)
 against the `master` branch of [our GitHub repository](https://github.com/prebid/prebid-server/compare).
 
-Pull Requests will be vetted through [Travis CI](https://travis-ci.com/).
+Pull Requests will be vetted through GitHub Actions.
 To reproduce these same tests locally, do:
 
 ```bash
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 8cc8e9bd..1f3d622b 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -35,10 +35,10 @@ import (
 const defaultAmpRequestTimeoutMillis = 900
 
 type AmpResponse struct {
-	Targeting map[string]string                                       `json:"targeting"`
-	Debug     *openrtb_ext.ExtResponseDebug                           `json:"debug,omitempty"`
-	Errors    map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"errors,omitempty"`
-	Warnings  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"warnings,omitempty"`
+	Targeting map[string]string                                         `json:"targeting"`
+	Debug     *openrtb_ext.ExtResponseDebug                             `json:"debug,omitempty"`
+	Errors    map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage `json:"errors,omitempty"`
+	Warnings  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage `json:"warnings,omitempty"`
 }
 
 // NewAmpEndpoint modifies the OpenRTB endpoint to handle AMP requests. This will basically modify the parsing
@@ -239,9 +239,12 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr))
 	}
 
-	warnings := make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError)
+	warnings := extResponse.Warnings
+	if warnings == nil {
+		warnings = make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)
+	}
 	for _, v := range errortypes.WarningOnly(errL) {
-		bidderErr := openrtb_ext.ExtBidderError{
+		bidderErr := openrtb_ext.ExtBidderMessage{
 			Code:    errortypes.ReadCode(v),
 			Message: v.Error(),
 		}
@@ -528,8 +531,9 @@ func readPolicy(consent string) (privacy.PolicyWriter, error) {
 		return ccpa.ConsentWriter{consent}, nil
 	}
 
-	return privacy.NilPolicyWriter{}, &errortypes.InvalidPrivacyConsent{
-		Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent),
+	return privacy.NilPolicyWriter{}, &errortypes.Warning{
+		Message:     fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent),
+		WarningCode: errortypes.InvalidPrivacyConsentWarningCode,
 	}
 }
 
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index e4567bbc..d26623da 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -139,6 +139,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 	if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) {
 		return
 	}
+	warnings := errortypes.WarningOnly(errL)
 
 	ctx := context.Background()
 
@@ -179,6 +180,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		RequestType:  labels.RType,
 		StartTime:    start,
 		LegacyLabels: labels,
+		Warnings:     warnings,
 	}
 
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
@@ -363,8 +365,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error {
 	if ccpaPolicy, err := ccpa.ReadFromRequest(req); err != nil {
 		return append(errL, err)
 	} else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil {
-		if _, invalidConsent := err.(*errortypes.InvalidPrivacyConsent); invalidConsent {
-			errL = append(errL, &errortypes.InvalidPrivacyConsent{Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err)})
+		if _, invalidConsent := err.(*errortypes.Warning); invalidConsent {
+			errL = append(errL, &errortypes.Warning{
+				Message:     fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err),
+				WarningCode: errortypes.InvalidPrivacyConsentWarningCode})
 			consentWriter := ccpa.ConsentWriter{Consent: ""}
 			if err := consentWriter.Write(req); err != nil {
 				return append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err))
@@ -915,12 +919,17 @@ func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequ
 }
 
 func (deps *endpointDeps) validateAliases(aliases map[string]string) error {
-	for thisAlias, coreBidder := range aliases {
+	for alias, coreBidder := range aliases {
+		if _, isCoreBidderDisabled := deps.disabledBidders[coreBidder]; isCoreBidderDisabled {
+			return fmt.Errorf("request.ext.prebid.aliases.%s refers to disabled bidder: %s", alias, coreBidder)
+		}
+
 		if _, isCoreBidder := deps.bidderMap[coreBidder]; !isCoreBidder {
-			return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", thisAlias, coreBidder)
+			return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, coreBidder)
 		}
-		if thisAlias == coreBidder {
-			return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", thisAlias)
+
+		if alias == coreBidder {
+			return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", alias)
 		}
 	}
 	return nil
diff --git a/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json b/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json
index f4379dc0..86239308 100644
--- a/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json
+++ b/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json
@@ -1,7 +1,7 @@
 {
   "description": "Request comes with an alias to a disabled bidder, we should throw error",
   "config": {
-    "disabledAdapters": ["appnexus", "rubicon"]
+    "disabledAdapters": ["appnexus"]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -32,5 +32,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.ext.prebid.aliases.test1 refers to unknown bidder: appnexus\n"
+  "expectedErrorMessage": "Invalid request: request.ext.prebid.aliases.test1 refers to disabled bidder: appnexus\n"
 }
diff --git a/endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json b/endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json
new file mode 100644
index 00000000..4297aa75
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json
@@ -0,0 +1,25 @@
+{
+  "description": "Native bid request. Context type content (1) is incompatible with 'social' subcontext types (20~29). Return error",
+  "mockBidRequest": {
+    "id": "req-id",
+    "site": {
+      "page": "some.page.com"
+    },
+    "tmax": 500,
+    "imp": [
+      {
+        "id": "some-imp",
+        "native": {
+          "request": "{\"context\":1,\"contextsubtype\":21,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}"
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ]
+  },
+  "expectedReturnCode": 400,
+  "expectedErrorMessage": "Invalid request"
+}
diff --git a/endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json b/endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json
new file mode 100644
index 00000000..31938336
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json
@@ -0,0 +1,27 @@
+{
+  "description": "Native bid request. Context type product (3) is incompatible with 'social' subcontext types (10~19). Return error",
+  "mockBidRequest": {
+    "id": "req-id",
+    "site": {
+      "page": "some.page.com"
+    },
+    "tmax": 500,
+    "imp": [
+      {
+        "id": "some-imp",
+        "native": {
+          "request": "{\"context\":3,\"contextsubtype\":11,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}"
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ]
+  },
+  "expectedReturnCode": 400,
+  "expectedErrorMessage": "Invalid request"
+}
+
+
diff --git a/endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json b/endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json
new file mode 100644
index 00000000..4ecbf449
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json
@@ -0,0 +1,27 @@
+{
+  "description": "Native bid request. Context type social (2) is incompatible with 'product' subcontext types (30~39). Return error",
+  "mockBidRequest": {
+    "id": "req-id",
+    "site": {
+      "page": "some.page.com"
+    },
+    "tmax": 500,
+    "imp": [
+      {
+        "id": "some-imp",
+        "native": {
+          "request": "{\"context\":2,\"contextsubtype\":31,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}"
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ]
+  },
+  "expectedReturnCode": 400,
+  "expectedErrorMessage": "Invalid request"
+}
+
+
diff --git a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json
new file mode 100644
index 00000000..9b422380
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json
@@ -0,0 +1,26 @@
+{
+  "description": "Native bid request comes with a subcontext type greater than 500. Return error",
+  "mockBidRequest": {
+    "id": "req-id",
+    "site": {
+      "page": "some.page.com"
+    },
+    "tmax": 500,
+    "imp": [
+      {
+        "id": "some-imp",
+        "native": {
+          "request": "{\"context\":1,\"contextsubtype\":550,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}"
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ]
+  },
+  "expectedReturnCode": 400,
+  "expectedErrorMessage": "Invalid request"
+}
+
diff --git a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json
index 395e7034..cdf4ef0c 100644
--- a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json
+++ b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json
@@ -1,5 +1,5 @@
 {
-  "description": "Bid request with invalid contextsubtype value inside the native.request field in its only imp element",
+  "description": "Native bid request comes with a contextsubtype greater than 32, 'ContextSubTypeProductReview'",
   "mockBidRequest": {
     "id": "req-id",
     "site": {
@@ -10,7 +10,7 @@
       {
         "id": "some-imp",
         "native": {
-          "request": "{\"context\":1,\"contextsubtype\":21,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}"
+          "request": "{\"context\":1,\"contextsubtype\":41,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}"
         },
         "ext": {
           "appnexus": {
diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json
new file mode 100644
index 00000000..dbf7b9c5
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json
@@ -0,0 +1,41 @@
+{
+  "description": "Native bid request comes with a context type product (3) and compatible subcontext type",
+  "mockBidRequest": {
+    "id": "req-id",
+    "site": {
+      "page": "some.page.com"
+    },
+    "tmax": 500,
+    "imp": [
+      {
+        "id": "some-imp",
+        "native": {
+          "request": "{\"context\":3,\"contextsubtype\":31,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}"
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ]
+  },
+  "expectedBidResponse": {
+    "id": "req-id",
+    "bidid": "test bid id",
+    "nbr": 0,
+      "seatbid": [
+        {
+          "bid": [
+            {
+              "id": "appnexus-bid",
+              "impid": "",
+              "price": 0
+            }
+          ],
+          "seat": "appnexus-bids"
+        }
+      ]
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json
new file mode 100644
index 00000000..41fb833d
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json
@@ -0,0 +1,41 @@
+{
+  "description": "Native bid request comes with a context type social (2) and compatible subcontext type",
+  "mockBidRequest": {
+    "id": "req-id",
+    "site": {
+      "page": "some.page.com"
+    },
+    "tmax": 500,
+    "imp": [
+      {
+        "id": "some-imp",
+        "native": {
+          "request": "{\"context\":2,\"contextsubtype\":21,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}"
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ]
+  },
+  "expectedBidResponse": {
+    "id": "req-id",
+    "bidid": "test bid id",
+    "nbr": 0,
+      "seatbid": [
+        {
+          "bid": [
+            {
+              "id": "appnexus-bid",
+              "impid": "",
+              "price": 0
+            }
+          ],
+          "seat": "appnexus-bids"
+        }
+      ]
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json
new file mode 100644
index 00000000..2ccdfb7c
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json
@@ -0,0 +1,52 @@
+{
+  "description": "Well formed amp request with invalid CCPA consent value",
+  "mockBidRequest": {
+    "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
+    "site": {
+      "page": "prebid.org",
+      "publisher": {
+        "id": "a3de7af2-a86a-4043-a77b-c7e86744155e"
+      }
+    },
+    "source": {
+      "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5"
+    },
+    "tmax": 1000,
+    "imp": [
+      {
+        "id": "/19968336/header-bid-tag-0",
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        },
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 300
+            }
+          ]
+        }
+      }
+    ],
+    "regs": {
+      "ext": {
+        "us_privacy": "{invalid}"
+      }
+    },
+    "user": {
+      "ext": {}
+    }
+  },
+  "expectedBidResponse": {
+      "id":"b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
+      "bidid":"test bid id",
+      "nbr":0
+  },
+  "expectedReturnCode": 200
+}
diff --git a/errortypes/code.go b/errortypes/code.go
index 80a5eb45..2749b978 100644
--- a/errortypes/code.go
+++ b/errortypes/code.go
@@ -17,6 +17,8 @@ const (
 const (
 	UnknownWarningCode               = 10999
 	InvalidPrivacyConsentWarningCode = iota + 10000
+	AccountLevelDebugDisabledWarningCode
+	BidderLevelDebugDisabledWarningCode
 )
 
 // Coder provides an error or warning code with severity.
diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go
index c953f9b7..1fed2d7d 100644
--- a/errortypes/errortypes.go
+++ b/errortypes/errortypes.go
@@ -167,7 +167,8 @@ func (err *BidderTemporarilyDisabled) Severity() Severity {
 
 // Warning is a generic non-fatal error.
 type Warning struct {
-	Message string
+	Message     string
+	WarningCode int
 }
 
 func (err *Warning) Error() string {
@@ -175,26 +176,9 @@ func (err *Warning) Error() string {
 }
 
 func (err *Warning) Code() int {
-	return UnknownWarningCode
+	return err.WarningCode
 }
 
 func (err *Warning) Severity() Severity {
 	return SeverityWarning
 }
-
-// InvalidPrivacyConsent is a warning for when the privacy consent string is invalid and is ignored.
-type InvalidPrivacyConsent struct {
-	Message string
-}
-
-func (err *InvalidPrivacyConsent) Error() string {
-	return err.Message
-}
-
-func (err *InvalidPrivacyConsent) Code() int {
-	return InvalidPrivacyConsentWarningCode
-}
-
-func (err *InvalidPrivacyConsent) Severity() Severity {
-	return SeverityWarning
-}
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 6df5fafb..b0818dfd 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -164,9 +164,17 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi
 		// - debugContextKey (url param) in true
 		// - account debug is allowed
 		// - bidder debug is allowed
-		if accountDebugAllowed && bidder.config.DebugInfo.Allow {
-			if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) {
-				seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
+		if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) {
+			if accountDebugAllowed {
+				if bidder.config.DebugInfo.Allow {
+					seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo))
+				} else {
+					debugDisabledWarning := errortypes.Warning{
+						WarningCode: errortypes.BidderLevelDebugDisabledWarningCode,
+						Message:     "debug turned off for bidder",
+					}
+					errs = append(errs, &debugDisabledWarning)
+				}
 			}
 		}
 
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 45081c16..d8ac60ba 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -66,7 +66,8 @@ type exchange struct {
 // Container to pass out response ext data from the GetAllBids goroutines back into the main thread
 type seatResponseExtra struct {
 	ResponseTimeMillis int
-	Errors             []openrtb_ext.ExtBidderError
+	Errors             []openrtb_ext.ExtBidderMessage
+	Warnings           []openrtb_ext.ExtBidderMessage
 	// httpCalls is the list of debugging info. It should only be populated if the request.test == 1.
 	// This will become response.ext.debug.httpcalls.{bidder} on the final Response.
 	HttpCalls []*openrtb_ext.ExtHttpCall
@@ -106,6 +107,7 @@ type AuctionRequest struct {
 	UserSyncs   IdFetcher
 	RequestType metrics.RequestType
 	StartTime   time.Time
+	Warnings    []error
 
 	// LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests
 	// in HoldAuction until we get to factoring it away. Do not use for anything new.
@@ -138,9 +140,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 		debugLog = &DebugLog{Enabled: false}
 	}
 
-	debugInfo := getDebugInfo(r.BidRequest, requestExt)
+	requestDebugInfo := getDebugInfo(r.BidRequest, requestExt)
 
-	debugInfo = debugInfo && r.Account.DebugAllow
+	debugInfo := requestDebugInfo && r.Account.DebugAllow
 	debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow
 
 	if debugInfo {
@@ -236,6 +238,22 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 		}
 	}
 
+	if !r.Account.DebugAllow && requestDebugInfo {
+		accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{
+			Code:    errortypes.AccountLevelDebugDisabledWarningCode,
+			Message: "debug turned off for account",
+		}
+		bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], accountDebugDisabledWarning)
+	}
+
+	for _, warning := range r.Warnings {
+		generalWarning := openrtb_ext.ExtBidderMessage{
+			Code:    errortypes.ReadCode(warning),
+			Message: warning.Error(),
+		}
+		bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], generalWarning)
+	}
+
 	// Build the response
 	return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs)
 }
@@ -397,11 +415,11 @@ func (e *exchange) getAllBids(
 
 			// Timing statistics
 			e.me.RecordAdapterTime(bidderRequest.BidderLabels, time.Since(start))
-			serr := errsToBidderErrors(err)
 			bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterBids)
 			bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err)
 			// Append any bid validation errors to the error list
-			ae.Errors = serr
+			ae.Errors = errsToBidderErrors(err)
+			ae.Warnings = errsToBidderWarnings(err)
 			brw.adapterExtra = ae
 			if bids != nil {
 				for _, bid := range bids.bids {
@@ -494,13 +512,29 @@ func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} {
 	return ret
 }
 
-func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderError {
-	serr := make([]openrtb_ext.ExtBidderError, len(errs))
-	for i := 0; i < len(errs); i++ {
-		serr[i].Code = errortypes.ReadCode(errs[i])
-		serr[i].Message = errs[i].Error()
+func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderMessage {
+	sErr := make([]openrtb_ext.ExtBidderMessage, 0)
+	for _, err := range errortypes.FatalOnly(errs) {
+		newErr := openrtb_ext.ExtBidderMessage{
+			Code:    errortypes.ReadCode(err),
+			Message: err.Error(),
+		}
+		sErr = append(sErr, newErr)
 	}
-	return serr
+
+	return sErr
+}
+
+func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage {
+	sWarn := make([]openrtb_ext.ExtBidderMessage, 0)
+	for _, warn := range errortypes.WarningOnly(errs) {
+		newErr := openrtb_ext.ExtBidderMessage{
+			Code:    errortypes.ReadCode(warn),
+			Message: warn.Error(),
+		}
+		sWarn = append(sWarn, newErr)
+	}
+	return sWarn
 }
 
 // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester
@@ -749,7 +783,8 @@ func getPrimaryAdServer(adServerId int) (string, error) {
 func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, errList []error) *openrtb_ext.ExtBidResponse {
 	req := r.BidRequest
 	bidResponseExt := &openrtb_ext.ExtBidResponse{
-		Errors:               make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError, len(adapterBids)),
+		Errors:               make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)),
+		Warnings:             make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)),
 		ResponseTimeMillis:   make(map[openrtb_ext.BidderName]int, len(adapterBids)),
 		RequestTimeoutMillis: req.TMax,
 	}
@@ -771,6 +806,9 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb
 		if debugInfo && len(responseExtra.HttpCalls) > 0 {
 			bidResponseExt.Debug.HttpCalls[bidderName] = responseExtra.HttpCalls
 		}
+		if len(responseExtra.Warnings) > 0 {
+			bidResponseExt.Warnings[bidderName] = responseExtra.Warnings
+		}
 		// Only make an entry for bidder errors if the bidder reported any.
 		if len(responseExtra.Errors) > 0 {
 			bidResponseExt.Errors[bidderName] = responseExtra.Errors
diff --git a/main.go b/main.go
index 2ae75a2e..ebeabf29 100644
--- a/main.go
+++ b/main.go
@@ -20,7 +20,7 @@ import (
 // Rev holds binary revision string
 // Set manually at build time using:
 //    go build -ldflags "-X main.Rev=`git rev-parse --short HEAD`"
-// Populated automatically at build / release time via .travis.yml
+// Populated automatically at build / releases
 //   `gox -os="linux" -arch="386" -output="{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...;`
 // See issue #559
 var Rev string
diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go
index 02370d19..d517704f 100644
--- a/openrtb_ext/response.go
+++ b/openrtb_ext/response.go
@@ -8,7 +8,8 @@ import (
 type ExtBidResponse struct {
 	Debug *ExtResponseDebug `json:"debug,omitempty"`
 	// Errors defines the contract for bidresponse.ext.errors
-	Errors map[BidderName][]ExtBidderError `json:"errors,omitempty"`
+	Errors   map[BidderName][]ExtBidderMessage `json:"errors,omitempty"`
+	Warnings map[BidderName][]ExtBidderMessage `json:"warnings,omitempty"`
 	// ResponseTimeMillis defines the contract for bidresponse.ext.responsetimemillis
 	ResponseTimeMillis map[BidderName]int `json:"responsetimemillis,omitempty"`
 	// RequestTimeoutMillis returns the timeout used in the auction.
@@ -47,8 +48,8 @@ type ExtUserSync struct {
 	Type UserSyncType `json:"type"`
 }
 
-// ExtBidderError defines an error object to be returned, consiting of a machine readable error code, and a human readable error message string.
-type ExtBidderError struct {
+// ExtBidderMessage defines an error object to be returned, consiting of a machine readable error code, and a human readable error message string.
+type ExtBidderMessage struct {
 	Code    int    `json:"code"`
 	Message string `json:"message"`
 }
diff --git a/privacy/ccpa/parsedpolicy.go b/privacy/ccpa/parsedpolicy.go
index 3c934e67..7b9c2d1f 100644
--- a/privacy/ccpa/parsedpolicy.go
+++ b/privacy/ccpa/parsedpolicy.go
@@ -43,7 +43,10 @@ func (p Policy) Parse(validBidders map[string]struct{}) (ParsedPolicy, error) {
 	consentOptOut, err := parseConsent(p.Consent)
 	if err != nil {
 		msg := fmt.Sprintf("request.regs.ext.us_privacy %s", err.Error())
-		return ParsedPolicy{}, &errortypes.InvalidPrivacyConsent{Message: msg}
+		return ParsedPolicy{}, &errortypes.Warning{
+			Message:     msg,
+			WarningCode: errortypes.InvalidPrivacyConsentWarningCode,
+		}
 	}
 
 	noSaleForAllBidders, noSaleSpecificBidders, err := parseNoSaleBidders(p.NoSaleBidders, validBidders)


Test patch:
diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go
index a25a4f19..1e3178ed 100644
--- a/adapters/adapterstest/test_json.go
+++ b/adapters/adapterstest/test_json.go
@@ -123,7 +123,7 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd
 	diffErrorLists(t, fmt.Sprintf("%s: MakeBids", filename), bidsErrs, spec.MakeBidsErrors)
 
 	for i := 0; i < len(spec.BidResponses); i++ {
-		diffBidLists(t, filename, bidResponses[i].Bids, spec.BidResponses[i].Bids)
+		diffBidLists(t, filename, bidResponses[i], spec.BidResponses[i].Bids)
 	}
 }
 
@@ -227,9 +227,24 @@ func diffErrorLists(t *testing.T, description string, actual []error, expected [
 	}
 }
 
-func diffBidLists(t *testing.T, filename string, actual []*adapters.TypedBid, expected []expectedBid) {
+func diffBidLists(t *testing.T, filename string, response *adapters.BidderResponse, expected []expectedBid) {
 	t.Helper()
 
+	if (response == nil || len(response.Bids) == 0) != (len(expected) == 0) {
+		if len(expected) == 0 {
+			t.Fatalf("%s: expectedBidResponses indicated a nil response, but mockResponses supplied a non-nil response", filename)
+		}
+
+		t.Fatalf("%s: mockResponses included unexpected nil or empty response", filename)
+	}
+
+	// Expected nil response - give diffBids something to work with.
+	if response == nil {
+		response = new(adapters.BidderResponse)
+	}
+
+	actual := response.Bids
+
 	if len(actual) != len(expected) {
 		t.Fatalf("%s: MakeBids returned wrong bid count. Expected %d, got %d", filename, len(expected), len(actual))
 	}
diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video.json b/adapters/beachfront/beachfronttest/exemplary/adm-video.json
index 26b050f2..edde1301 100644
--- a/adapters/beachfront/beachfronttest/exemplary/adm-video.json
+++ b/adapters/beachfront/beachfronttest/exemplary/adm-video.json
@@ -96,6 +96,7 @@
 
   "expectedBidResponses": [
     {
+      "currency": "USD",
       "bids": [
         {
           "bid": {
diff --git a/adapters/beachfront/beachfronttest/exemplary/banner.json b/adapters/beachfront/beachfronttest/exemplary/banner.json
index e3dcffa3..1a8e8305 100644
--- a/adapters/beachfront/beachfronttest/exemplary/banner.json
+++ b/adapters/beachfront/beachfronttest/exemplary/banner.json
@@ -45,6 +45,7 @@
           ],
           "domain": "some.domain.us",
           "page": "https://some.domain.us/some/page.html",
+          "real204": true,
           "referrer": "",
           "search": "",
           "secure": 1,
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json b/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json
new file mode 100644
index 00000000..edba490e
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json
@@ -0,0 +1,79 @@
+{
+  "mockBidRequest": {
+    "id": "some_test_ad",
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "imp": [
+      {
+        "id": "dudImp",
+        "bidfloor": 0.02,
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "bidfloor": 0.02,
+            "appId": "dudAppId1"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/prebid_display",
+        "body": {
+          "slots": [
+            {
+              "slot": "dudImp",
+              "id": "dudAppId1",
+              "bidfloor": 0.02,
+              "sizes": [
+                {
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ],
+          "domain": "some.domain.us",
+          "page": "https://some.domain.us/some/page.html",
+          "real204": true,
+          "referrer": "",
+          "search": "",
+          "secure": 1,
+          "requestId": "some_test_ad",
+          "isMobile": 0,
+          "ip": "",
+          "deviceModel": "",
+          "deviceOs": "",
+          "dnt": 0,
+          "ua": "",
+          "adapterName": "BF_PREBID_S2S",
+          "adapterVersion": "0.9.2",
+          "user": {
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 204,
+        "body": [
+          {
+            "something": "where nothing should be"
+          }
+        ]
+      }
+    }
+  ],
+
+  "expectedBidResponses": []
+}
+
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json b/adapters/beachfront/beachfronttest/supplemental/banner-204.json
similarity index 81%
rename from adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json
rename to adapters/beachfront/beachfronttest/supplemental/banner-204.json
index 8e607f67..69aa0b88 100644
--- a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-204.json
@@ -46,6 +46,7 @@
           ],
           "domain": "some.domain.us",
           "page": "https://some.domain.us/some/page.html",
+          "real204": true,
           "referrer": "",
           "search": "",
           "secure": 1,
@@ -63,18 +64,13 @@
         }
       },
       "mockResponse": {
-        "status": 200,
-        "body": []
+        "status": 204,
+        "body": ""
       }
     }
   ],
 
-  "expectedBidResponses": [],
+  "expectedBidResponses": [
 
-  "expectedMakeBidsErrors": [
-    {
-      "value": "no content or truncated content received from server. status code 200 from https://qa.beachrtb.com/prebid_display. Run with request.debug = 1 for more info",
-      "comparison": "literal"
-    }
   ]
 }
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json
index c326a33a..b060efe6 100644
--- a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json
@@ -60,6 +60,7 @@
           ],
           "domain": "some.domain.us",
           "page": "https://some.domain.us/some/page.html",
+          "real204": true,
           "referrer": "",
           "search": "",
           "secure": 1,
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json
new file mode 100644
index 00000000..240994bb
--- /dev/null
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json
@@ -0,0 +1,171 @@
+{
+  "mockBidRequest": {
+    "id": "banner-and-video",
+    "imp": [
+      {
+        "id": "mix1",
+        "ext": {
+          "bidder": {
+            "bidfloor": 0.41,
+            "appIds": {
+              "banner": "bannerAppId1",
+              "video": "videoAppId1"
+            }
+          }
+        },
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "context": "instream",
+          "w": 300,
+          "h": 250
+        },
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        }
+      }
+    ],
+    "site": {
+      "page": "https://some.domain.us/some/page.html"
+    },
+    "device":{
+      "ip":"255.255.255.255"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/prebid_display",
+        "body": {
+          "slots": [
+            {
+              "slot": "mix1",
+              "id": "bannerAppId1",
+              "bidfloor": 0.41,
+              "sizes": [
+                {
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ],
+          "domain": "some.domain.us",
+          "page": "https://some.domain.us/some/page.html",
+          "real204": true,
+          "referrer": "",
+          "search": "",
+          "secure": 1,
+          "deviceOs": "",
+          "deviceModel": "",
+          "isMobile": 0,
+          "ua": "",
+          "ip": "255.255.255.255",
+          "dnt": 0,
+          "user": {},
+          "adapterName": "BF_PREBID_S2S",
+          "adapterVersion": "0.9.2",
+          "requestId": "banner-and-video"
+        }
+      },
+      "mockResponse": {
+        "status": 204,
+        "body": ""
+      }
+    },
+    {
+      "expectedRequest": {
+        "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1",
+        "body": {
+          "id": "banner-and-video",
+          "imp": [
+            {
+              "video": {
+                "w": 300,
+                "h": 250,
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "bidfloor": 0.41,
+              "secure": 1,
+              "id": "mix1"
+            }
+          ],
+          "site": {
+            "domain": "some.domain.us",
+            "page": "https://some.domain.us/some/page.html"
+          },
+          "device": {
+            "devicetype": 2,
+            "ip": "255.255.255.255"
+          },
+          "cur": [
+            "USD"
+          ]
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "61b87329-8790-47b7-90dd-c53ae7ce1723",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "5d839458f73decdc1572b7f6",
+                  "impid": "mix1",
+                  "price": 20,
+                  "adm": "<VAST version=\"2.0\"><Ad><Wrapper>http://example.com/vast.xml</Wrapper></Ad></VAST>",
+                  "adid": "1088",
+                  "adomain": [
+                    "beachfront.io"
+                  ],
+                  "cid": "277",
+                  "crid": "532",
+                  "w": 300,
+                  "h": 250
+                }
+              ],
+              "seat": "bfb-io-s1"
+            }
+          ],
+          "bidid": "5d839458f73decdc1572b7f6",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {"bids":[]},
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "mix1AdmVideo",
+            "impid": "mix1",
+            "price": 20,
+            "adm": "<VAST version=\"2.0\"><Ad><Wrapper>http://example.com/vast.xml</Wrapper></Ad></VAST>",
+            "adid": "1088",
+            "adomain": [
+              "beachfront.io"
+            ],
+            "cid": "277",
+            "crid": "532",
+            "w": 300,
+            "h": 250
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json
index f67903d0..18e8ca7a 100644
--- a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json
@@ -59,6 +59,7 @@
           ],
           "domain": "some.domain.us",
           "page": "https://some.domain.us/some/page.html",
+          "real204": true,
           "referrer": "",
           "search": "",
           "secure": 1,
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json
index b950d734..2ef8ca58 100644
--- a/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json
@@ -57,6 +57,7 @@
           ],
           "domain": "some.domain.us",
           "page": "https://some.domain.us/some/page.html",
+          "real204": true,
           "referrer": "",
           "search": "",
           "secure": 1,
diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json
index 1771f393..35926b3c 100644
--- a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json
+++ b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json
@@ -45,6 +45,7 @@
           ],
           "domain": "some.domain.us",
           "page": "https://some.domain.us/some/page.html",
+          "real204": true,
           "referrer": "",
           "search": "",
           "secure": 1,
diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json
index b751f763..e7869d36 100644
--- a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json
+++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json
@@ -44,6 +44,7 @@
           ],
           "domain": "some.domain.us",
           "page": "https://some.domain.us/some/page.html",
+          "real204": true,
           "referrer": "",
           "search": "",
           "secure": 1,
diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json
index f5d4e822..7ebe6cf4 100644
--- a/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json
+++ b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json
@@ -44,6 +44,7 @@
           ],
           "domain": "some.domain.us",
           "page": "https://some.domain.us/some/page.html",
+          "real204": true,
           "referrer": "",
           "search": "",
           "secure": 1,
diff --git a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json
index 4c009ff0..9a8ca734 100644
--- a/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json
+++ b/adapters/beachfront/beachfronttest/supplemental/six-nine-combo.json
@@ -189,6 +189,7 @@
           ],
           "domain": "example.com",
           "page": "http://example.com/whatever/something.html",
+          "real204": true,
           "referrer": "",
           "search": "",
           "secure": 0,
diff --git a/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json b/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json
index d48962e6..ac1ca19f 100644
--- a/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json
+++ b/adapters/beachfront/beachfronttest/supplemental/two-four-combo.json
@@ -97,6 +97,7 @@
           ],
           "domain":"example.com",
           "page":"http://example.com/whatever/something.html",
+          "real204": true,
           "referrer":"",
           "search":"",
           "secure":0,
diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json
index 76c64ff9..b85c28de 100644
--- a/adapters/rubicon/rubicontest/exemplary/simple-video.json
+++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json
@@ -9,6 +9,52 @@
       "id": "1",
       "bundle": "com.wls.testwlsapplication"
     },
+    "user": {
+      "data": [
+        {
+          "ext": {
+            "taxonomyname": "iab"
+          },
+          "segment": [
+            {
+              "id": "idToCopy"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "taxonomyname": "someValue"
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "taxonomyname": "IaB"
+          },
+          "segment": [
+            {
+              "id": "idToCopy2"
+            }
+          ]
+        },
+        {
+          "ext": {
+            "taxonomyname": [
+              "wrong iab type"
+            ]
+          },
+          "segment": [
+            {
+              "id": "shouldNotBeCopied2"
+            }
+          ]
+        }
+      ]
+    },
     "imp": [
       {
         "id": "test-imp-id",
@@ -30,8 +76,8 @@
             "video": {
             },
             "accountId": 1001,
-            "siteId":113932,
-            "zoneId":535510
+            "siteId": 113932,
+            "zoneId": 535510
           }
         }
       }
@@ -52,6 +98,63 @@
             "ip": "123.123.123.123",
             "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx"
           },
+          "user": {
+            "data": [
+              {
+                "ext": {
+                  "taxonomyname": "iab"
+                },
+                "segment": [
+                  {
+                    "id": "idToCopy"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "taxonomyname": "someValue"
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "taxonomyname": "IaB"
+                },
+                "segment": [
+                  {
+                    "id": "idToCopy2"
+                  }
+                ]
+              },
+              {
+                "ext": {
+                  "taxonomyname": [
+                    "wrong iab type"
+                  ]
+                },
+                "segment": [
+                  {
+                    "id": "shouldNotBeCopied2"
+                  }
+                ]
+              }
+            ],
+            "ext": {
+              "digitrust": null,
+              "rp": {
+                "target": {
+                  "iab": [
+                    "idToCopy",
+                    "idToCopy2"
+                  ]
+                }
+              }
+            }
+          },
           "app": {
             "id": "1",
             "ext": {
@@ -91,7 +194,7 @@
               },
               "ext": {
                 "rp": {
-                  "track":{
+                  "track": {
                     "mint": "",
                     "mint_version": ""
                   },
diff --git a/docs/developers/automated-tests.md b/docs/developers/automated-tests.md
index 0dff9b04..6814fba3 100644
--- a/docs/developers/automated-tests.md
+++ b/docs/developers/automated-tests.md
@@ -1,6 +1,6 @@
 # Automated Tests
 
-This project uses [TravisCI](https://travis-ci.org/) to make sure that every PR passes automated tests.
+This project uses GitHub Actions to make sure that every PR passes automated tests.
 To reproduce these tests locally, use:
 
 ```
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 08461d40..77e76613 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -395,103 +395,119 @@ func TestCCPAConsent(t *testing.T) {
 	}
 }
 
-func TestNoConsent(t *testing.T) {
-	// Build Request
-	bid, err := getTestBidRequest(true, nil, true, nil)
-	if err != nil {
-		t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err)
+func TestConsentWarnings(t *testing.T) {
+	type inputTest struct {
+		regs              *openrtb_ext.ExtRegs
+		invalidConsentURL bool
+		expectedWarnings  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage
 	}
+	invalidConsent := "invalid"
 
-	// Simulated Stored Request Backend
-	stored := map[string]json.RawMessage{"1": json.RawMessage(bid)}
+	bidderWarning := openrtb_ext.ExtBidderMessage{
+		Code:    10003,
+		Message: "debug turned off for bidder",
+	}
+	invalidCCPAWarning := openrtb_ext.ExtBidderMessage{
+		Code:    10001,
+		Message: "Consent '" + invalidConsent + "' is not recognized as either CCPA or GDPR TCF.",
+	}
+	invalidConsentWarning := openrtb_ext.ExtBidderMessage{
+		Code:    10001,
+		Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)",
+	}
 
-	// Build Exchange Endpoint
-	mockExchange := &mockAmpExchange{}
-	endpoint, _ := NewAmpEndpoint(
-		mockExchange,
-		newParamsValidator(t),
-		&mockAmpStoredReqFetcher{stored},
-		empty_fetcher.EmptyFetcher{},
-		&config.Configuration{MaxRequestSize: maxSize},
-		newTestMetrics(),
-		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
-		map[string]string{},
-		[]byte{},
-		openrtb_ext.BuildBidderMap(),
-	)
+	testData := []inputTest{
+		{
+			regs:              nil,
+			invalidConsentURL: false,
+			expectedWarnings:  nil,
+		},
+		{
+			regs:              nil,
+			invalidConsentURL: true,
+			expectedWarnings:  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning}},
+		},
+		{
+			regs:              &openrtb_ext.ExtRegs{USPrivacy: "invalid"},
+			invalidConsentURL: true,
+			expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{
+				openrtb_ext.BidderReservedGeneral:  {invalidCCPAWarning, invalidConsentWarning},
+				openrtb_ext.BidderName("appnexus"): {bidderWarning},
+			},
+		},
+		{
+			regs:              &openrtb_ext.ExtRegs{USPrivacy: "1NYN"},
+			invalidConsentURL: false,
+			expectedWarnings:  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderName("appnexus"): {bidderWarning}},
+		},
+	}
 
-	// Invoke Endpoint
-	request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil)
-	responseRecorder := httptest.NewRecorder()
-	endpoint(responseRecorder, request, nil)
+	for _, testCase := range testData {
 
-	// Parse Response
-	var response AmpResponse
-	if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil {
-		t.Fatalf("Error unmarshalling response: %s", err.Error())
-	}
+		bid, err := getTestBidRequest(true, nil, testCase.regs == nil, testCase.regs)
+		if err != nil {
+			t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err)
+		}
 
-	// Assert Result
-	result := mockExchange.lastRequest
-	assert.NotNil(t, result, "lastRequest")
-	assert.Nil(t, result.User, "lastRequest.User")
-	assert.Nil(t, result.Regs, "lastRequest.Regs")
-	assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors)
-	assert.Empty(t, response.Warnings)
-}
+		// Simulated Stored Request Backend
+		stored := map[string]json.RawMessage{"1": json.RawMessage(bid)}
 
-func TestInvalidConsent(t *testing.T) {
-	// Build Request
-	bid, err := getTestBidRequest(true, nil, true, nil)
-	if err != nil {
-		t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err)
-	}
+		// Build Exchange Endpoint
+		var mockExchange exchange.Exchange
+		if testCase.regs != nil {
+			mockExchange = &mockAmpExchangeWarnings{}
+		} else {
+			mockExchange = &mockAmpExchange{}
+		}
+		endpoint, _ := NewAmpEndpoint(
+			mockExchange,
+			newParamsValidator(t),
+			&mockAmpStoredReqFetcher{stored},
+			empty_fetcher.EmptyFetcher{},
+			&config.Configuration{MaxRequestSize: maxSize},
+			newTestMetrics(),
+			analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+			map[string]string{},
+			[]byte{},
+			openrtb_ext.BuildBidderMap(),
+		)
 
-	// Simulated Stored Request Backend
-	stored := map[string]json.RawMessage{"1": json.RawMessage(bid)}
+		// Invoke Endpoint
+		var request *http.Request
 
-	// Build Exchange Endpoint
-	mockExchange := &mockAmpExchange{}
-	endpoint, _ := NewAmpEndpoint(
-		mockExchange,
-		newParamsValidator(t),
-		&mockAmpStoredReqFetcher{stored},
-		empty_fetcher.EmptyFetcher{},
-		&config.Configuration{MaxRequestSize: maxSize},
-		newTestMetrics(),
-		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
-		map[string]string{},
-		[]byte{},
-		openrtb_ext.BuildBidderMap(),
-	)
+		if testCase.invalidConsentURL {
+			request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_string="+invalidConsent, nil)
 
-	// Invoke Endpoint
-	invalidConsent := "invalid"
-	request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_string="+invalidConsent, nil)
-	responseRecorder := httptest.NewRecorder()
-	endpoint(responseRecorder, request, nil)
+		} else {
+			request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil)
+		}
 
-	// Parse Response
-	var response AmpResponse
-	if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil {
-		t.Fatalf("Error unmarshalling response: %s", err.Error())
-	}
+		responseRecorder := httptest.NewRecorder()
+		endpoint(responseRecorder, request, nil)
 
-	// Assert Result
-	expectedWarnings := map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{
-		openrtb_ext.BidderReservedGeneral: {
-			{
-				Code:    10001,
-				Message: "Consent '" + invalidConsent + "' is not recognized as either CCPA or GDPR TCF.",
-			},
-		},
+		// Parse Response
+		var response AmpResponse
+		if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil {
+			t.Fatalf("Error unmarshalling response: %s", err.Error())
+		}
+
+		// Assert Result
+		if testCase.regs == nil {
+			result := mockExchange.(*mockAmpExchange).lastRequest
+			assert.NotNil(t, result, "lastRequest")
+			assert.Nil(t, result.User, "lastRequest.User")
+			assert.Nil(t, result.Regs, "lastRequest.Regs")
+			assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors)
+			if testCase.invalidConsentURL {
+				assert.Equal(t, testCase.expectedWarnings, response.Warnings)
+			} else {
+				assert.Empty(t, response.Warnings)
+			}
+
+		} else {
+			assert.Equal(t, testCase.expectedWarnings, response.Warnings)
+		}
 	}
-	result := mockExchange.lastRequest
-	assert.NotNil(t, result, "lastRequest")
-	assert.Nil(t, result.User, "lastRequest.User")
-	assert.Nil(t, result.Regs, "lastRequest.Regs")
-	assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors)
-	assert.Equal(t, expectedWarnings, response.Warnings)
 }
 
 func TestNewAndLegacyConsentBothProvided(t *testing.T) {
@@ -929,7 +945,7 @@ type mockAmpExchange struct {
 	lastRequest *openrtb.BidRequest
 }
 
-var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{
+var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{
 	openrtb_ext.BidderName("openx"): {
 		{
 			Code:    1,
@@ -962,6 +978,21 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq
 	return response, nil
 }
 
+type mockAmpExchangeWarnings struct{}
+
+func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+	response := &openrtb.BidResponse{
+		SeatBid: []openrtb.SeatBid{{
+			Bid: []openrtb.Bid{{
+				AdM: "<script></script>",
+				Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`),
+			}},
+		}},
+		Ext: json.RawMessage(`{ "warnings": {"appnexus": [{"code": 10003, "message": "debug turned off for bidder"}] }}`),
+	}
+	return response, nil
+}
+
 func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, regsExt *openrtb_ext.ExtRegs) ([]byte, error) {
 	var width uint64 = 300
 	var height uint64 = 300
@@ -1025,7 +1056,6 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool,
 			Ext:   regsExtData,
 		}
 	}
-
 	return json.Marshal(bidRequest)
 }
 
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 768e6059..19c58f05 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -48,7 +48,7 @@ type testConfigValues struct {
 	AliasJSON           string   `json:"aliases"`
 	BlacklistedAccounts []string `json:"blacklistedAccts"`
 	BlacklistedApps     []string `json:"blacklistedApps"`
-	AdapterList         []string `json:"disabledAdapters"`
+	DisabledAdapters    []string `json:"disabledAdapters"`
 }
 
 func TestJsonSampleRequests(t *testing.T) {
@@ -229,9 +229,9 @@ func (tc *testConfigValues) getBlackListedAccountMap() map[string]bool {
 func (tc *testConfigValues) getAdaptersConfigMap() map[string]config.Adapter {
 	var adaptersConfig map[string]config.Adapter
 
-	if len(tc.AdapterList) > 0 {
-		adaptersConfig = make(map[string]config.Adapter, len(tc.AdapterList))
-		for _, adapterName := range tc.AdapterList {
+	if len(tc.DisabledAdapters) > 0 {
+		adaptersConfig = make(map[string]config.Adapter, len(tc.DisabledAdapters))
+		for _, adapterName := range tc.DisabledAdapters {
 			adaptersConfig[adapterName] = config.Adapter{Disabled: true}
 		}
 	}
@@ -1505,7 +1505,9 @@ func TestCCPAInvalid(t *testing.T) {
 
 	errL := deps.validateRequest(&req)
 
-	expectedWarning := errortypes.InvalidPrivacyConsent{Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)"}
+	expectedWarning := errortypes.Warning{
+		Message:     "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)",
+		WarningCode: errortypes.InvalidPrivacyConsentWarningCode}
 	assert.ElementsMatch(t, errL, []error{&expectedWarning})
 
 	assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request")
@@ -2104,6 +2106,55 @@ func TestIOS14EndToEnd(t *testing.T) {
 	assert.Equal(t, &lmtOne, result.Device.Lmt)
 }
 
+func TestAuctionWarnings(t *testing.T) {
+	reqBody := validRequest(t, "us-privacy-invalid.json")
+	deps := &endpointDeps{
+		&warningsCheckExchange{},
+		newParamsValidator(t),
+		&mockStoredReqFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		&config.Configuration{MaxRequestSize: int64(len(reqBody))},
+		newTestMetrics(),
+		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+		map[string]string{},
+		false,
+		[]byte{},
+		openrtb_ext.BuildBidderMap(),
+		nil,
+		nil,
+		hardcodedResponseIPValidator{response: true},
+	}
+
+	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
+	recorder := httptest.NewRecorder()
+
+	deps.Auction(recorder, req, nil)
+
+	if recorder.Code != http.StatusOK {
+		t.Errorf("Endpoint should return a 200")
+	}
+	warnings := deps.ex.(*warningsCheckExchange).auctionRequest.Warnings
+	if !assert.Len(t, warnings, 1, "One warning should be returned from exchange") {
+		t.FailNow()
+	}
+	actualWarning := warnings[0].(*errortypes.Warning)
+	expectedMessage := "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)"
+	assert.Equal(t, expectedMessage, actualWarning.Message, "Warning message is incorrect")
+
+	assert.Equal(t, errortypes.InvalidPrivacyConsentWarningCode, actualWarning.WarningCode, "Warning code is incorrect")
+}
+
+// warningsCheckExchange is a well-behaved exchange which stores all incoming warnings.
+type warningsCheckExchange struct {
+	auctionRequest exchange.AuctionRequest
+}
+
+func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) {
+	e.auctionRequest = r
+	return nil, nil
+}
+
 // nobidExchange is a well-behaved exchange which always bids "no bid".
 type nobidExchange struct {
 	gotRequest *openrtb.BidRequest
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index dbf8a022..e4eb7b95 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -20,6 +20,7 @@ import (
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/metrics"
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -109,9 +110,13 @@ func TestSingleBidder(t *testing.T) {
 		}
 
 		// Make sure the returned values are what we expect
-		if len(errs) != 0 {
+		if len(errortypes.FatalOnly(errs)) != 0 {
 			t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs))
 		}
+
+		if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 {
+			t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs))
+		}
 		if len(seatBid.bids) != len(mockBidderResponse.Bids) {
 			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids))
 		}
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index f615d412..3eaa625f 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -6,6 +6,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"github.com/prebid/prebid-server/errortypes"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
@@ -141,7 +142,7 @@ func TestCharacterEscape(t *testing.T) {
 	adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, 1)
 	adapterExtra["appnexus"] = &seatResponseExtra{
 		ResponseTimeMillis: 5,
-		Errors:             []openrtb_ext.ExtBidderError{{Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\""}},
+		Errors:             []openrtb_ext.ExtBidderMessage{{Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\""}},
 	}
 
 	var errList []error
@@ -180,59 +181,68 @@ func TestDebugBehaviour(t *testing.T) {
 	}
 
 	type aTest struct {
-		desc      string
-		in        inTest
-		out       outTest
-		debugData debugData
+		desc             string
+		in               inTest
+		out              outTest
+		debugData        debugData
+		generateWarnings bool
 	}
 	testCases := []aTest{
 		{
-			desc:      "test flag equals zero, ext debug flag false, no debug info expected",
-			in:        inTest{test: 0, debug: false},
-			out:       outTest{debugInfoIncluded: false},
-			debugData: debugData{true, true},
+			desc:             "test flag equals zero, ext debug flag false, no debug info expected",
+			in:               inTest{test: 0, debug: false},
+			out:              outTest{debugInfoIncluded: false},
+			debugData:        debugData{true, true},
+			generateWarnings: false,
 		},
 		{
-			desc:      "test flag equals zero, ext debug flag true, debug info expected",
-			in:        inTest{test: 0, debug: true},
-			out:       outTest{debugInfoIncluded: true},
-			debugData: debugData{true, true},
+			desc:             "test flag equals zero, ext debug flag true, debug info expected",
+			in:               inTest{test: 0, debug: true},
+			out:              outTest{debugInfoIncluded: true},
+			debugData:        debugData{true, true},
+			generateWarnings: false,
 		},
 		{
-			desc:      "test flag equals 1, ext debug flag false, debug info expected",
-			in:        inTest{test: 1, debug: false},
-			out:       outTest{debugInfoIncluded: true},
-			debugData: debugData{true, true},
+			desc:             "test flag equals 1, ext debug flag false, debug info expected",
+			in:               inTest{test: 1, debug: false},
+			out:              outTest{debugInfoIncluded: true},
+			debugData:        debugData{true, true},
+			generateWarnings: false,
 		},
 		{
-			desc:      "test flag equals 1, ext debug flag true, debug info expected",
-			in:        inTest{test: 1, debug: true},
-			out:       outTest{debugInfoIncluded: true},
-			debugData: debugData{true, true},
+			desc:             "test flag equals 1, ext debug flag true, debug info expected",
+			in:               inTest{test: 1, debug: true},
+			out:              outTest{debugInfoIncluded: true},
+			debugData:        debugData{true, true},
+			generateWarnings: false,
 		},
 		{
-			desc:      "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected",
-			in:        inTest{test: 2, debug: false},
-			out:       outTest{debugInfoIncluded: false},
-			debugData: debugData{true, true},
+			desc:             "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected",
+			in:               inTest{test: 2, debug: false},
+			out:              outTest{debugInfoIncluded: false},
+			debugData:        debugData{true, true},
+			generateWarnings: false,
 		},
 		{
-			desc:      "test flag not equal to 0 nor 1, ext debug flag true, debug info expected",
-			in:        inTest{test: -1, debug: true},
-			out:       outTest{debugInfoIncluded: true},
-			debugData: debugData{true, true},
+			desc:             "test flag not equal to 0 nor 1, ext debug flag true, debug info expected",
+			in:               inTest{test: -1, debug: true},
+			out:              outTest{debugInfoIncluded: true},
+			debugData:        debugData{true, true},
+			generateWarnings: true,
 		},
 		{
-			desc:      "test account level debug disabled",
-			in:        inTest{test: -1, debug: true},
-			out:       outTest{debugInfoIncluded: false},
-			debugData: debugData{true, false},
+			desc:             "test account level debug disabled",
+			in:               inTest{test: -1, debug: true},
+			out:              outTest{debugInfoIncluded: false},
+			debugData:        debugData{true, false},
+			generateWarnings: true,
 		},
 		{
-			desc:      "test bidder level debug disabled",
-			in:        inTest{test: -1, debug: true},
-			out:       outTest{debugInfoIncluded: false},
-			debugData: debugData{false, true},
+			desc:             "test bidder level debug disabled",
+			in:               inTest{test: -1, debug: true},
+			out:              outTest{debugInfoIncluded: false},
+			debugData:        debugData{false, true},
+			generateWarnings: true,
 		},
 	}
 
@@ -243,9 +253,9 @@ func TestDebugBehaviour(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(noBidServer))
 	defer server.Close()
 
-	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
-	if error != nil {
-		t.Errorf("Failed to create a category Fetcher: %v", error)
+	categoriesFetcher, err := newCategoryFetcher("./test/category-mapping")
+	if err != nil {
+		t.Errorf("Failed to create a category Fetcher: %v", err)
 	}
 
 	bidRequest := &openrtb.BidRequest{
@@ -305,6 +315,13 @@ func TestDebugBehaviour(t *testing.T) {
 			UserSyncs:  &emptyUsersync{},
 			StartTime:  time.Now(),
 		}
+		if test.generateWarnings {
+			var errL []error
+			errL = append(errL, &errortypes.Warning{
+				Message:     fmt.Sprintf("CCPA consent test warning."),
+				WarningCode: errortypes.InvalidPrivacyConsentWarningCode})
+			auctionRequest.Warnings = errL
+		}
 
 		// Run test
 		outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil)
@@ -339,6 +356,35 @@ func TestDebugBehaviour(t *testing.T) {
 		} else {
 			assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty")
 		}
+
+		if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed {
+			assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning")
+			assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present")
+			assert.Equal(t, "debug turned off for account", actualExt.Warnings["general"][0].Message, "account debug disabled message should be present")
+		}
+
+		if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed {
+			if test.generateWarnings {
+				assert.Len(t, actualExt.Warnings, 2, "warnings should have one warning")
+			} else {
+				assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning")
+			}
+			assert.NotNil(t, actualExt.Warnings["appnexus"], "bidder warning should be present")
+			assert.Equal(t, "debug turned off for bidder", actualExt.Warnings["appnexus"][0].Message, "account debug disabled message should be present")
+		}
+
+		if test.generateWarnings {
+			assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present")
+			CCPAWarningPresent := false
+			for _, warn := range actualExt.Warnings["general"] {
+				if warn.Code == errortypes.InvalidPrivacyConsentWarningCode {
+					CCPAWarningPresent = true
+					break
+				}
+			}
+			assert.True(t, CCPAWarningPresent, "CCPA Warning should be present")
+		}
+
 	}
 }
 
@@ -393,7 +439,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.categoriesFetcher = categoriesFetcher
 
-	debugLog := DebugLog{}
+	debugLog := DebugLog{Enabled: true}
 
 	for _, testCase := range testCases {
 		bidRequest := &openrtb.BidRequest{
@@ -762,7 +808,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 	adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{
 		bidderName: {
 			ResponseTimeMillis: 5,
-			Errors: []openrtb_ext.ExtBidderError{
+			Errors: []openrtb_ext.ExtBidderMessage{
 				{
 					Code:    999,
 					Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\"",
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index f089d705..1945aee1 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -637,7 +637,10 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) {
 			description: "Invalid Consent",
 			reqExt:      json.RawMessage(`{"prebid":{"nosale":["*"]}}`),
 			reqRegsExt:  json.RawMessage(`{"us_privacy":"malformed"}`),
-			expectError: &errortypes.InvalidPrivacyConsent{"request.regs.ext.us_privacy must contain 4 characters"},
+			expectError: &errortypes.Warning{
+				Message:     "request.regs.ext.us_privacy must contain 4 characters",
+				WarningCode: errortypes.InvalidPrivacyConsentWarningCode,
+			},
 		},
 		{
 			description: "Invalid No Sale Bidders",