Act 0 · Null Witness
Chapter 0.7
Architecture
16–22 May 2035, Wednesday to Tuesday
Seven days, start to finish: a two-week contract I closed in one, which felt like competence then. Run straight through on a single track, it leaves out everything that was happening around me on the same days, close enough to touch, that I didn’t know about yet. That’s the point. I saw the week alone.
The access package arrived that Wednesday afternoon, the sixteenth of May, which I registered on my phone in the back row of a WAIT lecture on distributed systems, a subject I was technically enrolled in for credit and actually using for context in a way Professor Erikson had definitely not intended.
VPN credentials. Repository access. A documentation bundle that ran to several hundred pages when I extracted it. Architecture diagrams, network topology, deployment specifications, a scope document that was professionally thorough and carefully bounded. At the bottom: a message from Pryce through the engagement channel. Welcome aboard. I’m your primary contact. Response time within the day. Questions through this channel.
I spent two days reading before I touched anything, carrying the documentation everywhere: diagrams on my phone between classes, the bundle open in the campus library, topology on the walk to the quad with the earbuds in and the audio off because I just needed the camouflage. I attended lectures. I probably appeared to be taking notes.
What two days of reading told me, assembled in fragments across lecture halls and commutes and one late night in the library that ran later than I intended: whoever built NIGHTGLASS was serious. The codebase was organized. The architecture was thoughtful. Whoever built it had considered failure modes and built against them. The test environment I’d been given access to was cleanly provisioned, the network segmentation correct, the access controls doing what access controls are supposed to do. Nothing about the technical surface announced carelessness, and carelessness is where most pen testers spend most of their professional lives.
What the documentation didn’t say: the infrastructure numbers didn’t add up. The production deployment specifications described bandwidth requirements and processing capacity that didn’t follow from what the software was documented to do. I made a note in the margin of my reading notes and moved on. Comes back to this.
On the morning of day three I opened a terminal and started working.
The kickoff call connected at exactly the time it was scheduled, which I noted as a data point. Pryce appeared in a workspace organized to be seen rather than used, nothing personal, a mostly-erased whiteboard, an office that communicated professional and nothing else. Forty or so, good posture, a face that had been sharp once and gone slightly soft at the edges. Reading glasses pushed up onto his forehead that he hadn’t acknowledged.
“Cade.” He nodded, the nod of a person who has done enough of these calls to have a kickoff nod. “William Pryce. Good to finally put a face to it.”
“Hey.” I pulled up the scope document on my second monitor.
“I’ll keep this short, you’ve read the docs, I can tell.” He settled back slightly. “This engagement is pretty standard scope for us. Two weeks, staged deliverables, everything through the channel. I’ll be your main point of contact. Response time within the day, usually faster.”
“Works for me.”
“What I’m looking for is genuine work, not a compliance exercise. Our internal team built this. They’re good. What they can’t do is see what they can’t see, you don’t have their assumptions, which is the whole point of bringing someone external.” He paused, the pause of having phrased this before and checking that it landed. “Any questions on the access package before we get into it?”
“The test environment looks clean.” I clicked through a few screens. “I noticed the deployment specs reference production bandwidth figures I’d like to understand better. The numbers are on a different scale than I’d expect for —”
“That’s infrastructure context, not scope.” He said it before I finished the sentence. Not impolite. Ready. “We’re a serious operation. The architecture is sized for serious use. What I’d like you focused on is the application and authentication surfaces. That’s where the actionable security posture lives for the purposes of this engagement.”
“Sure.” I let that sit for a second. “The scope document lists the transport layer as deprioritized. Can you tell me anything about the protocol implementation? Even a high-level overview would help me establish what normal network behavior looks like before I start generating abnormal.”
The reading glasses came off his forehead. He set them on the desk beside his keyboard without looking at them, a gesture that had the feeling of something he did when he was thinking rather than listening. When he looked back up his expression was the same, exactly the same, a face that had been professionally managed into stillness.
“The network transport layer is proprietary,” he said. “Our own protocol, our own compression scheme, developed internally. There’s no external documentation because it doesn’t exist outside our organization. Reverse engineering a custom protocol isn’t feasible in a two-week window, and more to the point, it’s not what we’re asking you to do. Application and authentication. That’s the scope.”
He said that’s the scope the way you’d say that’s the weather, a fact of the environment, not a limit he was imposing. I wrote ngl/2.1 in my notes and underlined it.
“Understood,” I said.
“Good.” He picked the glasses back up and put them back on his forehead. “I’ll check in at milestone one in four days. Anything you need between now and then, channel’s always open.”
We ended the call. The screen went dark and the loft was quiet except for the servers.
Deprioritized, he’d said in the scope document. Not what we’re asking you to do, he’d said on the call. Two different ways of drawing the same line around the same thing. People who genuinely believe a thing isn’t feasible don’t draw it twice.
Day three. Passive observation first, always. I ran tcpdump against the test environment for an hour while I worked the codebase, not analyzing, just watching what normal looked like before I started generating abnormal. Five million packets in an hour, in a test environment.
Then I pulled the TLS handshakes out of the capture. Not to break the encryption, to read what surrounds it: the handshake runs in plaintext before the tunnel goes up, and it announces, in the clear, the application protocol the two sides will speak once the channel closes. Standard values are short and familiar, h2, http/1.1, h3. What the NIGHTGLASS handshakes advertised was a string I’d never seen anywhere.
ngl/2.1
I wrote it down, and pulled the server certificate while I was in there. Standard X.509, nothing unusual until the issuer:
Issuer: CN=Sigil Internal CA, O=Sigil Systems, C=US
SHA-256: e3:b4:f2:9a:1c:7d:08:f6:2b:44:a9:03:5e:c1:d7:f8:
9b:22:06:4a:e8:3c:10:5d:7f:b9:cc:28:41:0e:6a:93
A private CA means the organization vouches for its own servers instead of paying an outside party to do it. Ordinary enough. What matters is whose walls the trust chain ends inside. Not Meridian Group’s. The NDA had told me Meridian was a Sigil subsidiary; the certificate told me this wasn’t a subsidiary running its own infrastructure, it was Sigil’s infrastructure with a subsidiary’s name on the front door. I filed the fingerprint and the protocol string in the things-that-don’t-fit document.
Day three · the certificate chain
Then I pulled the production deployment specs and ran the arithmetic I’d been putting off, twice, because the first answer wanted verifying. The requirements were at a scale I’d last seen in the AI training clusters of 2025 and 2026, before the Bust turned them into monuments to a wager that didn’t pay out: exaflop compute and high-speed interconnect built for exactly one thing, ingesting enormous volumes of data very fast. NIGHTGLASS apparently had one of those problems. I added it under the header I’d started keeping, things that don’t fit yet.
Then I opened the code repository.
The access package had included what Pryce described in the scope document as NIGHTGLASS application codebase, approved review package. Thirty-seven directories. Several hundred source files. I’d been saving it for after the passive observation, the most substantial meal for last.
It took me about ninety minutes to understand what I was actually looking at.
The repository was not the application. It was the application’s luggage, the libraries, utilities, and dependencies the actual program was built on top of. Cryptographic primitives, serialization helpers, logging frameworks, data validation modules. All of it clean and well-organized, the kind of code that gets maintained carefully because it’s foundational, and all of it pointing outward: import statements referencing modules not in the repository, calls to functions defined somewhere else, interfaces implemented by something I didn’t have.
The scaffolding without the building inside it.
I sat with this for a moment. Then I sent Pryce a message through the channel.
The provided codebase appears to be a library package rather than the main application source. I’m not seeing the core application logic, the code that handles the primary program flow, the main service, the actual NIGHTGLASS implementation. Can you confirm whether the full application source is available for review, or direct me to the relevant repository?
His response came in twenty-two minutes. Faster than the previous ones, which was its own kind of information.
Cade, I want to make sure we’re aligned on scope here. I’m sorry if I wasn’t clear before, but we are restricting the scope of this engagement to the application layer endpoints, the authentication surface, and the code review for the library package we sent you. That library package represents the components where external security review is both appropriate and actionable for us. I would appreciate if you respect the scope as written and don’t get tempted to go down tangential routes. The library review combined with your authentication surface testing will give us everything we need from this engagement. Let me know if you have any other questions.
I read it three times.
I’m sorry if I wasn’t clear before is what managers say when they were entirely clear before and have now decided to reframe your question as a comprehension failure. The remainder of the message was professional in the way that a closed door is professional: nothing hostile in the materials, nothing useful in the content.
What Pryce had confirmed, without appearing to confirm anything: there was a main application binary and I was not going to be shown it. The library package was the controlled surface, enough to look like cooperation, scoped carefully enough to prevent me from seeing what the application actually did.
The code review I’d been authorized to conduct was the map they were willing to give me. The territory was their problem.
I noted it and went back to the work I was authorized to do, which still needed doing, and which was about to get more interesting than Pryce expected.
Day four.
Three authentication endpoints: token issuance, token refresh, session validation. I spent the better part of a morning mapping their request and response structure, not probing yet, just reading. Authentication endpoints have a grammar of their own. How they respond to malformed requests tells you about the decisions someone made when they wrote the code. The particular contour of an error message is a window into whether the system was built defensively or whether someone trusted their assumptions and stopped checking.
NIGHTGLASS’s auth layer read like the work of competent engineers who hadn’t expected anyone to read their error messages carefully.
Broken authentication has been on the top-ten list forever, not because developers are incompetent but because auth fails in the error paths, and error paths get built fast, tested lightly, forgotten. Proprietary means well-designed, maybe. It doesn’t mean reviewed.
I started with the JWT implementation.
A JWT is how an application proves you’re still you after you’ve logged in, a signed package containing your identity, which the server checks instead of remembering anything about you. The signature is the whole point: the wax seal that proves the letter hasn’t been opened and rewritten. The specification also permits an algorithm value of "none", for narrow internal cases. The question is whether a system enforces its own requirements on top of the spec, or whether it will accept a token that says trust me, I’m not signed.
I presented the validation endpoint with a token whose header declared "alg": "none", signature stripped, and watched what came back.
POST /auth/token/validate HTTP/1.1
...
{
"header": {"alg": "none", "typ": "JWT"},
"payload": {"sub": "test_client", "iat": 1747234800},
"signature": ""
}
Response:
HTTP/1.1 200 OK
{"status": "valid", "client": "test_client", "session_token": "..."}
It accepted the unsigned token as valid. The token said trust me, no signature required and the system said sounds right. Medium severity, non-trivial to exploit through proper segmentation, but real and textbook, and it told me how this layer was built.
I sent Pryce a brief note. JWT algorithm validation issue, medium, in the report. His reply: Thanks. I’ll make sure the team sees it. Keep going.
I kept going.
Day five.
The fuzzer was the next instrument. Not because manual exploration had run dry, three endpoints, I’d barely started, but because manual testing is limited to the inputs I could think to try. The fuzzer’s job was to find the inputs I couldn’t think to try.
The common mental model is wrong. Fuzzing isn’t throwing random garbage at a system until something gives, for an input space the size of all possible authentication tokens, random would still be running when the sun burns out. It’s breeding. You start with a population of inputs, some valid and some malformed, and score each response for how interesting it is: timing, error divergence, anything off baseline. The interesting ones reproduce, their bit patterns recombining and mutating; the useless ones die. Run enough generations and the population converges on whatever breaks the thing.
I seeded it with valid NIGHTGLASS tokens from my earlier capture sessions and a corpus of known-malformed inputs from previous engagements. Fitness function: deviation from baseline response behavior. Three endpoints simultaneously. I set it running and went to sleep.
$ ./kh_fuzz --target https://nightglass-test.internal:8443/auth \
--seed corpus/nightglass_tokens/ \
--fitness response_anomaly \
--generations 500 \
--population 200
[*] Generation 0 — best fitness: 0.04 (baseline deviation minimal)
[*] Generation 12 — best fitness: 0.31 (response time anomaly: +340ms)
[*] Generation 47 — best fitness: 0.67 (error handler divergence detected)
[*] Generation 89 — best fitness: 0.94 (content-length anomaly: 2847 vs baseline 312)
[*] Generation 134 — fitness plateau — mutating high-performing candidates
[*] Generation 201 — best fitness: 1.00 — TRIGGER FOUND
[*] Saving trigger payload → corpus/trigger_001.bin
The trigger was a combination of fields in the token structure: valid algorithm header, malformed issuer claim, and a client_id field containing a character sequence the parser was apparently handing downstream without sanitizing.
Here’s where this diverges from the algorithm issue. The JWT problem was in the token validation logic, the part that decides whether to trust the token you present. What the fuzzer found was a floor below that: the client_id value was processed by the error handler, which ran regardless of whether validation passed or failed. Two different parts of the system, two different failure modes. The first lets you in without credentials. The second is something else entirely.
Some systems build their error messages with a template engine, software that takes a string like “Authentication failed for client {client_id}” and fills in the variable. Fine when the engine substitutes text. Not fine when it evaluates what it substitutes, because then the message is no longer a message. It’s a place to put instructions. Server-Side Template Injection, a known class with known probes, sitting in a custom authentication implementation nobody had thought to check.
I ran the probe manually.
POST /auth/token HTTP/1.1
...
{
"alg": "HS256",
"iss": "PROBE_INVALID",
"client_id": "49"
}
Response:
HTTP/1.1 401 Unauthorized
{"error": "Authentication failed for client: 49", "code": "AUTH_ISSUER_INVALID"}
The server had evaluated 49 and returned 49. That is a template engine running code I put there. That is not a medium-severity finding.
Getting from “the server evaluates my arithmetic” to “the server runs my commands” took six hours and several dead ends I’ll spare you. Template engines vary in capability; some can read files, some execute system commands, some need chained techniques depending on which framework is running underneath. The genetic algorithm is still useful here, it just changes what it’s optimizing for. Not find the edge anymore. Find what the edge can do.
The NIGHTGLASS error handler’s template engine was running with access to system execution primitives. By hour six I had a shell on the test environment server.
$ id
uid=1000(nightglass_svc) gid=1000(nightglass_svc)
$ hostname
nightglass-test-01
$ ls /opt/nightglass/bin/
nightglass_svc
Limited user context. Not root. Enough.
Code review has a hard floor: you can only review what you can read. Source tells you what the authors meant. A compiled binary tells you what the machine will actually do, the only one of the two that’s ever load-bearing. I’d been given source. The thing I needed to read, I hadn’t.
The binary was forty-one megabytes in /opt/nightglass/bin/. Getting it wasn’t the technical problem. Getting it without the Security Operations Center knowing was. A transfer that size to an external IP shows up in a network log like a flare in a dark field, and it would tell them exactly which contractor had decided the scope was a suggestion.
So I wasn’t going to transfer a file. I was going to mail it, one sentence at a time.
DNS is so fundamental that most networks watch it the way you watch the air, not examined closely because the alternative is examining every breath. Every query carries data in its subdomain labels; control the domain and run a listener on your own nameserver, and you receive whatever is packed into them. Forty-one megabytes becomes a sequence of encoded fragments, each one a lookup that reads, to any monitor, as routine resolution noise for an unfamiliar domain.
DNS tunneling is the network equivalent of shredding a classified document and mailing it out one strip at a time, in ordinary envelopes: the mailroom doesn’t open letters, it delivers them. The protocol was never designed to carry contraband. Nobody told it not to.
I wrote the exfiltration script on my local machine, uploaded it to the test server via the established session, and ran it against the binary while the auth work continued on the other monitor.
[*] ng_exfil — target: 41943042 bytes / 910 chunks
[*] Chunk 0/910...
[*] Chunk 100/910...
[*] Chunk 500/910...
[*] Chunk 900/910...
[+] Complete — 910 chunks received, hash verified
Twelve minutes. From Meridian’s perspective: DNS queries to an unfamiliar domain, elevated slightly above baseline, resolved nowhere. Background radiation. My nameserver logged every query in sequence, decoded the labels, assembled them in order. The binary arrived intact.
Before I ran anything against the binary I ran strings on it. Standard practice: read what a binary says about itself before you try to understand how it behaves.
$ strings nightglass_svc | grep -iE "sigil|copyright|build_path"
Copyright (c) 2031-2035 Sigil Systems Inc. All rights reserved.
sigil_build@nightglass-ci-04:/home/sigil_build/nightglass/src/main.c
sigil_auth_lib_v3
sigil_compress_ngl4
ngl_svc_core — Sigil Systems Internal Release 2.1.7
Sixty-one matches for the organization name. Build paths with hostnames. Internal library names. A copyright header that had survived the compile step. The binary hadn’t been stripped of identifying information, which was either an oversight or a sign that nobody expected it to end up on a machine it wasn’t running on. I added the findings to the notes document under the CA issuer field I’d already written down. The picture was consistent: Meridian Group is the surface. Sigil Systems Inc. is the infrastructure.
I want to be precise about what I just did. Copying the binary was a knowing, deliberate violation of the engagement terms, the scope forbade it, Pryce’s message discouraged anything adjacent to it. I did it anyway, because the alternative was reviewing a system whose main application I was being prevented from seeing and calling that a review. The word for that isn’t review. It’s theater, and I’ve done enough of it to recognize the shape. I documented the decision in my own notes, not the findings report. Some things you write down for yourself.
I sent Pryce a message: Template injection in the auth error handling path. Critical severity, confirmed command execution in the test environment. Full PoC documented.
His response came back in under an hour. Noted. Good catch. Escalating internally. Continue with remaining scope.
I read it twice. Critical severity, confirmed code execution on the test server, and the response was continue with remaining scope, the same bounded, neutral register as the JWT note. A normal security organization gets a critical finding in a penetration test and there are calls, meetings, a conversation about whether to pause the engagement while they assess. There was none of that.
I filed it. Put it in the document. Kept moving.
Day six.
The service encrypts its traffic through a standard SSL library. The encryption is real and I was never going to break it. But there’s a moment before it happens, when the application hands its plaintext to the library, and at that moment the plaintext is just sitting in memory, passed between two pieces of software that trust each other. Frida lets me stand in that gap: hook one function call in a running process, read the arguments, log what goes past, without touching the binary or the encryption. It needs a process I’m running, which is the whole reason I needed the file on my own machine.
ldd on the pulled binary confirmed it linked against OpenSSL. I wrote the intercept script.
$ frida -l intercept.js ./nightglass_svc
____
/ _ | Frida 16.2.1
| (_| |
> _ |
/_/ |_|
[*] Attaching...
[*] SSL_write hooked — logging plaintext output
The city was quiet outside. The fan in my second drive clicked at irregular intervals, as it always did after midnight. The capture file started growing.
What came through was not directly readable. Expected. What I had was compressed plaintext, the application’s output after its own compression layer processed it, before the SSL call. The data, before the envelope. But written in a shorthand I’d never seen.
Every message opened with the same four bytes: 0xA4 0x1C 0x00 0x07. Sigil’s own magic signature, the fingerprint of a format that existed nowhere in any repository I could search.
Compression leaves fingerprints. High entropy says the bytes were squeezed; the way the back-references cluster says which family of algorithm did the squeezing. The histogram came back tight and near-uniform, and under it the pointer patterns of an LZ variant I could recognize. I had the family. The rest was four hours of mapping, header, block framing, the back-reference scheme, enough to write a first-pass decompressor. It made forty percent of the messages legible, not readable but legible: field boundaries, a timestamp whose byte signature was unmistakable.
And a count field. Repeating in every message. A number of something, consistent within sessions, variable across them.
I multiplied its average by the message rate by the concurrent production connections the documentation expected. The message rate alone was an estimate, so call the result a rough one, and I sat with it for a long time anyway.
That’s not a security product, I thought, and wrote it down below ngl/2.1, in the section for things I hadn’t decided what to do about.
Then I went to bed.
Day seven.
Ki’s shop sat on 4th Street in a converted auto-bay that still smelled of motor oil, front half legitimate repair work, back half organized around a logic only Ki understood, well enough that he could put a hand on any component in thirty seconds.
The errand was practical: the capture data was filling my storage faster than I’d planned and I wasn’t going to delete captures I might want later. A practical errand, and, if I was honest, a reason to leave the loft.
Ki was behind the counter, a tablet open with its back panel off, magnifying headset pushed up on his forehead, both hands occupied. He looked up when the door opened. Looked at me. Looked back at the tablet.
“You look terrible,” he said.
I paused in the threshold and stared back with what must have been the thousand-yard stare of my sleep-allergic tendencies, communicating what little regard I had for his opinions on my habits.
“Thank you.”
He set the screwdriver down and turned on the stool to face me fully, which was what he did when he’d decided something was worth his full attention. His hands went still. For Ki, that was a tell as clear as other people raising their eyebrows.
M.I.H.N Custom Systems, 4th Street · day seven · the hands go still
“When did you last sleep?”
“Recently.”
“Define recently.”
“This week.”
He exhaled slowly through his nose, not a sigh, Ki didn’t sigh, more like a controlled release, a prepared response set aside. He turned back to the bench, pulled the magnifying headset down over his eyes, examined something in the circuit board for a few seconds, then pushed it back up.
“What do you need?”
“Storage. Capture data filling up faster than I budgeted for.”
He was already moving before I finished the sentence, off the stool, down the aisle behind the counter, hands running along the shelves with the practiced automaticity of long habit; he could do this in the dark and probably had. He came back with two drives and set them on the counter, and tapped the edge of one twice, which he did when he was deciding whether to say the thing he was thinking.
“How big is the system you’re running captures on?” he said, in the practical tone that was never only practical.
“Distributed architecture. Multiple nodes. The test environment alone generates more traffic than I’d scoped for.”
“Test environment.” He repeated it like a word he was keeping separate from the sentence. “How many nodes in production?”
“The documentation describes several hundred in the initial deployment. Scaling from there.”
He set the drive down with more care than it needed.
“You haven’t told me what it does,” he said.
Two years of coming here had calibrated me to what it looked like when Ki was being careful with a question. The question was informational, what does it do, but what it was actually asking was whether I knew.
“Infrastructure monitoring,” I said. “Distributed data management. That’s what it says it does.”
He leaned his forearms on the counter. Another tell: the lean meant the conversation had acquired weight, even if his voice hadn’t caught up to it yet.
“The things it says it does,” he said.
“There are things in the capture data I’m still working through.”
He was quiet. When Ki went quiet like this, hands still, weight on the counter, eyes on mine instead of on the work, it meant he was doing arithmetic. He did it in the open so you knew he was doing it. A courtesy, of sorts.
“This is the contract that came through Ashfield,” he said.
“Yeah.”
“The company that turned you down. Their subsidiary.”
“Different door.”
“Hm.” He straightened up and turned back to the bench. “I’m just noting the shape of it. System that big, running that much traffic, and you’re still working out what it does. That’s a specific kind of not-knowing.”
“Or it’s a two-week engagement and I’m on day seven.”
“Sure,” he said, in the tone that filed the explanation without endorsing it. The screwdriver didn’t pause.
I picked up one of the drives and turned it over. The satisfaction I’d been carrying quietly, you ended up inside the building you couldn’t get through the front door of, was present enough to look at directly now that I was here. It felt good. I let it.
Ki glanced up. Read my face. The corner of his mouth moved, not quite a smile, the thing before one.
“You’re pleased,” he said.
“I’m not not pleased.”
“After that interview, you’re entitled.” He set the screwdriver down and looked at me properly, the full look, the one he used when he was about to say something and wanted to know it had landed. “Just don’t let pleased get in the way of careful. Especially the part where you’re still working out what it does.”
My phone buzzed. I pulled it out, Pryce, a milestone scheduling note through the engagement channel. Below it a Dark Cypher leaderboard notification. I checked both and put it away.
When I looked up Ki was still watching me. He had picked the screwdriver back up but wasn’t using it.
“How many things are you running right now?” he said.
“A manageable number.”
“Cade.”
“Three,” I said. “The contract, the competition, and the coursework that exists whether I think about it or not. I’ve been doing laundry at one in the morning to keep the rest of the schedule clear. Ran into one of my neighbors last week doing the same thing. She keeps similar hours.”
Ki looked up from the bench. A beat that wasn’t the usual beat. Then his eyes went back to the work.
The screwdriver tapped once against the edge of the bench. He was looking at the bench now rather than at me.
“Your last semester,” he said.
“Allegedly.”
“And you’re spending it like this.”
“I’m spending it on things that matter more than the degree at this point. The degree is a formality.”
He didn’t argue. Didn’t agree either. The silence had its own quality, not disapproval, something older than disapproval, the patience of a man who’s watched enough people stop finishing things to recognize the word they use right before they do it.
He put the second drive on the counter and told me the price.
“You eating?” he said.
“Occasionally.”
“Good.” He turned back to the tablet, the dismissal, warm and deliberate. “Come back when you’re not running three tracks at once.”
“That’ll be a while.”
“I know.” He didn’t look up. “Come back anyway.”
I paid. I took the drives. The door closed behind me with the sound it always made, not quite a rattle, not quite a click, and I stood in the Santa Monica afternoon a moment before starting back.
System that big, running that much traffic, and you’re still working out what it does. That’s a specific kind of not-knowing.
I put my earbuds in. I started walking.
That night.
I almost missed it. Not because it was hidden well, though it was, three layers deep in the binary inside a module dressed to look like routine health monitoring. But almost isn’t would have, and being tired and behind and six hours at the desk doesn’t change what I found.
This is what the inside of a surveillance weapon looks like.
Static analysis of a compiled binary pays out in inverse proportion to how much you feel you’re accomplishing. You’re reading machine code stripped of every human decision the source once carried, the behavior of the thing without the intent behind it. More honest. Much harder to read.
I’d been working through the binary in sections since I pulled it off the test server, building a picture from the Frida intercepts and the static analysis together, two views of the same object. Each told me what the other couldn’t.
The module I hadn’t been able to explain was a small one, maybe eight percent of the binary by instruction count, with an initialization sequence that ran separately from the main application startup. It loaded after the main process was already running. It registered its own network interface. It maintained an internal data structure I had been interpreting as a session management table, the kind of thing you’d use to track connected clients, manage connection state, routine infrastructure.
I ran the decompiler on it the same way I ran the decompiler on everything: methodically, section by section, building a function map before I tried to interpret what any individual function did.
Six hours in, I found the outbound connection handler.
Here’s what I want you to understand about that sentence. A system that receives data from devices has an inbound connection handler, devices connect to it and send information. This is what NIGHTGLASS was documented as: a network security monitoring platform. Monitoring means receiving. Devices talk to the platform, not the platform talking back to devices. There is no architectural reason for a passive monitoring system to maintain outbound connection logic to the devices it monitors, for the same reason a smoke detector doesn’t call the fire, it alerts someone else to act.
NIGHTGLASS had outbound connection logic.
Not just connection logic. The module maintained a deployment function: a method that took a signed payload, verified it against a key hardcoded into the binary itself, and pushed it to a device. The signing key embedded in the code meant that whatever Sigil signed, NIGHTGLASS would deploy. No app store. No user authorization. No review. Just the signed payload arriving at a device and executing.
I sat with this for a long time.
1847 Entrada, unit 3B · six hours into the binary · three in the morning
The legitimate interpretation: Mobile Device Management systems sometimes work this way. Enterprise software that manages corporate phones does exactly this, deploys signed packages to enrolled devices remotely. A security monitoring platform that also managed the devices it monitored would look, architecturally, like this. The hardcoded key was unusual but not impossible for an enterprise product in early development, before proper key management infrastructure was in place.
I wrote all of that down. Carefully, in the notes document, in the section for things I couldn’t explain.
The other interpretation I did not write down. That one stayed behind my eyes, unfiled, the way the things I hadn’t decided about tended to.
The module also kept a device list that grew dynamically, a live registry that added an entry every time the main stream processed a new connection. NIGHTGLASS was cataloguing every device it received data from, and the deployment function could push to any of them. In the test traffic the registry had reached several hundred thousand synthetic entries. Production, per the documentation, was multi-datacenter and hundreds of millions of concurrent connections.
Hundreds of millions of devices, each reachable by the outbound push, each deployment authenticated by a key that lived inside Sigil’s binary.
I added it to the findings report under a new heading I titled Architectural Anomalies, Requires Clarification. The entry read:
Module designation: ngl_deploy.so. Function: unknown, behavioral analysis indicates outbound connection capability to registered device pool with signed payload delivery mechanism. Hardcoded signing key detected (SHA-256 fingerprint: [redacted]). Architecture inconsistent with passive monitoring documentation. Possible interpretation: remote device management / MDM-adjacent functionality. Recommend clarification from development team regarding intended scope and operational justification.
I used the word possible. I used the phrase recommend clarification. I wrote MDM-adjacent because that was what it could be, and being a pen tester means documenting what the code does, not prosecuting what the code is for.
I submitted the report.
Pryce’s response came in four hours.
Great work on this engagement. Comprehensive report, exactly the depth we were hoping for. The team is reviewing the findings. The architectural anomaly you flagged in section four is a known component, I should have included it in the scope documentation, that’s on me. It’s part of our remote diagnostics infrastructure, MDM-adjacent as you noted. I’ll get you more context in the follow-up briefing. Payment is processing today. Lena asked me to let you know she’d like to schedule a call this week.
He had an explanation ready. He had described it before I could ask. He called it known component and remote diagnostics and agreed with my MDM-adjacent framing, which was the interpretation I had written down as the charitable one.
His explanation was technically consistent with what I’d found. An experienced mobile security professional reading the report would accept it. I had written the anomaly note carefully enough that the explanation he offered fit snugly inside it.
I wrote acknowledged and moved on.