Skip to content

Commit e3e430e

Browse files
authored
Merge pull request #10360 from NuGet/dev-vhus-strictCSP
[VHUS] add strict CSP in report-only mode
2 parents dc8163d + e26d552 commit e3e430e

17 files changed

Lines changed: 185 additions & 123 deletions

File tree

Lines changed: 80 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,83 @@
11
.page-upload {
2-
#browse-for-package-button {
3-
margin: 0;
2+
#browse-for-package-button {
3+
margin: 0;
4+
}
5+
6+
#input-select-file {
7+
display: none;
8+
}
9+
10+
.readme-tabs {
11+
margin-top: 0.5em;
12+
13+
.nav-tabs > li {
14+
padding-right: 2px;
415
}
5-
6-
.readme-tabs {
7-
margin-top: 0.5em;
8-
9-
.nav-tabs > li {
10-
padding-right: 2px;
11-
}
12-
13-
.nav-tabs > li.active > a {
14-
border-bottom-color: var(--brandStrokeCompoundRest);
15-
border-bottom-width: 2px;
16-
-webkit-text-stroke-width: calc(0.5 * 0.04ex);
17-
text-shadow: calc(0.5 * -0.03ex) 0 0 currentColor,
18-
calc(0.5 * 0.03ex) 0 0 currentColor;
19-
margin-bottom: -1px;
20-
background-color: transparent;
21-
22-
&:hover {
23-
border-bottom-color: var(--brandStrokeCompoundHover);
24-
border-bottom-width: 2px;
25-
}
26-
27-
&:active {
28-
border-bottom-color: var(--brandStrokeCompoundPressed);
29-
border-bottom-width: 2px;
30-
}
31-
32-
&:focus {
33-
border-radius: 2px;
34-
outline: 3px solid var(--neutralStrokeFocus2Rest);
35-
outline-offset: 1px;
36-
}
37-
}
38-
39-
.nav-tabs > li.active > a.body-warning-tab > {
40-
background-color: var(--statusWarningBackground1Rest);
41-
border-bottom-color: var(--statusWarningStroke1Rest);
42-
border-bottom-width: 2px;
43-
}
44-
45-
.nav-tabs > li > a {
46-
border-left: 0px;
47-
border-right: 0px;
48-
border-top: 0px;
49-
border-bottom-width: 2px;
50-
font-size: 14px;
51-
font-family: @font-family-base;
52-
color: var(--neutralForeground1Rest);
53-
background-color: transparent;
54-
55-
&:hover {
56-
border-bottom-color: var(--neutralStroke1Hover);
57-
border-bottom-width: 2px;
58-
background-color: var(--neutralBackgroundSubtleHover);
59-
border-radius: 2px 2px 0px 0px;
60-
}
61-
62-
&:active {
63-
border-bottom-color: var(--neutralStroke1Pressed);
64-
border-bottom-width: 2px;
65-
background-color: var(--neutralBackgroundSubtlePressed);
66-
}
67-
68-
&:focus {
69-
border-radius: 2px;
70-
outline: 3px solid var(--neutralStrokeFocus2Rest);
71-
outline-offset: 1px;
72-
}
73-
74-
.ms-Icon {
75-
position: relative;
76-
top: 2px;
77-
}
78-
}
16+
17+
.nav-tabs > li.active > a {
18+
border-bottom-color: var(--brandStrokeCompoundRest);
19+
border-bottom-width: 2px;
20+
-webkit-text-stroke-width: calc(0.5 * 0.04ex);
21+
text-shadow: calc(0.5 * -0.03ex) 0 0 currentColor, calc(0.5 * 0.03ex) 0 0 currentColor;
22+
margin-bottom: -1px;
23+
background-color: transparent;
24+
25+
&:hover {
26+
border-bottom-color: var(--brandStrokeCompoundHover);
27+
border-bottom-width: 2px;
7928
}
80-
}
29+
30+
&:active {
31+
border-bottom-color: var(--brandStrokeCompoundPressed);
32+
border-bottom-width: 2px;
33+
}
34+
35+
&:focus {
36+
border-radius: 2px;
37+
outline: 3px solid var(--neutralStrokeFocus2Rest);
38+
outline-offset: 1px;
39+
}
40+
}
41+
42+
.nav-tabs > li.active > a.body-warning-tab > {
43+
background-color: var(--statusWarningBackground1Rest);
44+
border-bottom-color: var(--statusWarningStroke1Rest);
45+
border-bottom-width: 2px;
46+
}
47+
48+
.nav-tabs > li > a {
49+
border-left: 0px;
50+
border-right: 0px;
51+
border-top: 0px;
52+
border-bottom-width: 2px;
53+
font-size: 14px;
54+
font-family: @font-family-base;
55+
color: var(--neutralForeground1Rest);
56+
background-color: transparent;
57+
58+
&:hover {
59+
border-bottom-color: var(--neutralStroke1Hover);
60+
border-bottom-width: 2px;
61+
background-color: var(--neutralBackgroundSubtleHover);
62+
border-radius: 2px 2px 0px 0px;
63+
}
64+
65+
&:active {
66+
border-bottom-color: var(--neutralStroke1Pressed);
67+
border-bottom-width: 2px;
68+
background-color: var(--neutralBackgroundSubtlePressed);
69+
}
70+
71+
&:focus {
72+
border-radius: 2px;
73+
outline: 3px solid var(--neutralStrokeFocus2Rest);
74+
outline-offset: 1px;
75+
}
76+
77+
.ms-Icon {
78+
position: relative;
79+
top: 2px;
80+
}
81+
}
82+
}
83+
}

src/NuGetGallery/App_Code/ViewHelpers.cshtml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,9 @@
332332
@* In Azure, we want the Instance ID. The Machine Name is total garbage *@
333333
}
334334

335-
@helper CookieComplianceScript()
335+
@helper CookieComplianceScript(IHtmlString cspNonce)
336336
{
337-
<script src="https://wcpstatic.microsoft.com/mscc/lib/v2/wcp-consent.js"></script>
337+
<script src="https://wcpstatic.microsoft.com/mscc/lib/v2/wcp-consent.js" nonce="@cspNonce"></script>
338338
}
339339

340340
@helper AnalyticsScript(dynamic viewBag)
@@ -481,7 +481,7 @@ var hlp = new AccordionHelper(name, formModelStatePrefix, expanded, page);
481481
var viewSections = GetSections(viewPage.ViewBag);
482482
if (viewSections != null)
483483
{
484-
<script type="text/javascript">
484+
<script type="text/javascript" nonce="@viewPage.Html.GetCSPNonce()">
485485
var sections = @viewPage.Html.Raw(Json.Encode(viewSections));
486486
for (var i in sections) {
487487
var configureSection = function (section) {

src/NuGetGallery/App_Start/OwinStartup.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -7,6 +7,7 @@
77
using System.Diagnostics;
88
using System.Linq;
99
using System.Net;
10+
using System.Security.Cryptography;
1011
using System.Threading;
1112
using System.Threading.Tasks;
1213
using System.Web.Hosting;
@@ -148,6 +149,34 @@ public static void Configuration(IAppBuilder app)
148149
auther.Startup(config, app).Wait();
149150
}
150151

152+
// enables Content-Security-Policy with nonce and strict dynamic
153+
app.Use(async (context, next) =>
154+
{
155+
var fontAndIconSrc = string.Concat("https://res-1.cdn.office.net/files/fabric-cdn-prod_20221201.001/assets/fonts/", ' ',
156+
"https://res-1.cdn.office.net/files/fabric-cdn-prod_20221201.001/assets/icons/");
157+
158+
var scriptFileHashes =
159+
string.Concat(
160+
"'sha512-gU7kztaQEl7SHJyraPfZLQCNnrKdaQi5ndOyt4L4UPL/FHDd/uB9Je6KDARIqwnNNE27hnqoWLBq+Kpe4iHfeQ=='", ' ',
161+
"'sha512-DXYctkkhmMYJ4vYp4Dm6jprD4ZareZ7ud/d9mGCKif/Dt3FnN95SjogHvwKvxXHoMAAkZX6EO6ePwpDIR1Y8jw=='", ' ',
162+
"'sha512-mz4SrGyk+dtPY9MNYOMkD81gp8ajViZ4S0VDuM/Zqg40cg9xgIBYSiL5fN79Htbz4f2+uR9lrDO6mgcjM+NAXA=='", ' ',
163+
"'sha512-pnt8OPBTOklRd4/iSW7msOiCVO4uvffF17Egr3c7AaN0h3qFnSu7L6UmdZJUCednMhhruTLRq7X9WbyAWNBegw=='", ' '
164+
);
165+
166+
using var rng = RandomNumberGenerator.Create();
167+
var nonceBytes = new byte[32];
168+
rng.GetBytes(nonceBytes);
169+
var nonce = Convert.ToBase64String(nonceBytes);
170+
var reportUri = ConfigurationManager.AppSettings["CspReportUri"];
171+
172+
var contentSecurityPolicyReportHeader = new[] { string.Format("default-src 'self' 'nonce-{0}' 'strict-dynamic' https:; script-src 'nonce-{0}' {3} 'strict-dynamic' https:; font-src 'self' {1} 'nonce-{0}'; base-uri 'none'; form-action 'self' 'nonce-{0}'; style-src 'self' 'nonce-{0}'; report-uri {2}; object-src 'none'; frame-ancestors 'none'; ", nonce, fontAndIconSrc, reportUri,scriptFileHashes) };
173+
174+
context.Set("cspNonce", nonce);
175+
context.Response.Headers.Add("Content-Security-Policy-Report-Only", contentSecurityPolicyReportHeader);
176+
context.Response.Headers.Add("X-XSS-Protection", ["1; mode=block"]);
177+
await next();
178+
});
179+
151180
var featureFlags = DependencyResolver.Current.GetService<IFeatureFlagCacheService>();
152181
if (featureFlags != null)
153182
{
@@ -210,4 +239,4 @@ private static void StartFeatureFlags(IFeatureFlagCacheService featureFlags)
210239
HostingEnvironment.QueueBackgroundWorkItem(featureFlags.RunAsync);
211240
}
212241
}
213-
}
242+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Web;
8+
using System.Web.Mvc;
9+
10+
namespace NuGetGallery.Helpers
11+
{
12+
public static class CSPHelper
13+
{
14+
public static IHtmlString GetCSPNonce(this HtmlHelper helper)
15+
{
16+
var owinContext = helper.ViewContext.HttpContext.GetOwinContext();
17+
var cspNonce = string.Empty;
18+
var owinContextCSPNonce = owinContext.Get<string>("cspNonce");
19+
20+
if (!string.IsNullOrEmpty(owinContextCSPNonce))
21+
{
22+
cspNonce = owinContextCSPNonce;
23+
}
24+
return new HtmlString(cspNonce);
25+
}
26+
}
27+
}

src/NuGetGallery/NuGetGallery.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@
241241
<Compile Include="Filters\AdminActionAttribute.cs" />
242242
<Compile Include="Filters\RequiresUserAgentAttribute.cs" />
243243
<Compile Include="Helpers\AdminHelper.cs" />
244+
<Compile Include="Helpers\CSPHelper.cs" />
244245
<Compile Include="Helpers\InvalidZipEntry.cs" />
245246
<Compile Include="Helpers\SearchResponseHelper.cs" />
246247
<Compile Include="Helpers\ViewModelExtensions\DeleteAccountListPackageItemViewModelFactory.cs" />

src/NuGetGallery/Views/Packages/DisplayPackage.cshtml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@using NuGet.Services.Validation
1+
@using NuGet.Services.Validation
22
@using NuGet.Services.Licenses
33

44
@model DisplayPackageViewModel
@@ -1473,11 +1473,11 @@
14731473
}
14741474
}
14751475

1476-
<style type="text/css">
1476+
<style type="text/css" nonce="@Html.GetCSPNonce()">
14771477
@Html.Raw(packageManagersCss)
14781478
</style>
14791479

1480-
<script type="text/javascript">
1480+
<script type="text/javascript" nonce="@Html.GetCSPNonce()">
14811481
var packageId = @Html.ToJson(Model.Id);
14821482
var packageVersion = @Html.ToJson(Model.Version);
14831483
var packageManagers = @Html.ToJson(
@@ -1494,7 +1494,7 @@
14941494
{
14951495
@ViewHelpers.IncludeSyntaxHighlightScript();
14961496

1497-
<script>
1497+
<script nonce="@Html.GetCSPNonce()">
14981498
document.addEventListener('DOMContentLoaded', (event) => {
14991499
document.querySelectorAll('pre code').forEach((el) => {
15001500
hljs.highlightElement(el);
@@ -1503,5 +1503,5 @@
15031503
</script>
15041504
}
15051505

1506-
@Scripts.Render("~/Scripts/gallery/page-display-package.min.js")
1507-
}
1506+
@Scripts.RenderFormat("<script src=\"{0}\" nonce='" + @Html.GetCSPNonce().ToString() + "'></script>", "~/Scripts/gallery/page-display-package.min.js");
1507+
}

src/NuGetGallery/Views/Packages/Manage.cshtml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
</section>
6464

6565
@section BottomScripts {
66-
<script type="text/javascript">
66+
<script type="text/javascript" nonce="@Html.GetCSPNonce()">
6767
// Set up owners section
6868
var packageId = "@Model.Id";
6969
var isUserAnAdmin = "@Model.IsCurrentUserAnAdmin";
@@ -149,4 +149,4 @@
149149
@Scripts.Render("~/Scripts/gallery/page-delete-package.min.js")
150150
@Scripts.Render("~/Scripts/gallery/page-edit-readme.min.js")
151151
@Scripts.Render("~/Scripts/gallery/syntaxhighlight.min.js")
152-
}
152+
}

src/NuGetGallery/Views/Packages/UploadPackage.cshtml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
<input type="text" class="form-control input-brand" id="file-select-feedback" value="@placeholder" aria-label="Upload Your Package" readonly />
5757
<label for="input-select-file" id="PackageFileLabel" class="input-group-btn">
5858
<span id="browse-for-package-button" class="btn btn-brand" tabindex="0" aria-label="Browse for package" role="button">
59-
Browse&hellip;<input type="file" name="UploadFile" accept="@acceptExtensions" aria-labelledby="PackageFileLabel" id="input-select-file" style="display:none;" />
59+
Browse&hellip;<input type="file" name="UploadFile" accept="@acceptExtensions" aria-labelledby="PackageFileLabel" id="input-select-file" />
6060
</span>
6161
</label>
6262
</div>
@@ -89,10 +89,10 @@
8989
@section BottomScripts
9090
{
9191
@* Right now this is the only page that uses this script. If we increase our usage of it, we should put it in our bundles *@
92-
@Scripts.Render("~/Scripts/gallery/page-edit-readme.min.js")
93-
@Scripts.Render("~/Scripts/gallery/async-file-upload.min.js")
94-
@Scripts.Render("~/Scripts/gallery/syntaxhighlight.min.js")
95-
<script type="text/javascript">
92+
@Scripts.RenderFormat("<script src=\"{0}\" nonce='" + @Html.GetCSPNonce().ToString() + "'></script>", "~/Scripts/gallery/page-edit-readme.min.js");
93+
@Scripts.RenderFormat("<script src=\"{0}\" nonce='" + @Html.GetCSPNonce().ToString() + "'></script>", "~/Scripts/gallery/async-file-upload.min.js");
94+
@Scripts.RenderFormat("<script src=\"{0}\" nonce='" + @Html.GetCSPNonce().ToString() + "'></script>", "~/Scripts/gallery/syntaxhighlight.min.js");
95+
<script type="text/javascript" nonce="@Html.GetCSPNonce()">
9696
var InProgressPackage = @Html.Raw(Json.Encode(Model.InProgressUpload));
9797
window.nuget.configureExpanderHeading("upload-package-form");
9898
$(function () {

src/NuGetGallery/Views/Pages/Downloads.cshtml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
</div>
6363
</section>
6464

65-
<script type="text/html" id="tool-list-template">
65+
<script type="text/html" id="tool-list-template" nonce="@Html.GetCSPNonce()">
6666
<h2 class="ms-fontSize-xxl" data-bind="text: displayName"></h2>
6767
<!-- ko if: note -->
6868
<p data-bind="html: note"></p>
@@ -80,7 +80,7 @@
8080
</script>
8181

8282
@section BottomScripts {
83-
<script type="text/javascript">
83+
<script type="text/javascript" nonce="@Html.GetCSPNonce()">
8484
$(function () {
8585
$.ajax({
8686
url: '//dist.nuget.org/index.json',
@@ -101,5 +101,5 @@
101101
});
102102
});
103103
</script>
104-
@Scripts.Render("~/Scripts/gallery/page-downloads.min.js")
104+
@Scripts.RenderFormat("<script src=\"{0}\" nonce='" + @Html.GetCSPNonce().ToString() + "'></script>", "~/Scripts/gallery/page-downloads.min.js");
105105
}

src/NuGetGallery/Views/Pages/Home.cshtml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ else if (AskUserToEnable2FA)
124124
</section>
125125

126126
@section BottomScripts {
127-
<script type="text/javascript">
127+
<script type="text/javascript" nonce="@Html.GetCSPNonce()">
128128
var feedbackUrl = "@Url.Send2FAFeedback()";
129129
var changeMultiFactorAuthenticationUrl = "@Url.ChangeMultiFactorAuthentication()";
130130
</script>
131-
@Scripts.Render("~/Scripts/gallery/page-home.min.js")
132-
}
131+
@Scripts.RenderFormat("<script src=\"{0}\" nonce='" + @Html.GetCSPNonce().ToString() + "'></script>", "~/Scripts/gallery/page-home.min.js");
132+
}

0 commit comments

Comments
 (0)