Skip to content

Instantly share code, notes, and snippets.

@dominictarr
Created November 26, 2018 22:39
Show Gist options
  • Save dominictarr/9fd9c1024c94592bc7268d36b8d83b3a to your computer and use it in GitHub Desktop.
Save dominictarr/9fd9c1024c94592bc7268d36b8d83b3a to your computer and use it in GitHub Desktop.
statement on event-stream compromise

Hey everyone - this is not just a one off thing, there are likely to be many other modules in your dependency trees that are now a burden to their authors. I didn't create this code for altruistic motivations, I created it for fun. I was learning, and learning is fun. I gave it away because it was easy to do so, and because sharing helps learning too. I think most of the small modules on npm were created for reasons like this. However, that was a long time ago. I've since moved on from this module and moved on from that thing too and in the process of moving on from that as well. I've written way better modules than this, the internet just hasn't fully caught up.

@broros

otherwise why would he hand over a popular package to a stranger?

If it's not fun anymore, you get literally nothing from maintaining a popular package.

One time, I was working as a dishwasher in a resturant, and I made the mistake of being too competent, and I got promoted to cook. This was only a 50 cents an hour pay rise, but massively more responsibility. It didn't really feel worth it. Writing a popular module like this is like that times a million, and the pay rise is zero.

I've shared publish rights with other people before. Of course, If I had realized they had a malicious intent I wouldn't have, but at the time it looked like someone who was actually trying to help me. Since the early days of node/npm, sharing commit access/publish rights, with other contributors was a widespread community practice. https://felixge.de/2013/03/11/the-pull-request-hack.html open source is driven by sharing! It's great! it worked really well before bitcoin got popular.

So right now, we are in a weird valley where you have a bunch of dependencies that are "maintained" by someone who's lost interest, or is even starting to burnout, and that they no longer use themselves. You can easily share the code, but no one wants to share the responsibility for maintaining that code. Like a module is like a piece of digital property, a right that can be transferred, but you don't get any benefit owning it, like being able to sell or rent it, however you still retain the responsibility.

I see two strong solutions to this problem...

  1. Pay the maintainers!! Only depend on modules that you know are definitely maintained!
  2. When you depend on something, you should take part in maintaining it.

Personally, I prefer the second, but the first probably has it's place. These arn't really mutually exclusive, anyway.

As to this particular issue, I have emailed npm support and suggested that they give the module to @FallingSnow and ar @XhmikosR

@reidelliott
Copy link

@dominictarr – This all sounds very frustrating for you to deal with such a backlash. ༼ つ ◕_◕ ༽つ

@PhilippLgh
Copy link

One solution would be to multi-sign modules. Just "signing" code would already be great actually :D
But imagine a new collaborator was added or a new author would take over, make a change, and sign with his certificate. Then the original author would check (or not) and countersign.
You can now actually have tooling that trusts "original author" because you are using his software for 7 years but detects a "new author's" signature without any trust. Add some security / threat model to the mix and let your module manager reject updates or alert you when this happens.
In this kind of situation developers could then targeted audit modules in question...
This would also have the advantage that CI could publish releases without the need to access your certificate dongle / hardware token.

@jonerer
Copy link

jonerer commented Nov 28, 2018

Just a comment here. You say this:

Pay the maintainers!! Only depend on modules that you know are definitely maintained!
When you depend on something, you should take part in maintaining it.

I'm not saying I disagree with this. It's perfectly resonable. The thing that has seemed to cause this mess is that even if I choose carefully which maintainers I trust, I can't possibly know what they will trust 4 steps away.
The reason why Web-Of-Trust didn't really become a big thing is that trust doesn't transfer that easily.

Even if I trust nodemon, that doesn't mean I trust nodemon->x->y->z->a->b dependency. Maybe that's why the node ecosystem is so brittle; it's great reliance on small modules means that trust is delegated in an irresponsible way.

@aercolino
Copy link

I think a problem is that npm updates are built with hotfix in mind. Dependencies are immediately updated. Instead, whatever the version bump, there should be a buffer between release and widespread use. Something based on time and community vetting, maybe. But something that is unavoidable would mean a lot.

@nirizr
Copy link

nirizr commented Nov 28, 2018

One of the reasons you're not getting donations, in my opinion, to your packages with tens of millions in weekly downloads, is that the majority of them are not groundbreaking and/or sophisticated enough to warrant a donation. When the community is so used to use a package dependency instead of implementing a single function of a few lines of code (e.g. the left-pad fiasco), it feels keeping things simple doesn't get enough weight in (some) development efforts.
Instead of condensing on the valuable and meaningful packages, it appears the npm community strives to do the opposite - create as many similar, loosely maintained packages.

@eirslett
Copy link

@fzammetti
Copy link

I see some people calling for an apology. I don't think that's in order. I have my own not positive feelings about what @dominictarr did, but I'd prefer we just learn from this rather than pointing fingers at someone who contributed when he didn't have to. Most developers never take that step so I'd prefer not to pile on one who did.

Speaking as someone who has created, maintained and, for all intents and purposes, abandoned F/OSS projects, it's a tough situation to be in. Hopefully every maintainer of every project does feel a sense of responsibility to the community they build, but on the other hand, we can't expect people to not walk away for various reasons (including, as some have pointed out: death). It's simply the nature of the game. But knowing that, you've got to do it responsibly (when possible).

Now, is that responsibility entirely on the single person maintaining the project? Effectively yes, but it probably shouldn't be - it should also be on the community I think. After all, many people seem to think a maintainer has a responsibility to the community (which I agree with), but doesn't the community also have some responsibility to the maintainer? Isn't that kind of one of the basic principles of OSS? I think so.

Given that, my suggestion would be the kind of model that Meetup.com uses for abandoned groups, with some additions/mods.

First, a maintainer clicks a button somewhere that says "I intend to abandon this project". At that point, a clock starts ticking. By the time it goes off, say maybe 90 days in the future, one of two things happens. The first possibility is someone steps up and says "I would like to take stewardship" (and the maintainer can nominate people if they already have someone lined up too). If more than one, that's fine too. At that point, verified members of the community begin to vote (one vote per account naturally, and the vote can be changed up until the clock stops). Whoever has the most at the end is the new maintainer. Simple as that! The expectation is that, in basic open-source fashion, more eyeballs will yield better results. Voters would be expected to do some vetting, in whatever way makes sense. They can comment and discuss, and those up for a vote can, of course, comment back, try and sell themselves in essence.

In short: it becomes an election process that the entire community of the project participates in.

The other possibility is nobody steps up (or, I suppose, nobody gets any votes). In that case, the answer is simple: the project is frozen at the point that "I'm outta here" button is clicked (and that would happen either way in fact, but just temporarily if someone is ultimately elected). It's still available for use, but no more commits, no more bug reports, no nothing. It is then marked abandoned, deprecated, whatever status makes sense (maybe a new one even) and that's that. You make sure it's abundantly obvious on the screen that this project is no longer maintained, big red flashing banner, whatever.

If someone comes along later and wants to take over, they go through the voting process as described but they then have to fork the project with a new name. Some links can be added from the original project to indicate "hey, switch to this if you want the maintained version", but the old continues to exist in its final state, no longer able to hurt anyone (aside from bit rot).

I would suggest that anyone interested in the project must sign up for a mailing list and those people will be automatically contacted when a maintainer says they intend to abandon a project. I suppose it could go out to a wider audience, all registered NPM users or something like that, but I'd think something more akin to a "hey, I'm interested in/use this project, lemme know if anything happens with it" kind of self-limiting list would be more appropriate.

As a corollary, I would also suggest, essentially, a "dead man's switch" applied to all packages that leads to the same election process. If the maintainer doesn't perform some action for, say, six months, then the process automatically kicks off. Well, first they get an automated "hey, you still good?" message, with maybe a week or two to reply to halt the process from beginning. If not, THEN it's election time!

I think, in my mind, something like this would be the best of all possibilities. Projects can still be abandoned, but now it's a much more transparent and planned for event, even in the case of unavoidable sudden departures. I'm sure it's not a perfect system, but to me, I think it makes a lot of sense to at least explore such a thing. It probably makes the most sense to be built right into the NPM infrastructure, but it really might not have to be (aside from the whole freezing the project aspect) because I can envision someone building this as a third-party service that ANY F/OSS project anywhere could then hook into. I wish I had the time to put into building it myself, but I don't. I'll regret it I make someone else a millionaire of course :) But, I'd rather put the suggestion out there and let it either die on the vine or let someone run with it for the benefit of all.

@vitali2y
Copy link

@dominictarr thank you for all your contribution!

@macfreek
Copy link

macfreek commented Nov 29, 2018

@Forevernowandgone, I'm afraid I disagree with your statement:

If you write code and open source it, fine, leave it at that.

But becoming a maintainer of a package that is used by millions of projects puts some modicum of trust and responsibility on you. The two are completely different.

In light of what happened, It would probably be good if these two are completely different, but in fact, they're now exactly the same. The OP wrote code, and posted it on GitHub. From what I read, he did like to leave it at that. Case in point: the OP points to alternative libraries he wrote later that he deems 'better', it is implied that he rather saw those become popular.

You seem to assume that it is possible for a community to bestow responsibility upon an open source author, even if the author is not willing to or unable to take that responsibility.

As for your argument that the author must mark the library as "archived", I think that's already implied: the author explicitly included a no-warranty statement, and you can see that it is not actively maintained. Relying on an active "archive" flag would fail for authors who are no longer able to modify. People move on, or even pass away.

I agree that we need better indications of high-quality, well-maintained libraries. However, forcing non-active authors to tag their no-longer-of-interest projects does not seem like the most effective way to do that.

@dfraser-veea
Copy link

@macfreek Well stated all around. I have thumbed quickly through this thread and I was startled to see the assumptions that people make about "hey, you wrote this, now you must maintain it". For free. Forever. All responsibility and no reward.

Your final line speaks truth. I don't know what the solution is, but access tracking to the project has to provide some indicator.

Yes, we all want safe secure code, but telling people that they own lifetime responsibility just by the mere act of publishing is going to shove otherwise responsible people out. Most things I see on github are in the realm of 'here is a thing I did that I used for this purpose". Feel free to fork it and play around, but don't assume someone is going to 'own' it for your sake. If it is important to you, fork it, and take responsibility yourself.

@StephenLynx
Copy link

protip: stop using too many libraries. That's the whole npm community problem. People have a dependency fetishism.

@StephenLynx
Copy link

I don't think the problem is too many modules or how a maintainer should or shouldn't behave.
The problem is that the platform has irrestricted access to the host machine, no sandbox, no lockdown.
We need to install modules without worrying.
It's a lot better to invest time trying to give the right permissions so a project can run, instead of wasting time auditing a gazilion modules.

Servers are not smartphones. It's an utter waste of time doing either. People are just using the tools they have in a irresponsible way.

@eckerdj7
Copy link

Well after thoroughly reading through a large portion of the original issue and this post here, I have a few things I'd like to mention to anyone who wants to take the time to read my short essay here. I hope it sheds some light for people reading. I also want to point out that I hold no ill will to the two users I am going to reply to. I just disagree with some of your points. (I should also mention that I have not personally been affected by this issue. I just see a lot of discussion about the moral responsibilities we have as programmers that are concerning to me.)

Firstly, @macfreek, I would disagree with your statements, too.

True, non-active authors shouldn't be expected to mark stuff as archived. They are non-active. But the OP is not non-active. He took the time to hand the project off. In regards to marking something archived for those who are actually non-active, I would think that should be something built into the ecosystem. NPM and GitHub should probably do that automatically after no maintenance has occurred for some period of time. But neither of those scenarios happened with the OP.

With due respect, @Forevernowandgone you couldn't be more wrong in blaming @dominictarr in any way or form. If his motivation is fun then that's what his motivation is. If he also shared his work, be grateful -- and that's where it ends. Those who decided to use his code are responsible for said decision.

And with due respect to you, @chx, I believe you couldn't be more wrong here. You are making a claim that this guy is wrong, so you obviously believe in some kind of moral absolutes. But then you completely let the OP off the hook in a way that implies moral relativism saying his motivation was "fun", so that's what his motivation is, implying nothing else he did matters? If his motivation was fun, then why publish it online? You don't need to do that to have fun. Publishing online implies you want to help others by providing your software to them. He chose to do so. And we should definitely be thankful to @dominictarr that he did choose to share it, as it has clearly been used by a large portion of the community.

I'm not saying people aren't responsible for using his code. They certainly are. But to resolve @dominictarr of all responsibility makes no sense. Obviously, he is not legally responsible as most people have been saying. But he does hold some responsibility.

Life is full of responsibilities that we incur on ourselves and sometimes we have them thrust upon us without our consent. Think of it like this. If the OP was walking alone down the sidewalk and saw a toddler about to crawl onto a busy roadway, would it not be his moral responsibility as a human to stop the toddler from crawling onto the road? He did not ask for or want that responsibility, but I think any reasonable person would say he should be held responsible (as well as the irresponsible parent mind you) if he had the ability and opportunity to stop harm from coming to the child, yet chose not to do so. In the same manner, he created and offered this code freely online for anyone to use. He then chose to hand that code off to someone else. Whether or not he did any vetting of the guy or not, he chose that action. Then there is this:

As it happened, I was coming around to this decision. A while after giving event-stream away, I shifted 343 modules to an account that I no longer control - so that I can more easily say "no, I can't help, I cannot publish that module anymore". I had been intending to archive them on npm, but neither npm or github have tools to do things like this in bulk, and I had hundreds of modules to disown. I was gonna write a script to do this, I just hadn't gotten around to it yet... At the time of disowning all these modules, I had already transferrered event-stream, and no malicious activity had been reported, so I just removed my self.

He gave away the event-stream code, but decided to not do so for the 343 other modules, so he obviously realized abandoning them was a better option than giving them all away to a stranger. And he states when he was disowning them, he had already transferred event-stream. At any rate he obviously knows he made a bad decision. And I agree that nowhere in his statements (that I've seen at least) has he expressed remorse or apologized for making the bad decision. I think that's the biggest reason as to why so many people are angry with him. It's not that he made the bad decision, it's that he continues to defend himself without expressing remorse.

That being said, it is very easy to just want to defend yourself when you have so openly and on such a large stage been exposed to basically public lashing and vitriol by so many people. It's a very understandable response. But I hope that he will eventually just add that he made a mistake and that he is sorry for it. Explicitly stating it and showing vulnerability here would go a long way towards diminishing a lot of the hatred he's been shown. If you read this, @dominictarr, that's my only advice. But thanks for all the work you have done for the community.

@mirskiy
Copy link

mirskiy commented Nov 29, 2018

I wanted to add my support for a distributed audit/review process along the lines of what @PhilippLgh and @eirslett (https://medium.com/@eiriksletteberg/a-proposition-to-collectively-security-audit-the-most-used-npm-packages-36ed8cbc1f8d) are suggesting. I believe this audit process should be more than just a set of signatures; it should include a weakest-link philosophy for dependencies, a trust metric to highlight weaknesses without having to recursively inspect each dependency, and a review process to facilitate the community.

Code review has become a standard part of many developer environments, but it has not yet embedded itself in the open-source culture. It is easy to publish a new package to npm or other package managers - and this is important, keeping the barrier to entry low encourages new members to the community. This also creates millions of points of failure - as we saw in this example, a change made by one person spread to hundreds or thousands of packages through dependencies. The traditional answer has always been, "Audit your dependencies," but I think we can safely say this is unrealistic; sub dependencies are a vulnerability too, so either you have to place trust in your dependencies to audit their dependencies (creating another single point of failure) or you have potentially thousands of dependencies to audit.

Some of the proposed solutions discuss paying maintainers, but this cannot solve the problem. Imagine a scenario where a Bad Actor bribes or compromises a developer of a single-maintainer package. Instead, by allowing distributed review and auditing of commits, we immensely decrease the attack surface and increase the likelihood that an attack like this will be caught sooner. Other solutions mention marking modules as maintained/un-maintained. While this would be a nice feature, it does nothing to mitigate the risk of a single rogue/compromised maintainer.

I imagine the solution consisting of a set of signatures on (ideally) every commit or (at least) every release. This is no different than the code review processes at a large organization - you would never allow code into production if it has only seen one set of eyes. The challenge is determining who to trust - a bad actor could still potentially use bot accounts to create lots of signatures on a malicious commit. This is probably the hardest part of the model, but one that I believe can be solved. One possibility is a web of trust that starts with well known developers in the community and allows vetting or delegation of trust, somewhat similar to Linux's "trusted lieutenants" with many layers. The model should follow a weakest-link philosophy - a package can only be as strong as the weakest of its dependencies.

Unlike others have proposed, I do believe that there needs to be a trust metric in this model. While a metric opens the potential for gaming the system, I do not believe that the model can succeed without it. Unless a centralized set of "auditors" form that can guarantee a package's safety, each developer will still have to inspect the reviewers for each of their dependencies and place trust in them to audit their dependencies - bringing us back to the original trade-off of trust vs. thousands of sub-dependencies to verify.

Such a model would also facilitate some of the other solutions mentioned. As @eirslett discusses, it would allow the formation of bounties and paid auditors to encourage the code reviews. Also, unlike solutions which may increase the barrier to entry to publishing a package, I believe this would encourage it and facilitate the community. As a developer, knowing that a package I publish could potentially be used in someone else's production system greatly increases the burden on me before publishing. While the MIT license lifts the legal responsibility, I still feel a personal responsibility for my work. This would lower that burden, as a package would be reviewed multiple times before it is likely to be used in production.

Ideally, this model would be separate from the existing package managers - there are plenty of times when I see a useful repo on Github that hasn't been published to a package manager. We already use community information to determine the status of the package at a glance - stars, commit frequency, and the author's affiliation/activity - but these metrics still leave a single point of failure.

Not only would this facilitate security, it would encourage the community to grow. I can envision new developers following and auditing popular projects they are using as well as benefiting from the review comments - as many already do from the code reviews at organizations. Obviously, the model of code review is not new, the goal is to extend it past organizations and into the open source community at large.

TLDR; We need a distributed code review model for open source that utilizes trust to remove the thousands of single-point failures that we currently have.

@Stargateur
Copy link

Stargateur commented Nov 29, 2018

@eckerdj7

Life is full of responsibilities that we incur on ourselves and sometimes we have them thrust upon us without our consent. Think of it like this. If the OP was walking alone down the sidewalk and saw a toddler about to crawl onto a busy roadway, would it not be his moral responsibility as a human to stop the toddler from crawling onto the road? He did not ask for or want that responsibility, but I think any reasonable person would say he should be held responsible (as well as the irresponsible parent mind you) if he had the ability and opportunity to stop harm from coming to the child, yet chose not to do so. In the same manner, he created and offered this code freely online for anyone to use. He then chose to hand that code off to someone else. Whether or not he did any vetting of the guy or not, he chose that action.

Wrong, please, again, stop talk about what you know nothing, you have legal responsibility to help people if they are in immediate danger, you also have legal responsibility to take care of your child. A code is not a child stop this stupid comparaison, MIT licence explicitly say I don't take any responsibility what so ever. Stop impose your own vision of responsibility, law are fucking here for that.

He gave away the event-stream code, but decided to not do so for the 343 other modules, so he obviously realized abandoning them was a better option than giving them all away to a stranger.

Not at all, again, you understand nothing, he was asked by someone about this project, give it away and then decide to just throw away the other module, obviously he wanted to turn a page and not have to handle futur mail about people asking to maintain his past project. He just do it for this one because it was probably the first where someone trouble him with mail.

It's not that he made the bad decision, it's that he continues to defend himself without expressing remorse.

Again stop impose your moral to other people ! You are totally wrong. He didn't do anything wrong. Also, he didn't defend himself, he just say fact. We are not in a trial.

@yonjah
Copy link

yonjah commented Nov 30, 2018

Random people on the internet are getting angry because they trusted some random person on the internet who ended up trusting the wrong random person on the internet.

This sounds like a bad joke but this is how our industry operates.

The anger towards the original maintainer is not justifiable but we can certainly understand where its coming from.
Even a small project will use a few dozen dependencies even if you took the time to check each of those superficially on every update each of those dependencies has it's own dependencies and routinely check all the 3rd party code incorporated into our apps is not an option even for a big and well founded team (and don't forget the OS and other code your server is running to support your app).
So anyone affected is probably feeling defenseless and bruised from this situation and looking for someone to blame is just natural.

So instead of pointing fingers we should thing how as an industry we are going to solve this issue. collective security audits as suggested might be useful but are probably not enough. Validation of code is important but requires a lot of time and a rogue maintainer can easily out resource even the most highly founded audits. So the first step should be validating identity of lead maintainers and maybe even offering some training about basic security concepts specifically related to supply chain attacks so at least we know we can trust the lead maintainer and that he will be cautious about who he give publish permissions and the code he accepts from other contributors.

I don't think this is something the community can handle without the support of NPM and probably Node.js Foundation.
NPM at minimum should offer a way to identify verified packages (who's owners been verified) but should probably handle and fund the personal verification process.

@JoshuaVSherman
Copy link

Well said, @dominictarr. This is not a problem with Node. This is not a problem with npm, this is not a problem with you. This is a problem with people being put in a state of responsibility for something they didn't ask to be put in a state of responsibility for.

You wrote code because you wanted to and gave it away free. Someone else decided to depend on that code, which in return got depended on by someone else, next thing you know you're little package is being used by 2/3rds of the internet.. And you're somehow required to give 100% SLA to people who use that code, because they decided to build a business that depends on that code and feel they aren't responsible for paying for it, because IT'S OPEN SOURCE!!!

I see your point, however, there is a problem when people are trying to help maintain some code and submit a pull request to the project, only to have it sit there and never get reviewed or merged, then what?

@KyleMit
Copy link

KyleMit commented Nov 30, 2018

Lots of people here blaming @dominictarr as the proximate cause, but missing the larger picture. If a single bad actor can compromise the security of your entire system, it's not a well designed system. Databases with thousands of credit cards don't stay secure because all the employee simultaneously opt to be virtuous. It might be a heavy lift to rule out this class of attack, but it'll remain a viable attack vector if your solution is to just yell at npm authors

@acsteitz
Copy link

acsteitz commented Dec 1, 2018

There are many people on here complaining about Dominic's "lack of remorse." Guessing that those people live somewhere that does not allow law suits on a whim. With the magnitude of this incident, if Dominic publically expresses remorse then you can bet someone will construe that as accepting legal responsibility and try to sue him. If you want more people to apologize when they mess up, do something about the courts that allow stupid law suits to proceed.

@noscripter
Copy link

our mortgage, car, insurance or food. The fact is this guy used his own free time and his repo exploded helping thousands of other developers not have to write something themselves. I don't agree with what happened but pets be real. This kind of response is bullshit. I hate reading this every time something like this happens. Take your honor and respect and throw it out in the trash. Most developers don't make shit tons of money.

I mean what I mean, just archive it is a much better way.

@gaybro8777
Copy link

gaybro8777 commented Dec 31, 2018

We hear and are with you buddy. (Edit.)

@Dealscribs
Copy link

Man what a terrible situation, I saw many open source maintainers asking for help on projects. No doubt.

@daniaruba
Copy link

This game is superior to wordle and wordle-like games absurdle

@joeroot909
Copy link

As everyone who has a connection with the gaming world knows that Roblox is a heaven of games. Here, you can get a number of well-known and user-created games. However, Verdant Moon Trello these games are entertaining, energetic, and attractive to play.

@irisharaba
Copy link

Many open-source developers have been posting requests for assistance with project automation and functional testing.
snow rider 3d

@eckerdj7
Copy link

Welp, I have been getting emails for this thread for some time now and finally decided to unsubscribe, but got curious about what it was. I didn't even remember I posted on this and then found this reply:

Again stop impose your moral to other people ! You are totally wrong. He didn't do anything wrong. Also, he didn't defend himself, he just say fact. We are not in a trial.

@Stargateur The irony of this statement and the fact you totally missed it is hilarious. Telling me I am "totally wrong" and to "stop impos[ing]" my morals, while you are doing exactly that. I was posting my opinion and you're also allowed to have yours. But I hope you consider taking a different approach in the future.

Also, the point of making a comparison between two things like I did is to draw parallels that help concepts to be understood. I wasn't saying code is a child. The point was that sometimes we have social and civic responsibilities that we didn't ask for. The MIT license is great and all, but I wasn't talking about legal responsibility. Just because someone slaps an MIT license on some code and shares it online, doesn't mean they can put malware in it and not have consequences. I'm not saying he did that. Just making another comparison.

Not at all, again, you understand nothing

I don't understand most things, I'll give you that. But I like to think I understand some things pretty well. Like how to talk to random strangers on the internet. Maybe try being more respectful and compassionate to others? I'm not sure why you felt the need to respond to my comments in the way you did, but I genuinely hope you don't treat those you interact with in real life in this way.

@caseyloomis
Copy link

@geometry dash lite talk with me: "Many developers create open-source modules for personal enjoyment and learning. Sharing these modules helps others and fosters further learning."

@eromelife
Copy link

This is so gorgeous, Sarah! I love the softness of the flowers against that earthy stone. Beautiful
https://eromelife.com/

@spotifypremium1
Copy link

Download TopFollow the latest version of for Android. Enjoy unlimited free followers on Instagram. It’s 100% effective and completely free. Get the most recent official Top Follow APK with all the key features included.

@spotifypremium1
Copy link

spotifypremium1 commented Oct 13, 2024

HD Streamz APK is a 100% amazing app for watching TV channels and shows on various devices. Download HD Streamz Live TV App 2024.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment