Git Product home page Git Product logo

securethenews's Introduction

By contributing to this project, you agree to abide by our Code of Conduct.

Secure the News

This project has been retired. Please see our blog post for more information: https://freedom.press/news/five-years-of-secure-the-news/

Prerequisites

The installation instructions below assume you have the following software on your machine:

Note that you can either install docker-compose natively or via pipenv. If you choose the pipenv route you'll need to run these commands:

# The very first time run
$ pipenv install
# Each subsequent time run this to enter a virtualenv shell
$ pipenv shell

Local Development Instructions

Then run the following, which will be need to be run once at clone of this repo:

make dev-init

To start up the development environment you can use the normal docker-compose flow:

docker-compose up

If this command completes successfully, your development site will be available at: http://localhost:8000

To import the example data, run:

docker-compose exec django ./manage.py createdevdata

This will also create an admin user for the web interface at http://localhost:8000/admin/ (username: test, password: test).

If you want to start the TLS scan for all the news sites in your development environment, run:

make dev-scan

For a full list of all helper commands in the Makefile, run make help. And, of course, you can obtain a shell directly into any of the containers using docker-compose syntax. Just keep in mind the default shell is ash under alpine. Here is an example of entering the django container:

$ docker-compose exec django ash

Debugging

If you want to use the PDB program for debugging, it is possible. First, add this line to an area of the code you wish to debug:

import ipdb; ipdb.set_trace()

Second, attach to the running Django container. This must be done in a shell, and it is within this attached shell that you will be able to interact with the debugger. The command to attach is docker attach <ID_OF_DJANGO_CONTAINER>, and on UNIX-type systems, you can look up the ID and attach to the container with this single command:

docker attach $(docker-compose ps -q django)

Once you have done this, you can load the page that will run the code with your import ipdb and the debugger will activate in the shell you attached. To detach from the shell without stopping the container press Control+P followed by Control+Q.

Getting Started with the Production Environment

The environment is fairly similar to development with the exception that your code will not auto-reload and be reflected in the container. So this is not a great environment to development under but it reflects a production-like environment run under gunicorn and behind a reverse-proxy nginx server.

The flow is this:

# Build the prod container (everytime you make a code-change need to re-do this)
make build-prod-container

# Run the prod environment
docker-compose -f prod-docker-compose.yaml up

# Run production apptests
make app-tests-prod

# Run ops tests
make ops-tests

# Teardown prod
docker-compose -f prod-docker-compose.yaml down

Dependency Management

Adding new requirements

New requirements should be added to *requirements.in files, for use with pip-compile. There are two Python requirements files:

  • requirements.in production application dependencies
  • dev-requirements.in local testing and CI requirements

Add the desired dependency to the appropriate .in file, then run:

$ make compile-pip-dependencies

All requirements files will be regenerated based on compatible versions. Multiple .in files can be merged into a single .txt file, for use with pip. The Makefile target handles the merging of multiple files.

This process is the same if a requirement needs to be changed (i.e. its version number restricted) or removed. Make the appropriate change in the correct requirements.in file, then run the above command to compile the dependencies.

Upgrading existing requirements

There are separate commands to upgrade a package without changing the requirements.in files. The command

$ make upgrade-pip PACKAGE=package-name

will update the package named package-name to the latest version allowed by the constraints in requirements.in and compile a new dev-requirements.txt and requirements.txt based on that version.

If the package appears only in dev-requirements.in, then you must use this command:

$ make upgrade-pip-dev PACKAGE=package-name

which will update the package named package-name to the latest version allowed by the constraints in requirements.in and compile a new dev-requirements.txt.

Development Fixtures

The createdevdata management commands loads Site and Scan data from the fixtures in sites/fixtures/dev.json. If you change the schema of sites.Site or sites.Scan, you will need to update these fixtures, or future invocations of createdevdata will fail.

The general process for updating the development fixtures is:

  1. Migrate your database to the last migration where the fixtures were updated.

  2. Load the fixtures.

  3. Run the migrations that you've added.

  4. Export the migrated fixtures:

    $ python3 manage.py dumpdata sites.{Site,Scan} > sites/fixtures/dev.json
    

The test suite includes a smoke test for createdevdata, so you can easily verify that the command is working without disrupting your own development environment.

API

If everything is working correctly, you should be able to find an API endpoint at localhost:8000/api (it will redirect to the current API version).

The API is read-only and can be used to obtain site metadata and the latest scan for a given site (e.g., /api/v1/sites will return a directory, and /api/v1/sites/bbc.co.uk will return details about the BBC). Various filters and sort options are supported; click the "filters" dialog in the UI to explore them.

To get all scans for a given site, you can use a path like /api/v1/sites/bbc.co.uk/scans. This URL can also be found in the all_scans field for a given site result.

If you run a public site, note that read access to the API is available to any origin via CORS.

The API is implemented using the Django REST framework; documentation for it can be found here:

http://www.django-rest-framework.org/

securethenews's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

securethenews's Issues

BlogPosts published on the same day seem to be out of order

Steps to Reproduce

  1. Publish a test blog post.
  2. Publish a second test blog post, with the same Publication date as the first post.
  3. View the blog post index (/blog)

Expected behavior

The second blog post, which was published after the first, should appear above the first blog post in the list of blog posts, since blogs typically display posts sorted from first to last posted.

Observed Behavior

The first blog post appears above the second blog post.

Additional Notes

This happens because we only store and sort by BlogPost date, which is a DateField, not a DateTimeField. There are at least two ways to fix this:

  1. Change the BlogPost.date DateField to a BlogPost.published DateTimeField. This might make entering the publication date a little more annoying in the admin, I'm not sure if the admin widget has ergonomic defaults.
  2. Additionally sort by another factor in addition to BlogPost.date, e.g. leverage the auto-incrementing primary key.

Fork friendly

Hi team,

I want to fork securethenews into something related to securethe.au - i've got a list of 500~ Australian webpages I'd like to monitor.

  1. I've been having problems with the inability to delete the existing records and bulk add new records...

  2. I cannot find a way to manually/force an update of the site I've added and so I'm also unable to force the frontend to be updated (I think I'm missing something)

  3. Please update the guide with the production deployment steps, as I'm currently running in Vagrant and I want to host in Digital ocean VPS, but cannot find the steps to host and maintain on the long term

Some apparently failed scans marked with live == true

Some scans appear to be failing and returning a grade of zero, while stile returning the live value as true. Here's an example of one recent such scan of The Intercept:

            "grade": "F",
            "timestamp": "2018-03-02T16:15:53.857707Z",
            "live": true,
            "valid_https": false,
            "downgrades_https": false,
            "defaults_to_https": true,
            "hsts": false,
            "hsts_max_age": null,
            "hsts_entire_domain": null,
            "hsts_preload_ready": false,
            "hsts_preloaded": true,
            "score": 0

(There have been numerous examples on other sites, including The New Yorker and El Periódico, which I can dig up if it is helpful.)

This is a problem because filtering on the live value has been a pretty good way of dropping failed scans from results, which is necessary for doing data analysis and also for the Twitter bot to report changes. I haven't compiled stats on whether there has been an uptick in failed scans recently, but there has been an uptick in misfired Tweets that are the result of a successful scan following a failed scan, which points to a possible failure rate increase.

Feed results into HTTPS Everywhere

Since you're already maintaining a list of news sites with HTTPS support, you could easily auto-generate rulesets for HTTPS Everywhere.

Any site which is fully available over HTTPS (e.g. no content is unavailable when a domain is only loaded over HTTPS), but not HSTS preloaded is eligible for inclusion in HTTPS Everywhere.

To create a new HTTPS Everywhere ruleset, you can clone the repo and run a simple ruleset generation script:

git clone [email protected]:EFForg/https-everywhere.git
cd https-everywhere/rules
./make-trivial-rule ExampleNewsSite.com

You can follow the common format generated from this example to create rules for other sites and subdomains. Refer to https://github.com/EFForg/https-everywhere/blob/master/CONTRIBUTING.md for full contribution documentation.

Change elpais.es to elpais.com

Someone pointed out in a Redmine ticket that STN is scanning EL PAÍS's elpais.es domain (no HTTPS; gets an F), which actually redirects to elpais.com (has HTTPS). Let's check into this and change it accordingly.

Deployment: sanely handle gulp builds

The gulpfile.js blocks, causing Ansible tasks calling gulp to hang indefinitely. This is due to dev-specific live reload logic that should not be run in production. Further research is needed to modify the gulpfile.js such that it supports invocation like gulp build --production to build assets but not run and livereloading logic.

See #37 for a bit of context.

Gulp doesn't auto-reload when JS is modified

Steps to Reproduce

In Vagrant VM: cd /vagrant/securethenews && gulp

On host machine:

  1. Open securethenews/client/src/javascript/index.js
  2. Make a change (e.g. console.log('testing 1,2,3...')), then save the file.

Expected Behavior

Gulp detects the modified file, and automatically rebuilds the assets to be served. If the corresponding page is refreshed, the updated Javascript is executed.

Observed behavior

Gulp does not detect when the file is modified (there is no logging triggered upon saving the modified javascript source file). Javascript is not automatically rebuilt. If the corresponding page is refresh, the updated Javascript is not executed (the old Javascript is re-used).

In order to get the served/built JS to be updated, I have to restart gulp.

Introduce support to embedd the scorecards as a JS widget

This ticket is to Introduce support to embedd the scorecards as a JS widget in order to enable third party organizations to integrate the scorecards into specific campaign websites (ie: a campaign dedicated/focused on italian media, with a single-page-website for the campaign in italian, integrating the widget).

The widget should provide the ability to have country selectors, in order to display only media of a specific country.

Deployment: ensure fixtures apply

In #37 we saw that fixtures were applying cleanly to vagrant/sqlite, but failing against remote/postgres. We don't have a firm conclusion about root cause, so investigate further and update the config as necessary so it works against both providers.

Improve Open Graph and Twitter cards

Our Open Graph and Twitter cards are currently quite, ah, minimalist. It would be nice to make it so they are more attractive, distinctive, and display more relevant information at a glance.

For a showcase of our currently lackluster Twitter cards, see them used in most of the posts in the @SecureTheNews Twitter timeline.

Unicode slugs are broken

Back in #48 we implemented Unicode support for site slugs, since many of the news sites we track contain non-ASCII characters. Unfortunately there's since been a regression. Stack trace from local dev env:

Request Method: | POST
-- | --
http://127.0.0.1:8000/admin/sites/site/edit/15/
1.11.5
ValidationError
{'slug': ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]}
/usr/local/lib/python3.4/site-packages/django/db/models/base.py in full_clean, line 1249
/usr/local/bin/python
3.4.6
['/var/www/django',  '/usr/local/lib/python34.zip',  '/usr/local/lib/python3.4',  '/usr/local/lib/python3.4/plat-linux',  '/usr/local/lib/python3.4/lib-dynload',  '/usr/local/lib/python3.4/site-packages',  '/src/django-logging-json']

So we'll have to get it back up to speed again. Likely related to Wagtail/Django version bumps in #108 and #123. Discovered while working on #127.

`make dev` fails when attempting to obtain pshtt

The Ansible playbook fails on the securethenews-deploy : Install pshtt task.

pshtt_failure

I've tried building this using both Docker and Virtualbox as the provider: both produced the same failure. I initially suspected the issue may have been the specific commit the install path linked to (7362a8bdabe7190a933e4c5e18561ba9d07dc561), but I was able to install locally on Ubuntu 16.04.

I connected to the VM via vagrant ssh and ran sudo /usr/local/bin/pip2.7 install -e git+git://github.com/dhs-ncats/pshtt@7362a8bdabe7190a933e4c5e18561ba9d07dc561#egg=pshtt. This resulted in:

Obtaining pshtt from git+git://github.com/dhs-ncats/pshtt@7362a8bdabe7190a933e4c5e18561ba9d07dc561#egg=pshtt
  Skipping because already up-to-date.
Collecting requests>=2.10.0 (from pshtt)
  Using cached requests-2.17.3-py2.py3-none-any.whl
Collecting sslyze>=0.13.6 (from pshtt)
  Using cached SSLyze-1.1.1.zip
    Complete output from command python setup.py egg_info:
    error in SSLyze setup command: Invalid environment marker: python_version < "3.4"
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-2ClI_P/sslyze/

Run scans more frequently

Currently, we run scans every 8 hours. If we ran scans more often (say, every hour), then realtime consumers of our data, such as the @SecureTheNews Twitter bot, would be more useful as timely news sources.

Unfortunately, a consequence of the current database design is significant storage and query performance overhead for running scans so frequently (at least, I think--haven't actually measured this). Therefore, this issue probably depends on #84.

Deployment: permit debug=False

Mind-bendingly, the prod deployment logic currently seems to required that debug=True. This must be fixed prior to production roll-out, but we're leaving debug=True in place during staging.

See #37 for a bit of context.

Tests

The goal of the initial development phase of Secure the News was to get something released as quickly as possible, and as a result we sadly neglected writing comprehensive tests for most of the code.

Inspired by Obey the Testing Goat, I'd like to write some or all of the following tests for Secure the News:

  • Functional tests for the whole site
  • Unit tests for our Django apps: blog, home, pledges, and sites
  • Unit tests for the frontend Javascript: client/src/javascript

Replace Vagrant with Docker/Docker Compose

Secure the News currently uses a Vagrant-based development environment. This is less than ideal for a number of reasons:

  1. Vagrant continues to be a hassle to set up and use. This is especially true for FPF staff, many of whom use hardened Linux kernels (significant work is required to get grsecurity and any of the virtualized providers to work together) or Qubes (where only the Docker provider works anyway, because you cannot nest Vagrant's virtual machines in a Qubes AppVM).
  2. vagrant destroy -f && vagrant up (to test configuration changes from scratch) is slow, clocking in at 9m15s on my workstation. A lot of this slow-down is due to re-running slow commands (like apt-get install) in the Ansible provisioner. It's my understanding that this kind of repeated task gets optimized in Docker thanks to their layered filesystem approach.

In the long term, FPF is interested in experimenting with moving to container-based infrastructure for several of our current projects, so Secure the News seems like a good testbed for exploring our options and learning the ropes.

API

Other developers may wish to consume Secure the New's data for their own applications. For example, the @SecureTheNews Twitter bot currently scrapes and parses the JSON from STNsiteData embedded in the leaderboard's HTML. We should provide a convenient RESTful API instead.

Evaluate tracking committment to deploy HTTPS

It may be worthwhile to track and display information about a new organization pledging to implement HTTPS (or to improve their existing implementation). Sketching out a rough list of talking points for tackling the feature:

Pros

  • public information about commitment raises awareness and accountability
  • encourages news organizations to take HTTPS seriously
  • potential cross-linking benefit, raising awareness of the STN project (consider requiring public commitment on news org website)

Cons

  • lots of manual updates draining staff time
  • domain/org authentication (requiring a blog post on the org domain may simplify)
  • what about missed deadlines? what's the UX for that? does the org still qualify as committed?

Persist leaderboard state

An issue I noticed while reviewing #17: the state of the leaderboard view (e.g. which column is being used to sort, the direction of the sort, etc.) is not persisted. This was very noticeable in #17 because of the following (likely to be common) user story:

  1. User views leaderboard
  2. User clicks site's name to view the site-specific page (added in #17)
  3. User clicks Back button to go back to the leaderboard

Expected result

The leaderboard should look the same after the user hits Back as it did before they clicked the Site link.

Actual result

The leaderboard reverts to the default sort/order/pagination.

Graph long-term progress

Now that we're regularly scanning sites for compliance with our methodology, we have the data necessary to draw pretty graphs of the rate at which major news sites are adopting security best practices. Such a graph might look good on the homepage, and/or we could create a new page to display metrics over time.

For performance, this issue will probably end up depending on #84.

Region and/or topic-based leaderboards

We've been getting a lot of requests to add specific sites from other countries to the Secure the News leaderboard. As the leaderboard grows, it gets harder and harder to for humans to parse visually (although the search feature is still very handy). It would be nice to be able to filter the leaderboard by other criteria, such as country of origin or topic (e.g. IT news, finance, etc.).

At a minimum, we need to consider:

  • Adding additional filtering criteria to the Site model (easy)
  • Devising a process to collect this data, and maintain it over time (medium).
  • Design a user-friendly way to filter the leaderboard based on the new criteria (medium). There are at least a few different options for how to do this:
    • Keep a single leaderboard, and add optional filter controls to the frontend.
    • Create separate leaderboard pages for specific countries, topics, etc.

Upgrade to pshtt 0.2.3

We're currently pinning our installed version of pshtt to a specific commit, because it contains a fix that we needed to ensure accurate results on Secure the News, and there was a while between when the fix was committed and the pshtt maintainers created a new release.

Now the pshtt maintainers have released pshtt 0.1.6, which includes that fix and some others as well.

Notably, pshtt 0.1.6 upgrades the SSLyze dependency to 1.x, so we will have to make sure that transition is smooth.

Consider using CSV file as canonical list of sites

Right now the CSV file is mostly useful for dev data, so developers can spin up a local instance that tracks site. In production (at https://securethe.news/), we manage the list of sites via the Wagtail admin interface. That works well enough for internal staff managing changes, but doesn't jive well with contributions to this repo.

If we had an idempotent manage.py command that loads the sites, we could merge new sites to that list, then deploy the changes directly to the prod site, without needing to futz with the clicky-clicky.

See discussion in #106 (comment) @eloquence expressed interest in implementing.

Serve fonts from webserver

Right now the STN staging website pulls in fonts from an external location, fonts.googleapis.com:

securethe-news-google-fonts-include

Possibly related to #39. If we can get the Gulpfile to handle packaging up the fonts as a local static asset, that'd be rad.

API not using FQDN as base URL

First reported by @thisisparker . The API is not returning well structured URLs for subsequent calls:

$ curl -s https://securethe.news/api/v1/sites/ | grep -P 'https://[\w.]+' -o | sort | uniq -c
    101 https://127.0.0.1

Why?

Lint Python code throughout project

Right now we have a fair number of Python linting violations as reported by flake8:

$ flake8 api blog home pledges search securethenews sites --exclude 'migrations/' --count | tail -n1
118

We should step through the Python files and clean up to enforce PEP8 standards. As a checklist for progress:

  • Create Makefile target for easy linting via make flake8.
  • Clean up all flake8 violations until none are reported.
  • Add linting command to CI to enforce standards going forward.

About redirects and www subdomains

Currently, securethenews states for 'The Verge':

  • The default for this website is secure.
  • This website allows clients to enforce HTTPS on the primary domain.

However, https://theverge.com actually redirects to http://www.theverge.com (as does http://theverge.com) and has no HSTS header set, either.

That way, typing in 'theverge.com' in your browser will never connect to the site using HTTPS in the beginning (apart from redirect caching).

Websites should always redirect to the same domain on HTTPS, where HSTS should be set. Then any other redirects can be done there. (These are also requirements for the preload list)

screenshot

Enable Circle CI for automated builds

We're currently using Travis CI only to run the ./manage.py test suite after installing requirements. That's good, but for other websites we manage, we're also running through a more prod-like CI environment to catch configuration errors prior to deploying.

Let's drop in a .circle/config.yml file to start running container-based testing via Molecule (relevant: #83).

Installing nassl fails (pshtt dep) in vagrant using docker

Up to date Ubuntu 16.04.1 bare server install. Installed vagrant, ansible, docker.io

TASK [securethenews-deploy : Install pshtt] ************************************
fatal: [securethenews]: FAILED! => {"changed": false, "cmd": "/usr/local/bin/pip install -e git+git://github.com/dhs-ncats/pshtt@7362a8bdabe7190a933e4c5e18561ba9d07dc561#egg=pshtt", "failed": true, "msg": "stdout: Obtaining pshtt from git+git://github.com/dhs-ncats/pshtt@7362a8bdabe7190a933e4c5e18561ba9d07dc561#egg=pshtt\n Cloning git://github.com/dhs-ncats/pshtt (to 7362a8bdabe7190a933e4c5e18561ba9d07dc561) to ./src/pshtt\nRequirement already satisfied: requests>=2.10.0 in /usr/local/lib/python3.4/dist-packages (from pshtt)\nCollecting sslyze>=0.13.6 (from pshtt)\n Downloading SSLyze-0.14.2.tar.gz (1.1MB)\nCollecting wget>=3.2 (from pshtt)\n Downloading wget-3.2.zip\nCollecting docopt (from pshtt)\n Downloading docopt-0.6.2.tar.gz\nCollecting requests_cache (from pshtt)\n Downloading requests_cache-0.4.13-py2.py3-none-any.whl\nCollecting nassl<0.15.0,>=0.14.0 (from sslyze>=0.13.6->pshtt)\n Downloading nassl-0.14.2.tar.gz (15.2MB)\n Complete output from command python setup.py egg_info:\n Traceback (most recent call last):\n File "", line 1, in \n File "/tmp/pip-build-um741sgh/nassl/setup.py", line 8, in \n from nassl import author, version\n File "/tmp/pip-build-um741sgh/nassl/nassl/init.py", line 24\n SSL_MODE_SEND_FALLBACK_SCSV = 0x00000080L\n ^\n SyntaxError: invalid syntax\n \n ----------------------------------------\n\n:stderr: Could not find a tag or branch '7362a8bdabe7190a933e4c5e18561ba9d07dc561', assuming commit.\nCommand "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-um741sgh/nassl/\n"}

PLAY RECAP *********************************************************************
securethenews : ok=17 changed=12 unreachable=0 failed=1

Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again.
Makefile:8: recipe for target 'dev' failed
make: *** [dev] Error 1

Full log:

steve@ubuntu-dev:~/securethenews$ make dev
make ansible
make[1]: Entering directory '/home/steve/securethenews'
ansible-galaxy install -r ansible/requirements.yml -p ansible/roles

  • geerlingguy.nodejs is already installed, skipping.
    make[1]: Leaving directory '/home/steve/securethenews'
    vagrant up --provision
    Bringing machine 'securethenews' up with 'docker' provider...
    ==> securethenews: Building the container from a Dockerfile...
    securethenews: Sending build context to Docker daemon 2.048 kB
    securethenews: Step 1 : FROM jesselang/debian-vagrant:jessie
    securethenews: jessie: Pulling from jesselang/debian-vagrant
    securethenews: 5040bd298390: Pulling fs layer
    securethenews: 2267135e1e07: Pulling fs layer
    securethenews: 8b6c5f74664e: Pulling fs layer
    securethenews: 9bdc99613608: Pulling fs layer
    securethenews: 8d31f1db809a: Pulling fs layer
    securethenews: 3a35cc614636: Pulling fs layer
    securethenews: d7cf751a7af0: Pulling fs layer
    securethenews: cf074cfdd2e2: Pulling fs layer
    securethenews: 6c7158c01a4d: Pulling fs layer
    securethenews: e554a7e3ebaf: Pulling fs layer
    securethenews: 9844c47a3dc7: Pulling fs layer
    securethenews: b0536c63952a: Pulling fs layer
    securethenews: e7bfcad7fb37: Pulling fs layer
    securethenews: e02d59f474b9: Pulling fs layer
    securethenews: 862ea11a5693: Pulling fs layer
    securethenews: 77e9570dbfe8: Pulling fs layer
    securethenews: 9bdc99613608: Waiting
    securethenews: 8d31f1db809a: Waiting
    securethenews: 3a35cc614636: Waiting
    securethenews: d7cf751a7af0: Waiting
    securethenews: cf074cfdd2e2: Waiting
    securethenews: 6c7158c01a4d: Waiting
    securethenews: e554a7e3ebaf: Waiting
    securethenews: 9844c47a3dc7: Waiting
    securethenews: b0536c63952a: Waiting
    securethenews: e7bfcad7fb37: Waiting
    securethenews: e02d59f474b9: Waiting
    securethenews: 862ea11a5693: Waiting
    securethenews: 77e9570dbfe8: Waiting
    securethenews: 8b6c5f74664e:
    securethenews: Verifying Checksum
    securethenews: 8b6c5f74664e:
    securethenews: Download complete
    securethenews: 9bdc99613608: Verifying Checksum
    securethenews: 9bdc99613608: Download complete
    securethenews: 2267135e1e07: Verifying Checksum
    securethenews: 2267135e1e07: Download complete
    securethenews: 8d31f1db809a: Verifying Checksum
    securethenews: 8d31f1db809a: Download complete
    securethenews: 3a35cc614636: Verifying Checksum
    securethenews: 3a35cc614636: Download complete
    securethenews: d7cf751a7af0: Verifying Checksum
    securethenews: d7cf751a7af0: Download complete
    securethenews: cf074cfdd2e2: Verifying Checksum
    securethenews: cf074cfdd2e2: Download complete
    securethenews: 6c7158c01a4d: Verifying Checksum
    securethenews: 6c7158c01a4d: Download complete
    securethenews: e554a7e3ebaf: Verifying Checksum
    securethenews: e554a7e3ebaf: Download complete
    securethenews: 9844c47a3dc7: Verifying Checksum
    securethenews: 9844c47a3dc7: Download complete
    securethenews: b0536c63952a: Verifying Checksum
    securethenews: b0536c63952a: Download complete
    securethenews: e7bfcad7fb37: Verifying Checksum
    securethenews: e7bfcad7fb37: Download complete
    securethenews: e02d59f474b9: Verifying Checksum
    securethenews: e02d59f474b9: Download complete
    securethenews: 862ea11a5693: Verifying Checksum
    securethenews: 862ea11a5693: Download complete
    securethenews: 77e9570dbfe8: Verifying Checksum
    securethenews: 77e9570dbfe8: Download complete
    securethenews: 5040bd298390: Verifying Checksum
    securethenews: 5040bd298390: Download complete
    securethenews: 5040bd298390:
    securethenews: Pull complete
    securethenews: 2267135e1e07:
    securethenews: Pull complete
    securethenews: 8b6c5f74664e:
    securethenews: Pull complete
    securethenews: 9bdc99613608:
    securethenews: Pull complete
    securethenews: 8d31f1db809a: Pull complete
    securethenews: 3a35cc614636: Pull complete
    securethenews: d7cf751a7af0: Pull complete
    securethenews: cf074cfdd2e2: Pull complete
    securethenews: 6c7158c01a4d: Pull complete
    securethenews: e554a7e3ebaf: Pull complete
    securethenews: 9844c47a3dc7: Pull complete
    securethenews: b0536c63952a: Pull complete
    securethenews: e7bfcad7fb37: Pull complete
    securethenews: e02d59f474b9: Pull complete
    securethenews: 862ea11a5693: Pull complete
    securethenews: 77e9570dbfe8: Pull complete
    securethenews: Digest: sha256:d13dbf0e978d10ae7c4d25c1647cccc4bea2d1c2c2bd4969754e6eaf8b83a814
    securethenews: Status: Downloaded newer image for jesselang/debian-vagrant:jessie
    securethenews: ---> d2da839b9175
    securethenews: Step 2 : RUN apt-get install --no-install-recommends -y python
    securethenews: ---> Running in ba4dd43361a1
    securethenews: Reading package lists...
    securethenews: Building dependency tree...
    securethenews:
    securethenews: Reading state information...
    securethenews: The following extra packages will be installed:
    securethenews: libexpat1 libffi6 libpython-stdlib libpython2.7-minimal libpython2.7-stdlib
    securethenews: libsqlite3-0 mime-support python-minimal python2.7 python2.7-minimal
    securethenews: Suggested packages:
    securethenews: python-doc python-tk python2.7-doc binutils binfmt-support
    securethenews: Recommended packages:
    securethenews: file
    securethenews: The following NEW packages will be installed:
    securethenews: libexpat1 libffi6 libpython-stdlib libpython2.7-minimal libpython2.7-stdlib
    securethenews: libsqlite3-0 mime-support python python-minimal python2.7 python2.7-minimal
    securethenews: 0 upgraded, 11 newly installed, 0 to remove and 0 not upgraded.
    securethenews: Need to get 4663 kB of archives.
    securethenews: After this operation, 18.0 MB of additional disk space will be used.
    securethenews: Get:1 http://deb.debian.org/debian/ jessie/main libsqlite3-0 amd64 3.8.7.1-1+deb8u2 [438 kB]
    securethenews: Get:2 http://deb.debian.org/debian/ jessie/main libpython2.7-minimal amd64 2.7.9-2+deb8u1 [378 kB]
    securethenews: Get:3 http://deb.debian.org/debian/ jessie/main python2.7-minimal amd64 2.7.9-2+deb8u1 [1401 kB]
    securethenews: Get:4 http://deb.debian.org/debian/ jessie/main python-minimal amd64 2.7.9-1 [40.3 kB]
    securethenews: Get:5 http://deb.debian.org/debian/ jessie/main mime-support all 3.58 [36.0 kB]
    securethenews: Get:6 http://deb.debian.org/debian/ jessie/main libexpat1 amd64 2.1.0-6+deb8u3 [80.0 kB]
    securethenews: Get:7 http://deb.debian.org/debian/ jessie/main libffi6 amd64 3.1-2+b2 [20.1 kB]
    securethenews: Get:8 http://deb.debian.org/debian/ jessie/main libpython2.7-stdlib amd64 2.7.9-2+deb8u1 [1847 kB]
    securethenews: Get:9 http://deb.debian.org/debian/ jessie/main python2.7 amd64 2.7.9-2+deb8u1 [252 kB]
    securethenews: Get:10 http://deb.debian.org/debian/ jessie/main libpython-stdlib amd64 2.7.9-1 [19.5 kB]
    securethenews: Get:11 http://deb.debian.org/debian/ jessie/main python amd64 2.7.9-1 [151 kB]
    securethenews: debconf: delaying package configuration, since apt-utils is not installed
    securethenews:
    securethenews: Fetched 4663 kB in 3s (1238 kB/s)
    securethenews: Selecting previously unselected package libsqlite3-0:amd64.
    securethenews: (Reading database ...
    securethenews: (Reading database ... 5%
    securethenews: (Reading database ... 10%
    securethenews: (Reading database ... 15%
    securethenews: (Reading database ... 20%
    (Reading database ... 30%ng database ... 25%
    (Reading database ... 40%ng database ... 35%
    (Reading database ... 50%ng database ... 45%
    (Reading database ... 60%ng database ... 55%
    (Reading database ... 70%ng database ... 65%
    (Reading database ... 80%ng database ... 75%
    (Reading database ... 90%ng database ... 85%
    (Reading database ... 100%g database ... 95%
    securethenews: (Reading database ... 7814 files and directories currently installed.)
    securethenews: Preparing to unpack .../libsqlite3-0_3.8.7.1-1+deb8u2_amd64.deb ...
    securethenews: Unpacking libsqlite3-0:amd64 (3.8.7.1-1+deb8u2) ...
    securethenews: Selecting previously unselected package libpython2.7-minimal:amd64.
    securethenews: Preparing to unpack .../libpython2.7-minimal_2.7.9-2+deb8u1_amd64.deb ...
    securethenews: Unpacking libpython2.7-minimal:amd64 (2.7.9-2+deb8u1) ...
    securethenews: Selecting previously unselected package python2.7-minimal.
    securethenews: Preparing to unpack .../python2.7-minimal_2.7.9-2+deb8u1_amd64.deb ...
    securethenews: Unpacking python2.7-minimal (2.7.9-2+deb8u1) ...
    securethenews: Selecting previously unselected package python-minimal.
    securethenews: Preparing to unpack .../python-minimal_2.7.9-1_amd64.deb ...
    securethenews: Unpacking python-minimal (2.7.9-1) ...
    securethenews: Selecting previously unselected package mime-support.
    securethenews: Preparing to unpack .../mime-support_3.58_all.deb ...
    securethenews: Unpacking mime-support (3.58) ...
    securethenews: Selecting previously unselected package libexpat1:amd64.
    securethenews: Preparing to unpack .../libexpat1_2.1.0-6+deb8u3_amd64.deb ...
    securethenews: Unpacking libexpat1:amd64 (2.1.0-6+deb8u3) ...
    securethenews: Selecting previously unselected package libffi6:amd64.
    securethenews: Preparing to unpack .../libffi6_3.1-2+b2_amd64.deb ...
    securethenews: Unpacking libffi6:amd64 (3.1-2+b2) ...
    securethenews: Selecting previously unselected package libpython2.7-stdlib:amd64.
    securethenews: Preparing to unpack .../libpython2.7-stdlib_2.7.9-2+deb8u1_amd64.deb ...
    securethenews: Unpacking libpython2.7-stdlib:amd64 (2.7.9-2+deb8u1) ...
    securethenews: Selecting previously unselected package python2.7.
    securethenews: Preparing to unpack .../python2.7_2.7.9-2+deb8u1_amd64.deb ...
    securethenews: Unpacking python2.7 (2.7.9-2+deb8u1) ...
    securethenews: Selecting previously unselected package libpython-stdlib:amd64.
    securethenews: Preparing to unpack .../libpython-stdlib_2.7.9-1_amd64.deb ...
    securethenews: Unpacking libpython-stdlib:amd64 (2.7.9-1) ...
    securethenews: Setting up libpython2.7-minimal:amd64 (2.7.9-2+deb8u1) ...
    securethenews: Setting up python2.7-minimal (2.7.9-2+deb8u1) ...
    securethenews: Linking and byte-compiling packages for runtime python2.7...
    securethenews: Setting up python-minimal (2.7.9-1) ...
    securethenews: Selecting previously unselected package python.
    securethenews: (Reading database ...
    (Reading database ... 10%ng database ... 5%
    (Reading database ... 20%ng database ... 15%
    (Reading database ... 30%ng database ... 25%
    (Reading database ... 40%ng database ... 35%
    (Reading database ... 50%ng database ... 45%
    (Reading database ... 60%ng database ... 55%
    (Reading database ... 70%ng database ... 65%
    (Reading database ... 80%ng database ... 75%
    (Reading database ... 90%ng database ... 85%
    (Reading database ... 100%g database ... 95%
    securethenews: (Reading database ... 8610 files and directories currently installed.)
    securethenews: Preparing to unpack .../python_2.7.9-1_amd64.deb ...
    securethenews: Unpacking python (2.7.9-1) ...
    securethenews: Setting up libsqlite3-0:amd64 (3.8.7.1-1+deb8u2) ...
    securethenews: Setting up mime-support (3.58) ...
    securethenews: Setting up libexpat1:amd64 (2.1.0-6+deb8u3) ...
    securethenews: Setting up libffi6:amd64 (3.1-2+b2) ...
    securethenews: Setting up libpython2.7-stdlib:amd64 (2.7.9-2+deb8u1) ...
    securethenews: Setting up python2.7 (2.7.9-2+deb8u1) ...
    securethenews: Setting up libpython-stdlib:amd64 (2.7.9-1) ...
    securethenews: Setting up python (2.7.9-1) ...
    securethenews: Processing triggers for libc-bin (2.19-18+deb8u7) ...
    securethenews: ---> b6a4dc08fad9
    securethenews: Removing intermediate container ba4dd43361a1
    securethenews: Successfully built b6a4dc08fad9
    securethenews:
    securethenews: Image: b6a4dc08fad9
    ==> securethenews: Creating the container...
    securethenews: Name: securethenews_securethenews_1486162108
    securethenews: Image: b6a4dc08fad9
    securethenews: Volume: /home/steve/securethenews:/vagrant
    securethenews: Port: 8000:8000
    securethenews: Port: 35729:35729
    securethenews: Port: 127.0.0.1:2222:22
    securethenews:
    securethenews: Container created: 036d4b38da609472
    ==> securethenews: Starting container...
    ==> securethenews: Waiting for machine to boot. This may take a few minutes...
    securethenews: SSH address: 172.17.0.2:22
    securethenews: SSH username: vagrant
    securethenews: SSH auth method: private key
    securethenews:
    securethenews: Vagrant insecure key detected. Vagrant will automatically replace
    securethenews: this with a newly generated keypair for better security.
    securethenews:
    securethenews: Inserting generated public key within guest...
    securethenews: Removing insecure key from the guest if it's present...
    securethenews: Key inserted! Disconnecting and reconnecting using new SSH key...
    ==> securethenews: Machine booted and ready!
    ==> securethenews: Running provisioner: ansible...
    securethenews: Running ansible-playbook...

PLAY [Deploy Secure The News development machine] ******************************

TASK [setup] *******************************************************************
ok: [securethenews]

TASK [geerlingguy.nodejs : include] ********************************************
skipping: [securethenews]

TASK [geerlingguy.nodejs : include] ********************************************
included: /home/steve/securethenews/ansible/roles/geerlingguy.nodejs/tasks/setup-Debian.yml for securethenews

TASK [geerlingguy.nodejs : Ensure apt-transport-https is installed.] ***********
changed: [securethenews]

TASK [geerlingguy.nodejs : Add Nodesource apt key.] ****************************
changed: [securethenews]

TASK [geerlingguy.nodejs : Add NodeSource repositories for Node.js.] ***********
changed: [securethenews] => (item=deb https://deb.nodesource.com/node_4.x jessie main)
changed: [securethenews] => (item=deb-src https://deb.nodesource.com/node_4.x jessie main)

TASK [geerlingguy.nodejs : Update apt cache if repo was added.] ****************
ok: [securethenews]

TASK [geerlingguy.nodejs : Ensure Node.js and npm are installed.] **************
changed: [securethenews]

TASK [geerlingguy.nodejs : Define nodejs_install_npm_user] *********************
ok: [securethenews]

TASK [geerlingguy.nodejs : Create npm global directory] ************************
changed: [securethenews]

TASK [geerlingguy.nodejs : Add npm_config_prefix bin directory to global $PATH.] ***
changed: [securethenews]

TASK [geerlingguy.nodejs : Ensure npm global packages are installed.] **********

TASK [geerlingguy.nodejs : Ensure npm global packages are at the latest release.] ***

TASK [securethenews-deploy : Install apt dependencies.] ************************
changed: [securethenews] => (item=[u'aptitude', u'git', u'ipython3', u'libjpeg-dev', u'postgresql-server-dev-all', u'python-dev', u'python-pip', u'python3-pip', u'ruby', u'tmux', u'vim', u'zlib1g-dev'])

TASK [securethenews-deploy : Upgrade all packages.] ****************************
ok: [securethenews]

TASK [securethenews-deploy : Upgrade pip for Python 2 to avoid ImportError] ****
changed: [securethenews]

TASK [securethenews-deploy : Upgrade pip for Python 3 to avoid ImportError] ****
changed: [securethenews]

TASK [securethenews-deploy : Install Django project requirements] **************
changed: [securethenews]

TASK [securethenews-deploy : Install Node dependencies] ************************
changed: [securethenews]

TASK [securethenews-deploy : Install gulp globally] ****************************
changed: [securethenews]

TASK [securethenews-deploy : Install pshtt] ************************************
fatal: [securethenews]: FAILED! => {"changed": false, "cmd": "/usr/local/bin/pip install -e git+git://github.com/dhs-ncats/pshtt@7362a8bdabe7190a933e4c5e18561ba9d07dc561#egg=pshtt", "failed": true, "msg": "stdout: Obtaining pshtt from git+git://github.com/dhs-ncats/pshtt@7362a8bdabe7190a933e4c5e18561ba9d07dc561#egg=pshtt\n Cloning git://github.com/dhs-ncats/pshtt (to 7362a8bdabe7190a933e4c5e18561ba9d07dc561) to ./src/pshtt\nRequirement already satisfied: requests>=2.10.0 in /usr/local/lib/python3.4/dist-packages (from pshtt)\nCollecting sslyze>=0.13.6 (from pshtt)\n Downloading SSLyze-0.14.2.tar.gz (1.1MB)\nCollecting wget>=3.2 (from pshtt)\n Downloading wget-3.2.zip\nCollecting docopt (from pshtt)\n Downloading docopt-0.6.2.tar.gz\nCollecting requests_cache (from pshtt)\n Downloading requests_cache-0.4.13-py2.py3-none-any.whl\nCollecting nassl<0.15.0,>=0.14.0 (from sslyze>=0.13.6->pshtt)\n Downloading nassl-0.14.2.tar.gz (15.2MB)\n Complete output from command python setup.py egg_info:\n Traceback (most recent call last):\n File "", line 1, in \n File "/tmp/pip-build-um741sgh/nassl/setup.py", line 8, in \n from nassl import author, version\n File "/tmp/pip-build-um741sgh/nassl/nassl/init.py", line 24\n SSL_MODE_SEND_FALLBACK_SCSV = 0x00000080L\n ^\n SyntaxError: invalid syntax\n \n ----------------------------------------\n\n:stderr: Could not find a tag or branch '7362a8bdabe7190a933e4c5e18561ba9d07dc561', assuming commit.\nCommand "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-um741sgh/nassl/\n"}

PLAY RECAP *********************************************************************
securethenews : ok=17 changed=12 unreachable=0 failed=1

Ansible failed to complete successfully. Any error output should be
visible above. Please fix these errors and try again.
Makefile:8: recipe for target 'dev' failed
make: *** [dev] Error 1

Update "Methodology" page content

Suggested change:

We will publish the Secure The News source code in the near future, and look forward to working with the community to encourage news organizations to protect their readers.

to:

If you're interested in getting involved, the Secure The News source code is public on GitHub (under the AGPLv3 License). If you don't know what to work on, we suggest taking a look at issues that are tagged with "contributors-welcome".

It might also make sense to reference the API somewhere, though that might be better as a top-level link.

Store BlogPost preview in the database

Currently, the preview blurb displayed for each BlogPost on the BlogIndexPage is re-computed every time the page is loaded (since it is an @property attribute on the model). It would be better to memoize this computation and only store it when the BlogPost changes. The easiest way to do this is to add a preview field to BlogPost and use a custom save method to compute and save the preview only when necessary.

Frontend cache invalidation for blog

When a new blog post is published, we should use the frontend cache invalidator to update the BlogIndexPage; otherwise, the index page fails to list the new blog post for a long time.

Developer setup instructions on Mac

Running through the dev setup instructions on Mac OS X: following the instructions in the Readme with Ansible version 2.1.1.0 and Vagrant 1.8.5, I run into a bump at make dev:

$ make dev
make ansible
ansible-galaxy install -r ansible/requirements.yml -p ansible/roles
- downloading role 'nodejs', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-nodejs/archive/4.0.0.tar.gz
- extracting geerlingguy.nodejs to ansible/roles/geerlingguy.nodejs
- geerlingguy.nodejs was installed successfully
vagrant up --provision
Bringing machine 'securethenews' up with 'docker' provider...
==> securethenews: Box 'debian/contrib-jessie64' could not be found. Attempting to find and install...
    securethenews: Box Provider: docker
    securethenews: Box Version: >= 0
==> securethenews: Loading metadata for box 'debian/contrib-jessie64'
    securethenews: URL: https://atlas.hashicorp.com/debian/contrib-jessie64
The box you're attempting to add doesn't support the provider
you requested. Please find an alternate box or use an alternate
provider. Double-check your requested provider to verify you didn't
simply misspell it.

If you're adding a box from HashiCorp's Atlas, make sure the box is
released.

Name: debian/contrib-jessie64
Address: https://atlas.hashicorp.com/debian/contrib-jessie64
Requested provider: [:docker]
make: *** [dev] Error 1

Trying to pop off again again after setting VAGRANT_DEFAULT_PROVIDER to virtualbox:

$ make dev
make ansible
ansible-galaxy install -r ansible/requirements.yml -p ansible/roles
- geerlingguy.nodejs is already installed, skipping.
vagrant up --provision
Bringing machine 'securethenews' up with 'virtualbox' provider...
There are errors in the configuration of this machine. Please fix
the following errors and try again:

vm:
* A box must be specified.


make: *** [dev] Error 1

Possibly wrong security rating for "The Indian Express"

Secure the news entry for "The Indian Express" has Security Rating D. The only reason it received D instead of F is because it was considered to have a valid HTTPS certificate.

However, in reality it doesn't. It can be checked here directly. So, in my opinion the security rating for The Indian Express should be F.

Please have a look into that if it is genuine !

P.S: This was an accidental found. I am not sure if there are any similar mistakes in the script while rating other sites. Also, it could be possible due to historic reasons, i.e. The Indian Express used to have a valid certificate, but not any more.

Travis CI

We already have a .travis.yml, so I think I just need to hook this up in Travis once we make the Github repo public. We should also add a badge to the README.

Update markdown

From safety:

-> markdown2, installed 2.3.4, affected <2.3.5, id 35760
An issue was discovered in markdown2 (aka python-markdown2) through 2.3.5. The safe_mode feature, which is supposed to sanitize user input against XSS, is flawed and does not escape the input properly. With a crafted payload, XSS can be triggered, as demonstrated by omitting the final '>' character from an IMG tag.

CI requires us to update this to pass

Confusing results for some sites

While reviewing the leaderboard in #14, I noticed that some sites have confusing results for their scans. For example:

screen shot 2016-10-11 at 3 01 22 pm

Notice that The Intercept, TechCrunch, and the Sun "default to HTTPS" and "enforce to HTTPS", but don't "use HTTPS". Huh??

Fix logging back-end

The current logging configuration for production is dropping vital logging signals/messages.

We have recent success with fixing this problem on other Django based sites by expanding the logging dictconf and bringing in another python json logging library. Lets reproduce that work here.

Evaluate Tor-based access to news sites

Secure The News is not yet one year old, and to date we've seen strong adoption of HTTPS across a wide swath of news websites. We already have nearly 10 sites scored with A+—and 15 sites scored with A+ or A-.

Let's raise the bar a bit. For instance, ProPublica is one of the few news organizations to provide an Onion Service to browse their website: propub3r6espa33w.onion Hosting an Onion Service is hugely beneficial for reader privacy, and ProPublica gets no credit for such effort and innovation from Secure The News—arguably the authority on evaluating security of news websites.

Another problem is CAPTCHA walls, or JS browser validation checks. Cloudflare infamously provides a one-click solution for treating visitors connecting over Tor as second-class citizens. If two sites are ranked as A+ on their HTTPS deployments, but one CAPTCHAs Tor users and the other does not, our grading schema should to be updated to address the disparity.

I propose adding new attributes to site model, and writing additional scanning logic that's run over Tor. A few of the criteria we look at:

  • Onion URL (have one y/n)
  • Site loads fine over Tor
  • Site forces CAPTCHA to Tor users
  • Site requires JavaScript (will not work with high security To Browser settings)

The stem project may make scanning over Tor straightforward, since we already lean on pshtt heavily for the HTTP/S logic. Otherwise can simply proxy the requests over Tor via SOCKS5.

The goal of Secure The News is to enforce a modern and progressive rubric that promotes reader privacy and mitigates censorship opportunity from network attackers. Tor provides both criteria quite well—and with the advent of next-generation Onion Services on the horizon, it'll soon be even better—so let's start tracking it.

Clean up SASS

As a SASS novice and person who generally hates dealing with styling, I have committed terrible crimes against DRY in client/src/styles. See some of the TODOs scattered in there for a sense of places where I broke bad. I would like to clean these up, and ensure that we have consistent and attractive styles across the site.

In particular, ContentPages and BlogPosts should probably have the entirely or mostly the same set of styles.

Optimize database performance

Secure the News was very much designed as an "MVP," along with the mantra of "premature optimization is the root of all evil." As a result, querying Scans in the database is exceptionally slow. To see this slowness in action, load the site from the development environment and profile loading either the homepage or the leaderboard (both trigger expensive database operations).

The only reason why this slowness does not affect the production site is our extensive use of front-end caching (thanks, Cloudflare!).

There are some other issues with our database design as well. For example, we re-scan and store the full set of scan data for each site every 8 hours, even though the results of the scans change infrequently. It seems like it would be much more efficient to somehow only store the deltas of each scan, or only store a full set of scan data when the results change.

Finally, since the majority of the sites app's data is serialized into a relational database from the JSON output of our scanning tool pshtt, I've wondered in the past if using a database that is optimized for storing and querying JSON directly might be a better approach. During the initial development phase I wanted to avoid the complexity of using something other than the default Django ORM, so this idea was never explored in depth.

Finally: I am an amateur when it comes to database design, so I welcome any and all ideas here!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.