Metadata-Version: 2.4
Name: awtunnel
Version: 0.1.0
Summary: Ingress rule validation that catches scheme mismatches and shadowing — hostname/path rules mapping public names to origins, with conflict detection.
License: Apache-2.0
Project-URL: Homepage, https://github.com/Aitherium/awtunnel
Project-URL: Documentation, https://github.com/Aitherium/awtunnel#readme
Project-URL: Repository, https://github.com/Aitherium/awtunnel.git
Project-URL: Issues, https://github.com/Aitherium/awtunnel/issues
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# awtunnel

<!-- aither-header:start GENERATED from the ecosystem registry. Edits here are overwritten; change the registry instead. -->

**[Docs](https://aitherium.github.io/awtunnel/)**  ·  [Source](https://github.com/Aitherium/awtunnel)  ·  `pip install awtunnel`  ·  [The Aither World](https://aitherium.github.io/)

> **The Aither World** is an operating system for agents — a Linux you can hand to one, the runtimes it works in, and the tools it works with. [awnix](https://github.com/Aitherium/awnix) is the Linux underneath it; **awtunnel** is one of its 33 bricks — each installs on its own, runs offline, and needs no account.
>
> **Start here:** Expose one local port to one remote caller and take it away again.

<!-- aither-header:end -->

Ingress rule validation that **catches scheme mismatches** and **detects shadowing**.

```bash
pip install awtunnel
```

```python
from awtunnel import validate_rules

rules = [
    {"hostname": "api.aitherium.com", "path": "^/v1", "origin": "http://gateway:8080"},
    {"hostname": "api.aitherium.com", "path": "^/auth", "origin": "https://identity:8115"},
]

result = validate_rules(rules)
if not result.ok:
    for finding in result.findings:
        print(finding)
```

```bash
awtunnel validate rules.yaml    # 0 ok · 1 real problem · 2 could not judge
awtunnel check   rules.yaml hostname path  # check if a specific route exists
```

## The bug this package is shaped around

Tunnel connectors are unforgiving: a rule saying `http://security-core:8115` while that
service serves **TLS** fails as **"origin unreachable"** rather than **"origin serves
TLS, rule is wrong"**. The symptom names the innocent service, costs a session debugging
the healthy container, and is completely silent to every configuration tool.

Three related traps, all invisible to config alone:

1. **Scheme mismatch**: `http://` into a TLS listener closes the socket immediately.
   The connector reports "Unable to reach the origin", but the origin is running and
   healthy. Nothing in the config says which is wrong.

2. **Duplicate schemes on same host:port**: One rule says `http://security-core:8115`,
   another says `https://security-core:8115`. They are different rules, both valid
   config, and one or both will fail at runtime depending on which the connector tries
   first.

3. **Shadowing**: A broad hostname pattern (`*.aitherium.com`) or a short path prefix
   (e.g., `^/api`) can match before a more specific rule. The more-specific rule's
   route is unreachable, and the request lands on the wrong origin. No error, no
   rejected config — the path works, just on the wrong host.

## What this package proves

`validate_rules()` reads a rule set and asserts three invariants:

- **No scheme conflicts**: the same `(host, port)` pair appears in at most one rule
- **No unresolvable hostnames**: every `host` either is an IP or resolves via DNS
- **No shadowing**: rule ordering cannot hide a later rule behind an earlier one

Each is its own finding, with the rule and the conflicting peer named. Because during
an incident they call for different responses:

| finding | fix |
|---|---|
| **SCHEME CONFLICT** | pick one scheme (likely TLS), remove the other rule |
| **UNRESOLVABLE HOST** | fix the hostname or move it behind a load balancer that does resolve |
| **SHADOWED** | reorder the rules, or narrow the pattern |

## What it does not do

This validates the **rule set alone**, not the **actual services**. A rule can pass
validation and still fail at runtime if the origin is down, the scheme is right but
the port is wrong, or the service binds a different interface.

- `validate_rules()` does NOT reach out to origins — that is runtime work.
- It does NOT check whether a hostname actually resolves (the resolver might not
  yet exist when rules are written).
- It does NOT verify that the origin actually listens on the declared scheme and port.

A rule that passes validation may still fail to serve requests. To get a real guarantee,
test the rule **live**: send a request through the connector and verify it reaches the
origin you intended.

## Design notes

- **No dependencies.** Config validation that needs an install before it can run is
  one nobody runs during an incident.
- **Findings name the rule.** A validator that reports "there is a problem" without
  saying which rule is shaped around the assumption that all rules are identical,
  which they are not.
- **Order matters.** `validate_rules()` checks shadowing by rule position, because
  routing is positional — the first matching rule wins. A rule that is valid in
  position 3 might be shadowed if moved to position 5.
- **Hostname patterns are literal.** Glob patterns and regex are interpreted by the
  connector, not this package. The validator does not parse them — it reads them as
  literal strings for collision detection. If two rules have the same hostname string,
  they conflict; if one is `*.aitherium.com` and another is `api.aitherium.com`, the
  validator does not know whether they overlap.

## Tests

Every finding has a test that produces it, and the suite includes both negative cases
(rules that should pass) and positive ones (rules that should fail). Tests run in
both directions: a suite that only checks for problems passes trivially if validation
always fails, so both "rule is valid" and "rule is invalid" assertions are present.

```bash
pip install -e ".[dev]" && pytest
```

## Where it sits

In a larger system, ingress rules live in multiple places with conflicting truths:

- `config files` (static YAML the owner wrote)
- `the connector's live config` (what Cloudflare/nginx is actually using right now)
- `the deployed services` (what origins are really listening)

This package validates the **config files**. To get a complete picture:

1. Validate the source config → `awtunnel` (this package)
2. Compare config to live connector → edge-specific tool (e.g., `cf_dns_list`)
3. Test the connector output → network tool (e.g., curl with the right User-Agent)

Only step 1 is portable. Steps 2 and 3 are connector-specific and belong elsewhere.

Apache-2.0.

<!-- aither-ecosystem:start GENERATED from the ecosystem registry. Edits here are overwritten; change the registry instead. -->

## The aw family

Standalone tools that share one idea: **replace something you would otherwise have to _trust_ with something you can _check_.**

Each installs on its own, works offline, and needs no account.

| | instead of trusting | you check |
|---|---|---|
| [awdk](https://github.com/Aitherium/awdk) | a framework's idea of how your agents should run | one loop you can read, pointed at a backend you already pay for |
| [awskills](https://github.com/Aitherium/awskills) | that an agent knows your procedure | the procedure written down, versioned, and loadable by any agent |
| [awm](https://github.com/Aitherium/awm) | that memory stayed in its lane | tenant:user:project scopes, so a write cannot cross a boundary |
| [awnode](https://github.com/Aitherium/awnode) | a vendor's cloud with every prompt | a local gateway routing to backends you chose |
| [awgraph](https://github.com/Aitherium/awgraph) | that grep found everything | an AST + tree-sitter call graph an agent can traverse |
| [awgit](https://github.com/Aitherium/awgit) | that no one else is editing this file | a lease, refused at commit time if you do not hold it |
| [awseal](https://github.com/Aitherium/awseal) | that the artifact came from who you think | an Ed25519 seal — the key that verifies is not the key that forges |
| [awshare](https://github.com/Aitherium/awshare) | that the download is intact | content-addressed bundles, verified on fetch |
| [awnest](https://github.com/Aitherium/awnest) | that there is a person on the other end | a verdict with evidence, where "we could not tell" is not "yes" |
| [awnboard](https://github.com/Aitherium/awnboard) | a share link anyone who sees it can use | an invitation addressed to one person, for one gate, revocable |
| [awnix](https://github.com/Aitherium/awnix) | that the box is what you left it as | an immutable image you built, with atomic rollback |
| [awrecover](https://github.com/Aitherium/awrecover) | that the restore worked | a restore that fully lands or does not land at all |
| [awrelay](https://github.com/Aitherium/awrelay) | a SaaS in the middle of your agents | findings, alerts and coordination over your own transport |
| [awmail](https://github.com/Aitherium/awmail) | a mailbox somebody else can read | mail your agents send and receive over your own server |
| [awfind](https://github.com/Aitherium/awfind) | one vendor's idea of the web | results from whichever providers you configured |
| [awbrowse](https://github.com/Aitherium/awbrowse) | that the page said what you were told | the render, the DOM and the requests it made |
| [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | a vendor's quantisation defaults | sub-byte KV cache kernels you can benchmark yourself |
| [AitherZero](https://github.com/Aitherium/AitherZero) | a pile of scripts nobody has numbered | numbered, discoverable automation with declarative playbooks |
| [AitherConnect](https://github.com/Aitherium/AitherConnect) | what a page tells your browser to do | a federated search and desktop bridge you host |
| [awreason](https://github.com/Aitherium/awreason) | a confident paragraph | the phases it went through, and every tool call it made to get there |
| [awrecurse](https://github.com/Aitherium/awrecurse) | that everything you pasted in was actually read | which slices it opened, and what it concluded from each |
| [awprism](https://github.com/Aitherium/awprism) | the first explanation that fits | the ranked alternatives, and the observation that separates them |
| [awrepl](https://github.com/Aitherium/awrepl) | what the agent believes the value is | the value, printed from the live session |
| [awresearch](https://github.com/Aitherium/awresearch) | a summary of pages nobody opened | every claim against the source it came from |
| [awpredict](https://github.com/Aitherium/awpredict) | a model because it trained without erroring | its prediction against a self-updating lookup, on the rows that are actually novel |
| [awkno](https://github.com/Aitherium/awkno) | that the docs site is up, or that you remember the family | the whole ecosystem in your terminal, with no network at all |

[**awnix**](https://github.com/Aitherium/awnix) is the ground floor — A Linux you can hand to an agent — immutable base, capabilities included.

## The Aitherium ecosystem

Every repository here is public. Each publishes an `aither-manifest.json` beside its page, so any surface can read every sibling's — the network is browsable from any node in it.

| repo | what it is | pages |
|---|---|---|
| [awdk](https://github.com/Aitherium/awdk) | Build AI agent fleets — 3 lines, any backend, local or cloud | [docs](https://aitherium.github.io/awdk/) |
| [awskills](https://github.com/Aitherium/awskills) | Portable agent skills — self-contained procedures an agent loads on demand | [docs](https://aitherium.github.io/awskills/) |
| [awm](https://github.com/Aitherium/awm) | A portable, scoped agent memory | [docs](https://aitherium.github.io/awm/) |
| [awnode](https://github.com/Aitherium/awnode) | A lightweight local gateway — bridges your apps to the AI backends you chose | [docs](https://aitherium.github.io/awnode/) |
| [awrun](https://github.com/Aitherium/awrun) | A priority-aware queue and dispatcher for agentic runs and ad-hoc CI builds | [docs](https://aitherium.github.io/awrun/) |
| [awgraph](https://github.com/Aitherium/awgraph) | A semantic code graph for agents — AST + tree-sitter, call graphs | [docs](https://aitherium.github.io/awgraph/) |
| [awgit](https://github.com/Aitherium/awgit) | Semantic version control on top of git — edit-ops and leases | [docs](https://aitherium.github.io/awgit/) |
| [awseal](https://github.com/Aitherium/awseal) | Sign an artifact so a stranger can verify it | [docs](https://aitherium.github.io/awseal/) |
| [awshare](https://github.com/Aitherium/awshare) | Publish an artifact and fetch it back verified | [docs](https://aitherium.github.io/awshare/) |
| [awdit](https://github.com/Aitherium/awdit) | An append-only audit trail whose gaps are DETECTABLE | [docs](https://aitherium.github.io/awdit/) |
| [awbac](https://github.com/Aitherium/awbac) | Role-based access control that fails closed and explains itself | [docs](https://aitherium.github.io/awbac/) |
| [awiam](https://github.com/Aitherium/awiam) | Who is this caller? A directory and session store that fails honestly | [docs](https://aitherium.github.io/awiam/) |
| **awtunnel** _(you are here)_ | Reach a service that has no public address | [docs](https://aitherium.github.io/awtunnel/) |
| [awnest](https://github.com/Aitherium/awnest) | Prove there is a human before you let them into the nest | [docs](https://aitherium.github.io/awnest/) |
| [awnboard](https://github.com/Aitherium/awnboard) | A front gate you can put in front of anything, and hand someone the key to | [docs](https://aitherium.github.io/awnboard/) |
| [awnix](https://github.com/Aitherium/awnix) | A Linux you can hand to an agent — immutable base, capabilities included | [docs](https://aitherium.github.io/awnix/) |
| [awrecover](https://github.com/Aitherium/awrecover) | Labelled snapshots with an all-or-nothing restore | [docs](https://aitherium.github.io/awrecover/) |
| [awrelay](https://github.com/Aitherium/awrelay) | Portable agent messaging — findings, alerts, coordination | [docs](https://aitherium.github.io/awrelay/) |
| [awmail](https://github.com/Aitherium/awmail) | Give an agent an email address — send, and actually receive | [docs](https://aitherium.github.io/awmail/) |
| [awnet](https://github.com/Aitherium/awnet) | The agentic web — agents host a mesh, and agents join one | [docs](https://aitherium.github.io/awnet/) |
| [awfind](https://github.com/Aitherium/awfind) | A portable search client — query, results, ranking | [docs](https://aitherium.github.io/awfind/) |
| [awbrowse](https://github.com/Aitherium/awbrowse) | A portable browser client — navigate, console, network, DOM, screenshot | [docs](https://aitherium.github.io/awbrowse/) |
| [awknowledge](https://github.com/Aitherium/awknowledge) | How to run a coding agent so the result survives — the laws, with evidence | [docs](https://aitherium.github.io/awknowledge/) |
| [aitherkvcache](https://github.com/Aitherium/aitherkvcache) | Near-optimal KV cache quantization for LLM inference — sub-byte compression | [docs](https://aitherium.github.io/aitherkvcache/) |
| [AitherZero](https://github.com/Aitherium/AitherZero) | PowerShell 7+ automation framework — numbered, self-describing scripts | [docs](https://aitherium.github.io/AitherZero/) |
| [AitherConnect](https://github.com/Aitherium/AitherConnect) | Browser extension — federated AI search, page context, and the Living OS overlay | [docs](https://aitherium.github.io/AitherConnect/) |
| [awreason](https://github.com/Aitherium/awreason) | A portable reasoning client — sessions, phases, thoughts, and the chain that produced the answer | [docs](https://aitherium.github.io/awreason/) |
| [awrecurse](https://github.com/Aitherium/awrecurse) | Answer a question over a context far larger than the window — recursively, with the trace kept | [docs](https://aitherium.github.io/awrecurse/) |
| [awprism](https://github.com/Aitherium/awprism) | Turn a failure into ranked hypotheses — and say what would confirm each one | [docs](https://aitherium.github.io/awprism/) |
| [awrepl](https://github.com/Aitherium/awrepl) | A REPL an agent can actually use — state that survives between turns | [docs](https://aitherium.github.io/awrepl/) |
| [awresearch](https://github.com/Aitherium/awresearch) | Ask a research question, get a cited report you can check | [docs](https://aitherium.github.io/awresearch/) |
| [awpredict](https://github.com/Aitherium/awpredict) | Predict what your environment does next, and how surprised you were | [docs](https://aitherium.github.io/awpredict/) |
| [awkno](https://github.com/Aitherium/awkno) | The man page for the Aither World — every brick, stack and law, offline | [docs](https://aitherium.github.io/awkno/) |

<div id="aither-constellation" data-self="awtunnel"></div>
<script src="aither-constellation.js"></script>

<!-- aither-ecosystem:end -->
