Giter Site home page Giter Site logo

gbip / sentry_tunnel Goto Github PK

View Code? Open in Web Editor NEW
24.0 0.0 4.0 67 KB

Proxy sentry request to a sentry server using a tunnel/proxy endpoint

License: BSD 2-Clause "Simplified" License

Dockerfile 6.12% Shell 3.55% HTML 2.22% Rust 88.11%
sentry tunnel

sentry_tunnel's Introduction

tests Image Size Docker Pulls

Sentry Tunnel

This is a proxy that forwards tunneled sentry requests to the real sentry server. The implementation is based on the explanation provided by the official sentry documentation.

A tunnel is an HTTP endpoint that acts as a proxy between Sentry and your application. Because you control this server, there is no risk of any requests sent to it being blocked. When the endpoint lives under the same origin (although it does not have to in order for the tunnel to work), the browser will not treat any requests to the endpoint as a third-party request. As a result, these requests will have different security measures applied which, by default, don't trigger ad-blockers.

From the sentry documentation

Please note that the minimal supported Relay version is v21.6.0. Older versions might work, but are not supported by this project. Explanation here

Configuration

This proxy looks for the following environnement variables :

  • TUNNEL_REMOTE_HOST : A comma separted list of sentry relays which are allowed to be tuneled by this service. Example : TUNNEL_REMOTE_HOST=https://sentry.example.com, https://sentry2.example.com.
  • TUNNEL_PROJECT_IDS : A comma separated list of valid project ids. Request that are not from those projects will be rejected. Example : TUNNEL_PROJECT_IDS=456,78,10840.
  • TUNNEL_LISTEN_PORT : The port that this application will bind to. Example : TUNNEL_LISTEN_PORT=7878. This is optional, the default value is 7878.
  • TUNNEL_PATH : The url path where the tunnel will be waiting for tunneled request. Example : TUNNEL_PATH=/tunnel. This is optional, the default value is '/tunnel'.
  • TUNNEL_IP : The ip that this application will listen on. Optional, the default value is 127.0.0.1.

Running with docker

The docker image lives here.

An example docker-compose file is provided. Otherwise :

docker run --rm -e 'TUNNEL_REMOTE_HOST=https://sentry.example.com' -e 'TUNNEL_PROJECT_IDS=1,5' sigalen/sentry_tunnel

Running without docker

git clone https://github.com/gbip/sentry_tunnel # Clone the project
cd sentry_tunnel 
cargo run --release # Build & run

License

BSD-2

sentry_tunnel's People

Contributors

gbip avatar

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

sentry_tunnel's Issues

Server certificate cannot be validated

Hello,

using the latest docker image as of today I receive an error message that the server certificate cannot be validated (copied verbatim below). The server is using Let's encrypt certificates which work fine when visiting the domain by browser (it's self-hosting sentry).

Any quick suggestions on what might be going? I saw there was a fix a while back dealing with missing SSL certs and think this should still be active. I haven't dug deeper into building the image myself and for whatever reasons cannot run a shell in the container to dig around in there myself (slightly puzzled by that actually).

Thanks!

sentry_tunnel | ERROR - the server certificate could not be validated - Host = not-shown-here

Big request body yields HTTP 400 Bad Request

In our Sentry deployment, we routinely get envelopes upwards of 8 kB in size. An example envelope might look as follows:

{"event_id":"394892e5d08540629f04030f086fd7a1","sent_at":"2023-07-28T12:35:10.454Z","sdk":{"name":"sentry.javascript.angular-ivy","version":"7.57.0"},"dsn":"https://[email protected]/1"}
{"type":"event"}
{"exception":{"values":[{"type":"TypeError","value":"null has no properties","stacktrace":{"frames":[{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"Me","in_app":true,"lineno":3,"colno":1823190},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"Ae/t[s]","in_app":true,"lineno":1,"colno":34874},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"16518/</</</</<","in_app":true,"lineno":1,"colno":546},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"ngDoCheck","in_app":true,"lineno":3,"colno":139492},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"detectChanges","in_app":true,"lineno":3,"colno":1866126},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"wc","in_app":true,"lineno":3,"colno":1864024},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"ic","in_app":true,"lineno":3,"colno":1855751},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"v_","in_app":true,"lineno":3,"colno":1857009},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"template","in_app":true,"lineno":3,"colno":142743},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"Ku","in_app":true,"lineno":3,"colno":1872812},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"Ro","in_app":true,"lineno":3,"colno":1859256},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"yd","in_app":true,"lineno":3,"colno":1864573},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"set ngIf","in_app":true,"lineno":3,"colno":1773982},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"_updateView","in_app":true,"lineno":3,"colno":1774371},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"createEmbeddedView","in_app":true,"lineno":3,"colno":1906839},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"createEmbeddedView","in_app":true,"lineno":3,"colno":1905948},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"Il","in_app":true,"lineno":3,"colno":1855275},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"v_","in_app":true,"lineno":3,"colno":1857009},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"Yo","in_app":true,"lineno":3,"colno":135807},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"eh","in_app":true,"lineno":3,"colno":1874809},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"O","in_app":true,"lineno":3,"colno":1875507},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"listen","in_app":true,"lineno":3,"colno":109488},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"listen","in_app":true,"lineno":3,"colno":2540963},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"addEventListener","in_app":true,"lineno":3,"colno":2535567},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"addEventListener","in_app":true,"lineno":3,"colno":2542769},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"k/</</<","in_app":true,"lineno":3,"colno":1063456},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"Ae/t[s]","in_app":true,"lineno":1,"colno":34874},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"16518/</</</</<","in_app":true,"lineno":1,"colno":546},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"p/<","in_app":true,"lineno":1,"colno":45268},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"scheduleEventTask","in_app":true,"lineno":1,"colno":24848},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"scheduleTask","in_app":true,"lineno":1,"colno":24457},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"scheduleTask","in_app":true,"lineno":1,"colno":28225},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"onScheduleTask","in_app":true,"lineno":1,"colno":25548},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"scheduleTask","in_app":true,"lineno":1,"colno":28327},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"De/o<","in_app":true,"lineno":1,"colno":43351},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"Ae/t[s]","in_app":true,"lineno":1,"colno":34874},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"EventListener.handleEvent*16518/</</</</<","in_app":true,"lineno":1,"colno":546},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"le","in_app":true,"lineno":1,"colno":42844},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"B","in_app":true,"lineno":1,"colno":42671},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"v","in_app":true,"lineno":1,"colno":42253},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"invokeTask","in_app":true,"lineno":1,"colno":29618},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"runTask","in_app":true,"lineno":1,"colno":23892},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"invokeTask","in_app":true,"lineno":1,"colno":28485},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"onInvokeTask","in_app":true,"lineno":3,"colno":1917496},{"filename":"https://our-app-url.com/de/polyfills.e2fee55d4d4e7362.js","function":"invokeTask","in_app":true,"lineno":1,"colno":28564},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"k/<","in_app":true,"lineno":3,"colno":2538415},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"G","in_app":true,"lineno":3,"colno":1876015},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"N","in_app":true,"lineno":3,"colno":1875844},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"Yo/<","in_app":true,"lineno":3,"colno":135872},{"filename":"https://our-app-url.com/de/main.0580ea5ff873bfe6.js","function":"crash","in_app":true,"lineno":3,"colno":141064}]},"mechanism":{"type":"angular","handled":false}}]},"level":"error","event_id":"394892e5d08540629f04030f086fd7a1","platform":"javascript","timestamp":1690547710.452,"environment":"nightly","release":"7.5.2","sdk":{"integrations":["InboundFilters","FunctionToString","Breadcrumbs","GlobalHandlers","LinkedErrors","Dedupe","HttpContext","BrowserTracing"],"name":"sentry.javascript.angular-ivy","version":"7.57.0","packages":[{"name":"npm:@sentry/angular-ivy","version":"7.57.0"}]},"contexts":{"angular":{"version":15}},"breadcrumbs":[{"timestamp":1690546950.965,"category":"console","data":{"arguments":["Matomo not found"],"logger":"console"},"level":"warning","message":"Matomo not found"},{"timestamp":1690546950.994,"category":"navigation","data":{"from":"/de/start","to":"/de/start"}},{"timestamp":1690546951.327,"category":"xhr","data":{"method":"GET","url":"/api/user","status_code":401},"type":"http"},{"timestamp":1690546951.333,"category":"xhr","data":{"method":"GET","url":"/api/quickstart/projects?showInvisible=false","status_code":200},"type":"http"},{"timestamp":1690546952.337,"category":"sentry.transaction","event_id":"08e72500c54f4a33882c3d75fc14d84f","message":"08e72500c54f4a33882c3d75fc14d84f"},{"timestamp":1690547707.477,"category":"ui.click","message":"mat-icon.mat-icon.notranslate.material-symbols-outlined.mat-icon-no-color"},{"timestamp":1690547709.084,"category":"ui.click","message":"div#mat-tab-label-0-3.mat-ripple.mat-tab-label.mat-focus-indicator.ng-star-inserted.cdk-focused.cdk-mouse-focused"},{"timestamp":1690547710.449,"category":"ui.click","message":"span.mat-button-wrapper"}],"request":{"url":"https://our-app-url.com/de/start","headers":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0"}}}

For these large requests, the Sentry tunnel Docker container always returns an HTTP 400 error with an empty response body. Smaller requests work just fine.

I have tried sending this request directly to the Sentry tunnel container via curl and get the same error. When I send the exact same request and simply shorten the third line of the request body to {"exception":{"values":[]}}, the request goes through just fine (HTTP 200 OK). Therefore, I hightly suspect that the size of the request is the problem somehow.

Any idea what the issue might be? Thanks in advance!

The tunnel returns an error

I configured the tunnel with the docker image but it returns an error like this one:

thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: RelativeUrlWithoutBase', src/envelope.rs:75:36
stack backtrace:
   0:     0x7f5316af1a50 - std::backtrace_rs::backtrace::libunwind::trace::hb16dbf761681cfc0
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/../../backtrace/src/backtrace/libunwind.rs:90:5
   1:     0x7f5316af1a50 - std::backtrace_rs::backtrace::trace_unsynchronized::h53bc5f57122de54d
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
   2:     0x7f5316af1a50 - std::sys_common::backtrace::_print_fmt::h7e86959aa36cde43
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/sys_common/backtrace.rs:67:5
   3:     0x7f5316af1a50 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::hf42958820747a8ac
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/sys_common/backtrace.rs:46:22
   4:     0x7f5316b2f5ec - core::fmt::write::h6f5ededa5074697e
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/core/src/fmt/mod.rs:1115:17
   5:     0x7f5316aec4c5 - std::io::Write::write_fmt::hdb84dc6c28fda870
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/io/mod.rs:1665:15
   6:     0x7f5316af3a2b - std::sys_common::backtrace::_print::hbb646398d13d0dcb
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/sys_common/backtrace.rs:49:5
   7:     0x7f5316af3a2b - std::sys_common::backtrace::print::ha3796c9cf0c5a732
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/sys_common/backtrace.rs:36:9
   8:     0x7f5316af3a2b - std::panicking::default_hook::{{closure}}::hb85a09d7e9a16432
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/panicking.rs:208:50
   9:     0x7f5316af3501 - std::panicking::default_hook::hdc924e74cb190bbb
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/panicking.rs:225:9
  10:     0x7f5316af40f4 - std::panicking::rust_panic_with_hook::hd63b080e78590a80
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/panicking.rs:622:17
  11:     0x7f5316af3bd7 - std::panicking::begin_panic_handler::{{closure}}::h27bfba1f7e931f90
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/panicking.rs:519:13
  12:     0x7f5316af1eec - std::sys_common::backtrace::__rust_end_short_backtrace::h2cc025f6c95b1f82
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/sys_common/backtrace.rs:141:18
  13:     0x7f5316af3b39 - rust_begin_unwind
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/panicking.rs:515:5
  14:     0x7f53166dcfc1 - core::panicking::panic_fmt::h9f5a85773697c5f5
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/core/src/panicking.rs:92:14
  15:     0x7f53166dd0b3 - core::result::unwrap_failed::h43465fb8e3273283
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/core/src/result.rs:1599:5
  16:     0x7f5316766436 - <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::try_fold::he5a303e7c20755b8
  17:     0x7f531676a8ca - sentry_tunnel::envelope::SentryEnvelope::dsn_host_is_valid::h30a61ee7ef4bf7a3
  18:     0x7f53167614cf - <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll::h45378b13883fce05
  19:     0x7f53169a2299 - <futures_util::future::future::Map<Fut,F> as core::future::future::Future>::poll::h44031fba5a98df6f
  20:     0x7f53169a8723 - <futures_util::future::try_future::try_flatten_err::TryFlattenErr<Fut,<Fut as futures_core::future::TryFuture>::Error> as core::future::future::Future>::poll::hffbccbab3c0ac311
  21:     0x7f53169a2087 - <futures_util::future::future::Map<Fut,F> as core::future::future::Future>::poll::h14a2da13cf059196
  22:     0x7f53169b544e - <futures_util::future::try_future::try_flatten::TryFlatten<Fut,<Fut as futures_core::future::TryFuture>::Ok> as core::future::future::Future>::poll::hc75c02f2ccdc0150
  23:     0x7f53169a6899 - <futures_util::future::try_future::AndThen<Fut1,Fut2,F> as core::future::future::Future>::poll::h447c699d0fe8a828
  24:     0x7f531671fa55 - <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll::h9fb6797044475bd9
  25:     0x7f531671f398 - <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll::h6f29778b3c2e7dbb
  26:     0x7f531671a7ac - hyper::proto::h1::dispatch::Dispatcher<D,Bs,I,T>::poll_inner::h2b6debe1116855b1
  27:     0x7f531674cb08 - <hyper::server::conn::upgrades::UpgradeableConnection<I,S,E> as core::future::future::Future>::poll::hdcf8a662511ecb96
  28:     0x7f5316705b8b - <futures_util::future::future::map::Map<Fut,F> as core::future::future::Future>::poll::h61d1508f30d73881
  29:     0x7f531671fc4c - <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll::hb8a6706da5256738
  30:     0x7f531675508e - tokio::runtime::task::core::CoreStage<T>::poll::hb55bce37b34d2b50
  31:     0x7f531670feb7 - tokio::runtime::task::harness::poll_future::he66fdad49a51c82e
  32:     0x7f5316710ddf - tokio::runtime::task::harness::Harness<T,S>::poll::h3481a8149d288967
  33:     0x7f5316abeb06 - tokio::runtime::thread_pool::worker::Context::run_task::h484d166afc49137b
  34:     0x7f5316abd927 - tokio::runtime::thread_pool::worker::Context::run::he56ca1e64e14a46b
  35:     0x7f5316aaa803 - tokio::macros::scoped_tls::ScopedKey<T>::set::h4f4d6d229172db0a
  36:     0x7f5316abd1c1 - tokio::runtime::thread_pool::worker::run::h10dbecfad0c64130
  37:     0x7f5316ac39f1 - tokio::loom::std::unsafe_cell::UnsafeCell<T>::with_mut::hcca2e7104e44bce9
  38:     0x7f5316aa65f8 - tokio::runtime::task::harness::Harness<T,S>::poll::h39ba1fac2c9f2fc6
  39:     0x7f5316ab39d1 - tokio::runtime::blocking::pool::Inner::run::hca3bbe70b8fdad36
  40:     0x7f5316aa72cb - std::sys_common::backtrace::__rust_begin_short_backtrace::h5a2acef6ae0efedd
  41:     0x7f5316ac5518 - core::ops::function::FnOnce::call_once{{vtable.shim}}::h00a79e75441986e7
  42:     0x7f5316af74b7 - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h4b43062ddf86e957
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/alloc/src/boxed.rs:1572:9
  43:     0x7f5316af74b7 - <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once::h140d9feeaae0eb97
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/alloc/src/boxed.rs:1572:9
  44:     0x7f5316af74b7 - std::sys::unix::thread::Thread::new::thread_start::hf02895d7c8c67d27
                               at /rustc/c8dfcfe046a7680554bf4eb612bad840e7631c4b/library/std/src/sys/unix/thread.rs:74:17

Any idea why?

Allow any project_id

Currently, a whitelist of project_ids is required.

We are running a local Sentry instance, where projects are created in self-service, and would like to provide projects with the option to use a central tunnel instance for their public client deployments instead of each project having to roll their own.

Currently, this is not possible without unacceptable maintenance overhead due to the project whitelist. We do not really see a security benefit in our case to have this whitelist, and would like to opt out of it.

Could we maybe include a special value of * or similar to explicitly allow any project_id? That would keep the config check, but give those wish to the option to opt out.

I could try to contribute a patch, however I'm not really good in Rust.

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.