Fossil SCM

Put the Content-Security-Policy in the HTTP reply header in addition to the HTML header. That way, the CSP is enforced even for raw HTML pages or if the skin provides an HTML header that omits the CSP. Add a new "default-csp" setting included with the skin that allows an administrator to change the CSP to allow for CDNs and such.

drh 2020-02-26 14:28 trunk
Commit 14c81d9d2b259d343cdb1ebd6d9f316b8abe4f0aed2add1213cd4b68b264d53f
+6
--- src/cgi.c
+++ src/cgi.c
@@ -209,10 +209,16 @@
209209
/*
210210
** Append text to the header of an HTTP reply
211211
*/
212212
void cgi_append_header(const char *zLine){
213213
blob_append(&extraHeader, zLine, -1);
214
+}
215
+void cgi_printf_header(const char *zLine, ...){
216
+ va_list ap;
217
+ va_start(ap, zLine);
218
+ blob_vappendf(&extraHeader, zLine, ap);
219
+ va_end(ap);
214220
}
215221
216222
/*
217223
** Set a cookie by queuing up the appropriate HTTP header output. If
218224
** !g.isHTTP, this is a no-op.
219225
--- src/cgi.c
+++ src/cgi.c
@@ -209,10 +209,16 @@
209 /*
210 ** Append text to the header of an HTTP reply
211 */
212 void cgi_append_header(const char *zLine){
213 blob_append(&extraHeader, zLine, -1);
 
 
 
 
 
 
214 }
215
216 /*
217 ** Set a cookie by queuing up the appropriate HTTP header output. If
218 ** !g.isHTTP, this is a no-op.
219
--- src/cgi.c
+++ src/cgi.c
@@ -209,10 +209,16 @@
209 /*
210 ** Append text to the header of an HTTP reply
211 */
212 void cgi_append_header(const char *zLine){
213 blob_append(&extraHeader, zLine, -1);
214 }
215 void cgi_printf_header(const char *zLine, ...){
216 va_list ap;
217 va_start(ap, zLine);
218 blob_vappendf(&extraHeader, zLine, ap);
219 va_end(ap);
220 }
221
222 /*
223 ** Set a cookie by queuing up the appropriate HTTP header output. If
224 ** !g.isHTTP, this is a no-op.
225
--- src/configure.c
+++ src/configure.c
@@ -107,10 +107,11 @@
107107
{ "timeline-tslink-info", CONFIGSET_SKIN },
108108
{ "timeline-utc", CONFIGSET_SKIN },
109109
{ "adunit", CONFIGSET_SKIN },
110110
{ "adunit-omit-if-admin", CONFIGSET_SKIN },
111111
{ "adunit-omit-if-user", CONFIGSET_SKIN },
112
+ { "default-csp", CONFIGSET_SKIN },
112113
{ "sitemap-docidx", CONFIGSET_SKIN },
113114
{ "sitemap-download", CONFIGSET_SKIN },
114115
{ "sitemap-license", CONFIGSET_SKIN },
115116
{ "sitemap-contact", CONFIGSET_SKIN },
116117
117118
--- src/configure.c
+++ src/configure.c
@@ -107,10 +107,11 @@
107 { "timeline-tslink-info", CONFIGSET_SKIN },
108 { "timeline-utc", CONFIGSET_SKIN },
109 { "adunit", CONFIGSET_SKIN },
110 { "adunit-omit-if-admin", CONFIGSET_SKIN },
111 { "adunit-omit-if-user", CONFIGSET_SKIN },
 
112 { "sitemap-docidx", CONFIGSET_SKIN },
113 { "sitemap-download", CONFIGSET_SKIN },
114 { "sitemap-license", CONFIGSET_SKIN },
115 { "sitemap-contact", CONFIGSET_SKIN },
116
117
--- src/configure.c
+++ src/configure.c
@@ -107,10 +107,11 @@
107 { "timeline-tslink-info", CONFIGSET_SKIN },
108 { "timeline-utc", CONFIGSET_SKIN },
109 { "adunit", CONFIGSET_SKIN },
110 { "adunit-omit-if-admin", CONFIGSET_SKIN },
111 { "adunit-omit-if-user", CONFIGSET_SKIN },
112 { "default-csp", CONFIGSET_SKIN },
113 { "sitemap-docidx", CONFIGSET_SKIN },
114 { "sitemap-download", CONFIGSET_SKIN },
115 { "sitemap-license", CONFIGSET_SKIN },
116 { "sitemap-contact", CONFIGSET_SKIN },
117
118
+20
--- src/db.c
+++ src/db.c
@@ -3620,10 +3620,30 @@
36203620
** SETTING: th1-uri-regexp width=40 block-text
36213621
** Specify which URI's are allowed in HTTP requests from
36223622
** TH1 scripts. If empty, no HTTP requests are allowed
36233623
** whatsoever.
36243624
*/
3625
+/*
3626
+** SETTING: default-csp width=40 block-text
3627
+**
3628
+** The text of the Content Security Policy that is included
3629
+** in the Content-Security-Policy: header field of the HTTP
3630
+** reply and in the default HTML <head> section that is added when the
3631
+** skin header does not specify a <head> section. The text "$nonce"
3632
+** is replaced by the random nonce that is created for each web page.
3633
+**
3634
+** If this setting is an empty string or is omitted, then
3635
+** the following default Content Security Policy is used:
3636
+**
3637
+** default-src 'self' data:;
3638
+** script-src 'self' 'nonce-$nonce';
3639
+** style-src 'self' 'unsafe-inline';
3640
+**
3641
+** The default CSP is recommended. The main reason to change
3642
+** this setting would be to add CDNs from which it is safe to
3643
+** load additional content.
3644
+*/
36253645
/*
36263646
** SETTING: uv-sync boolean default=off
36273647
** If true, automatically send unversioned files as part
36283648
** of a "fossil clone" or "fossil sync" command. The
36293649
** default is false, in which case the -u option is
36303650
--- src/db.c
+++ src/db.c
@@ -3620,10 +3620,30 @@
3620 ** SETTING: th1-uri-regexp width=40 block-text
3621 ** Specify which URI's are allowed in HTTP requests from
3622 ** TH1 scripts. If empty, no HTTP requests are allowed
3623 ** whatsoever.
3624 */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3625 /*
3626 ** SETTING: uv-sync boolean default=off
3627 ** If true, automatically send unversioned files as part
3628 ** of a "fossil clone" or "fossil sync" command. The
3629 ** default is false, in which case the -u option is
3630
--- src/db.c
+++ src/db.c
@@ -3620,10 +3620,30 @@
3620 ** SETTING: th1-uri-regexp width=40 block-text
3621 ** Specify which URI's are allowed in HTTP requests from
3622 ** TH1 scripts. If empty, no HTTP requests are allowed
3623 ** whatsoever.
3624 */
3625 /*
3626 ** SETTING: default-csp width=40 block-text
3627 **
3628 ** The text of the Content Security Policy that is included
3629 ** in the Content-Security-Policy: header field of the HTTP
3630 ** reply and in the default HTML <head> section that is added when the
3631 ** skin header does not specify a <head> section. The text "$nonce"
3632 ** is replaced by the random nonce that is created for each web page.
3633 **
3634 ** If this setting is an empty string or is omitted, then
3635 ** the following default Content Security Policy is used:
3636 **
3637 ** default-src 'self' data:;
3638 ** script-src 'self' 'nonce-$nonce';
3639 ** style-src 'self' 'unsafe-inline';
3640 **
3641 ** The default CSP is recommended. The main reason to change
3642 ** this setting would be to add CDNs from which it is safe to
3643 ** load additional content.
3644 */
3645 /*
3646 ** SETTING: uv-sync boolean default=off
3647 ** If true, automatically send unversioned files as part
3648 ** of a "fossil clone" or "fossil sync" command. The
3649 ** default is false, in which case the -u option is
3650
+1
--- src/doc.c
+++ src/doc.c
@@ -796,10 +796,11 @@
796796
if( !raw ){
797797
style_footer();
798798
}
799799
#endif
800800
}else{
801
+ fossil_free(style_csp(1));
801802
cgi_set_content_type(zMime);
802803
cgi_set_content(pBody);
803804
}
804805
}
805806
806807
--- src/doc.c
+++ src/doc.c
@@ -796,10 +796,11 @@
796 if( !raw ){
797 style_footer();
798 }
799 #endif
800 }else{
 
801 cgi_set_content_type(zMime);
802 cgi_set_content(pBody);
803 }
804 }
805
806
--- src/doc.c
+++ src/doc.c
@@ -796,10 +796,11 @@
796 if( !raw ){
797 style_footer();
798 }
799 #endif
800 }else{
801 fossil_free(style_csp(1));
802 cgi_set_content_type(zMime);
803 cgi_set_content(pBody);
804 }
805 }
806
807
+1
--- src/info.c
+++ src/info.c
@@ -1882,10 +1882,11 @@
18821882
}
18831883
if( zFName ) zMime = mimetype_from_name(zFName);
18841884
if( zMime==0 ) zMime = "application/x-fossil-artifact";
18851885
}
18861886
content_get(rid, &content);
1887
+ fossil_free(style_csp(1));
18871888
cgi_set_content_type(zMime);
18881889
cgi_set_content(&content);
18891890
}
18901891
18911892
/*
18921893
--- src/info.c
+++ src/info.c
@@ -1882,10 +1882,11 @@
1882 }
1883 if( zFName ) zMime = mimetype_from_name(zFName);
1884 if( zMime==0 ) zMime = "application/x-fossil-artifact";
1885 }
1886 content_get(rid, &content);
 
1887 cgi_set_content_type(zMime);
1888 cgi_set_content(&content);
1889 }
1890
1891 /*
1892
--- src/info.c
+++ src/info.c
@@ -1882,10 +1882,11 @@
1882 }
1883 if( zFName ) zMime = mimetype_from_name(zFName);
1884 if( zMime==0 ) zMime = "application/x-fossil-artifact";
1885 }
1886 content_get(rid, &content);
1887 fossil_free(style_csp(1));
1888 cgi_set_content_type(zMime);
1889 cgi_set_content(&content);
1890 }
1891
1892 /*
1893
--- src/security_audit.c
+++ src/security_audit.c
@@ -33,12 +33,12 @@
3333
}
3434
return 0;
3535
}
3636
3737
/*
38
-** Extract the content-security-policy from the reply header. Parse it
39
-** up into separate fields, and return a pointer to a null-terminated
38
+** Parse the content-security-policy
39
+** into separate fields, and return a pointer to a null-terminated
4040
** array of pointers to strings, one entry for each field. Or return
4141
** a NULL pointer if no CSP could be located in the header.
4242
**
4343
** Memory to hold the returned array and of the strings is obtained from
4444
** a single memory allocation, which the caller should free to avoid a
@@ -45,55 +45,44 @@
4545
** memory leak.
4646
*/
4747
static char **parse_content_security_policy(void){
4848
char **azCSP = 0;
4949
int nCSP = 0;
50
- const char *zHeader;
51
- const char *zAll;
50
+ char *zAll;
5251
char *zCopy;
5352
int nAll = 0;
54
- int ii, jj, n, nx = 0;
53
+ int jj;
5554
int nSemi;
5655
57
- zHeader = cgi_header();
58
- if( zHeader==0 ) return 0;
59
- for(ii=0; zHeader[ii]; ii+=n){
60
- n = html_token_length(zHeader+ii);
61
- if( zHeader[ii]=='<'
62
- && fossil_strnicmp(html_attribute(zHeader+ii,"http-equiv",&nx),
63
- "Content-Security-Policy",23)==0
64
- && nx==23
65
- && (zAll = html_attribute(zHeader+ii,"content",&nAll))!=0
66
- ){
67
- for(jj=nSemi=0; jj<nAll; jj++){ if( zAll[jj]==';' ) nSemi++; }
68
- azCSP = fossil_malloc( nAll+1 + (nSemi+2)*sizeof(char*) );
69
- zCopy = (char*)&azCSP[nSemi+2];
70
- memcpy(zCopy,zAll,nAll);
71
- zCopy[nAll] = 0;
72
- while( fossil_isspace(zCopy[0]) || zCopy[0]==';' ){ zCopy++; }
73
- azCSP[0] = zCopy;
74
- nCSP = 1;
75
- for(jj=0; zCopy[jj]; jj++){
76
- if( zCopy[jj]==';' ){
77
- int k;
78
- for(k=jj-1; k>0 && fossil_isspace(zCopy[k]); k--){ zCopy[k] = 0; }
79
- zCopy[jj] = 0;
80
- while( jj+1<nAll
81
- && (fossil_isspace(zCopy[jj+1]) || zCopy[jj+1]==';')
82
- ){
83
- jj++;
84
- }
85
- assert( nCSP<nSemi+1 );
86
- azCSP[nCSP++] = zCopy+jj;
87
- }
88
- }
89
- assert( nCSP<=nSemi+2 );
90
- azCSP[nCSP] = 0;
91
- return azCSP;
92
- }
93
- }
94
- return 0;
56
+ zAll = style_csp(0);
57
+ nAll = (int)strlen(zAll);
58
+ for(jj=nSemi=0; jj<nAll; jj++){ if( zAll[jj]==';' ) nSemi++; }
59
+ azCSP = fossil_malloc( nAll+1+(nSemi+2)*sizeof(char*) );
60
+ zCopy = (char*)&azCSP[nSemi+2];
61
+ memcpy(zCopy,zAll,nAll);
62
+ zCopy[nAll] = 0;
63
+ while( fossil_isspace(zCopy[0]) || zCopy[0]==';' ){ zCopy++; }
64
+ azCSP[0] = zCopy;
65
+ nCSP = 1;
66
+ for(jj=0; zCopy[jj]; jj++){
67
+ if( zCopy[jj]==';' ){
68
+ int k;
69
+ for(k=jj-1; k>0 && fossil_isspace(zCopy[k]); k--){ zCopy[k] = 0; }
70
+ zCopy[jj] = 0;
71
+ while( jj+1<nAll
72
+ && (fossil_isspace(zCopy[jj+1]) || zCopy[jj+1]==';')
73
+ ){
74
+ jj++;
75
+ }
76
+ assert( nCSP<nSemi+1 );
77
+ azCSP[nCSP++] = zCopy+jj;
78
+ }
79
+ }
80
+ assert( nCSP<=nSemi+2 );
81
+ azCSP[nCSP] = 0;
82
+ fossil_free(zAll);
83
+ return azCSP;
9584
}
9685
9786
/*
9887
** WEBPAGE: secaudit0
9988
**
10089
--- src/security_audit.c
+++ src/security_audit.c
@@ -33,12 +33,12 @@
33 }
34 return 0;
35 }
36
37 /*
38 ** Extract the content-security-policy from the reply header. Parse it
39 ** up into separate fields, and return a pointer to a null-terminated
40 ** array of pointers to strings, one entry for each field. Or return
41 ** a NULL pointer if no CSP could be located in the header.
42 **
43 ** Memory to hold the returned array and of the strings is obtained from
44 ** a single memory allocation, which the caller should free to avoid a
@@ -45,55 +45,44 @@
45 ** memory leak.
46 */
47 static char **parse_content_security_policy(void){
48 char **azCSP = 0;
49 int nCSP = 0;
50 const char *zHeader;
51 const char *zAll;
52 char *zCopy;
53 int nAll = 0;
54 int ii, jj, n, nx = 0;
55 int nSemi;
56
57 zHeader = cgi_header();
58 if( zHeader==0 ) return 0;
59 for(ii=0; zHeader[ii]; ii+=n){
60 n = html_token_length(zHeader+ii);
61 if( zHeader[ii]=='<'
62 && fossil_strnicmp(html_attribute(zHeader+ii,"http-equiv",&nx),
63 "Content-Security-Policy",23)==0
64 && nx==23
65 && (zAll = html_attribute(zHeader+ii,"content",&nAll))!=0
66 ){
67 for(jj=nSemi=0; jj<nAll; jj++){ if( zAll[jj]==';' ) nSemi++; }
68 azCSP = fossil_malloc( nAll+1 + (nSemi+2)*sizeof(char*) );
69 zCopy = (char*)&azCSP[nSemi+2];
70 memcpy(zCopy,zAll,nAll);
71 zCopy[nAll] = 0;
72 while( fossil_isspace(zCopy[0]) || zCopy[0]==';' ){ zCopy++; }
73 azCSP[0] = zCopy;
74 nCSP = 1;
75 for(jj=0; zCopy[jj]; jj++){
76 if( zCopy[jj]==';' ){
77 int k;
78 for(k=jj-1; k>0 && fossil_isspace(zCopy[k]); k--){ zCopy[k] = 0; }
79 zCopy[jj] = 0;
80 while( jj+1<nAll
81 && (fossil_isspace(zCopy[jj+1]) || zCopy[jj+1]==';')
82 ){
83 jj++;
84 }
85 assert( nCSP<nSemi+1 );
86 azCSP[nCSP++] = zCopy+jj;
87 }
88 }
89 assert( nCSP<=nSemi+2 );
90 azCSP[nCSP] = 0;
91 return azCSP;
92 }
93 }
94 return 0;
95 }
96
97 /*
98 ** WEBPAGE: secaudit0
99 **
100
--- src/security_audit.c
+++ src/security_audit.c
@@ -33,12 +33,12 @@
33 }
34 return 0;
35 }
36
37 /*
38 ** Parse the content-security-policy
39 ** into separate fields, and return a pointer to a null-terminated
40 ** array of pointers to strings, one entry for each field. Or return
41 ** a NULL pointer if no CSP could be located in the header.
42 **
43 ** Memory to hold the returned array and of the strings is obtained from
44 ** a single memory allocation, which the caller should free to avoid a
@@ -45,55 +45,44 @@
45 ** memory leak.
46 */
47 static char **parse_content_security_policy(void){
48 char **azCSP = 0;
49 int nCSP = 0;
50 char *zAll;
 
51 char *zCopy;
52 int nAll = 0;
53 int jj;
54 int nSemi;
55
56 zAll = style_csp(0);
57 nAll = (int)strlen(zAll);
58 for(jj=nSemi=0; jj<nAll; jj++){ if( zAll[jj]==';' ) nSemi++; }
59 azCSP = fossil_malloc( nAll+1+(nSemi+2)*sizeof(char*) );
60 zCopy = (char*)&azCSP[nSemi+2];
61 memcpy(zCopy,zAll,nAll);
62 zCopy[nAll] = 0;
63 while( fossil_isspace(zCopy[0]) || zCopy[0]==';' ){ zCopy++; }
64 azCSP[0] = zCopy;
65 nCSP = 1;
66 for(jj=0; zCopy[jj]; jj++){
67 if( zCopy[jj]==';' ){
68 int k;
69 for(k=jj-1; k>0 && fossil_isspace(zCopy[k]); k--){ zCopy[k] = 0; }
70 zCopy[jj] = 0;
71 while( jj+1<nAll
72 && (fossil_isspace(zCopy[jj+1]) || zCopy[jj+1]==';')
73 ){
74 jj++;
75 }
76 assert( nCSP<nSemi+1 );
77 azCSP[nCSP++] = zCopy+jj;
78 }
79 }
80 assert( nCSP<=nSemi+2 );
81 azCSP[nCSP] = 0;
82 fossil_free(zAll);
83 return azCSP;
 
 
 
 
 
 
 
 
 
 
84 }
85
86 /*
87 ** WEBPAGE: secaudit0
88 **
89
+49 -5
--- src/style.c
+++ src/style.c
@@ -473,10 +473,55 @@
473473
sqlite3_randomness(24, zSeed);
474474
encode16(zSeed,(unsigned char*)zNonce,24);
475475
}
476476
return zNonce;
477477
}
478
+
479
+/*
480
+** Return the default Content Security Policy (CSP) string.
481
+** If the toHeader argument is true, then also add the
482
+** CSP to the HTTP reply header.
483
+**
484
+** The CSP comes from the "default-csp" setting if it exists and
485
+** is non-empty. If that setting is an empty string, then the following
486
+** default is used instead:
487
+**
488
+** default-src 'self' data:;
489
+** script-src 'self' 'nonce-$nonce';
490
+** style-src 'self' 'unsafe-inline';
491
+**
492
+** The text '$nonce' is replaced by style_nonce() if and whereever it
493
+** occurs in the input string.
494
+**
495
+** The string returned is obtained from fossil_malloc() and
496
+** should be released by the caller.
497
+*/
498
+char *style_csp(int toHeader){
499
+ static const char zBackupCSP[] =
500
+ "default-src 'self' data:; "
501
+ "script-src 'self' 'nonce-$nonce'; "
502
+ "style-src 'self' 'unsafe-inline'";
503
+ const char *zFormat = db_get("default-csp","");
504
+ Blob csp;
505
+ char *zNonce;
506
+ char *zCsp;
507
+ if( zFormat[0]==0 ){
508
+ zFormat = zBackupCSP;
509
+ }
510
+ blob_init(&csp, 0, 0);
511
+ while( zFormat[0] && (zNonce = strstr(zFormat,"$nonce"))!=0 ){
512
+ blob_append(&csp, zFormat, (int)(zNonce - zFormat));
513
+ blob_append(&csp, style_nonce(), -1);
514
+ zFormat = zNonce + 6;
515
+ }
516
+ blob_append(&csp, zFormat, -1);
517
+ zCsp = blob_str(&csp);
518
+ if( toHeader ){
519
+ cgi_printf_header("Content-Security-Policy: %s\r\n", zCsp);
520
+ }
521
+ return zCsp;
522
+}
478523
479524
/*
480525
** Default HTML page header text through <body>. If the repository-specific
481526
** header template lacks a <body> tag, then all of the following is
482527
** prepended.
@@ -498,21 +543,20 @@
498543
/*
499544
** Initialize all the default TH1 variables
500545
*/
501546
static void style_init_th1_vars(const char *zTitle){
502547
const char *zNonce = style_nonce();
548
+ char *zDfltCsp;
549
+
550
+ zDfltCsp = style_csp(1);
503551
/*
504552
** Do not overwrite the TH1 variable "default_csp" if it exists, as this
505553
** allows it to be properly overridden via the TH1 setup script (i.e. it
506554
** is evaluated before the header is rendered).
507555
*/
508
- char *zDfltCsp = sqlite3_mprintf("default-src 'self' data: ; "
509
- "script-src 'self' 'nonce-%s' ; "
510
- "style-src 'self' 'unsafe-inline'",
511
- zNonce);
512556
Th_MaybeStore("default_csp", zDfltCsp);
513
- sqlite3_free(zDfltCsp);
557
+ fossil_free(zDfltCsp);
514558
Th_Store("nonce", zNonce);
515559
Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
516560
Th_Store("project_description", db_get("project-description",""));
517561
if( zTitle ) Th_Store("title", zTitle);
518562
Th_Store("baseurl", g.zBaseURL);
519563
520564
ADDED test/csp1.html
--- src/style.c
+++ src/style.c
@@ -473,10 +473,55 @@
473 sqlite3_randomness(24, zSeed);
474 encode16(zSeed,(unsigned char*)zNonce,24);
475 }
476 return zNonce;
477 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
479 /*
480 ** Default HTML page header text through <body>. If the repository-specific
481 ** header template lacks a <body> tag, then all of the following is
482 ** prepended.
@@ -498,21 +543,20 @@
498 /*
499 ** Initialize all the default TH1 variables
500 */
501 static void style_init_th1_vars(const char *zTitle){
502 const char *zNonce = style_nonce();
 
 
 
503 /*
504 ** Do not overwrite the TH1 variable "default_csp" if it exists, as this
505 ** allows it to be properly overridden via the TH1 setup script (i.e. it
506 ** is evaluated before the header is rendered).
507 */
508 char *zDfltCsp = sqlite3_mprintf("default-src 'self' data: ; "
509 "script-src 'self' 'nonce-%s' ; "
510 "style-src 'self' 'unsafe-inline'",
511 zNonce);
512 Th_MaybeStore("default_csp", zDfltCsp);
513 sqlite3_free(zDfltCsp);
514 Th_Store("nonce", zNonce);
515 Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
516 Th_Store("project_description", db_get("project-description",""));
517 if( zTitle ) Th_Store("title", zTitle);
518 Th_Store("baseurl", g.zBaseURL);
519
520 DDED test/csp1.html
--- src/style.c
+++ src/style.c
@@ -473,10 +473,55 @@
473 sqlite3_randomness(24, zSeed);
474 encode16(zSeed,(unsigned char*)zNonce,24);
475 }
476 return zNonce;
477 }
478
479 /*
480 ** Return the default Content Security Policy (CSP) string.
481 ** If the toHeader argument is true, then also add the
482 ** CSP to the HTTP reply header.
483 **
484 ** The CSP comes from the "default-csp" setting if it exists and
485 ** is non-empty. If that setting is an empty string, then the following
486 ** default is used instead:
487 **
488 ** default-src 'self' data:;
489 ** script-src 'self' 'nonce-$nonce';
490 ** style-src 'self' 'unsafe-inline';
491 **
492 ** The text '$nonce' is replaced by style_nonce() if and whereever it
493 ** occurs in the input string.
494 **
495 ** The string returned is obtained from fossil_malloc() and
496 ** should be released by the caller.
497 */
498 char *style_csp(int toHeader){
499 static const char zBackupCSP[] =
500 "default-src 'self' data:; "
501 "script-src 'self' 'nonce-$nonce'; "
502 "style-src 'self' 'unsafe-inline'";
503 const char *zFormat = db_get("default-csp","");
504 Blob csp;
505 char *zNonce;
506 char *zCsp;
507 if( zFormat[0]==0 ){
508 zFormat = zBackupCSP;
509 }
510 blob_init(&csp, 0, 0);
511 while( zFormat[0] && (zNonce = strstr(zFormat,"$nonce"))!=0 ){
512 blob_append(&csp, zFormat, (int)(zNonce - zFormat));
513 blob_append(&csp, style_nonce(), -1);
514 zFormat = zNonce + 6;
515 }
516 blob_append(&csp, zFormat, -1);
517 zCsp = blob_str(&csp);
518 if( toHeader ){
519 cgi_printf_header("Content-Security-Policy: %s\r\n", zCsp);
520 }
521 return zCsp;
522 }
523
524 /*
525 ** Default HTML page header text through <body>. If the repository-specific
526 ** header template lacks a <body> tag, then all of the following is
527 ** prepended.
@@ -498,21 +543,20 @@
543 /*
544 ** Initialize all the default TH1 variables
545 */
546 static void style_init_th1_vars(const char *zTitle){
547 const char *zNonce = style_nonce();
548 char *zDfltCsp;
549
550 zDfltCsp = style_csp(1);
551 /*
552 ** Do not overwrite the TH1 variable "default_csp" if it exists, as this
553 ** allows it to be properly overridden via the TH1 setup script (i.e. it
554 ** is evaluated before the header is rendered).
555 */
 
 
 
 
556 Th_MaybeStore("default_csp", zDfltCsp);
557 fossil_free(zDfltCsp);
558 Th_Store("nonce", zNonce);
559 Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
560 Th_Store("project_description", db_get("project-description",""));
561 if( zTitle ) Th_Store("title", zTitle);
562 Th_Store("baseurl", g.zBaseURL);
563
564 DDED test/csp1.html
--- a/test/csp1.html
+++ b/test/csp1.html
@@ -0,0 +1,18 @@
1
+<!DOCTYPE html>
2
+<html>
3
+<head>
4
+<title>Title: Content Security Policy Test</title>
5
+</head>
6
+<body>
7
+<h1>Content Security Policy Test</h1>
8
+
9
+<p>If the content-security-policy is ineffective, a pop-up dialog
10
+box will appears. If there is no dialog box, then CSP is working
11
+correctly.</p>
12
+
13
+<script>alert('Content Security Policy is ineffective');</script>
14
+<img src='/' onerror='alert("CSP is ineffective")'>
15
+
16
+<p>As a double-check, open the Developer Console in your web-browser
17
+and verify that two CSP violations were detected and blocked.</p>
18
+</body>
--- a/test/csp1.html
+++ b/test/csp1.html
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/test/csp1.html
+++ b/test/csp1.html
@@ -0,0 +1,18 @@
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>Title: Content Security Policy Test</title>
5 </head>
6 <body>
7 <h1>Content Security Policy Test</h1>
8
9 <p>If the content-security-policy is ineffective, a pop-up dialog
10 box will appears. If there is no dialog box, then CSP is working
11 correctly.</p>
12
13 <script>alert('Content Security Policy is ineffective');</script>
14 <img src='/' onerror='alert("CSP is ineffective")'>
15
16 <p>As a double-check, open the Developer Console in your web-browser
17 and verify that two CSP violations were detected and blocked.</p>
18 </body>

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button