Skip to content

Add .well-known/security.txt file #2062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .well-known/security.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Policy: https://www.djangoproject.com/security/
Contact: https://www.djangoproject.com/security/
Expires: 2026-12-31T00:00:00.000Z
Encryption: https://keys.openpgp.org/vks/v1/by-fingerprint/AF3516D27D0621171E0CCE25FCB84B8D1D17F80B
Copy link
Member

Choose a reason for hiding this comment

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

For the sake of a future maintainer, it would be nice to document where one could find a link to the most up-to-date key. I was going to suggest a comment above this line, but I'm not sure if that's the best place for such documentation 🤔 (happy to hear other suggestions)

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this file might be a good place for putting that comment, since this file is targetted more towards people who want to report a security issue. It should ideally be there somewhere in a readme for contributors and maintainers. Maybe SECURITY.md (which I am planning on adding)?

Preferred-Languages: en

# Hello security researcher!
# We appreciate your help in keeping Django secure.
# Please report security issues that concern the Django website (djangoproject.com) to ops@djangoproject.com
Copy link
Member

Choose a reason for hiding this comment

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

This seems to contradict the Policy and Contact directives above which state that one should email security@djangoproject.com.

Copy link
Member

Choose a reason for hiding this comment

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

I think we could change this with the website WG email: website-wg@djangoproject.com to keep the security issues for Django itself separated for the fellows, but the security page need a new line somewhere to mention that security issues related to the website are supposed to be send to this email instead.

# This helps us make sure your report is seen by the right people.
39 changes: 39 additions & 0 deletions djangoproject/tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from datetime import datetime, timedelta
from http import HTTPStatus
from io import StringIO

from django.conf import settings
from django.core.management import call_command
from django.test import TestCase
from django.urls import NoReverseMatch, get_resolver
Expand Down Expand Up @@ -164,3 +166,40 @@ def test_single_h1_per_page(self):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "<h1", count=1)


class SecurityTxtFileTests(TestCase):
"""
Tests for the security.txt file.
"""

def test_security_txt_not_expired(self):
"""
The security.txt file should not be expired.
"""
FILE_PATH = settings.BASE_DIR / ".well-known" / "security.txt"
with open(FILE_PATH) as f:
content = f.read()
Comment on lines +181 to +182
Copy link
Member

Choose a reason for hiding this comment

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

Since we have a Path object we can directly use:

Suggested change
with open(FILE_PATH) as f:
content = f.read()
content = FILE_PATH.read_text()

(and remove a level of indentation for the rest of the method)

# Read the line that starts with "Expires:", and parse the date.
for line in content.splitlines():
if line.startswith("Expires:"):
expires = line.strip("Expires: ")
break
else:
self.fail("No Expires line found in security.txt")
Comment on lines +183 to +189
Copy link
Member

Choose a reason for hiding this comment

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

Perfect use of a for ... else 👏🏻 However I think a regex perfect for this kind of situation:

Suggested change
# Read the line that starts with "Expires:", and parse the date.
for line in content.splitlines():
if line.startswith("Expires:"):
expires = line.strip("Expires: ")
break
else:
self.fail("No Expires line found in security.txt")
match = re.search("^Expires: (.*)$", content, flags=re.MULTILINE)
if match is None:
self.fail("No Expires line found in security.txt")
else:
expires = match[1]

Copy link
Contributor

Choose a reason for hiding this comment

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

TIL for ... else. 🤯 🐍 ❤️
Thank you. 🫶


expires_date = datetime.strptime(
expires,
"%Y-%m-%dT%H:%M:%S.%fZ",
).date()
Comment on lines +191 to +194
Copy link
Member

Choose a reason for hiding this comment

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

You can use datetime.fromisoformat():

Suggested change
expires_date = datetime.strptime(
expires,
"%Y-%m-%dT%H:%M:%S.%fZ",
).date()
expires_date = datetime.fromisoformat(expires).date()
expires_date = datetime.strptime(
expires,
"%Y-%m-%dT%H:%M:%S.%fZ",
).date()

# We should ideally be two weeks early with updating - active over reactive
cutoff = (datetime.now() - timedelta(days=15)).date()
self.assertGreater(
expires_date,
cutoff,
"The security.txt file is close to expiring. \
Please update the 'Expires' line in to confirm the contents are \
still accurate: {}".format(
FILE_PATH
),
Comment on lines +200 to +204
Copy link
Member Author

Choose a reason for hiding this comment

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

'E501 line too long' is set up in this project (one that I generally disable) – any suggestions on how to clean this up?

Copy link
Member

Choose a reason for hiding this comment

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

You could build the string outside of the assert method or shorten the message.

Is a test needed for this though? How about a calendar reminder? It seems like one day this will start failing the test suite on every PR/commit.

)