Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | 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. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
14c81d9d2b259d343cdb1ebd6d9f316b |
| User & Date: | drh 2020-02-26 14:28:31.191 |
Context
|
2020-02-26
| ||
| 15:23 | Implement the --keep-merge-files option for the merge and update commands. Merge-conflict files are omitted without this option. The merge conflict files continue to exist for the stash command. check-in: d20ead10c3 user: drh tags: trunk | |
| 14:28 | 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. check-in: 14c81d9d2b user: drh tags: trunk | |
|
2020-02-25
| ||
| 20:04 | Added --admin-user flag to the import command. check-in: 6c0dfc8cc5 user: drh tags: trunk | |
Changes
Changes to src/cgi.c.
| ︙ | ︙ | |||
207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
}
/*
** Append text to the header of an HTTP reply
*/
void cgi_append_header(const char *zLine){
blob_append(&extraHeader, zLine, -1);
}
/*
** Set a cookie by queuing up the appropriate HTTP header output. If
** !g.isHTTP, this is a no-op.
**
** Zero lifetime implies a session cookie.
| > > > > > > | 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
}
/*
** Append text to the header of an HTTP reply
*/
void cgi_append_header(const char *zLine){
blob_append(&extraHeader, zLine, -1);
}
void cgi_printf_header(const char *zLine, ...){
va_list ap;
va_start(ap, zLine);
blob_vappendf(&extraHeader, zLine, ap);
va_end(ap);
}
/*
** Set a cookie by queuing up the appropriate HTTP header output. If
** !g.isHTTP, this is a no-op.
**
** Zero lifetime implies a session cookie.
|
| ︙ | ︙ |
Changes to src/configure.c.
| ︙ | ︙ | |||
105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
{ "timeline-plaintext", CONFIGSET_SKIN },
{ "timeline-truncate-at-blank", CONFIGSET_SKIN },
{ "timeline-tslink-info", CONFIGSET_SKIN },
{ "timeline-utc", CONFIGSET_SKIN },
{ "adunit", CONFIGSET_SKIN },
{ "adunit-omit-if-admin", CONFIGSET_SKIN },
{ "adunit-omit-if-user", CONFIGSET_SKIN },
{ "sitemap-docidx", CONFIGSET_SKIN },
{ "sitemap-download", CONFIGSET_SKIN },
{ "sitemap-license", CONFIGSET_SKIN },
{ "sitemap-contact", CONFIGSET_SKIN },
#ifdef FOSSIL_ENABLE_TH1_DOCS
{ "th1-docs", CONFIGSET_TH1 },
| > | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
{ "timeline-plaintext", CONFIGSET_SKIN },
{ "timeline-truncate-at-blank", CONFIGSET_SKIN },
{ "timeline-tslink-info", CONFIGSET_SKIN },
{ "timeline-utc", CONFIGSET_SKIN },
{ "adunit", CONFIGSET_SKIN },
{ "adunit-omit-if-admin", CONFIGSET_SKIN },
{ "adunit-omit-if-user", CONFIGSET_SKIN },
{ "default-csp", CONFIGSET_SKIN },
{ "sitemap-docidx", CONFIGSET_SKIN },
{ "sitemap-download", CONFIGSET_SKIN },
{ "sitemap-license", CONFIGSET_SKIN },
{ "sitemap-contact", CONFIGSET_SKIN },
#ifdef FOSSIL_ENABLE_TH1_DOCS
{ "th1-docs", CONFIGSET_TH1 },
|
| ︙ | ︙ |
Changes to src/db.c.
| ︙ | ︙ | |||
3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 | */ /* ** SETTING: th1-uri-regexp width=40 block-text ** Specify which URI's are allowed in HTTP requests from ** TH1 scripts. If empty, no HTTP requests are allowed ** whatsoever. */ /* ** SETTING: uv-sync boolean default=off ** If true, automatically send unversioned files as part ** of a "fossil clone" or "fossil sync" command. The ** default is false, in which case the -u option is ** needed to clone or sync unversioned files. */ | > > > > > > > > > > > > > > > > > > > > | 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 | */ /* ** SETTING: th1-uri-regexp width=40 block-text ** Specify which URI's are allowed in HTTP requests from ** TH1 scripts. If empty, no HTTP requests are allowed ** whatsoever. */ /* ** SETTING: default-csp width=40 block-text ** ** The text of the Content Security Policy that is included ** in the Content-Security-Policy: header field of the HTTP ** reply and in the default HTML <head> section that is added when the ** skin header does not specify a <head> section. The text "$nonce" ** is replaced by the random nonce that is created for each web page. ** ** If this setting is an empty string or is omitted, then ** the following default Content Security Policy is used: ** ** default-src 'self' data:; ** script-src 'self' 'nonce-$nonce'; ** style-src 'self' 'unsafe-inline'; ** ** The default CSP is recommended. The main reason to change ** this setting would be to add CDNs from which it is safe to ** load additional content. */ /* ** SETTING: uv-sync boolean default=off ** If true, automatically send unversioned files as part ** of a "fossil clone" or "fossil sync" command. The ** default is false, in which case the -u option is ** needed to clone or sync unversioned files. */ |
| ︙ | ︙ |
Changes to src/doc.c.
| ︙ | ︙ | |||
794 795 796 797 798 799 800 801 802 803 804 805 806 807 |
Th_Render(blob_str(pBody));
}
if( !raw ){
style_footer();
}
#endif
}else{
cgi_set_content_type(zMime);
cgi_set_content(pBody);
}
}
/*
| > | 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 |
Th_Render(blob_str(pBody));
}
if( !raw ){
style_footer();
}
#endif
}else{
fossil_free(style_csp(1));
cgi_set_content_type(zMime);
cgi_set_content(pBody);
}
}
/*
|
| ︙ | ︙ |
Changes to src/info.c.
| ︙ | ︙ | |||
1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 |
" WHERE blob.rid=%d"
" AND attachment.src=blob.uuid", rid);
}
if( zFName ) zMime = mimetype_from_name(zFName);
if( zMime==0 ) zMime = "application/x-fossil-artifact";
}
content_get(rid, &content);
cgi_set_content_type(zMime);
cgi_set_content(&content);
}
/*
** Render a hex dump of a file.
*/
| > | 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 |
" WHERE blob.rid=%d"
" AND attachment.src=blob.uuid", rid);
}
if( zFName ) zMime = mimetype_from_name(zFName);
if( zMime==0 ) zMime = "application/x-fossil-artifact";
}
content_get(rid, &content);
fossil_free(style_csp(1));
cgi_set_content_type(zMime);
cgi_set_content(&content);
}
/*
** Render a hex dump of a file.
*/
|
| ︙ | ︙ |
Changes to src/security_audit.c.
| ︙ | ︙ | |||
31 32 33 34 35 36 37 |
if( strchr(zCap, zTest[0]) ) return 1;
zTest++;
}
return 0;
}
/*
| | | < | | | < < < < < < < | < | | | | | | | | | | | | | | | | | | | | | | | | > | < < < | 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
if( strchr(zCap, zTest[0]) ) return 1;
zTest++;
}
return 0;
}
/*
** Parse the content-security-policy
** into separate fields, and return a pointer to a null-terminated
** array of pointers to strings, one entry for each field. Or return
** a NULL pointer if no CSP could be located in the header.
**
** Memory to hold the returned array and of the strings is obtained from
** a single memory allocation, which the caller should free to avoid a
** memory leak.
*/
static char **parse_content_security_policy(void){
char **azCSP = 0;
int nCSP = 0;
char *zAll;
char *zCopy;
int nAll = 0;
int jj;
int nSemi;
zAll = style_csp(0);
nAll = (int)strlen(zAll);
for(jj=nSemi=0; jj<nAll; jj++){ if( zAll[jj]==';' ) nSemi++; }
azCSP = fossil_malloc( nAll+1+(nSemi+2)*sizeof(char*) );
zCopy = (char*)&azCSP[nSemi+2];
memcpy(zCopy,zAll,nAll);
zCopy[nAll] = 0;
while( fossil_isspace(zCopy[0]) || zCopy[0]==';' ){ zCopy++; }
azCSP[0] = zCopy;
nCSP = 1;
for(jj=0; zCopy[jj]; jj++){
if( zCopy[jj]==';' ){
int k;
for(k=jj-1; k>0 && fossil_isspace(zCopy[k]); k--){ zCopy[k] = 0; }
zCopy[jj] = 0;
while( jj+1<nAll
&& (fossil_isspace(zCopy[jj+1]) || zCopy[jj+1]==';')
){
jj++;
}
assert( nCSP<nSemi+1 );
azCSP[nCSP++] = zCopy+jj;
}
}
assert( nCSP<=nSemi+2 );
azCSP[nCSP] = 0;
fossil_free(zAll);
return azCSP;
}
/*
** WEBPAGE: secaudit0
**
** Run a security audit of the current Fossil setup, looking
** for configuration problems that might allow unauthorized
|
| ︙ | ︙ |
Changes to src/style.c.
| ︙ | ︙ | |||
471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
if( zNonce[0]==0 ){
unsigned char zSeed[24];
sqlite3_randomness(24, zSeed);
encode16(zSeed,(unsigned char*)zNonce,24);
}
return zNonce;
}
/*
** Default HTML page header text through <body>. If the repository-specific
** header template lacks a <body> tag, then all of the following is
** prepended.
*/
static char zDfltHeader[] =
| > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 |
if( zNonce[0]==0 ){
unsigned char zSeed[24];
sqlite3_randomness(24, zSeed);
encode16(zSeed,(unsigned char*)zNonce,24);
}
return zNonce;
}
/*
** Return the default Content Security Policy (CSP) string.
** If the toHeader argument is true, then also add the
** CSP to the HTTP reply header.
**
** The CSP comes from the "default-csp" setting if it exists and
** is non-empty. If that setting is an empty string, then the following
** default is used instead:
**
** default-src 'self' data:;
** script-src 'self' 'nonce-$nonce';
** style-src 'self' 'unsafe-inline';
**
** The text '$nonce' is replaced by style_nonce() if and whereever it
** occurs in the input string.
**
** The string returned is obtained from fossil_malloc() and
** should be released by the caller.
*/
char *style_csp(int toHeader){
static const char zBackupCSP[] =
"default-src 'self' data:; "
"script-src 'self' 'nonce-$nonce'; "
"style-src 'self' 'unsafe-inline'";
const char *zFormat = db_get("default-csp","");
Blob csp;
char *zNonce;
char *zCsp;
if( zFormat[0]==0 ){
zFormat = zBackupCSP;
}
blob_init(&csp, 0, 0);
while( zFormat[0] && (zNonce = strstr(zFormat,"$nonce"))!=0 ){
blob_append(&csp, zFormat, (int)(zNonce - zFormat));
blob_append(&csp, style_nonce(), -1);
zFormat = zNonce + 6;
}
blob_append(&csp, zFormat, -1);
zCsp = blob_str(&csp);
if( toHeader ){
cgi_printf_header("Content-Security-Policy: %s\r\n", zCsp);
}
return zCsp;
}
/*
** Default HTML page header text through <body>. If the repository-specific
** header template lacks a <body> tag, then all of the following is
** prepended.
*/
static char zDfltHeader[] =
|
| ︙ | ︙ | |||
496 497 498 499 500 501 502 503 504 505 506 507 |
;
/*
** Initialize all the default TH1 variables
*/
static void style_init_th1_vars(const char *zTitle){
const char *zNonce = style_nonce();
/*
** Do not overwrite the TH1 variable "default_csp" if it exists, as this
** allows it to be properly overridden via the TH1 setup script (i.e. it
** is evaluated before the header is rendered).
*/
| > > > < < < < | | 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 |
;
/*
** Initialize all the default TH1 variables
*/
static void style_init_th1_vars(const char *zTitle){
const char *zNonce = style_nonce();
char *zDfltCsp;
zDfltCsp = style_csp(1);
/*
** Do not overwrite the TH1 variable "default_csp" if it exists, as this
** allows it to be properly overridden via the TH1 setup script (i.e. it
** is evaluated before the header is rendered).
*/
Th_MaybeStore("default_csp", zDfltCsp);
fossil_free(zDfltCsp);
Th_Store("nonce", zNonce);
Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
Th_Store("project_description", db_get("project-description",""));
if( zTitle ) Th_Store("title", zTitle);
Th_Store("baseurl", g.zBaseURL);
Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
Th_Store("home", g.zTop);
|
| ︙ | ︙ |
Added test/csp1.html.
> > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!DOCTYPE html>
<html>
<head>
<title>Title: Content Security Policy Test</title>
</head>
<body>
<h1>Content Security Policy Test</h1>
<p>If the content-security-policy is ineffective, a pop-up dialog
box will appears. If there is no dialog box, then CSP is working
correctly.</p>
<script>alert('Content Security Policy is ineffective');</script>
<img src='/' onerror='alert("CSP is ineffective")'>
<p>As a double-check, open the Developer Console in your web-browser
and verify that two CSP violations were detected and blocked.</p>
</body>
|