Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.git
target
.tools
.references
docs/benchmarks
examples/enterprise/state
**/__pycache__
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ jobs:
run: .tools/almide/almide check src/main.almd
- name: Build
run: .tools/almide/almide build src/main.almd -o target/porta
# TLS is rustls only (reqwest without its default features), so the Linux
# binary needs no libssl.so.3 from whatever image it lands in (#35). A
# dependency that turns native-tls back on shows up here.
- name: No OpenSSL linked
if: runner.os == 'Linux'
run: |
ldd target/porta
! ldd target/porta | grep -E 'libssl|libcrypto'
- name: Test
run: .tools/almide/almide test --ci
# The memory ceiling is a cgroup v2 scope asked of the systemd user
Expand All @@ -96,6 +104,10 @@ jobs:
run: command -v strace || (sudo apt-get update -qq && sudo apt-get install -y -qq strace)
- name: Integration
run: python3 scripts/integration.py target/porta
# The job service: policy, worker, records and the HTTP API, with every
# worker under the OS sandbox (os_sandbox = "required").
- name: Job service
run: python3 scripts/job_integration.py target/porta
- name: Escape corpus
run: python3 scripts/escapes.py target/porta
# The runner restricts unprivileged user namespaces through AppArmor, as
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,19 @@ jobs:
python-version: "3.12"
- uses: dtolnay/rust-toolchain@stable
- name: Install pinned Almide
if: runner.os == 'macOS'
run: bash scripts/install-almide.sh
- name: Build
if: runner.os == 'macOS'
run: .tools/almide/almide build src/main.almd -o target/porta
# Linux builds in Debian bullseye, not on the runner: a binary linked on
# Ubuntu 24.04 asks for glibc 2.39 and does not start on Debian 12,
# Ubuntu 22.04 or the python:*-slim images, which is most containers an
# agent runs in (#35). The script refuses a result past glibc 2.31 or one
# linking OpenSSL.
- name: Build (Debian bullseye, glibc 2.31)
if: runner.os == 'Linux'
run: bash scripts/build-linux.sh target/porta
# A binary nobody checked is worse than no binary: the same suite that
# gates a commit gates a release, on the machine that produced the file.
- name: Verify the binary that will ship
Expand Down
7 changes: 6 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ as a definition does.
| `setup.rs` | `sudo porta setup`: a copy in a directory only root can write (`/usr/local/bin`, else `/usr/libexec/porta` linked from it) and the AppArmor profile that gives that copy user namespaces |
| `snapshot.rs` | `--snapshot`: the writable mounts copied before a run to `~/.porta/snapshots`, what the run changed, and `porta rollback` |
| `recipes.rs` + `recipes/*.toml` | `porta init claude` / `codex`: a `porta.toml` measured to what each agent needs |
| `http_proxy.rs`, `proxy_audit.rs` | the loopback CONNECT proxy and its decision trail |
| `http_proxy.rs`, `proxy_egress.rs`, `proxy_audit.rs` | the loopback CONNECT proxy, its run credential and address guard, and its decision trail |
| `credential_broker.rs` | credentials as placeholders: the run's CA, TLS terminated for bound hosts only, the real value put on where it is bound and refused elsewhere |
| `http_client.rs`, `host_process.rs`, `wasm_inspect.rs` | checked host services: one HTTP request, process helpers, module inspection |
| `agent_runtime.rs` + `agent_runtime/` | the broker: `loading` (config, pins, team), `guest`, `model`, `verification`, `tools`, `inspect`, `ffi` |
| `agent_journal.rs` | durable broker records, exclusive access, replay validation |
| `job_service.rs` + `job_service/{policy,spec,worker,http,supervisor}.rs`, `supervisor/{launch,collect}.rs` | the evaluation job API (`job-check`, `job-run`, `job-serve`): operator policy, job validation, one worker process per job (under `porta run` when `os_sandbox = "required"`), run records and `events.jsonl`; see `docs/enterprise/` |
| `agent_mcp.rs` | explicitly granted remote MCP tool calls |
| `json_text.rs`, `locking.rs` | JSON escaping for hand-built replies; lock acquisition that survives poisoning |

Expand Down Expand Up @@ -136,6 +138,9 @@ accessors use `value.*`; serialization and typed key lookups use `json.*`.
- Proxy mode permits only the loopback proxy endpoint, without UDP, Unix
sockets or any other egress channel — including a ring that would open a
socket without `socket(2)`.
- A credential's value never enters the child's environment, never reaches a
log, and is put on only for a host and path it is bound to; a host no
credential names is tunnelled, never intercepted.
- MCP stdio uses newline-delimited JSON; diagnostics belong on stderr.
- MCP result adaptation must preserve multi-field payloads and error status; only legacy single-key Result envelopes unwrap.
- Agent and tool instances inherit no host environment or implicit directories.
Expand Down
47 changes: 47 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Porta Enterprise evaluation image: `porta job-serve` with the sample policy.
#
# docker build -t porta-eval .
# docker run --rm -p 8080:8080 -e PORTA_JOB_TOKEN=<16+ chars> porta-eval
#
# Stages: `build` compiles porta from this checkout with the pinned Almide;
# `test` runs the job integration suite on that binary as an unprivileged
# user; `runtime` is the image that ships. Build `--target test` to run the
# suite; the default target does not.

FROM rust:1-trixie AS build
RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends python3 curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --create-home dev
USER dev
WORKDIR /home/dev/src
COPY --chown=dev . .
RUN rm -rf target .tools \
&& bash scripts/install-almide.sh \
&& .tools/almide/almide build src/main.almd -o target/porta \
&& strip target/porta

FROM build AS test
RUN python3 scripts/job_integration.py target/porta

FROM debian:trixie-slim AS runtime
ARG PORTA_UID=10001
# CA certificates for the TLS porta's own clients speak (rustls; no OpenSSL).
RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --system --uid ${PORTA_UID} --create-home --home-dir /var/lib/porta porta
COPY --from=build /home/dev/src/target/porta /usr/local/bin/porta
COPY deploy/container/policy.toml deploy/container/policy-wasm-only.toml /etc/porta/
COPY examples/enterprise/sample-job/sample-job.wasm /opt/porta/modules/sample-job.wasm
COPY examples/enterprise/catalog/ /opt/porta/catalog/
COPY LICENSE THIRD_PARTY_LICENSES.md /usr/share/doc/porta/
USER porta
ENV HOME=/var/lib/porta
# Fails the build when a policy's pinned digest does not match the module.
RUN porta job-check --policy /etc/porta/policy.toml > /dev/null \
&& porta job-check --policy /etc/porta/policy-wasm-only.toml > /dev/null
EXPOSE 8080
STOPSIGNAL SIGTERM
# policy.toml needs the OS sandbox and refuses to start where the kernel
# cannot apply it; policy-wasm-only.toml runs on the WASM boundary alone.
ENTRYPOINT ["porta", "job-serve"]
CMD ["--policy", "/etc/porta/policy.toml", "--listen", "0.0.0.0:8080"]
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ curl -fsSL https://raw.githubusercontent.com/almide/porta/main/scripts/install.s

| 環境 | 方法 |
|---|---|
| macOS (Apple silicon)、Linux (x86-64, arm64) | 上のスクリプト。SHA-256 を検証し、`cosign` があれば Sigstore 署名も検証 |
| macOS (Apple silicon)、Linux (x86-64, arm64。glibc 2.31 以降:Debian 11、Ubuntu 20.04 以降) | 上のスクリプト。SHA-256 を検証し、`cosign` があれば Sigstore 署名も検証 |
| Debian、Ubuntu | [リリース](https://github.com/almide/porta/releases)の `sudo apt install ./porta_<version>_amd64.deb`。Ubuntu 23.10 以降では namespace に必要な AppArmor プロファイルも読み込む |
| それ以外の方法で入れた Ubuntu | 一度だけ `sudo porta setup` で同じプロファイルを入れる |
| GitHub Actions | `- uses: almide/[email protected]` の後、どのステップでも `porta run …` |
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ curl -fsSL https://raw.githubusercontent.com/almide/porta/main/scripts/install.s

| Where | How |
|---|---|
| macOS (Apple silicon), Linux (x86-64, arm64) | the script above: checks the SHA-256, and the Sigstore signature where `cosign` is installed |
| macOS (Apple silicon), Linux (x86-64, arm64; glibc 2.31+: Debian 11, Ubuntu 20.04 and later) | the script above: checks the SHA-256, and the Sigstore signature where `cosign` is installed |
| Debian, Ubuntu | `sudo apt install ./porta_<version>_amd64.deb` from a [release](https://github.com/almide/porta/releases); on Ubuntu 23.10+ it also loads the AppArmor profile porta's namespaces need |
| Ubuntu, installed any other way | `sudo porta setup` once, for the same profile |
| GitHub Actions | `- uses: almide/[email protected]`, then `porta run …` in any step |
Expand Down Expand Up @@ -167,8 +167,17 @@ Linux ([overhead](docs/benchmarks/overhead.md)).
loop and each tool in separate instances, keeps model credentials in the
host, and resumes a crashed run without repeating completed writes
([agent runtime](docs/agent-runtime.md)).
- **A job API with records.** `porta job-serve --policy policy.toml` takes
WASM jobs over HTTP, checks each against an operator policy, runs it in its
own worker under fuel, memory, time and output limits, and returns the
result, why it stopped, and a run record — the evaluation build of
[Porta Enterprise](docs/enterprise/README.md).
- **One API and a log.** `--proxy-allow api.example.com --proxy-audit
egress.jsonl` lets through only that host and records every decision.
- **Keys the command cannot read.** `--credential ANTHROPIC_API_KEY=api.anthropic.com`
hands the command a placeholder; porta's proxy puts the real key on requests
to that host alone, and refuses the placeholder anywhere else it looks
([enforcement](docs/enforcement.md#credentials-as-placeholders)).

## Docs

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ curl -fsSL https://raw.githubusercontent.com/almide/porta/main/scripts/install.s

| 平台 | 方法 |
|---|---|
| macOS (Apple silicon)、Linux (x86-64, arm64) | 上面的脚本:校验 SHA-256,安装了 `cosign` 时还会校验 Sigstore 签名 |
| macOS (Apple silicon)、Linux (x86-64, arm64;glibc 2.31+:Debian 11、Ubuntu 20.04 及以上) | 上面的脚本:校验 SHA-256,安装了 `cosign` 时还会校验 Sigstore 签名 |
| Debian、Ubuntu | 从 [release](https://github.com/almide/porta/releases) 下载后 `sudo apt install ./porta_<version>_amd64.deb`;在 Ubuntu 23.10+ 上还会加载命名空间所需的 AppArmor 配置 |
| 以其他方式安装的 Ubuntu | 运行一次 `sudo porta setup`,加载同样的配置 |
| GitHub Actions | `- uses: almide/[email protected]`,之后任意步骤中都可 `porta run …` |
Expand Down
Loading
Loading