theupdateframework / python-tuf Goto Github PK
View Code? Open in Web Editor NEWPython reference implementation of The Update Framework (TUF)
Home Page: https://theupdateframework.com/
License: Apache License 2.0
Python reference implementation of The Update Framework (TUF)
Home Page: https://theupdateframework.com/
License: Apache License 2.0
vladdd since you changed the directory structure, should we rename the repository to 'TUF' instead of 'tuf'? Now we have a directory 'tuf/tuf/'.
Suppose the user enters this URL prefix...
...'url_prefix': 'http://localhost:8001/'...
...instead of the one here:
https://github.com/akonst/tuf/blob/d2a799f73f7172fe1774c23fe063c614adf22671/example_client.py#L41
The trailing '/' throws TUF off because it is unable to handle the extra separator. For example, it says:
Downloading: http://localhost:8001//metadata/timestamp.txt
Download failed from http://localhost:8001//metadata/timestamp.txt.
Needs a review.
Silence the TUF logger in all the test modules.
This should happen...
delegated_targets_directory = os.path.abspath( delegated_targets_directory )
...before the following line:
Example: suppose that the user enters "./repository/targets/role1" as the delegated targets directory?
Expiration time setting needs to be fixed.
TODO: set the expires time another way.
Lines 411, 456, 496, 544
https://github.com/akonst/tuf/blob/master/tuf/formats.py
Currently, all the top-level metadata are set to expire a year from its creation date. This is not an ideal default for the timestamp role, for example. The signing machine should be allowed to decide exactly when each metadata is set to expire. I am going to add an 'expires' parameter to all the 'make_metada' methods and prompt the user for expiration times in the 'quickstart.py' and 'signercli.py' scripts. The unit tests for affected modules will have to be updated.
I am going to also double-check that we are correctly handling time in TUF (UTC timestamps, etc.)
http://lucumr.pocoo.org/2011/7/15/eppur-si-muove/
http://news.ycombinator.com/item?id=2766775
http://stackoverflow.com/questions/1599060/how-can-i-get-an-accurate-utc-time-with-python
http://unix4lyfe.org/time/
The client fails to fetch an updated metadata file and his current version is no longer valid.
Should we get rid of the delegated metadata files? We shouldn't need to, but we need to check the trust implications of the current implementation.
See the _update_metadata_if_changed() function in updater.py.
In path_in_confined_paths(), what is the reason for the following?
# Get the directory name (i.e., strip off the file_path+extension)
directory_name = os.path.dirname(test_path)
if directory_name == os.path.dirname(pattern):
return True
I've mentioned this issue before. This is where a relative path
'test_path' is checked against a 'confined_paths'. The paths are
stripped, and only the directories are compared. What is the reason
behind this?
So, if the 'test_path' is 'a/c.txt' and a confined path is
'/a/b/c.txt' (given c.txt's are the same) it's a no go. They'll
match only when test_path = 'a/b/c.txt' and a confined path =
'/a/b/c.txt'.
I, probably, should have said: a path in the list of the confined
paths instead of a "confined path", to be more clear.
Changed 'tuf.BadHash' to 'tuf.BadHashError'.
Suppose the server (like updateframework.com now) does not send the Content-Length. Then tuf.download fails with this incomprehensible message:
[tuf.download] [ERROR] int() argument must be a string or a number, not 'NoneType'
https://github.com/akonst/tuf/blob/master/tuf/download.py#L187
According to Vlad:
For target files belonging to the 'targets.txt' role, you can add/remove them from the /targets directory on the repository.
You will then regenerate 'targets.txt', 'release.txt', and 'timestamp.txt' so clients are aware of the update.$ signercli.py --maketargets ./keystore $ signercli.py --makerelease ./keystore $ signercli.py --maketimestamp ./keystoreUpdating target files for a delegated role in this manner is not implemented in the master branch. I believe Kon has begun to write code to make this happen in the interposition branch. It will be a simple:
$ signercli.py --makedelegatedtargets ./keystoreFor now, the master branch will require another
$ --makedelegation
to regenerate a new delegated role metadata that tracks the changed targets.
Presently, this is what the pip+TUF demo does:
$ pip install Django -v --log pip.log --no-install
[2013-03-14 21:40:23,557] [tuf.hash] [WARNING] Pycrypto hash algorithms could not be imported. Supported libraries: ['hashlib', 'pycrypto']
[2013-03-14 21:40:23,569] [tuf.interposition] [INFO] Updater added for Configuration(netloc=pypi.python.org:80).
Downloading/unpacking Django
[2013-03-14 21:40:23,585] [tuf.interposition] [INFO] Found updater for hostname=pypi.python.org
[2013-03-14 21:40:23,587] [tuf.interposition] [INFO] Interposing for https://pypi.python.org/simple/Django/
[2013-03-14 21:40:23,587] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/timestamp.txt
[2013-03-14 21:40:25,066] [tuf] [INFO] Metadata 'release.txt' has changed.
[2013-03-14 21:40:25,066] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/release.txt
[2013-03-14 21:40:25,482] [tuf] [INFO] Metadata 'targets.txt' has changed.
[2013-03-14 21:40:25,483] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/targets.txt
[2013-03-14 21:40:25,900] [tuf] [INFO] Metadata u'targets/packages.txt' has changed.
[2013-03-14 21:40:25,901] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/targets/packages.txt
[2013-03-14 21:40:26,318] [tuf] [INFO] Metadata u'targets/packages/source.txt' has changed.
[2013-03-14 21:40:26,318] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/targets/packages/source.txt
[2013-03-14 21:40:26,734] [tuf] [INFO] Metadata u'targets/packages/source/D.txt' has changed.
[2013-03-14 21:40:26,734] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/targets/packages/source/D.txt
[2013-03-14 21:40:27,171] [tuf] [INFO] Metadata u'targets/packages/source/D/Django.txt' has changed.
[2013-03-14 21:40:27,171] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/targets/packages/source/D/Django.txt
[2013-03-14 21:40:27,588] [tuf] [INFO] Metadata u'targets/simple.txt' has changed.
[2013-03-14 21:40:27,589] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/targets/simple.txt
[2013-03-14 21:40:28,024] [tuf] [INFO] Metadata u'targets/simple/Django.txt' has changed.
[2013-03-14 21:40:28,024] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/targets/simple/Django.txt
[2013-03-14 21:40:28,439] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/targets/simple/Django/index.html
[2013-03-14 21:40:28,857] [tuf.download] [INFO] The file's sha256 hash is correct: ca697ea11d91d393cf56f92e342ca2d04cfd81c5f5535e4ac4976ac0dcc90893
[2013-03-14 21:40:28,862] [tuf.interposition] [WARNING] No updater for hostname=www.djangoproject.com
[2013-03-14 21:40:28,862] [tuf.interposition] [WARNING] No updater or interposition for url=http://www.djangoproject.com/
[2013-03-14 21:40:28,962] [tuf.interposition] [WARNING] No updater for hostname=www.djangoproject.com
[2013-03-14 21:40:28,962] [tuf.interposition] [WARNING] No updater or interposition for url=http://www.djangoproject.com/m/bad-installer.txt
[2013-03-14 21:40:28,979] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.3/Django-1.3.2.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,037] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.3/Django-1.3.3.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,092] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.3/Django-1.3.4.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,148] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.4/Django-1.4.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,203] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.4/Django-1.4.1.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,258] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.4/Django-1.4.4.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,311] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.4/Django-1.4.2.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,367] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.4/Django-1.4.5.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,420] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.3/Django-1.3.7.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,475] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.3/Django-1.3.5.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,533] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.4/Django-1.4.3.tar.gz because it is not a GET request.
[2013-03-14 21:40:29,583] [tuf.interposition] [WARNING] Skipping HEAD request to https://www.djangoproject.com/m/releases/1.3/Django-1.3.6.tar.gz because it is not a GET request.
Using version 1.4.5 (newest of versions: 1.4.5, 1.4.5, 1.4.4, 1.4.4, 1.4.3, 1.4.3, 1.4.2, 1.4.2, 1.4.1, 1.4.1, 1.4, 1.4, 1.3.7, 1.3.7, 1.3.6, 1.3.6, 1.3.5, 1.3.5, 1.3.4, 1.3.3, 1.3.3, 1.3.2, 1.3.2, 1.3.1, 1.3, 1.2.7, 1.2.6, 1.2.5, 1.2.4, 1.2.3, 1.2.2, 1.2.1, 1.2, 1.1.4, 1.1.3)
[2013-03-14 21:40:29,646] [tuf.interposition] [INFO] Found updater for hostname=pypi.python.org
[2013-03-14 21:40:29,646] [tuf.interposition] [INFO] Interposing for https://pypi.python.org/packages/source/D/Django/Django-1.4.5.tar.gz
[2013-03-14 21:40:29,646] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/timestamp.txt
[2013-03-14 21:40:30,066] [tuf] [INFO] Metadata 'release.txt' has changed.
[2013-03-14 21:40:30,066] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/release.txt
[2013-03-14 21:40:30,498] [tuf] [INFO] Metadata 'targets.txt' has changed.
[2013-03-14 21:40:30,498] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/metadata/targets.txt
[2013-03-14 21:40:30,923] [tuf.download] [INFO] Downloading: https://updateframework.com/pypi/repository/targets/packages/source/D/Django/Django-1.4.5.tar.gz
[2013-03-14 21:41:30,386] [tuf.download] [INFO] The file's sha256 hash is correct: 0e1e8c4217299672bbf9404994717fca2d8d4b7a4f7b8b3b74d413e1fda81428
Downloading Django-1.4.5.tar.gz (unknown size):
Downloading from URL https://pypi.python.org/packages/source/D/Django/Django-1.4.5.tar.gz#md5=851d00905eb70e4aa6384b3b8b111fb7 (from https://pypi.python.org/simple/Django/)
...Downloading Django-1.4.5.tar.gz (unknown size): 7.7MB downloaded
Running setup.py egg_info for package Django
running egg_info
creating pip-egg-info/Django.egg-info
writing pip-egg-info/Django.egg-info/PKG-INFO
writing top-level names to pip-egg-info/Django.egg-info/top_level.txt
writing dependency_links to pip-egg-info/Django.egg-info/dependency_links.txt
writing manifest file 'pip-egg-info/Django.egg-info/SOURCES.txt'
warning: manifest_maker: standard file '-c' not found
reading manifest file 'pip-egg-info/Django.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'pip-egg-info/Django.egg-info/SOURCES.txt'
Successfully downloaded Django
Vlad noticed that the release
and targets
metadata are being downloaded twice. The question is: why? Is it happening because tuf.interposition
is not correctly using tuf.client.updater
, or because of something else?
https://github.com/akonst/tuf/blob/master/tuf/client/updater.py#L584
The _update_metadata() function in updater.py needs to further verify the metadata that it downloads. We currently check the following:
(1) A valid threshold of signatures.
(2) A properly formatted metadata file.
(3) All the specified targets are allowed (for 'targets' metadata)
(4) Ensure downloaded top-level metadata have not expired (in refresh())
What we currently do NOT verify:
(1) A valid timestamp (we only check its format). Was the metadata created at a future date? Some other invalid date? We need to better check the metadata's timestamp value.
(2) That the downloaded metadata is newer than our current version.
(3) Delegated targets metadata have not expired (we only check for the top-level roles).
This should be easy to fix, but we need to carefully consider the problem of clients with incorrect clocks. Do we:
(1) Disregard users with bad clocks and assume all our clients are in sync with the signing server.
(2) Have an option for bogus clocks, although these users would be open to metadata replay attacks.
(3) Have clients fetch valid times, perhaps through an authenticated NTP server.
(4) Some other solution?
When I use signercli.py to make a target role delegation (e.g. from "targets" to "targets/role1"), the delegated target role ("target/role1") seems to be limited to using 1 key. Why is it so? Should we not allow any positive number of keys for better security?
tuf.download uses urllib to do its job. Unless urllib is immune to the endless data attack, how is tuf.download immune to it?
Needs a review.
Presently, there is no thread-safety for some important things, such as:
tuf.conf
global configuration variables between different TUF clients.From a cursory glance, I do not think we explicitly detect slow retrieval attacks, although we implicitly do so simply because urllib does so.
If I invoke:
$ signercli.py --listkeys ./keystore
I get a list of keys, which is nice, but I wonder whether it'd be possible to list what roles those keys play?
Presently, we allow tuf.interposition users to match and transform URL paths with regular expressions.
There are a few use cases for this feature:
The question is: more functionality is nicer for the developer, but will it affect default TUF security? For example, the user may match or mismatch URL paths against her intentions.
The first use case has a simpler fix where we simply get TUF to generate a duplicate target for the key "/path/to/directory/" when it sees files of the form "/path/to/directory/index.html". However, this fix does not handle the second use case.
Say that there are two files in repository/targets/D/Django/:
repository/targets/D/Django/A.txt
repository/targets/D/Django/B.txt
D already delegates to Django, right? Great. Now, could Django delegate Django/A.txt to developer A, and Django/B.txt to developer B?
At the moment, as I understand it, signercli.py could do this, but only if we do modify the targets directory to this:
repository/targets/D/Django/A/A.txt
repository/targets/D/Django/B/B.txt
So now we have these delegations:
targets -> D -> Django
Django -> A
Django -> B
Then A could sign A.txt, and B could sign B.txt, and not interfere with each other.
Interposing with module B as a module A requires that B completely copies the behaviour of A (sometime even its bugs for backwards-compatibility).
Now, tuf.interposition is interposing as the urllib/urllib2 modules, and it does not completely copy their behaviour.
Examples:
For each of these examples and more, we need to think about how to have tuf.interposition gracefully copy their behaviour.
Presently, if a URL path for a network location does not match user-specified regular expression patterns, then we do not interpose for it, but we do log this "soft" exception as a warning. This is acceptable, since the user is made aware of this with the documentation.
However, if we fail to transform a matching URL path (e.g. with string formatting), we also treat this as a "soft" exception and log it as a warning. This is actually a critical failure that must be raised to the client for further handling, because the client has misconfigured the URL transformation.
Nick Coghlan would like some measurements with TUF.
According to Monzur Muhammad: "He would like to see any overhead that TUF would add and any delay that will be seen if a new package was added/modified. He also mentioned that it might be nice for us to become a mirror for Pypi so we can run experiments/measurements."
If we are using the interposition method to implement TUF into our sofware updater, and TUF has a bug that prevents it from pulling updates, then the software updater breaks completely. That is we will not be able to push any new updates including updating the software updater (depending on the severity of the bug).
The application developer may write defensive code to handle this case. For example the application developer may surround the urlretrieve call with a try/except block and default to the original urllib in case of an unexpected exception. However this will defeat the purpose of TUF interposition and will no longer make TUF transparent to the application.
The tuf.interposition module, when given a urllib2.Request object, needs to ignore it if it is not a GET HTTP method.
I noticed that signercli.py, when generating RSA keys, confirms passwords with the user. This is a good feature.
However, quickstart.py does not do the same, and it would be nice to confirm passwords there too.
This optional module needs a unit test.
Review the committed changes to the following modules:
mirrors.py
test_mirrors.py
download.py
test_download.py
Following Daniel Holth's suggestion, investigate how JSON Web Signature would fit with TUF.
According to Monzur Muhammad: "He mentioned that it was more secure than RSA-2048 despite the key being a small size. You can use pip to install wheel, which can be used to generate/verify/sign keys."
Presently, the "paths" attribute of the delegation metadata object explicitly lists all the target files that the delegating role A trusts the delegated role B with.
Following the specification, we should implement the option of allowing A to trust B with any possible target file under its targets directory.
Hello, I am running Windows 8 x64 on python 2.7, and was able to successfully install TUF through the instructions provided here: https://github.com/dachshund/pip/wiki/pip-over-TUF
I get up to the installing django step, and I get this error:
File "C:\Users\leonw_000\Trishank Test\pipovertuf\lib\site-packages\tuf\interposition\updater.py", line 9, in
import tuf.client.updater
File "C:\Users\leonw_000\Trishank Test\pipovertuf\lib\site-packages\tuf\client\updater.py", line 110, in
import tuf.keydb
File "C:\Users\leonw_000\Trishank Test\pipovertuf\lib\site-packages\tuf\keydb.py", line 35, in
import tuf.rsa_key
File "C:\Users\leonw_000\Trishank Test\pipovertuf\lib\site-packages\tuf\rsa_key.py", line 42, in
import evpy.signature
File "C:\Users\leonw_000\Trishank Test\pipovertuf\lib\site-packages\evpy\signature.py", line 38, in
import evp
File "C:\Users\leonw_000\Trishank Test\pipovertuf\lib\site-packages\evpy\evp.py", line 27, in
libraries["ssl"] = ctypes.CDLL(ctypes.util.find_library("libeay32"))
File "C:\python27\Lib\ctypes__init__.py", line 365, in init
self._handle = _dlopen(self._name, mode)
WindowsError: [Error 193] %1 is not a valid Win32 application
Needs a review.
Situation: After making a delegation, on the download by the client, client's metadata was updated and the file download itself was successful. However, on the second try, when current metadata files are moved to the previous directory, following happens:
Traceback (most recent call last):
File "test_extraneous_dependencies_attack.py", line 213, in
test()
File "test_extraneous_dependencies_attack.py", line 192, in test
urllib_tuf.urlretrieve(url_to_file, downloaded_file)
File "/home/zk/Git/tuf/tuf/interposition/init.py", line 124, in __urllib_urlretrieve
return updater.retrieve(url, filename=filename, reporthook=reporthook, data=data)
File "/home/zk/Git/tuf/tuf/interposition/updater.py", line 141, in retrieve
temporary_directory, temporary_filename = self.download_target(target_filepath)
File "/home/zk/Git/tuf/tuf/interposition/updater.py", line 60, in download_target
targets = [self.updater.target(target_filepath)]
File "/home/zk/Git/tuf/tuf/client/updater.py", line 1432, in target
self._refresh_targets_metadata(include_delegations=True)
File "/home/zk/Git/tuf/tuf/client/updater.py", line 1268, in _refresh_targets_metadata
self._update_metadata_if_changed(rolename)
File "/home/zk/Git/tuf/tuf/client/updater.py", line 814, in _update_metadata_if_changed
self._update_metadata(metadata_role, compression=compression)
File "/home/zk/Git/tuf/tuf/client/updater.py", line 700, in _update_metadata
shutil.move(current_filepath, previous_filepath)
File "/usr/lib/python2.7/shutil.py", line 299, in move
copy2(src, real_dst)
File "/usr/lib/python2.7/shutil.py", line 128, in copy2
copyfile(src, dst)
File "/usr/lib/python2.7/shutil.py", line 83, in copyfile
with open(dst, 'wb') as fdst:
IOError: [Errno 2] No such file or directory: u'/home/zk/Git/tuf/tuf/tests/system_tests/tmp56Of78/tuf_client/metadata/previous/targets/role1.txt'
Needs a review.
tuf.download
needs stronger SSL verification (i.e. use stronger SSL ciphers and protocol versions).
This optional module needs a unit test.
Presently, the server and client examples are in separate directories.
The old layout of having all server+client examples in the same directory made sense to me because I could easily find everything.
Furthermore, the client examples should not live in the root directory because they are not meant to be run off the bat without setting up the server.
Maybe I didn't follow your discussion about what a replay attack means, but I am little confused about the current replay attack test.
What we have now is that the attacker replaces the contents of a target with an earlier one. I thought that a replay attack should be more about this sequence of events:
Please correct my understanding here.
I fast-forwarded my system clock beyond the root metadata expiry time, and found that the current implementation did not considered it expired.
I think that, presently, the time comparison is wrong. The comparison is happening between two time values of different units.
Should it not be like this instead?
- if expires < time.time():
- expires_formatted = tuf.formats.format_time(expires)
- message = 'Metadata '+repr(rolepath)+' expired on '+expires_formatted+'.'
+ if tuf.formats.parse_time( expires ) < time.time():
+ message = 'Metadata '+repr(rolepath)+' expired on '+ expires +'.'
The current specification of tuf.interposition interposes as urllib and urllib2 in an implicit fashion:
tuf.interposition.interpose()
# Now you have TUF...
url = 'http://example.com/path/to/document'
urllib.urlopen( url )
urllib.urlretrieve( url )
urllib2.urlopen( url )
tuf.interposition.go_away()
# ...and now you don't!
Justin's argument is that it is not obvious that tuf.interposition.interpose()
modifies urllib and urllib2. What else might the function modify unseen?
Following Python's guideline of "explicit is better than implicit", and the principle of least astonishment, we should change this interposition mechanism to look more like:
from tuf.interposition import urllib, urllib2
urllib.urlopen( url )
urllib2.urlopen( url )
How does the implementation plan to handle metadata for a software update repository with a large number of targets and target delegations? Presently, it looks like the metadata will be quite large if left uncompressed for a sufficiently large number of targets and target delegations.
A few solutions:
The test leaves temp directories. This needs to be fixed.
This optional module needs a unit test.
The client modules need your review.
Based on your experience integrating TUF with Seattle's software updater and working on the interposition module(s), how is TUF's usability? What about the code's readability? What can be improved, other than the documentation that will updated following the refactoring effort?
It is acceptable to require Python to generate TUF metadata on the server, but it is not acceptable to require Python to use TUF on the client.
We should think about how to port the tuf.client and tuf.interposition modules to Ruby, C and other programming languages.
This might involve designing and implementing a small "trusted base" in C, and then porting to other languages from there.
This optional module needs a unit test.
Document what would happen with our PyPI + TUF design in the various cases where an attacker compromises PyPI or its developers.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.