Operation Diamond Forge: running a 200-person encampment on three web apps

Civil Air Patrol, Arkansas Wing · Google Apps Script, Google Workspace · 2026

What was needed

Civil Air Patrol is the civilian auxiliary of the United States Air Force. Its summer encampment is a twelve-day residential training program, and the 2026 Arkansas Wing event drew 200 participants from 60 squadrons across 18 states.

None of those sixty units share a system. Registration, cadet staff applications, rosters, flight assignments, org charts, printed check-in paperwork, contact directories, and the daily schedule all have to exist somewhere — for a population that is mostly minors, assembled from units that have never shared a database, for twelve days in a place where nobody is at a desk.

The default is a pile of spreadsheets emailed between volunteers. It fails in one specific way: the moment somebody needs to update a roster from a phone at 2200 in a barracks hallway, the pile stops working.

There was one hard constraint that shaped the entire design. Google Apps Script web apps that accept public traffic cannot safely expose any read endpoint against participant data — not a filtered one, not a narrow one. The platform's access model is all-or-nothing per deployment, and it doesn't give you a place to stand. And the participants are children. So a single application serving both the public registration form and the staff roster was never an option, however much more convenient it would have been to build.

What I built

Three separate deployments, divided along the security boundary rather than along features.

A public portal that is write-only by design. It accepts registration and cadet staff applications and exposes no capability to read or list anything back.

A public dashboard, read-only, touching only non-sensitive data: the schedule, published contacts, the org chart, aggregate population counts. It never opens the registration data at all. Staff directories behind it sit behind an identity challenge with a cached session and return an allow-listed set of fields and nothing more.

An internal operations app for staff: the roster across four participant categories with inline editing, section document management, activity logging, reconciliation against the external registration export, and packet generation.

Around those: printed check-in packets generated across eleven form templates, so nobody assembles paperwork by hand for two hundred people. A signature-packet workflow using hashed tokens with a twenty-one-day expiry. An org chart resolved across three separate data sources with a manually-maintained override tier for the assignments a machine can't decide. A Slack command for staff contact lookup, because during an event people are already in Slack and not in a browser. And a mobile-first dashboard throughout — during twelve days in the field, the phone is the computer.

What it did

It ran the encampment. More than 100 people used it, in the weeks of preparation beforehand and every day during the event — registering, applying, checking rosters, finding each other, and working from the same schedule.

Two hundred participants, sixty squadrons, eighteen states, twelve days.

Verifying it afterward

Five days after the encampment ended, I audited the system and wrote a document whose opening instruction is to not trust its own prior claims.

The audit read the live deployment manifests rather than the documentation. The documentation said the internal operations app was restricted to the organization's domain. The live manifest said it accepted anyone. It had been switched months earlier as part of a login change, and the documentation never caught up.

That one finding cascaded. Seven API functions touched participant data with no permission check at all, protected in practice only by the domain restriction the documentation described and the manifest contradicted. The public portal's write path assigned columns by hardcoded position, so reordering one spreadsheet column would have silently written every field into the wrong place. Cadet staff applications had no duplicate check, unlike registration, which did. A roster edit path performed a read-modify-write with no lock. And an entire fourth application — built, deployed, functional — had never been used by anyone and was still sitting there holding five database tables.

All of it closed the same day: permission gates on all seven functions, header-resolved writes that fail loudly with the missing column names rather than writing misaligned data, a duplicate guard matching registration's behavior, a script lock around the read-modify-write, 3.8 MB removed from every page load, and the unused application deleted in full.

One item I closed on my own recollection rather than on a check — I stated the login layer was live, and the audit couldn't independently confirm it. Before publishing this page I opened the URL in a private window and got a login prompt. It was live. But I had asked everyone else on this site to verify rather than remember, and that was the one place I hadn't.

Field notes

  1. Split deployments along the security boundary, not the feature boundary. Three applications is more work than one. It is also the only way to have a genuinely public form and genuinely private data on a platform whose access model is all-or-nothing per deployment.

  2. Read the deployed manifest, not the documentation. They diverge silently, and it is always the documentation that's wrong — because nothing fails when documentation is wrong.

  3. A per-function permission check is a checklist, not a security model. Any checklist applied by hand will have gaps; seven of mine did. If a platform-level restriction is what you are actually relying on, go verify the platform-level restriction.

  4. Positional column writes are a time bomb. The code works perfectly until somebody drags a column in a spreadsheet, and then it writes valid-looking data into the wrong fields with no error. Resolve by header name and fail loudly on a missing one.

  5. Delete the thing nobody used. An application that was built, deployed, and never adopted is not neutral. It holds credentials, owns tables, widens the attack surface, and quietly tells the next reader that it matters.