Skip to main content

· 9 min read
zach wick


Financial literacy is a body of knowledge necessary to have in order to make well-informed decisions when presented with financial situations. It is generally assumed that financial education can have a positive impact on financial literacy. However, individual characteristics of both the learner and the financial education itself have direct and significant effects on the efficacy of the financial education. This literature review explores how learner wealth, alignment of financial educational content to other desired outcomes, and the nature of the transferred knowledge impact financial education's efficacy.

Does financial education increase financial literacy?

In the most high level view, financial education does have some positive effect on financial literacy. In a meta-analysis of 201 studies comprising 168 papers, Fernandes et. al. found that "interventions to improve financial literacy explain only 0.1% of the variance in financial behaviors studied..." (2014, p. 1861). Furthermore, Fernandes et. al.'s meta-analysis revealed that "measured knowledge of financial facts had a weak relationship to financial behavior in econometric studies controlling for omitted variable bias" (2014, p. 1873).

A later meta-analysis by Kaiser and Menkhoff of 126 impact evaluation studies found that "financial education significantly impacts financial behavior and, to an even larger extent, financial literacy" (2017, p. 611). This meta-analysis also found "financial education is less effective for low-income clients as well as in low- and lower-middle-income economies" (Kaiser & Menkhoff, 2017, p. 611). This distinction in financial education efficacy along a demographic property is also present in the meta-analysis completed by Fernandes et. al. as well as in individual studies (Fernandes et. al., 2014, p. 1861).

What should financial education teach?

In a study by Postmus et. al., 85.1% of the 195 participants reported a yearly household income of less than $25,000 ( 2015, p. 255). This study sought to understand the impact of a financial education intervention provided to a random sample of female survivors of domestic abuse. Both women in general, and domestic abuse survivors specifically, are noted for being demographic groups at risk for financial challenges (Postmus et. al., 2015). Postmus et. al. note that financial education programs deployed by nonprofit domestic violence organizations speak to topics such as "basic financial management skills, such as saving, budgeting, getting or repairing credit, cash flow management, purchasing a home, predatory lending practices, financing major purchases, investing, and wise spending habits" (2015, p. 251). The financial challenges faced by these two groups are not too far removed from the financial challenges faced by other demographic groups. In a study by Friedline and West, adults born between the early 1980s and 2000s - generally " millennials" - "earn the lowest incomes of their careers while making financial decisions about attending postsecondary education, living independently from families of origin, finding employment, repaying educational debt, purchasing a home, and saving for retirement" (2016, p. 649).

How should financial education teach?

The study by Friedline and West analyzed data collected in the 2012 National Finance Capability Survey (2016, p. 649). This survey was "comissioned by the FINRA Investor Education Foundation and was completed online by a sample of 25509 adults in the United States between July and October 2012, which was nationally representative when population weights were applied" (Friedline & West, 2016, p. 653). Friedline and West's study used the responses of 6865 millennials to show that financial education that "focus solely on financial education or inclusion may be insufficient for facilitating Millennial's healthy financial behaviors; interventions should instead develop financial capability" (2016, p. 653). Friedline and West describe "financial capability" as the combination of the declarative knowledge from financial education as well as the procedural knowledge of how to apply it (2016, p. 653).

This theme of impactful financial education needing to provide both declarative and procedural knowledge can be found in other studies. In their study with female survivors of domestic abuse, Postmus et. al. used a financial education program that adhered to a "reasoned action approach (RAA), a manifestation of the Theory of Planned Behavior" (2015, p. 253). Viewed through the lens of RAA, a successfull financial education is one that "changes participants' knowledge, behavior, and intention to perform the behavior" (Postmus et. al., 2015, p. 253). The curriculum used is this study is specifically constructed to impart both declarative financial knowledge and procedural financial knowledge that is applicable to the lived experiences of domestic abuse survivors. Postmus et. al. did find that learning from such a tailor-made curriculum did significantly improve participant's outcomes.

Furthermore, Postmus et. al. found that "the impact of the curriculum persisted over the full 12-month, post-curriculum, follow-up period" (2015, p. 262). Juxstaposed to this result is a finding by Fernandes et. al. that "even large interventions with many hours of instruction have negligible effects on behavior 20 months or more from the time of intervention" (2014, p. 1861). This juxstapositioning suggests that it may be the case that a financial education aligned with the learner's specific needs will be most effective in the post-intervention period.

This result of enhanced efficacy due to content aligning with the specific needs of learners is significant enough that financial education has been used as a secondary concern in order to positively affect a primary concern. Courtney et. al. developed a smoking cessation study protocol to "evaluate the potential of co-managing financial stress as a means of enhancing smokers' capacity to quit smoking" (2014, p. 1602). While this "two-group parallel block randomized (ratio 1:1) open-label clinical trial (RCT) with allocation concealment" study protocol is not accompanied by the actual performance of the study protocol outlined, the authors do note that "financial stress consistently predicts lower probabilities of sustained abstinence, even after controlling for nicotine addiction, psychological stress and use of cessation aids" (2014, pp. 1602 - 1603).

When should financial education teach?

Aligning financial education with a particular learner's specific needs for increased efficacy can mean simply aligning the curriculum's financial content with it's non-financial content. Financial education efficacy can also be augmented by the temporal proximity of financial education to the immediate needs of the learners. While this temporal proximity of financial education to learner needs can be seen in the study by Postmus et. al., it also can be be seen in a study by Gerrans. In their study, Gerrans writes "The undergraduate years, therefore, appear to present a teachable moment when students could acquire financial knowledge and skills, as well as develop the attitudes of behavior required for financial independence" (2020, p. 1). In this regard, the results of these individual studies are also borne out in the meta-analysis by Fernandes et. al. who write "We suggest a real but narrower role for 'just-in-time' financial education tied to specific behaviors it intends to help" (2014, p. 1861).

It remains unclear which of facet of financial education, either temporal proximity of the instruction or the alignment of the content to more generalized goals, has the greatest effect on financial literacy. In either case, it appears that temporal proximity of financial education to the behaviors that it seeks to modify can help provide the procedural knowledge that is a necessary component of such financial education being successful. Gerrans found that among undergraduate students who receive financial education during these formative years "retain significant objective and subjective financial literacy effects, with modest decay, three years after completing a unit of personal finance education" (2020, p. 1). Notably, this study found a "sustained positive effect for checking the affordability of purchases, which remained for three years after the unit" (Gerrans, 2020, p. 16). This result suggests that learners who receive financial education temporally near behaviors using that information are likely to retain the learned knowledge for much longer than the knowledge gained from purely declarative financial education.


Financial education's impact on financial literacy as evidenced in human behavior attenuates over time. The rate of attenuation can be slowed however by aligning financial education content to the immediate needs of the particular learners and providing the instruction at a time just before the learners will need to apply it. These results indicate that financial education is most effective when it imparts both declarative and procedural knowledge. This result arises both via a meta-analysis of the literature and in particular studies looking at demographic groups known to be at-risk for financial problems. Educators should take these themes into account when designing and providing financial education.


Brugiavini, A., Cavapozzi, D., Padula, M., & Pettinicchi, Y. (2020). On the effect of financial education on financial literacy: Evidence from a sample of college students. Journal of Pension Economics & Finance, 19(3), 344-352.

Fernandes, D., Lynch, J. G., & Netemeyer, R. G. (2014). Financial literacy, financial education, and downstream financial behaviors. Management Science, 60(8), 1861-1883.

Friedline, T., & West, S. (2015;2016;). Financial education is not enough: Millennials may need financial capability to demonstrate healthier financial behaviors. Journal of Family and Economic Issues, 37(4), 649-671.

Gerrans, P. (2021). Undergraduate student financial education interventions: Medium term evidence of retention, decay, and confidence in financial literacy. Pacific-Basin Finance Journal, 67,


Iterbeke, K., De Witte, K., Declercq, K., & Schelfhout, W. (2020;2019;). The effect of ability matching and differentiated instruction in financial literacy education. evidence from two randomised control trials. Economics of Education Review, 78,


Kaiser, T., & Menkhoff, L. (2017). Does financial education impact financial literacy and financial behavior, and if so, when? The World Bank Economic Review, 31(3), 611-630.

Lusardi, A., & Mitchell, O. S. (2014). The economic importance of financial literacy: Theory and evidence: Theory and evidence. Journal of Economic Literature, 52(1), 5-44.

Postmus, J. L., Hetling, A., & L. Hoge, G. (2015). Evaluating a financial education curriculum as an intervention to improve financial behaviors and financial well-being of survivors of domestic violence: Results from a longitudinal randomized controlled study. The Journal of Consumer Affairs, 49(1), 250-266.

Wagner, J. (2019). Financial education and financial literacy by income and education groups. Financial Counseling and Planning, 30(1), 132.

Xiao, J. J., & O'Neill, B. (2016). Consumer financial education and financial capability. International Journal of Consumer Studies, 40(6), 712-721.


This work was created for Educational Foundations & Inquiry 6420 Research in Education at Bowling Green State University to fulfill a requirement to construct a literature review of an area of personal interest.

· 10 min read
zach wick

What is IndieHackers, and how does it align with the definition of community of practice?

IndieHackers is an online community of entrepreneurially minded people who are "seeking financial independence, creative freedom, and the ability to work on their own schedule" (Stripe, 2021). Most members of the IndieHackers online platform tend to be the founders or very early employees of tech-forward startups and small businesses. The IndieHackers website ( features:

  • a forum where members can "Talk shop with other indie hackers" (Stripe, 2021)
  • a list of upcoming meetups whereby members can "Meet indie hackers across the globe" (Stripe, 2021)
  • a database of member-created projects where members can "See what everyone's working on" (Stripe, 2021)
  • a store where members can "buy an Indie Hackers t-shirt" (Stripe, 2021)
  • a newsletter by which members can "Stay up-to-date in 5 minutes or less" (Stripe, 2021)
  • a section where members can submit long-form writings with the community to "Share your knowledge and experiences" ( Stripe, 2021)

A community of practice as defined by Etienne and Beverly Wenger-Trayner is a group of people "who share a concern or a passion for something they do and learn how to do it better as they interact regularly" (2015). The IndieHackers website is an archetypal example of an online community of practice.

Members of IndieHackers, who call themselves "indie hackers", are encouraged to share updates on their independent projects regularly and community members provide feedback to these updates that draw on their own areas of expertise and knowledge. Some project updates are celebrating successes, and some are asking for advice from the community. In either case, the community support and feedback is often useful and is always supportive.

In addition to the various web properties, the IndieHackers organization also has a podcast network of podcasts created by and for the community on topics often inspired by the community's discussions and posts.

How does IndieHackers demonstrate the three characteristics of a community of practice?

The Wenger-Trayners explicitly list three characteristics of a community of practice. With regard to the domain characteristic, the membership of a community of practice shares a "shared competence that distinguishes members from other people" (2015). In the IndieHackers community, this shared competence is in creating and executing on a tech-forward, usually software based, startup without going the traditional venture-capital backed route. This means that most of the projects made by members of the IndieHackers community are either self-funded or bootstrapped businesses. This defining characteristic of the financial structure of the project directly influences every aspect of the project and is the main contributor to indie hackers finding their tribe online with other entrepreneurs on similar paths.

As previously described, the members of the IndieHackers community routinely solicit and provide advice on business practices. In addition to this online information sharing via the community forum, there are also self-organizing IndieHacker meetups around the world. As would be expected, these in-person gatherings have diminished in frequency over the last ~two years due to the COVID-19 pandemic. They were replaced with online meetups and live community discussions, and are now beginning to make a reappearance as people become more comfortable meeting in person again in some situations.

The Wenger-Trayners third characteristic of a community of practice is "the practice", which is defined as "a shared repertoire of resources: experiences, stories, tools, ways of addressing recurring problems ..." (2015). In the IndieHackers community, this shared set of information and experiences to draw from is ever present. It is present when an indie hacker asks for advice on how to optimize a sales conversion funnel and a host of other indie hackers reply with what previously worked for them in a similar situation. It is present when an indie hacker is a guest on one of the various podcasts in the community's network and shares an anecdote about the "aha! moment" that led them to start their business. The various tips, tricks, practices, and tools that can be leveraged to help non-venture capital startups succeed are regularly discussed and dissected by the group to reason down to the root of why that particular solution works for the situations where it does and why it doesn't work in the others.

How does IndieHackers adopt the seven principles for cultivating a community of practice?

Wegner lists seven principles for cultivating a community of practice. For each of those principles listed below, IndieHackers provides a clear illustration of how that principle can be put into practice.

Design for evolution

IndieHackers originally began as nothing more than a series of ten interviews with business founders whose creating narratives resonated with the original creator of IndieHackers. The early dialogs and responses to these interviews caused a well-known Silicon Valley venture-backed startup, the antithesis of what an indie hacker would aspire to build, Stripe, to acquire IndieHackers. This acquisition was fueled by Stripe's desire to see more tech-forward online businesses in the world since that demographic is Stripe's ideal customer segment. Because Stripe views IndieHackers as a "top of the funnel" customer acquisition mechanism, Stripe provides IndieHackers with a budget and some general guidance but otherwise lets the community self-organize with the gentle nudges from the core IndieHackers staff. This has allowed areas of the community to develop such as written interviews turning into podcast interviews turning into a network of related but distinct podcast shows.

Open a dialogue between inside and outside perspectives

Because the IndieHackers community focuses on non-venture capital backed startups, which are the minority of tech startups, much of the discussion is through that lens. Occasionally, the IndieHackers podcast will have a guest who is not a self-identifying indie hacker. On those occasions, there is often informational discussion, both in the podcast episode and then in the community forum, about the advantages and disadvantages of applying the guest's provide information to indie hacker projects. While this isn't a clear two-way dialog and mixing of perspectives, it is almost always insightful and inspiring.

Invite different levels of participation

Because IndieHackers is in essence a customer acquisition strategy for Stripe with some ancillary benefits to members of the community who usually end up becoming Stripe customers, there is a wide variety of how members participate. Some members draw a salary from Stripe and their day job is to build the infrastructure that powers the IndieHackers web properties and serve as lead decision makers in the community. Other community members serve as volunteer moderators and help steward useful and respectful community interactions. The largest group of IndieHackers members are peripheral members who tend to consume more of the community's content than create it. Regardless of ones previous level of participation in the IndieHackers community, the community itself is encouraging and empowering of indie hackers however they choose to participate on any one topic/event/item.

Develop both public and private community spaces

While the IndieHackers community forum can be read by non-members, only members (i.e. someone who has created an account) can write in the forum. This allows the community to attract new members who find value in consuming the content and chose to move into the community and become a participant.

Additionally, the IndieHacker in-person meetups are private events that are only attended by community members. These private events share the same ethos as the online properties, but enable forming more personal connections within the community than the online properties do.

Focus on value

Usually when people find the IndieHackers community, they join it because they found some initial value in their interactions with the community. The exceptions to this rule are mostly people who Stripe has employed to enable IndieHackers and it is assumed that some of those employees only join the community initially because doing so is a prerequisite for doing their job functions. However, community members often quickly find value in the learnings that are gleaned from the various content and ensuing discussion put out by other community members. This is evidenced in the default ordering of forum posts by popularity as judged by the voting and engagement by the community. Presumably, a post would only be engaged with and then up-voted by a community member if they either received some value from it themselves or if they thought that the post would likely be of value to someone else in the community. This default ordering allows the most potentially valuable content to a community member to be presented first and foremost in the IndieHackers homepage.

Combine familiarity and excitement

While every business faces the same general shape of problem along its lifecycle, each business has their own unique nuance to their version of the particular general business problem. For members of the IndieHackers community, many of these business problems cannot be solved by simply "throwing money at the problem" as a venture capital backed business can because the companies that are created by indie hackers usually do not have extra cash on hand to spend. (As a member of the IndieHackers community myself, I cannot write that sentence without adding an aside that it is my belief that a VC backed business "throwing money at a problem" usually only solves the problem in the short-term as the solution to the problem becomes its own problem of being an often ongoing and often large cost).

Because of this nature of the problems faced by indie hackers, and because of the shared ethos that values clever non-monetary solutions to business problems that drive indie hackers to join the IndieHackers community, many times the community is able to discuss the issue in such a way as to unblock the stuck indie hacker in a way that allows all discussion participants to view their role and efforts in the community in a positive light. This "excitement" comes in the form of a dopamine hit by solving a problem within the bounds implied by the IndieHackers community ethos.

Create a rhythm for the community

The IndieHackers community rhythm is present, but is often imperceptible until after-the-fact. Because of the nature of the community, tech-forward startups, the projects and people involved in the IndieHackers community tend to be early adopters and advocates of new technology. This means that the IndieHackers community tends to be at the forefront of startups entering a new space. Currently, this is "web3" and cryptocurrency protocol backed projects, but only a year ago, the "new big thing" that many IndieHackers where building for were podcasts (Me included - see for an example of a project that was created during the previous tech hype cycle and has since been placed on the back burner).

The community does have regular rhythmic events such as a Black Friday deal sharing event where community members share their project's seasonal deals with the community, and many of the in-person meetups have their own regular cadence.


Stripe. (n.d.).Start a Profitable Side Project in 2020. Indie Hackers. Retrieved November 25, 2021, from

Wenger-Traynor, E. and Wenger-Traynor, B. (2015). Introduction to communities of practice. Retrieved from

Wenger, et al. (2002). Chapter 3. Seven Principles for Cultivating Communities of Practice.Cultivating Communities of Practice: A Guide to Managing Knowledge.


This work was created for Instructional Design 6740 Digital Learning Theories at Bowling Green State University.

· 3 min read
zach wick

If you are selling a tool to a profit center, you are enabling your customer to do more faster. To be somewhat reductive, this means that you are selling a time saving measure.

Selling to a profit center

If you are selling a tool to a cost center, then you are enabling your customer to do the same amount of work cheaper.

Selling to a cost center

Think about your customer's product as a vector in a 2-d space where the axes are "time savings" and "cost savings". The magnitude of your customer's product vector is the important part. A larger magnitude means "more success", while a smaller magnitude means "less success", however your customer defines "success". Your product is also a vector in that same space.

The magnitude of the sum of your customer's product vector and your product's vector is the success that your customer can have when they're using your product. This is obviously what you should try to optimize. This is shown as the dotted line in the above illustrations.

It doesn't matter which axis is increasing more because of the influence of your product's vector. This is because the same product can be sold as a time saving tool to a profit center, or as a cost saving measure to a cost center. The trick is to figure out if your target user is a cost center or profit center and then frame your product as the appropriate type of tool.

Selling tools to developers is usually selling to a profit center. This means that if you are selling to developers, the entire experience around your product must be quick and snappy.

Your product should be installable in either a single command or two, or be available to download, install, and use right away - without needing to have a back-and-forth with a salesperson.

Your documentation should have a quickstart guide that enables your users to get up and running in a matter of minutes.

The quicker that a user can be successful with your product, the larger the time savings offered by your product will seem. A larger (perceived or actual) time savings makes selling your product to a profit center that much easier, because you are already showing evidence of your product's value proposition.

When you are building a product, you are actually building two correlated products; your actual product, and the experience around using your product. Clearly, it is vitally important that your product actually solves your customer's issue. However, your product must also solve that issue in a way that is better along either the "cost savings" or "time savings" axis than any other solution. It is your job to figure out which of those axes is more important to your customer, based on whether they are a cost center or profit center, and market your product accordingly.

· 7 min read
zach wick

I am an elected legislator in Carey, Ohio. This post has been adapted from a proposal that I brought to the Village Council in May 2020, which was ultimately not pursued due to financial considerations brought on by the COVID-19 pandemic. I am sharing bits of it here to serve as a reference point for a non-government backed version of a similarly shaped program.

Where the following proposal reads "the Village of Carey", one can (and should) substitute "a community". A government is not required for people to better the community in which they find themselves, and therefore such a program need not be supported by a government but instead can be supported by any community that so chooses.


The Village of Carey should create, fund, and administer a program in which any person with an income tax obligation to the Village of Carey can have their costs for tuition and material covered if they enroll in, and successfully complete, a technical training course.


For period from 2010 - 2018, Wyandot county had the highest recorded job growth rate in the state at 23% [1]. Counteracting this job growth is a steady decline in county population, -3%, for that same period [2]. There is also a projected -11% population growth rate for the period of 2010 - 2040 [2]. Given these trends, there is an increased focus on helping the underemployed and removing barriers to entering the local workforce. One way in which the Village of Carey can have a measurable impact on both of these focus areas is by encouraging remote employees of technology companies to live and work in the village.


As of the 2000 census, the median household income in the Village of Carey was $33116/year[3]. We know that the current median household income in the Village of Carey is less than or equal to $45000/year because of the loan terms that the village qualified for to finance the construction of the wastewater treatment plant. According to Indeed, the average salary for an entry level software engineer in the state of Ohio is $67235[4].

This means, that on average, the expected difference in salary for a citizen who completes this program and is placed in an entry level software engineering role would be an increase of $34119/year. This implies that the Village of Carey would expect to see an increase of on average $511/year in income tax revenue from that citizen due to having a 1.5% income tax.

The sticker price for one year of access to all courses on is $299. This price is presumably open to negotiation, and OneMonth has already shown some appetite for offering a discount in casual conversations that the author has had with their team.

This has the implication that the Village would break even on this program in a single year if only half of the program’s participants were successful.

There may end up being other ancillary costs, such as office supplies and sundry supplies that the program participants may need for their workshops. However, the assumption is that workshop space and many of these other items will likely be donated by the community. The Village already has funds marked for economic development efforts, and this pro- gram would seemingly qualify to be covered by that account.

Course Selection

Since 2014, JavaScript has been the top language used by open-source repositories hosted on Github[5]. JavaScript is the primary language for building web applications, and is increasingly being used to construct mobile applications. By learning JavaScript, program participants will implicitly learn about HTML and CSS as well.

Program Structure

The following is a first attempt at structuring the program in a self-sustaining way.

Informational Session

A clear description of the program should be placed in the local newspapers, and an informal informational session should be held for interested parties to ask questions and receive an overview of the program.

Application Period

For a period of two weeks, any interested eligible person may apply to be in the pilot. The applications are to be turned in to the Village Administrator, who will review applications with the program’s operating committee.

Program Selection

Participants in the pilot program will be selected based on the following criteria:

  • Technical aptitude
  • Personal mission statement
  • Availability compatible with other program participants

Registration Workshop

A workshop session in which program participants for the pilot program each individually register for the selected course(s) on

(Bi-)Weekly Workshop Sessions

The weekly or bi-weekly workshops in which program participants get in-person (either live or via video chat) help from mentors and complete coursework from the selected course(s). These workshops will be held at a location with sufficient network bandwidth, grounded electrical sockets, and seating for the program participants.


After the successful completion of a course, program participants will be reimbursed any contracted fees. This would include any Ohio TechCred reimbursements for course mate- rials in addition to any reimbursement from the Village of Carey.

Job Search Assistance

After the successful completion of a course, program participants will receive coaching on resume construction, and will work with the selected recruiting firm to find and apply to relevant job openings.

Successful program participants will work with the selected recruiting firm to help find an apply to relevant job openings. The positions that applicants apply for should ensure that program participants continue to have an income tax obligation to the Village of Carey.

Post-Completion Mentoring

While completing their job search, program participants will be expected to serve as a mentor for at least 3 (bi-)weekly workshops for the following program participant cohort.

In addition to past program participants fulfilling their mentorship obligations, community members with appropriate skill and interest may serve as volunteer mentors and help unblock program participants during (bi-)weekly workshops.

Post-Program Projects

After the successful completion of a course, program participants may desire to continue practicing their skills, build out a professional portfolio, and help their community. They can do this by working in small teams on various civic technology projects such as the brainstormed list that follows.

Carey 311 app

A way for citizens to report issues such as potholes, downed limbs, dangerous sidewalks, etc.

Tree mapping app

A way for citizens to catalogue and adopt trees in the right-of-ways and village parks.

Village data portal

A type web portal for all datasets that the village maintains or generates.

Village document portal

A web portal for easily searching and retrieving any public record from the Village of Carey.

Example timeline

2020-05-04 Initial presentation to Council.\ 2020-05-18 Discussion of updated proposal addressing any initial feedback.\ 2020-05-19 Begin formalizing any pricing discounts with OneMonth.\ 2020-05-26 Public informational session\ 2020-05-26 Application period opens\ 2020-06-09 Application period closes\ 2020-06-12 Accepted Session #1 applicants are notified\ 2020-06-15 Session #1 begins with a Registration Workshop\ 2020-06-15 Session #1 weekly/bi-weekly workshop sessions begin meeting\ 2020-06-29 Session #2 application period opens\ 2020-07-13 Session #1 ends\ 2020-07-13 Session #2 application period closes\ 2020-07-17 Accepted Session #2 applicants are notified\ 2020-07-20 Session #2 begins


[1] A. DeMartini, “Rural job growth rate in ohio surpasses columbus rate,” May 2020\ [2] A. Huston, “County shows largest job growth in state,” The Progressor Times, p. 3A, Nov 2019.\ [3] Assorted, “Carey ohio wikipedia,” May 2020.\ [4] Indeed, “Entry level software engineer,” May 2020.\ [5] Github Inc., [“]The state of the octoverse,”]( September 2019.

· 8 min read
zach wick

The Ohio Dept of Health is doing contact tracing as part of their response to COVID-19. When a person tests positive for COVID-19, they provide their local health department with the name and phone number of any recent close contacts. Those close contacts are then contacted by the local health department and are asked to self-quarantine for 14 days and take their temperature twice a day. These close contacts are also asked to self-report their temperatures and any other COVID-19 symptoms that they may have either over the phone to a health department employee or via a web app.

If a person chooses to report their temperature and other symptoms via the web app, they are sent a URL daily at 1600 and then again every 30 minutes until they visit the URL and report their information. These URLs look like the following:

This was the URL that I was sent one day, as I was a close contact of a person who had tested positive for COVID-19 and I did self-quarantine for 14 days.

Breaking down the URL

Note that the query string of these URLs contains three fields; p, d, and language.

It's clear that language refers to the language that the requested page should be presented in. This can easily be tested by substituting es for en and noting that the page is now presented in Spanish. Further testing revealed that the self-reporting web app is only available in English and Spanish.

After receiving one of these URLs via SMS every day, it was apparent that the value of the p field didn't change, while the value of the d field did change each day. That suggested that the p field is the "patient id", while the d field is the date for which the user is self-reporting their temperatures and symptoms.

Noting that '%3D%3D' is the string '==' being URL encoded, suggests that the value of the d field is simply the date in the format 'MM/DD/YYY' base64 encoded (because base64 encoded strings can be padded with '=' or '==' at the end). A simple test of this showed that when I base64 encoded the string '07/18/2020', then URL encoded the result, and then supplied that value as the value of the d field, I could submit information for myself for any future date that was before the last day of my 14 day quarantine.

In my particular case, that meant that simply by changing the URL, I could submit temperature meausurements and other symptoms for myself for any date between 2020-07-07 and 2020-07-21. If I attempted to load the page for 2020-07-22, I was shown a message that I no longer need to complete the survey.

This ability to submit information for a day other than the current day probably isn't strictly a security bug, but it is somewhat poorly designed and the server-side code clearly doesn't do any validation on the reporting date since I was able to submit made up results for myself for 2020-07-18.

The more serious issue is the p field, which likely corresponds to a "patient id" or something of that ilk. Because the d field was base64 encoded, it would make sense that the p field is also something that has been base64 encoded. Note that the value of the p field does not have '%3D%3D' at the end. This is because base64 encoded only may be padded with '=' or '==' at the end, but a string doesn't strictly need to end with those characters. So, by taking the value of the p field and attempting to decode it as base64 yields (in my case) the string "ODH-29825". This seems like it is in fact some kind of unique identifier for myself in ODH's database.

So what can you do

Now that we understand what these fields are all used for, it is trivial to create query string parameters in reverse. For example one could attempt to use today's date (07/27/2020) and the patient id of "ODH-00000" to craft a URL like:

and attempt to load the page for that patient. This page will only load if you've both selected a valid ODH patient id, and that the date that you've used for the value of the d parameter is within the 14 day quarantine period for that ODH patient.

Originally, the patient's first and last name were displayed on this page. This means that one can simply enumerate patient IDs from ODH-00000 to ODH-9999999 (or some large upper bound), and when an actual person's name appears on the site, you then know that the person listed has been a close contact of a known positive COVID-19 case and is currently self-quarantining and monitoring. Once a person has been found, you must simply try up to the next 14 days as the value of the d field to determine when they were in contact with a positive COVID-19 case.

Reporting Timeline


I begin receiving these text messages from ODH.


I resolve to report this to the State of Ohio, as I feel it is a big enough issue to warrant doing so.

2020-07-16 @ 1530

After searching around on, I came across

2020-07-16 @ 1540

I called the listed number (614-644-8660) and was asked to email the details of what I was reporting to I was also asked if I was a public employee, to which I responded that while I'm not an employee of the State of Ohio, I am an elected legislator in my village.

2020-07-16 @ 1549

I sent in an email indicating that I had been asked to email this address and asked how they would like me to securely get the details of the vulnerability to their team.

2020-07-16 @ 1551

I received automated emails that an incident had been created and had been assigned to someone.

2020-07-16 @ 1658

I received an email at the email address corresponding to my position as an elected legislator, asking to confirm that this email belongs to the same individual who sent the email to the state at 1540.

2020-07-16 @ 1702

I responded in the affirmative to the above email.

2020-07-16 @ 2114

I tweet the SHA256 sum of this file.

2020-07-17 @ 1559

I received an email and then shortly after a phone call from a representative from the state and I provided the responding team a password-protected ZIP archive as an email attachment and provided the password to them over the phone. This was the alternative I proposed since the team didn't seem interested in providing a GPG/PGP key that I could use to encrypt my message to them.

2020-07-23 @ 0929

I received an automated email that the incident had been resolved. This email contained the following note:

ODH worked with the vendor to strip the last names from the data. It was determined that the only data that a bad actor could see would be the name of the citizen that is being traced. ODH has excepted the fix of just removing the last name because of the volume of citizens that are being tracked.

Alternative fixes

I understand the tradeoffs that go into building software, but accepting a fix of only no longer displaying the last name of the person seems less than ideal. Anyone who can construct these URLs can still submit made-up information for any person currently under a 14-day quarantine that has elected to self-report their temperatures and symptoms via the ODH's web app.

A few alternative, and somewhat more robust, fixes come to mind here. One option is to to create a single-use UUID that maps to a patient and date tuple in the ODH system backend, and then provide users with URLs that look like:<SOME_UUID_HERE>

These UUIDs should be created new for each patient for each day, and the server-side code that the self-reporting form submits to should ensure that the date for that UUID matches the current date and that the user has not yet submitted information for that day. This would make it much more difficult to enumerate all the possible UUIDs and extract the names of people who may have been exposed to COVID-19 because a malicious actor would have a much smaller time window in which a constructed URL would be valid. This also adds at least some server-side data validation.

Another option would be to just turn off the ability for citizens to self-report this health information and instead have local health department employees contact each citizen under quarantine via phone each day to record their health information. This would be much more time-consuming, and would cost much more, but would have the added benefit of not being susceptible to false data being reported for a citizen by a malicious actor simply constructing a URL.


I have a repo, which contains a simple python script that demonstrates how to generate the URL for a given ODH patient id and date. It would be unethical to submit false data for a person other than yourself, but I feel it is important to show how trivial it is to construct these URLs.

· 2 min read
zach wick

Genie is a tool for assigning arbitrary tags to file paths, and then performing search operations on those tags.

Like any person, I organize my files in a standard way on the machines that I use regularly:

├── Desktop
├── Documents
│   ├── Personal
│   ├── Work
├── Downloads
├── Documents
├── Repos
│   ├── APL
│   ├── C
│   ├── CPP
│   ├── Go
│   ├── Guile
│   ├── JS

This has its advantages, such as knowing where to go when looking for some particular project. This structure falls over for projects with components in multiple languages however, such as a Swift API with a JS client. In that case, this kind of file structure relies on naming conventions to indicate that two directory trees at Repos/Swift/projectx-server and Repos/JS/projectx-client are part of the same project.

This is where genie comes in handy, because the Swift API's directory and the JS client's directory can be assigned the same tag of "projectX" and then use genie search projectX to see all of the filepaths that are associated with that project's tag.

The other times where genie is useful is remembering every few months where exactly in a large codebase some change needs to be made. This is where genie really shines in my day job where I seem to do many semi-regular drive-by pull requests in the same area of the monorepo and can tag some deeply nested file with a self-evident tag.

genie is essentially a very small command line tool written in Swift that wraps some sqlite queries, but it serves its purpose well.

You can read more about genie in the very nascent docs.

· 3 min read
zach wick

This set of steps is the result of a weekend poking at how to get Travis-CI and GitHub configured to provide a CI pipeline for Swift packages. It is mostly for future reference for when I next start another Swift project.


  1. Create a new directory and navigate to it
mkdir Hello
cd Hello
  1. Every package must have a manifest file called Package.swift in its root directory. You can create a minimal package named Hello using:
swift package init
  1. Build your library with:
swift build
  1. Run your tests with:
swift test
  1. Initialize your package as a git repo
git init
  1. Create a new repository in your GitHub account

  2. Commit your local changes

git add *
git add .gitignore
git commit -sm "Initial Commit"
  1. Add your GitHub repo as a remote repo
git remote add origin
git push -u origin master
  1. Navigate to and connect it to your GitHub account

  2. Activate your new repo in Travis-CI (

  3. Create .travis.yml and populate it as below:

if: tag IS blank
- master
- SWIFT_BRANCH=swift-5.0.1-release
- stage: Linux test
os: linux
language: generic
dist: xenial
sudo: required
- sudo apt-get install clang libcurl3 libcurl4-openssl-dev libpython2.7 libpython2.7-dev
libicu-dev libstdc++6
- curl$SWIFT_BRANCH/ubuntu1604/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu16.04.tar.gz
> $SWIFT_VERSION-ubuntu16.04.tar.gz
- tar xzf $SWIFT_VERSION-ubuntu16.04.tar.gz
- export PATH="$(pwd)/$SWIFT_VERSION-ubuntu16.04/usr/bin:$PATH"
- swift package update
- swift test
- stage: OSX test
os: osx
osx_image: xcode10.2
language: swift
sudo: required
- sudo installer -pkg $SWIFT_VERSION-osx.pkg -target /
- export PATH="/Library/Developer/Toolchains/$SWIFT_VERSION.xctoolchain/usr/bin:$PATH"
- swift package update
- swift test
- stage: Set tag
- git config --global ""
- git config --global "Travis CI"
- git push --quiet https://$ --tag > /dev/null 2>&1
  1. Commit .travis.yml and push to GitHub

  2. Create a Personal Access Token in your GitHub account

  3. Install the Travis CLI tools by either

brew install travis


gem install travis -v 1.8.9 --no-rdoc --no-ri
  1. Set up and configure the travis CLI tool
travis login --auto
travis branches

The first command authenticates your CLI tool with your Travis-CI account using your local GitHub credentials. The second command ensures that the travis CLI tool is using your Swift project as its current project.

  1. Use the Travis CLI to add your GitHub Personal Access Token as an encrypted environment variable
echo GH_TOKEN=<YOUR TOKEN> | travis encrypt --add
  1. Add a Travis-CI build status badge to your project's README file by following the steps at

Now, everytime that you want to push a new version of your Swift project if all of your tests pass, you simply need to bump the version defined as PACKAGE_VERSION, in .travis.yml and them commit and push your changes.

· 2 min read
zach wick

The Homebrew package manager uses external commands to extend its functionality. These are either shell scripts or ruby scripts that can be added on top of the existing brew infrastructure via the brew tap command.

When creating the license external command, I needed to add an external ruby dependency of the octokit gem to facilitate attempting to fetch a formula's licensing information from the Github API. It was obvious that I needed to add

    require 'octokit'

to my brew-license.rb script, but it wasn't obvious how I could trigger that gem being installed on a user's machine if it wasn't already present.

The solution that I settled on was to create a Gemfile where I defined my dependencies:

source ''
gem 'octokit'

Then, at the beginning of my brew-license.rb script, which is what is executed when a user types brew license in their shell, I needed my ruby script to invoke bundler if the octokit gem wasn't installed locally on the user's machine. This can be accomplished with the following:

    REPO_ROOT = "#{File.dirname(__FILE__)}/.."
VENDOR_RUBY = "#{REPO_ROOT}/vendor/ruby".freeze
BUNDLER_SETUP = "#{VENDOR_RUBY}/bundler/setup.rb"
unless BUNDLER_SETUP.exist?
Homebrew.install_gem_setup_path! "bundler" do
safe_system "bundle", "install", "--standalone", "--path", "vendor/ruby"
require "rbconfig"
ENV["GEM_HOME"] = ENV["GEM_PATH"] = "#{VENDOR_RUBY}/#{RUBY_ENGINE}/#{RbConfig::CONFIG["ruby_version"]}"
require_relative BUNDLER_SETUP

Now, the first time that a user executes brew license, this bit of code will ensure that the octokit gem is installed locally.

You can see this in action by installing and using brew license, and if you're interested in adding licensing information for your favorite Homebrew formulae, please do feel free to submit issues or PRs!

· 5 min read
zach wick

When I finished undergrad, I went on a long backpacking trip by myself to the Boundary Waters Canoe Area Wilderness. I took a copy of Walden and a copy of Theory of Games and Economic Behavior to read. I also took my notebook, in which I ended up writing quite a bit. Below are unrevised typed versions of some of the things that I wrote.

Little Fish

The little fish stays amongst the rocks and the shallows and forever remains in its safe little world. Its whole existence is there. It is born there, it lives there, and it dies there. And yet it never realizes that is lives within a small portion of a much larger lake.

What joy there is in leaving the safe haven of the rocks and the shallows and venturing into the world. What happiness there is in facing the world and relying on yourself for survival.

Nature and Tech

Everything here is so, though I hate to say it, natural. There is more to it than that though. The wind and the waves beat on the rocks and shore as they ever have. The fish eat the bugs and the eagles eat the fish as they have for ages.

The trees grow, die, fall, and are recycled back into the earth as they have done for millennia. The better word to describe this place is “uninterrupted”. Humans have not yet soiled its primordial beauty. Given time, I am sure that we will, but for now it is still pristine. None of the natural cycles here care about any of modern man’s worries. What is the stock market trend to a wild blueberry? What is the newest human war to a fir tree? The Earth here is far more wise than any human will ever be.

It allows only the strongest to prosper and sheds no tears for the weak. It does not concern itself about its neighbors, it does what it must. It obeys no other mortal’s laws; it only does what is has done since its birth and knows is right.

To come here and sit and watch is to attend a lecture on the inferiority of man. This is the world’s classroom, taught by the best in the field and yet how many come to learn this free lesson? How far has man fallen that to observe the natural order of things and learn is considered not only a chore, but a backsliding to "more primitive" life? Can a television tell you how to navigate a river current? Can an ipod teach you what soil is best for a cedar? Let just one generation be taught here instead of modern schools and see how much more civilly they treat their fellow man. See how much more they can do without the aid of a transistor. Not all technology is to blame for the downfall of man, who was once poised to be masters of this planet. Contemporary medicine is a prime example of technology doing good. This is the only instance of this trend that I can think of though.

But you say "surely computers do good? They help predict storms and do computation lightning quick. They allow us to talk to people all around the world."

To this I say, nay shout "NO!" What computer is better than the human brain? The best computer today with the most efficient programming can do exactly one task almost as good as a rat. As for predicting storms, the most wizened farmer can tell when a storm is approaching just by observing the world around him. Imagine a world where every single person could do the same. As for computation, who needs to know the factors of some 1000 digit number? If all men treated each other with respect, there would be no thievery and no need for encryption. An honest man need only be able to count to a few hundred and add, subtract, multiply, and divide numbers at most that too and just lump the rest. If every man earned his own, who needs to know what percent they need to pay in taxes for there would be none who needed public support. For communication purposes, how much talk is just meaningless drivel of speech? How much correspondence is gossip and propaganda? Instead, let a man think about what it is that he wants to say, and mull it over enough so that it is perfectly clear, and then, and only then, if he feels that it absolutely must be shared, let him write is so that others may read it when they wish.

Better still, let the man travel and give his message in person. Would any prophet’s message have been as powerful if it was sent to others as an email? No, the prophets went out and spoke to men, face-to-face or face-to-faces. Technology has become an unneeded crutch for mankind. Man has become a tool to the very thing which began as his tool. We are so buried in the mire of diodes and resistors, ipods and computers, tv’s, microwaves and toasters that even just one hour without electricity stirs the hive of modern humanity into a craze. Light a candle, open your eyes, and act to prevent the demise of humankind; For it is only one massive power outage away.

· One min read
zach wick

We never really stop and marvel at the fact that we carry magic in our pockets.

We have most of the world at our fingertips and we worry about the color of someone’s skin or who they love. Humans have crystallized magic and we worry about things more mundane. The depth and breadth of how we have expanded each person’s reach now touches the outer limits of the solar system.

How come I can communicate with another human half the world away at the speed of light, but people right next to me want for food and shelter? Why has our magic not solved these obvious problems that humans face? Why it is easier for my neighbor to order a car than to get a sandwich?