pybun-cli 0.1.28__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pybun_cli-0.1.28/pybun_cli.egg-info → pybun_cli-0.2.1}/PKG-INFO +229 -2
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/README.md +228 -1
- {pybun_cli-0.1.28 → pybun_cli-0.2.1/pybun_cli.egg-info}/PKG-INFO +229 -2
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/pyproject.toml +1 -1
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/LICENSE +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/MANIFEST.in +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/pybun/__init__.py +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/pybun/__main__.py +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/pybun/bootstrap.py +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/pybun/cli.py +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/pybun_cli.egg-info/SOURCES.txt +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/pybun_cli.egg-info/dependency_links.txt +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/pybun_cli.egg-info/entry_points.txt +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/pybun_cli.egg-info/top_level.txt +0 -0
- {pybun_cli-0.1.28 → pybun_cli-0.2.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pybun-cli
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: PyBun shim bootstrapper for signed releases
|
|
5
5
|
Author: PyBun Authors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,6 +20,13 @@ Dynamic: license-file
|
|
|
20
20
|
|
|
21
21
|
# PyBun (Python Bundle)
|
|
22
22
|
|
|
23
|
+
# v0.2 backend transition
|
|
24
|
+
|
|
25
|
+
Normal project install/add/remove/lock/upgrade and project/PEP 723 execution now
|
|
26
|
+
delegate to uv. See [backend semantics and migration](docs/UV_BACKEND.md) for
|
|
27
|
+
the supported uv version, lock ownership, JSON v2, MCP parity, and explicit
|
|
28
|
+
native compatibility mode.
|
|
29
|
+
|
|
23
30
|
<p align="center">
|
|
24
31
|
<strong>🐍 The Agent-Native Python Control Plane 🤖</strong>
|
|
25
32
|
</p>
|
|
@@ -264,6 +271,18 @@ pybun x cowsay==6.1
|
|
|
264
271
|
pybun x black -- --check .
|
|
265
272
|
```
|
|
266
273
|
|
|
274
|
+
### Affected Tests
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pybun test --affected
|
|
278
|
+
pybun test --affected --since main --format=json
|
|
279
|
+
pybun test --affected --discover --format=json
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`--changed` is an alias for `--affected`. Selection is repository-wide and cannot be combined with explicit paths or `--member`. MCP `pybun_test` uses the same service with `{"changed": true, "since": "main"}`.
|
|
283
|
+
|
|
284
|
+
JSON includes `selection` with `direct`, `transitive`, or `conservative` reasons and import chains. The target Python's standard AST parser reads project source without executing it. Imports and ancestor `conftest.py` dependencies are modeled; uncertain parsing, known dynamic imports, or non-Python/configuration changes conservatively select all tests. Import-root ambiguity may select extra tests. Arbitrary runtime file dependencies are not inferred. Unavailable git/runtime evidence returns an error rather than a verified empty selection.
|
|
285
|
+
|
|
267
286
|
### Python Version Management
|
|
268
287
|
|
|
269
288
|
```bash
|
|
@@ -361,6 +380,8 @@ pybun audit --severity-threshold=medium
|
|
|
361
380
|
pybun audit --fail-on=high
|
|
362
381
|
```
|
|
363
382
|
|
|
383
|
+
The scanner follows OSV batch pagination and fetches advisory details before filtering or suggesting fixes. Missing severity is reported as `unknown`, remains visible at every threshold, and fails any `--fail-on` gate. Failed advisory lookups fail the scan. `PYBUN_OSV_URL` can point to a mirror's batch endpoint; its sibling `vulns/<id>` endpoint must serve full advisories.
|
|
384
|
+
|
|
364
385
|
### Profile Management
|
|
365
386
|
|
|
366
387
|
```bash
|
|
@@ -431,6 +452,8 @@ pybun self update --dry-run
|
|
|
431
452
|
pybun self update --channel nightly
|
|
432
453
|
```
|
|
433
454
|
|
|
455
|
+
MCP exposes the same doctor remediation plan: call `pybun_doctor` with `{"fix": true}` to preview, or `{"fix": true, "apply": true}` to apply low-risk, auto-applicable fixes. `applied_fixes` includes before/after cache counts; `skipped_fixes` explains candidates requiring manual action. The existing `pybun_fix` tool continues to fix lint violations with Ruff.
|
|
456
|
+
|
|
434
457
|
## Sandbox usage
|
|
435
458
|
|
|
436
459
|
Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
@@ -438,7 +461,34 @@ Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
|
438
461
|
pybun --format=json run --sandbox examples/hello.py
|
|
439
462
|
pybun --format=json run --sandbox --allow-network -c "print('net ok')"
|
|
440
463
|
```
|
|
441
|
-
|
|
464
|
+
Use repeatable `--allow-net=host[:port]` to permit selected network destinations
|
|
465
|
+
and `--allow-run=executable-name-or-path` to permit trusted executables. For example:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
pybun --format=json run --sandbox --allow-net=pypi.org:443 script.py
|
|
469
|
+
pybun --format=json run --sandbox --allow-run=/usr/bin/git script.py
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
`--allow-network` retains unrestricted network access. Hostnames are resolved
|
|
473
|
+
once at startup; permission includes those IP addresses (and hosts sharing them).
|
|
474
|
+
Ports are exact when provided; omit the port to allow all ports for that host.
|
|
475
|
+
Use brackets for IPv6, for example `[::1]:8080`. TCP and UDP sends, direct
|
|
476
|
+
connects, and DNS lookups are checked. Denials identify the target with
|
|
477
|
+
`E_SANDBOX_NET_DENIED` or `E_SANDBOX_RUN_DENIED` and a permission hint.
|
|
478
|
+
MCP `sandbox_policy` supports matching `allow_net` and `allow_run` arrays.
|
|
479
|
+
|
|
480
|
+
Executable names are resolved from the initial PATH and pinned to their real
|
|
481
|
+
paths. Subprocess PATH/cwd overrides cannot authorize a different executable.
|
|
482
|
+
Use explicit paths for direct exec/posix_spawn calls; raw fork and shell-command
|
|
483
|
+
APIs remain denied. An allowed executable is trusted: it can launch children and
|
|
484
|
+
access the host independently of Python guards. When `allow_run` is nonempty,
|
|
485
|
+
the blanket zero-child OS process limit is not applied; `resource_limits.unsupported`
|
|
486
|
+
reports `process`. Existing parent OS limits still apply.
|
|
487
|
+
|
|
488
|
+
These are Python runtime guards plus supported resource limits, not a kernel
|
|
489
|
+
containment boundary. Native extensions and trusted external programs require
|
|
490
|
+
an OS/container sandbox when hostile code containment is needed. Combine with
|
|
491
|
+
`--profile=prod` for production-like runs.
|
|
442
492
|
|
|
443
493
|
## Profiles
|
|
444
494
|
|
|
@@ -506,6 +556,121 @@ Requires `pip install pybun-cli`.
|
|
|
506
556
|
```
|
|
507
557
|
*Note: If `pybun` is not in the PATH, provide the absolute path (e.g., `/Users/username/bin/pybun`).*
|
|
508
558
|
|
|
559
|
+
### Server policy
|
|
560
|
+
|
|
561
|
+
MCP execution requires an operator-owned security policy. Configure it before
|
|
562
|
+
starting the server; requests cannot reload or override it. The server freezes
|
|
563
|
+
one configuration at startup, with this precedence: `PYBUN_MCP_POLICY` file,
|
|
564
|
+
then `.pybun/config.toml`, then `pyproject.toml`. A selected file with a missing
|
|
565
|
+
or malformed policy fails closed; it does not fall back to another file.
|
|
566
|
+
`pyproject.toml` and explicit files use `[tool.pybun.mcp.policy]`;
|
|
567
|
+
`.pybun/config.toml` also accepts `[mcp.policy]`.
|
|
568
|
+
|
|
569
|
+
For a restricted native execution server:
|
|
570
|
+
|
|
571
|
+
```toml
|
|
572
|
+
[tool.pybun.mcp.policy]
|
|
573
|
+
default_sandbox = true
|
|
574
|
+
require_sandbox = true
|
|
575
|
+
allow_network = false
|
|
576
|
+
max_execution_time_s = 30
|
|
577
|
+
max_memory_mb = 0 # Linux supports a nonzero memory cap
|
|
578
|
+
max_cpu_time_s = 0
|
|
579
|
+
allowed_working_directories = ["."]
|
|
580
|
+
allowed_backends = ["native"]
|
|
581
|
+
denied_tools = ["pybun_gc"]
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Start it with `PYBUN_BACKEND=native pybun mcp serve --stdio`. Relative boundaries
|
|
585
|
+
are resolved against the startup directory; start at the workspace root or
|
|
586
|
+
explicitly authorize its ancestor workspace. Keep policy files under operator
|
|
587
|
+
control, outside agent-writable paths for deployments. Audit logs must also be
|
|
588
|
+
outside the effective writable project roots.
|
|
589
|
+
|
|
590
|
+
Every tool and its resource aliases pass through authorization before audit
|
|
591
|
+
setup, filesystem snapshots or execution. Responses expose `policy_applied` and
|
|
592
|
+
stable `E_MCP_POLICY_*` denial codes. Requested limits may be stricter; zero
|
|
593
|
+
(unlimited) cannot relax an operator cap. Required sandboxing and active caps
|
|
594
|
+
prevent `unsafe_no_sandbox` from bypassing enforcement. Disallowed network
|
|
595
|
+
permission also removes granular network and executable grants. Sandbox reads
|
|
596
|
+
and writes default to the startup project boundary; explicit paths, symlinks,
|
|
597
|
+
backend-derived workspaces and environment/lock outputs are checked against the
|
|
598
|
+
operator's authorized directories. System-environment installation is denied.
|
|
599
|
+
Write snapshots skip symlinks and generated trees (`target`, `.git`, `.venv`,
|
|
600
|
+
`venv`, `__pycache__`, `node_modules`), and are bounded to 2,000 entries, depth
|
|
601
|
+
32, 4 MiB per file and 16 MiB read per snapshot. Audit records expose
|
|
602
|
+
`file_snapshot_complete=false` whenever evidence is skipped or bounded; their
|
|
603
|
+
`file_writes` then describes only observed changes, not a complete write trace.
|
|
604
|
+
Pure in-memory environment comparisons remain available without execution policy.
|
|
605
|
+
|
|
606
|
+
Enforcement support is explicit: native inline code and ordinary scripts support
|
|
607
|
+
these execution limits. Bounded uv preparation and native PEP 723 preparation
|
|
608
|
+
cannot currently propagate all server limits, so incompatible calls return
|
|
609
|
+
`E_MCP_POLICY_UNSUPPORTED` before preparation. Other tool handlers that cannot
|
|
610
|
+
enforce a requested sandbox/network/resource restriction also return that code.
|
|
611
|
+
Memory caps are unavailable on macOS/Windows; CPU caps require Unix. A dry run
|
|
612
|
+
can preview a bounded uv call without starting the backend.
|
|
613
|
+
|
|
614
|
+
Operators who authorize trusted uv project operations can set
|
|
615
|
+
`require_sandbox=false`, `allow_network=true`, `max_execution_time_s=0`,
|
|
616
|
+
`max_memory_mb=0`, and `max_cpu_time_s=0`; execution-time restrictions that a tool
|
|
617
|
+
cannot enforce must be disabled explicitly. Its existing backend deadlines still
|
|
618
|
+
apply. This does not grant requests permission to escape working-directory or
|
|
619
|
+
backend boundaries. The Python guard's documented containment limits still apply.
|
|
620
|
+
|
|
621
|
+
### Previewing execution
|
|
622
|
+
|
|
623
|
+
Call `pybun_run` with `dry_run: true` to return `would_execute`,
|
|
624
|
+
`estimated_risk` (`low`, `medium`, or `high`), `risk_reasons`, and
|
|
625
|
+
`sandbox_policy_that_would_apply` without executing code or preparing a backend.
|
|
626
|
+
The existing `risk_level` and `sandbox_policy` fields remain equivalent aliases.
|
|
627
|
+
The response also includes server policy and admission limits; previews consume
|
|
628
|
+
normal tool quota. Previewing an unsupported bounded backend reports
|
|
629
|
+
`policy_applied.execution_supported=false` and its denial code.
|
|
630
|
+
|
|
631
|
+
Risk is a substring heuristic over inline code or at most 2 MiB of regular UTF-8
|
|
632
|
+
script source. Missing script targets are rejected. Non-text, oversized or unreadable source reports
|
|
633
|
+
uncertainty as `medium`; filenames are not treated as source. If both are supplied,
|
|
634
|
+
inline code takes precedence, matching execution. `low` is not proof of safety:
|
|
635
|
+
dynamic imports, called code and later file changes can alter behavior. A preview
|
|
636
|
+
does not grant execution permission; the actual call is authorized independently.
|
|
637
|
+
|
|
638
|
+
### Tool rate limits
|
|
639
|
+
|
|
640
|
+
The same frozen configuration accepts `[tool.pybun.mcp.rate_limit]` (or
|
|
641
|
+
`[mcp.rate_limit]` in `.pybun/config.toml`):
|
|
642
|
+
|
|
643
|
+
```toml
|
|
644
|
+
[tool.pybun.mcp.rate_limit]
|
|
645
|
+
calls_per_minute = 60 # all calls in one stdio session
|
|
646
|
+
global_calls_per_minute = 600 # all sessions in this process/configuration
|
|
647
|
+
pybun_run_per_minute = 10
|
|
648
|
+
pybun_install_per_hour = 20
|
|
649
|
+
|
|
650
|
+
[tool.pybun.mcp.rate_limit.per_tool.pybun_lock]
|
|
651
|
+
calls = 5
|
|
652
|
+
window_secs = 60
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
These are token buckets: each starts with a full window's burst and refills
|
|
656
|
+
continuously using monotonic time. All applicable buckets are checked and debited
|
|
657
|
+
atomically after policy authorization, before filesystem snapshots, backend work
|
|
658
|
+
or subprocesses. Resource aliases share the corresponding tool's bucket. A denied
|
|
659
|
+
call debits no bucket; cancelling an admitted call retains its debit. Rejections
|
|
660
|
+
return `mcp.rate_limit_exceeded`, positive `retry_after_ms`, scope and applicable
|
|
661
|
+
limits in `rate_limit_applied`, and write a security audit event without invoking
|
|
662
|
+
the tool or producing its success receipt. Admitted responses expose their limits.
|
|
663
|
+
|
|
664
|
+
One stdio server owns one generated session UUID for its lifetime; client IDs and
|
|
665
|
+
arguments cannot change it. Global/per-tool quotas are shared by server instances
|
|
666
|
+
in the same process with the same canonical startup directory and frozen rate
|
|
667
|
+
configuration. Separate processes and restarts do not share quota state. State
|
|
668
|
+
is bounded to 64 configurations and 512 sessions per configuration; live entries
|
|
669
|
+
are never evicted to reset a quota. Idle sessions expire after two minutes and
|
|
670
|
+
configurations after twice their longest tool window (at least two hours).
|
|
671
|
+
Capacity exhaustion returns a retry hint. Configuration rejects unknown tool names
|
|
672
|
+
and fields, zero or more than 1,000,000 calls, and windows outside 1–86,400 seconds.
|
|
673
|
+
|
|
509
674
|
## JSON output examples
|
|
510
675
|
|
|
511
676
|
All commands support the `--format=json` option (schema v1). Examples:
|
|
@@ -679,3 +844,65 @@ Environment override: `PYBUN_TELEMETRY=0|1`
|
|
|
679
844
|
## License
|
|
680
845
|
|
|
681
846
|
MIT
|
|
847
|
+
|
|
848
|
+
### Local execution history
|
|
849
|
+
|
|
850
|
+
`pybun history --since 20m --format=json` queries CLI receipts for `run`, `test`,
|
|
851
|
+
`install`, `add`, and `remove`, including failed invocations. Receipts are appended
|
|
852
|
+
under `PYBUN_HOME/history.jsonl` (default `~/.cache/pybun/history.jsonl`), with a
|
|
853
|
+
private file mode on Unix and a file lock for concurrent writers. Existing files
|
|
854
|
+
with group/other permissions are rejected for appends; restrict them to mode 600. Set
|
|
855
|
+
`PYBUN_HISTORY=0` to disable recording. History queries never record themselves;
|
|
856
|
+
recording failures warn without changing the command's exit code.
|
|
857
|
+
|
|
858
|
+
Records contain timestamps, duration, exit code, lock digest when available,
|
|
859
|
+
backend identity, Python version/ABI, and known cache/policy/profile evidence.
|
|
860
|
+
Python observations identify whether they describe the execution interpreter or
|
|
861
|
+
the selected environment observed after the command; unavailable evidence is
|
|
862
|
+
`null`. Daemon use is false for the current direct execution paths. Inline code
|
|
863
|
+
and credential option values are removed, and the existing output redaction rules
|
|
864
|
+
apply before persistence and on reads. Program output and environment variables
|
|
865
|
+
are never stored. Arbitrary positional secrets cannot be inferred; disable
|
|
866
|
+
history when arguments contain such values. Queries accept positive `s`, `m`,
|
|
867
|
+
`h`, or `d` durations, report corrupt/partial lines without echoing them, and
|
|
868
|
+
reject files over 32 MiB; archive the local log when needed.
|
|
869
|
+
|
|
870
|
+
### Comparing captured environments
|
|
871
|
+
|
|
872
|
+
`pybun why <package> --format=json` explains every known declaration path to a
|
|
873
|
+
locked distribution. MCP `pybun_why` takes a `package` string and uses the same
|
|
874
|
+
service. The uv backend reads frozen, offline `uv workspace metadata`; it does
|
|
875
|
+
not resolve, install, or synchronize an environment. Paths retain markers and
|
|
876
|
+
classify direct, transitive, extra, dependency-group, and workspace provenance.
|
|
877
|
+
They describe lock alternatives rather than the currently active environment.
|
|
878
|
+
Results include backend/version, lock digest, `complete`, and diagnostics. An
|
|
879
|
+
unreachable or unavailable locked variant is explicitly `unknown`. Native
|
|
880
|
+
provenance uses current declarations with its lock and always reports partial
|
|
881
|
+
evidence. Traversal is bounded (10,000 nodes, 100,000 expansions, depth 256,
|
|
882
|
+
1,000 paths); reaching a bound also reports partial evidence.
|
|
883
|
+
|
|
884
|
+
`pybun environment diff before.json after.json --format=json` compares two JSON
|
|
885
|
+
captures, raw execution receipts, or JSON command envelopes. The MCP equivalent is
|
|
886
|
+
`pybun_environment_diff` with `before` and `after` objects; both use the same
|
|
887
|
+
read-only service. To compare CLI history entries, save the chosen `records[]`
|
|
888
|
+
objects from `pybun history --format=json` as the input files. Each input file is
|
|
889
|
+
limited to 4 MiB.
|
|
890
|
+
|
|
891
|
+
The response reports deterministic `changes` with reason codes for Python,
|
|
892
|
+
backend, lock, package, policy, profile and project evidence. `installed_packages`
|
|
893
|
+
describes interpreter-site inventory; `packages` separately describes a backend's
|
|
894
|
+
resolved lock inventory, which does not prove installation. History captures
|
|
895
|
+
standard isolated-venv site packages by reading distribution metadata without
|
|
896
|
+
importing project packages or running site hooks. System Python and venvs that
|
|
897
|
+
include system/user sites leave the installed inventory unavailable. Executable
|
|
898
|
+
digests are captured when readable; wheel artifact hashes remain unavailable
|
|
899
|
+
unless supplied by a capture.
|
|
900
|
+
|
|
901
|
+
Missing, masked and explicitly unknown values appear in `unavailable` and warning
|
|
902
|
+
diagnostics. They do not establish equality or a runtime state change. An identical
|
|
903
|
+
partial capture has an empty `changes` array while still reporting missing evidence.
|
|
904
|
+
Array ordering and normalized package spelling do not create differences. Only
|
|
905
|
+
typed, allowlisted metadata is compared; unrestricted environment variables and
|
|
906
|
+
unknown configuration keys are excluded, and existing redaction applies before
|
|
907
|
+
comparison. `causality: "not_inferred"` means the result describes recorded state
|
|
908
|
+
changes and never claims that they caused a program's output to change.
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# PyBun (Python Bundle)
|
|
2
2
|
|
|
3
|
+
# v0.2 backend transition
|
|
4
|
+
|
|
5
|
+
Normal project install/add/remove/lock/upgrade and project/PEP 723 execution now
|
|
6
|
+
delegate to uv. See [backend semantics and migration](docs/UV_BACKEND.md) for
|
|
7
|
+
the supported uv version, lock ownership, JSON v2, MCP parity, and explicit
|
|
8
|
+
native compatibility mode.
|
|
9
|
+
|
|
3
10
|
<p align="center">
|
|
4
11
|
<strong>🐍 The Agent-Native Python Control Plane 🤖</strong>
|
|
5
12
|
</p>
|
|
@@ -244,6 +251,18 @@ pybun x cowsay==6.1
|
|
|
244
251
|
pybun x black -- --check .
|
|
245
252
|
```
|
|
246
253
|
|
|
254
|
+
### Affected Tests
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
pybun test --affected
|
|
258
|
+
pybun test --affected --since main --format=json
|
|
259
|
+
pybun test --affected --discover --format=json
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`--changed` is an alias for `--affected`. Selection is repository-wide and cannot be combined with explicit paths or `--member`. MCP `pybun_test` uses the same service with `{"changed": true, "since": "main"}`.
|
|
263
|
+
|
|
264
|
+
JSON includes `selection` with `direct`, `transitive`, or `conservative` reasons and import chains. The target Python's standard AST parser reads project source without executing it. Imports and ancestor `conftest.py` dependencies are modeled; uncertain parsing, known dynamic imports, or non-Python/configuration changes conservatively select all tests. Import-root ambiguity may select extra tests. Arbitrary runtime file dependencies are not inferred. Unavailable git/runtime evidence returns an error rather than a verified empty selection.
|
|
265
|
+
|
|
247
266
|
### Python Version Management
|
|
248
267
|
|
|
249
268
|
```bash
|
|
@@ -341,6 +360,8 @@ pybun audit --severity-threshold=medium
|
|
|
341
360
|
pybun audit --fail-on=high
|
|
342
361
|
```
|
|
343
362
|
|
|
363
|
+
The scanner follows OSV batch pagination and fetches advisory details before filtering or suggesting fixes. Missing severity is reported as `unknown`, remains visible at every threshold, and fails any `--fail-on` gate. Failed advisory lookups fail the scan. `PYBUN_OSV_URL` can point to a mirror's batch endpoint; its sibling `vulns/<id>` endpoint must serve full advisories.
|
|
364
|
+
|
|
344
365
|
### Profile Management
|
|
345
366
|
|
|
346
367
|
```bash
|
|
@@ -411,6 +432,8 @@ pybun self update --dry-run
|
|
|
411
432
|
pybun self update --channel nightly
|
|
412
433
|
```
|
|
413
434
|
|
|
435
|
+
MCP exposes the same doctor remediation plan: call `pybun_doctor` with `{"fix": true}` to preview, or `{"fix": true, "apply": true}` to apply low-risk, auto-applicable fixes. `applied_fixes` includes before/after cache counts; `skipped_fixes` explains candidates requiring manual action. The existing `pybun_fix` tool continues to fix lint violations with Ruff.
|
|
436
|
+
|
|
414
437
|
## Sandbox usage
|
|
415
438
|
|
|
416
439
|
Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
@@ -418,7 +441,34 @@ Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
|
418
441
|
pybun --format=json run --sandbox examples/hello.py
|
|
419
442
|
pybun --format=json run --sandbox --allow-network -c "print('net ok')"
|
|
420
443
|
```
|
|
421
|
-
|
|
444
|
+
Use repeatable `--allow-net=host[:port]` to permit selected network destinations
|
|
445
|
+
and `--allow-run=executable-name-or-path` to permit trusted executables. For example:
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
pybun --format=json run --sandbox --allow-net=pypi.org:443 script.py
|
|
449
|
+
pybun --format=json run --sandbox --allow-run=/usr/bin/git script.py
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
`--allow-network` retains unrestricted network access. Hostnames are resolved
|
|
453
|
+
once at startup; permission includes those IP addresses (and hosts sharing them).
|
|
454
|
+
Ports are exact when provided; omit the port to allow all ports for that host.
|
|
455
|
+
Use brackets for IPv6, for example `[::1]:8080`. TCP and UDP sends, direct
|
|
456
|
+
connects, and DNS lookups are checked. Denials identify the target with
|
|
457
|
+
`E_SANDBOX_NET_DENIED` or `E_SANDBOX_RUN_DENIED` and a permission hint.
|
|
458
|
+
MCP `sandbox_policy` supports matching `allow_net` and `allow_run` arrays.
|
|
459
|
+
|
|
460
|
+
Executable names are resolved from the initial PATH and pinned to their real
|
|
461
|
+
paths. Subprocess PATH/cwd overrides cannot authorize a different executable.
|
|
462
|
+
Use explicit paths for direct exec/posix_spawn calls; raw fork and shell-command
|
|
463
|
+
APIs remain denied. An allowed executable is trusted: it can launch children and
|
|
464
|
+
access the host independently of Python guards. When `allow_run` is nonempty,
|
|
465
|
+
the blanket zero-child OS process limit is not applied; `resource_limits.unsupported`
|
|
466
|
+
reports `process`. Existing parent OS limits still apply.
|
|
467
|
+
|
|
468
|
+
These are Python runtime guards plus supported resource limits, not a kernel
|
|
469
|
+
containment boundary. Native extensions and trusted external programs require
|
|
470
|
+
an OS/container sandbox when hostile code containment is needed. Combine with
|
|
471
|
+
`--profile=prod` for production-like runs.
|
|
422
472
|
|
|
423
473
|
## Profiles
|
|
424
474
|
|
|
@@ -486,6 +536,121 @@ Requires `pip install pybun-cli`.
|
|
|
486
536
|
```
|
|
487
537
|
*Note: If `pybun` is not in the PATH, provide the absolute path (e.g., `/Users/username/bin/pybun`).*
|
|
488
538
|
|
|
539
|
+
### Server policy
|
|
540
|
+
|
|
541
|
+
MCP execution requires an operator-owned security policy. Configure it before
|
|
542
|
+
starting the server; requests cannot reload or override it. The server freezes
|
|
543
|
+
one configuration at startup, with this precedence: `PYBUN_MCP_POLICY` file,
|
|
544
|
+
then `.pybun/config.toml`, then `pyproject.toml`. A selected file with a missing
|
|
545
|
+
or malformed policy fails closed; it does not fall back to another file.
|
|
546
|
+
`pyproject.toml` and explicit files use `[tool.pybun.mcp.policy]`;
|
|
547
|
+
`.pybun/config.toml` also accepts `[mcp.policy]`.
|
|
548
|
+
|
|
549
|
+
For a restricted native execution server:
|
|
550
|
+
|
|
551
|
+
```toml
|
|
552
|
+
[tool.pybun.mcp.policy]
|
|
553
|
+
default_sandbox = true
|
|
554
|
+
require_sandbox = true
|
|
555
|
+
allow_network = false
|
|
556
|
+
max_execution_time_s = 30
|
|
557
|
+
max_memory_mb = 0 # Linux supports a nonzero memory cap
|
|
558
|
+
max_cpu_time_s = 0
|
|
559
|
+
allowed_working_directories = ["."]
|
|
560
|
+
allowed_backends = ["native"]
|
|
561
|
+
denied_tools = ["pybun_gc"]
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Start it with `PYBUN_BACKEND=native pybun mcp serve --stdio`. Relative boundaries
|
|
565
|
+
are resolved against the startup directory; start at the workspace root or
|
|
566
|
+
explicitly authorize its ancestor workspace. Keep policy files under operator
|
|
567
|
+
control, outside agent-writable paths for deployments. Audit logs must also be
|
|
568
|
+
outside the effective writable project roots.
|
|
569
|
+
|
|
570
|
+
Every tool and its resource aliases pass through authorization before audit
|
|
571
|
+
setup, filesystem snapshots or execution. Responses expose `policy_applied` and
|
|
572
|
+
stable `E_MCP_POLICY_*` denial codes. Requested limits may be stricter; zero
|
|
573
|
+
(unlimited) cannot relax an operator cap. Required sandboxing and active caps
|
|
574
|
+
prevent `unsafe_no_sandbox` from bypassing enforcement. Disallowed network
|
|
575
|
+
permission also removes granular network and executable grants. Sandbox reads
|
|
576
|
+
and writes default to the startup project boundary; explicit paths, symlinks,
|
|
577
|
+
backend-derived workspaces and environment/lock outputs are checked against the
|
|
578
|
+
operator's authorized directories. System-environment installation is denied.
|
|
579
|
+
Write snapshots skip symlinks and generated trees (`target`, `.git`, `.venv`,
|
|
580
|
+
`venv`, `__pycache__`, `node_modules`), and are bounded to 2,000 entries, depth
|
|
581
|
+
32, 4 MiB per file and 16 MiB read per snapshot. Audit records expose
|
|
582
|
+
`file_snapshot_complete=false` whenever evidence is skipped or bounded; their
|
|
583
|
+
`file_writes` then describes only observed changes, not a complete write trace.
|
|
584
|
+
Pure in-memory environment comparisons remain available without execution policy.
|
|
585
|
+
|
|
586
|
+
Enforcement support is explicit: native inline code and ordinary scripts support
|
|
587
|
+
these execution limits. Bounded uv preparation and native PEP 723 preparation
|
|
588
|
+
cannot currently propagate all server limits, so incompatible calls return
|
|
589
|
+
`E_MCP_POLICY_UNSUPPORTED` before preparation. Other tool handlers that cannot
|
|
590
|
+
enforce a requested sandbox/network/resource restriction also return that code.
|
|
591
|
+
Memory caps are unavailable on macOS/Windows; CPU caps require Unix. A dry run
|
|
592
|
+
can preview a bounded uv call without starting the backend.
|
|
593
|
+
|
|
594
|
+
Operators who authorize trusted uv project operations can set
|
|
595
|
+
`require_sandbox=false`, `allow_network=true`, `max_execution_time_s=0`,
|
|
596
|
+
`max_memory_mb=0`, and `max_cpu_time_s=0`; execution-time restrictions that a tool
|
|
597
|
+
cannot enforce must be disabled explicitly. Its existing backend deadlines still
|
|
598
|
+
apply. This does not grant requests permission to escape working-directory or
|
|
599
|
+
backend boundaries. The Python guard's documented containment limits still apply.
|
|
600
|
+
|
|
601
|
+
### Previewing execution
|
|
602
|
+
|
|
603
|
+
Call `pybun_run` with `dry_run: true` to return `would_execute`,
|
|
604
|
+
`estimated_risk` (`low`, `medium`, or `high`), `risk_reasons`, and
|
|
605
|
+
`sandbox_policy_that_would_apply` without executing code or preparing a backend.
|
|
606
|
+
The existing `risk_level` and `sandbox_policy` fields remain equivalent aliases.
|
|
607
|
+
The response also includes server policy and admission limits; previews consume
|
|
608
|
+
normal tool quota. Previewing an unsupported bounded backend reports
|
|
609
|
+
`policy_applied.execution_supported=false` and its denial code.
|
|
610
|
+
|
|
611
|
+
Risk is a substring heuristic over inline code or at most 2 MiB of regular UTF-8
|
|
612
|
+
script source. Missing script targets are rejected. Non-text, oversized or unreadable source reports
|
|
613
|
+
uncertainty as `medium`; filenames are not treated as source. If both are supplied,
|
|
614
|
+
inline code takes precedence, matching execution. `low` is not proof of safety:
|
|
615
|
+
dynamic imports, called code and later file changes can alter behavior. A preview
|
|
616
|
+
does not grant execution permission; the actual call is authorized independently.
|
|
617
|
+
|
|
618
|
+
### Tool rate limits
|
|
619
|
+
|
|
620
|
+
The same frozen configuration accepts `[tool.pybun.mcp.rate_limit]` (or
|
|
621
|
+
`[mcp.rate_limit]` in `.pybun/config.toml`):
|
|
622
|
+
|
|
623
|
+
```toml
|
|
624
|
+
[tool.pybun.mcp.rate_limit]
|
|
625
|
+
calls_per_minute = 60 # all calls in one stdio session
|
|
626
|
+
global_calls_per_minute = 600 # all sessions in this process/configuration
|
|
627
|
+
pybun_run_per_minute = 10
|
|
628
|
+
pybun_install_per_hour = 20
|
|
629
|
+
|
|
630
|
+
[tool.pybun.mcp.rate_limit.per_tool.pybun_lock]
|
|
631
|
+
calls = 5
|
|
632
|
+
window_secs = 60
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
These are token buckets: each starts with a full window's burst and refills
|
|
636
|
+
continuously using monotonic time. All applicable buckets are checked and debited
|
|
637
|
+
atomically after policy authorization, before filesystem snapshots, backend work
|
|
638
|
+
or subprocesses. Resource aliases share the corresponding tool's bucket. A denied
|
|
639
|
+
call debits no bucket; cancelling an admitted call retains its debit. Rejections
|
|
640
|
+
return `mcp.rate_limit_exceeded`, positive `retry_after_ms`, scope and applicable
|
|
641
|
+
limits in `rate_limit_applied`, and write a security audit event without invoking
|
|
642
|
+
the tool or producing its success receipt. Admitted responses expose their limits.
|
|
643
|
+
|
|
644
|
+
One stdio server owns one generated session UUID for its lifetime; client IDs and
|
|
645
|
+
arguments cannot change it. Global/per-tool quotas are shared by server instances
|
|
646
|
+
in the same process with the same canonical startup directory and frozen rate
|
|
647
|
+
configuration. Separate processes and restarts do not share quota state. State
|
|
648
|
+
is bounded to 64 configurations and 512 sessions per configuration; live entries
|
|
649
|
+
are never evicted to reset a quota. Idle sessions expire after two minutes and
|
|
650
|
+
configurations after twice their longest tool window (at least two hours).
|
|
651
|
+
Capacity exhaustion returns a retry hint. Configuration rejects unknown tool names
|
|
652
|
+
and fields, zero or more than 1,000,000 calls, and windows outside 1–86,400 seconds.
|
|
653
|
+
|
|
489
654
|
## JSON output examples
|
|
490
655
|
|
|
491
656
|
All commands support the `--format=json` option (schema v1). Examples:
|
|
@@ -659,3 +824,65 @@ Environment override: `PYBUN_TELEMETRY=0|1`
|
|
|
659
824
|
## License
|
|
660
825
|
|
|
661
826
|
MIT
|
|
827
|
+
|
|
828
|
+
### Local execution history
|
|
829
|
+
|
|
830
|
+
`pybun history --since 20m --format=json` queries CLI receipts for `run`, `test`,
|
|
831
|
+
`install`, `add`, and `remove`, including failed invocations. Receipts are appended
|
|
832
|
+
under `PYBUN_HOME/history.jsonl` (default `~/.cache/pybun/history.jsonl`), with a
|
|
833
|
+
private file mode on Unix and a file lock for concurrent writers. Existing files
|
|
834
|
+
with group/other permissions are rejected for appends; restrict them to mode 600. Set
|
|
835
|
+
`PYBUN_HISTORY=0` to disable recording. History queries never record themselves;
|
|
836
|
+
recording failures warn without changing the command's exit code.
|
|
837
|
+
|
|
838
|
+
Records contain timestamps, duration, exit code, lock digest when available,
|
|
839
|
+
backend identity, Python version/ABI, and known cache/policy/profile evidence.
|
|
840
|
+
Python observations identify whether they describe the execution interpreter or
|
|
841
|
+
the selected environment observed after the command; unavailable evidence is
|
|
842
|
+
`null`. Daemon use is false for the current direct execution paths. Inline code
|
|
843
|
+
and credential option values are removed, and the existing output redaction rules
|
|
844
|
+
apply before persistence and on reads. Program output and environment variables
|
|
845
|
+
are never stored. Arbitrary positional secrets cannot be inferred; disable
|
|
846
|
+
history when arguments contain such values. Queries accept positive `s`, `m`,
|
|
847
|
+
`h`, or `d` durations, report corrupt/partial lines without echoing them, and
|
|
848
|
+
reject files over 32 MiB; archive the local log when needed.
|
|
849
|
+
|
|
850
|
+
### Comparing captured environments
|
|
851
|
+
|
|
852
|
+
`pybun why <package> --format=json` explains every known declaration path to a
|
|
853
|
+
locked distribution. MCP `pybun_why` takes a `package` string and uses the same
|
|
854
|
+
service. The uv backend reads frozen, offline `uv workspace metadata`; it does
|
|
855
|
+
not resolve, install, or synchronize an environment. Paths retain markers and
|
|
856
|
+
classify direct, transitive, extra, dependency-group, and workspace provenance.
|
|
857
|
+
They describe lock alternatives rather than the currently active environment.
|
|
858
|
+
Results include backend/version, lock digest, `complete`, and diagnostics. An
|
|
859
|
+
unreachable or unavailable locked variant is explicitly `unknown`. Native
|
|
860
|
+
provenance uses current declarations with its lock and always reports partial
|
|
861
|
+
evidence. Traversal is bounded (10,000 nodes, 100,000 expansions, depth 256,
|
|
862
|
+
1,000 paths); reaching a bound also reports partial evidence.
|
|
863
|
+
|
|
864
|
+
`pybun environment diff before.json after.json --format=json` compares two JSON
|
|
865
|
+
captures, raw execution receipts, or JSON command envelopes. The MCP equivalent is
|
|
866
|
+
`pybun_environment_diff` with `before` and `after` objects; both use the same
|
|
867
|
+
read-only service. To compare CLI history entries, save the chosen `records[]`
|
|
868
|
+
objects from `pybun history --format=json` as the input files. Each input file is
|
|
869
|
+
limited to 4 MiB.
|
|
870
|
+
|
|
871
|
+
The response reports deterministic `changes` with reason codes for Python,
|
|
872
|
+
backend, lock, package, policy, profile and project evidence. `installed_packages`
|
|
873
|
+
describes interpreter-site inventory; `packages` separately describes a backend's
|
|
874
|
+
resolved lock inventory, which does not prove installation. History captures
|
|
875
|
+
standard isolated-venv site packages by reading distribution metadata without
|
|
876
|
+
importing project packages or running site hooks. System Python and venvs that
|
|
877
|
+
include system/user sites leave the installed inventory unavailable. Executable
|
|
878
|
+
digests are captured when readable; wheel artifact hashes remain unavailable
|
|
879
|
+
unless supplied by a capture.
|
|
880
|
+
|
|
881
|
+
Missing, masked and explicitly unknown values appear in `unavailable` and warning
|
|
882
|
+
diagnostics. They do not establish equality or a runtime state change. An identical
|
|
883
|
+
partial capture has an empty `changes` array while still reporting missing evidence.
|
|
884
|
+
Array ordering and normalized package spelling do not create differences. Only
|
|
885
|
+
typed, allowlisted metadata is compared; unrestricted environment variables and
|
|
886
|
+
unknown configuration keys are excluded, and existing redaction applies before
|
|
887
|
+
comparison. `causality: "not_inferred"` means the result describes recorded state
|
|
888
|
+
changes and never claims that they caused a program's output to change.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pybun-cli
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: PyBun shim bootstrapper for signed releases
|
|
5
5
|
Author: PyBun Authors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -20,6 +20,13 @@ Dynamic: license-file
|
|
|
20
20
|
|
|
21
21
|
# PyBun (Python Bundle)
|
|
22
22
|
|
|
23
|
+
# v0.2 backend transition
|
|
24
|
+
|
|
25
|
+
Normal project install/add/remove/lock/upgrade and project/PEP 723 execution now
|
|
26
|
+
delegate to uv. See [backend semantics and migration](docs/UV_BACKEND.md) for
|
|
27
|
+
the supported uv version, lock ownership, JSON v2, MCP parity, and explicit
|
|
28
|
+
native compatibility mode.
|
|
29
|
+
|
|
23
30
|
<p align="center">
|
|
24
31
|
<strong>🐍 The Agent-Native Python Control Plane 🤖</strong>
|
|
25
32
|
</p>
|
|
@@ -264,6 +271,18 @@ pybun x cowsay==6.1
|
|
|
264
271
|
pybun x black -- --check .
|
|
265
272
|
```
|
|
266
273
|
|
|
274
|
+
### Affected Tests
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pybun test --affected
|
|
278
|
+
pybun test --affected --since main --format=json
|
|
279
|
+
pybun test --affected --discover --format=json
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`--changed` is an alias for `--affected`. Selection is repository-wide and cannot be combined with explicit paths or `--member`. MCP `pybun_test` uses the same service with `{"changed": true, "since": "main"}`.
|
|
283
|
+
|
|
284
|
+
JSON includes `selection` with `direct`, `transitive`, or `conservative` reasons and import chains. The target Python's standard AST parser reads project source without executing it. Imports and ancestor `conftest.py` dependencies are modeled; uncertain parsing, known dynamic imports, or non-Python/configuration changes conservatively select all tests. Import-root ambiguity may select extra tests. Arbitrary runtime file dependencies are not inferred. Unavailable git/runtime evidence returns an error rather than a verified empty selection.
|
|
285
|
+
|
|
267
286
|
### Python Version Management
|
|
268
287
|
|
|
269
288
|
```bash
|
|
@@ -361,6 +380,8 @@ pybun audit --severity-threshold=medium
|
|
|
361
380
|
pybun audit --fail-on=high
|
|
362
381
|
```
|
|
363
382
|
|
|
383
|
+
The scanner follows OSV batch pagination and fetches advisory details before filtering or suggesting fixes. Missing severity is reported as `unknown`, remains visible at every threshold, and fails any `--fail-on` gate. Failed advisory lookups fail the scan. `PYBUN_OSV_URL` can point to a mirror's batch endpoint; its sibling `vulns/<id>` endpoint must serve full advisories.
|
|
384
|
+
|
|
364
385
|
### Profile Management
|
|
365
386
|
|
|
366
387
|
```bash
|
|
@@ -431,6 +452,8 @@ pybun self update --dry-run
|
|
|
431
452
|
pybun self update --channel nightly
|
|
432
453
|
```
|
|
433
454
|
|
|
455
|
+
MCP exposes the same doctor remediation plan: call `pybun_doctor` with `{"fix": true}` to preview, or `{"fix": true, "apply": true}` to apply low-risk, auto-applicable fixes. `applied_fixes` includes before/after cache counts; `skipped_fixes` explains candidates requiring manual action. The existing `pybun_fix` tool continues to fix lint violations with Ruff.
|
|
456
|
+
|
|
434
457
|
## Sandbox usage
|
|
435
458
|
|
|
436
459
|
Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
@@ -438,7 +461,34 @@ Use the sandbox for untrusted scripts or PEP 723 snippets:
|
|
|
438
461
|
pybun --format=json run --sandbox examples/hello.py
|
|
439
462
|
pybun --format=json run --sandbox --allow-network -c "print('net ok')"
|
|
440
463
|
```
|
|
441
|
-
|
|
464
|
+
Use repeatable `--allow-net=host[:port]` to permit selected network destinations
|
|
465
|
+
and `--allow-run=executable-name-or-path` to permit trusted executables. For example:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
pybun --format=json run --sandbox --allow-net=pypi.org:443 script.py
|
|
469
|
+
pybun --format=json run --sandbox --allow-run=/usr/bin/git script.py
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
`--allow-network` retains unrestricted network access. Hostnames are resolved
|
|
473
|
+
once at startup; permission includes those IP addresses (and hosts sharing them).
|
|
474
|
+
Ports are exact when provided; omit the port to allow all ports for that host.
|
|
475
|
+
Use brackets for IPv6, for example `[::1]:8080`. TCP and UDP sends, direct
|
|
476
|
+
connects, and DNS lookups are checked. Denials identify the target with
|
|
477
|
+
`E_SANDBOX_NET_DENIED` or `E_SANDBOX_RUN_DENIED` and a permission hint.
|
|
478
|
+
MCP `sandbox_policy` supports matching `allow_net` and `allow_run` arrays.
|
|
479
|
+
|
|
480
|
+
Executable names are resolved from the initial PATH and pinned to their real
|
|
481
|
+
paths. Subprocess PATH/cwd overrides cannot authorize a different executable.
|
|
482
|
+
Use explicit paths for direct exec/posix_spawn calls; raw fork and shell-command
|
|
483
|
+
APIs remain denied. An allowed executable is trusted: it can launch children and
|
|
484
|
+
access the host independently of Python guards. When `allow_run` is nonempty,
|
|
485
|
+
the blanket zero-child OS process limit is not applied; `resource_limits.unsupported`
|
|
486
|
+
reports `process`. Existing parent OS limits still apply.
|
|
487
|
+
|
|
488
|
+
These are Python runtime guards plus supported resource limits, not a kernel
|
|
489
|
+
containment boundary. Native extensions and trusted external programs require
|
|
490
|
+
an OS/container sandbox when hostile code containment is needed. Combine with
|
|
491
|
+
`--profile=prod` for production-like runs.
|
|
442
492
|
|
|
443
493
|
## Profiles
|
|
444
494
|
|
|
@@ -506,6 +556,121 @@ Requires `pip install pybun-cli`.
|
|
|
506
556
|
```
|
|
507
557
|
*Note: If `pybun` is not in the PATH, provide the absolute path (e.g., `/Users/username/bin/pybun`).*
|
|
508
558
|
|
|
559
|
+
### Server policy
|
|
560
|
+
|
|
561
|
+
MCP execution requires an operator-owned security policy. Configure it before
|
|
562
|
+
starting the server; requests cannot reload or override it. The server freezes
|
|
563
|
+
one configuration at startup, with this precedence: `PYBUN_MCP_POLICY` file,
|
|
564
|
+
then `.pybun/config.toml`, then `pyproject.toml`. A selected file with a missing
|
|
565
|
+
or malformed policy fails closed; it does not fall back to another file.
|
|
566
|
+
`pyproject.toml` and explicit files use `[tool.pybun.mcp.policy]`;
|
|
567
|
+
`.pybun/config.toml` also accepts `[mcp.policy]`.
|
|
568
|
+
|
|
569
|
+
For a restricted native execution server:
|
|
570
|
+
|
|
571
|
+
```toml
|
|
572
|
+
[tool.pybun.mcp.policy]
|
|
573
|
+
default_sandbox = true
|
|
574
|
+
require_sandbox = true
|
|
575
|
+
allow_network = false
|
|
576
|
+
max_execution_time_s = 30
|
|
577
|
+
max_memory_mb = 0 # Linux supports a nonzero memory cap
|
|
578
|
+
max_cpu_time_s = 0
|
|
579
|
+
allowed_working_directories = ["."]
|
|
580
|
+
allowed_backends = ["native"]
|
|
581
|
+
denied_tools = ["pybun_gc"]
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Start it with `PYBUN_BACKEND=native pybun mcp serve --stdio`. Relative boundaries
|
|
585
|
+
are resolved against the startup directory; start at the workspace root or
|
|
586
|
+
explicitly authorize its ancestor workspace. Keep policy files under operator
|
|
587
|
+
control, outside agent-writable paths for deployments. Audit logs must also be
|
|
588
|
+
outside the effective writable project roots.
|
|
589
|
+
|
|
590
|
+
Every tool and its resource aliases pass through authorization before audit
|
|
591
|
+
setup, filesystem snapshots or execution. Responses expose `policy_applied` and
|
|
592
|
+
stable `E_MCP_POLICY_*` denial codes. Requested limits may be stricter; zero
|
|
593
|
+
(unlimited) cannot relax an operator cap. Required sandboxing and active caps
|
|
594
|
+
prevent `unsafe_no_sandbox` from bypassing enforcement. Disallowed network
|
|
595
|
+
permission also removes granular network and executable grants. Sandbox reads
|
|
596
|
+
and writes default to the startup project boundary; explicit paths, symlinks,
|
|
597
|
+
backend-derived workspaces and environment/lock outputs are checked against the
|
|
598
|
+
operator's authorized directories. System-environment installation is denied.
|
|
599
|
+
Write snapshots skip symlinks and generated trees (`target`, `.git`, `.venv`,
|
|
600
|
+
`venv`, `__pycache__`, `node_modules`), and are bounded to 2,000 entries, depth
|
|
601
|
+
32, 4 MiB per file and 16 MiB read per snapshot. Audit records expose
|
|
602
|
+
`file_snapshot_complete=false` whenever evidence is skipped or bounded; their
|
|
603
|
+
`file_writes` then describes only observed changes, not a complete write trace.
|
|
604
|
+
Pure in-memory environment comparisons remain available without execution policy.
|
|
605
|
+
|
|
606
|
+
Enforcement support is explicit: native inline code and ordinary scripts support
|
|
607
|
+
these execution limits. Bounded uv preparation and native PEP 723 preparation
|
|
608
|
+
cannot currently propagate all server limits, so incompatible calls return
|
|
609
|
+
`E_MCP_POLICY_UNSUPPORTED` before preparation. Other tool handlers that cannot
|
|
610
|
+
enforce a requested sandbox/network/resource restriction also return that code.
|
|
611
|
+
Memory caps are unavailable on macOS/Windows; CPU caps require Unix. A dry run
|
|
612
|
+
can preview a bounded uv call without starting the backend.
|
|
613
|
+
|
|
614
|
+
Operators who authorize trusted uv project operations can set
|
|
615
|
+
`require_sandbox=false`, `allow_network=true`, `max_execution_time_s=0`,
|
|
616
|
+
`max_memory_mb=0`, and `max_cpu_time_s=0`; execution-time restrictions that a tool
|
|
617
|
+
cannot enforce must be disabled explicitly. Its existing backend deadlines still
|
|
618
|
+
apply. This does not grant requests permission to escape working-directory or
|
|
619
|
+
backend boundaries. The Python guard's documented containment limits still apply.
|
|
620
|
+
|
|
621
|
+
### Previewing execution
|
|
622
|
+
|
|
623
|
+
Call `pybun_run` with `dry_run: true` to return `would_execute`,
|
|
624
|
+
`estimated_risk` (`low`, `medium`, or `high`), `risk_reasons`, and
|
|
625
|
+
`sandbox_policy_that_would_apply` without executing code or preparing a backend.
|
|
626
|
+
The existing `risk_level` and `sandbox_policy` fields remain equivalent aliases.
|
|
627
|
+
The response also includes server policy and admission limits; previews consume
|
|
628
|
+
normal tool quota. Previewing an unsupported bounded backend reports
|
|
629
|
+
`policy_applied.execution_supported=false` and its denial code.
|
|
630
|
+
|
|
631
|
+
Risk is a substring heuristic over inline code or at most 2 MiB of regular UTF-8
|
|
632
|
+
script source. Missing script targets are rejected. Non-text, oversized or unreadable source reports
|
|
633
|
+
uncertainty as `medium`; filenames are not treated as source. If both are supplied,
|
|
634
|
+
inline code takes precedence, matching execution. `low` is not proof of safety:
|
|
635
|
+
dynamic imports, called code and later file changes can alter behavior. A preview
|
|
636
|
+
does not grant execution permission; the actual call is authorized independently.
|
|
637
|
+
|
|
638
|
+
### Tool rate limits
|
|
639
|
+
|
|
640
|
+
The same frozen configuration accepts `[tool.pybun.mcp.rate_limit]` (or
|
|
641
|
+
`[mcp.rate_limit]` in `.pybun/config.toml`):
|
|
642
|
+
|
|
643
|
+
```toml
|
|
644
|
+
[tool.pybun.mcp.rate_limit]
|
|
645
|
+
calls_per_minute = 60 # all calls in one stdio session
|
|
646
|
+
global_calls_per_minute = 600 # all sessions in this process/configuration
|
|
647
|
+
pybun_run_per_minute = 10
|
|
648
|
+
pybun_install_per_hour = 20
|
|
649
|
+
|
|
650
|
+
[tool.pybun.mcp.rate_limit.per_tool.pybun_lock]
|
|
651
|
+
calls = 5
|
|
652
|
+
window_secs = 60
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
These are token buckets: each starts with a full window's burst and refills
|
|
656
|
+
continuously using monotonic time. All applicable buckets are checked and debited
|
|
657
|
+
atomically after policy authorization, before filesystem snapshots, backend work
|
|
658
|
+
or subprocesses. Resource aliases share the corresponding tool's bucket. A denied
|
|
659
|
+
call debits no bucket; cancelling an admitted call retains its debit. Rejections
|
|
660
|
+
return `mcp.rate_limit_exceeded`, positive `retry_after_ms`, scope and applicable
|
|
661
|
+
limits in `rate_limit_applied`, and write a security audit event without invoking
|
|
662
|
+
the tool or producing its success receipt. Admitted responses expose their limits.
|
|
663
|
+
|
|
664
|
+
One stdio server owns one generated session UUID for its lifetime; client IDs and
|
|
665
|
+
arguments cannot change it. Global/per-tool quotas are shared by server instances
|
|
666
|
+
in the same process with the same canonical startup directory and frozen rate
|
|
667
|
+
configuration. Separate processes and restarts do not share quota state. State
|
|
668
|
+
is bounded to 64 configurations and 512 sessions per configuration; live entries
|
|
669
|
+
are never evicted to reset a quota. Idle sessions expire after two minutes and
|
|
670
|
+
configurations after twice their longest tool window (at least two hours).
|
|
671
|
+
Capacity exhaustion returns a retry hint. Configuration rejects unknown tool names
|
|
672
|
+
and fields, zero or more than 1,000,000 calls, and windows outside 1–86,400 seconds.
|
|
673
|
+
|
|
509
674
|
## JSON output examples
|
|
510
675
|
|
|
511
676
|
All commands support the `--format=json` option (schema v1). Examples:
|
|
@@ -679,3 +844,65 @@ Environment override: `PYBUN_TELEMETRY=0|1`
|
|
|
679
844
|
## License
|
|
680
845
|
|
|
681
846
|
MIT
|
|
847
|
+
|
|
848
|
+
### Local execution history
|
|
849
|
+
|
|
850
|
+
`pybun history --since 20m --format=json` queries CLI receipts for `run`, `test`,
|
|
851
|
+
`install`, `add`, and `remove`, including failed invocations. Receipts are appended
|
|
852
|
+
under `PYBUN_HOME/history.jsonl` (default `~/.cache/pybun/history.jsonl`), with a
|
|
853
|
+
private file mode on Unix and a file lock for concurrent writers. Existing files
|
|
854
|
+
with group/other permissions are rejected for appends; restrict them to mode 600. Set
|
|
855
|
+
`PYBUN_HISTORY=0` to disable recording. History queries never record themselves;
|
|
856
|
+
recording failures warn without changing the command's exit code.
|
|
857
|
+
|
|
858
|
+
Records contain timestamps, duration, exit code, lock digest when available,
|
|
859
|
+
backend identity, Python version/ABI, and known cache/policy/profile evidence.
|
|
860
|
+
Python observations identify whether they describe the execution interpreter or
|
|
861
|
+
the selected environment observed after the command; unavailable evidence is
|
|
862
|
+
`null`. Daemon use is false for the current direct execution paths. Inline code
|
|
863
|
+
and credential option values are removed, and the existing output redaction rules
|
|
864
|
+
apply before persistence and on reads. Program output and environment variables
|
|
865
|
+
are never stored. Arbitrary positional secrets cannot be inferred; disable
|
|
866
|
+
history when arguments contain such values. Queries accept positive `s`, `m`,
|
|
867
|
+
`h`, or `d` durations, report corrupt/partial lines without echoing them, and
|
|
868
|
+
reject files over 32 MiB; archive the local log when needed.
|
|
869
|
+
|
|
870
|
+
### Comparing captured environments
|
|
871
|
+
|
|
872
|
+
`pybun why <package> --format=json` explains every known declaration path to a
|
|
873
|
+
locked distribution. MCP `pybun_why` takes a `package` string and uses the same
|
|
874
|
+
service. The uv backend reads frozen, offline `uv workspace metadata`; it does
|
|
875
|
+
not resolve, install, or synchronize an environment. Paths retain markers and
|
|
876
|
+
classify direct, transitive, extra, dependency-group, and workspace provenance.
|
|
877
|
+
They describe lock alternatives rather than the currently active environment.
|
|
878
|
+
Results include backend/version, lock digest, `complete`, and diagnostics. An
|
|
879
|
+
unreachable or unavailable locked variant is explicitly `unknown`. Native
|
|
880
|
+
provenance uses current declarations with its lock and always reports partial
|
|
881
|
+
evidence. Traversal is bounded (10,000 nodes, 100,000 expansions, depth 256,
|
|
882
|
+
1,000 paths); reaching a bound also reports partial evidence.
|
|
883
|
+
|
|
884
|
+
`pybun environment diff before.json after.json --format=json` compares two JSON
|
|
885
|
+
captures, raw execution receipts, or JSON command envelopes. The MCP equivalent is
|
|
886
|
+
`pybun_environment_diff` with `before` and `after` objects; both use the same
|
|
887
|
+
read-only service. To compare CLI history entries, save the chosen `records[]`
|
|
888
|
+
objects from `pybun history --format=json` as the input files. Each input file is
|
|
889
|
+
limited to 4 MiB.
|
|
890
|
+
|
|
891
|
+
The response reports deterministic `changes` with reason codes for Python,
|
|
892
|
+
backend, lock, package, policy, profile and project evidence. `installed_packages`
|
|
893
|
+
describes interpreter-site inventory; `packages` separately describes a backend's
|
|
894
|
+
resolved lock inventory, which does not prove installation. History captures
|
|
895
|
+
standard isolated-venv site packages by reading distribution metadata without
|
|
896
|
+
importing project packages or running site hooks. System Python and venvs that
|
|
897
|
+
include system/user sites leave the installed inventory unavailable. Executable
|
|
898
|
+
digests are captured when readable; wheel artifact hashes remain unavailable
|
|
899
|
+
unless supplied by a capture.
|
|
900
|
+
|
|
901
|
+
Missing, masked and explicitly unknown values appear in `unavailable` and warning
|
|
902
|
+
diagnostics. They do not establish equality or a runtime state change. An identical
|
|
903
|
+
partial capture has an empty `changes` array while still reporting missing evidence.
|
|
904
|
+
Array ordering and normalized package spelling do not create differences. Only
|
|
905
|
+
typed, allowlisted metadata is compared; unrestricted environment variables and
|
|
906
|
+
unknown configuration keys are excluded, and existing redaction applies before
|
|
907
|
+
comparison. `causality: "not_inferred"` means the result describes recorded state
|
|
908
|
+
changes and never claims that they caused a program's output to change.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|