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
1 change: 1 addition & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"cve.org",
"www.cve.org",
"cveawg.mitre.org",
"api.osv.dev",
"oauth2.googleapis.com",
"gmail.googleapis.com",
"*.crates.io",
Expand Down
2 changes: 1 addition & 1 deletion docs/setup/secure-agent-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ below, annotated.
"objects.githubusercontent.com", "codeload.github.com", "uploads.github.com",
"pypi.org", "files.pythonhosted.org",
"lists.apache.org", "dist.apache.org", "downloads.apache.org", "archive.apache.org",
"cveprocess.apache.org", "cve.org", "www.cve.org", "cveawg.mitre.org",
"cveprocess.apache.org", "cve.org", "www.cve.org", "cveawg.mitre.org", "api.osv.dev",
"oauth2.googleapis.com", "gmail.googleapis.com",
// `*.crates.io` + `static.rust-lang.org` let the `lychee` rust
// hook bootstrap a rustup toolchain and `cargo install` lychee
Expand Down
6 changes: 3 additions & 3 deletions plugins/magpie-security/skills/issue-sync/gather.md
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ Concretely, for each closed-`announced` tracker in this run:
already read).
2. Call the API:
```bash
curl -sSf https://cveawg.mitre.org/api/cve/<CVE-ID> \
vetted-op-read --caller security-issue-sync cve-check-published <CVE-ID> \
| jq -r '{state: .cveMetadata.state, datePublished: .cveMetadata.datePublished}'
```
3. Interpret:
Expand All @@ -779,7 +779,7 @@ Concretely, for each closed-`announced` tracker in this run:
- `state == "REJECTED"` → **surface as a blocker**. The record
was withdrawn post-publication. Do not draft a reporter
email; flag to the security team.
- `curl` error (404 / 5xx / DNS) → record *"cve.org lookup
- lookup error (non-zero exit from `vetted-op-read` — exit 4) → record *"cve.org lookup
failed — <short error> — try again next sync"*. Do not
propose notification on an absent response.

Expand All @@ -794,7 +794,7 @@ tracker — not metered against the Gmail budget. Still, keep it
inside the skill's overall "≤ 1 extra HTTP round-trip per tracker"
soft limit for closed-bucket scans: if multiple closed trackers
are in scope, run the checks in parallel via the subagent fanout
(one curl per subagent), not serially in the orchestrator.
(one vetted-op-read check per subagent), not serially in the orchestrator.

**When the tracker has no CVE ID.** Closed trackers without a
`CVE-YYYY-NNNNN` in the *CVE tool link* body field are closing
Expand Down
6 changes: 3 additions & 3 deletions tools/cve-org/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ CVE.org publication client. Submits CVE records via the CVE.org REST API; consum

## Prerequisites

- **Runtime:** None of its own — this directory documents a read-only adapter; the publication-state check is a `curl` + `jq` one-liner (see `tool.md`).
- **CLIs:** `curl` and `jq`.
- **Runtime:** Python 3.11+ via `uv` (through `tools/vetted-ops` dispatcher).
- **CLIs:** `vetted-op-read` (from `tools/vetted-ops`) and `jq`.
- **Credentials / auth:** None — cve.org is the public CVE registry and the CVE Services API has no auth.
- **Network:** `cveawg.mitre.org` (CVE Services API v2) and `www.cve.org` (the HTML record); optionally `nvd.nist.gov` for the alternative public registry.
- **Network:** `cveawg.mitre.org` (CVE Services API v2), routed through `vetted-ops` HTTP read backend; `www.cve.org` (the HTML record); optionally `nvd.nist.gov` for the alternative public registry.

## Configuration

Expand Down
6 changes: 3 additions & 3 deletions tools/cve-org/tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Recipe:

```bash
# Read-only, no auth required. Returns JSON.
curl -sSf https://cveawg.mitre.org/api/cve/<CVE-ID> \
vetted-op-read --caller <caller> cve-check-published <CVE-ID> \
| jq -r '.cveMetadata.state'
```

Expand All @@ -81,7 +81,7 @@ Interpretation:
*CVE-published* email to the reporter.
- `REJECTED` → something went wrong post-publication. Surface to the
security team; do not notify the reporter on the happy path.
- Non-zero exit from `curl` (404, 5xx, DNS failure) → treat as
- Non-zero exit from `vetted-op-read` (exit 4, covering 404, 5xx, DNS failure, timeout) → treat as
*"unknown — try again next sync"*. Do not propose notification
on an absent response; cve.org sometimes returns transient 5xx
during CNA-feed propagation.
Expand All @@ -90,7 +90,7 @@ Extract the `datePublished` alongside the state when you need to
print *"published on YYYY-MM-DD"* in the reporter email:

```bash
curl -sSf https://cveawg.mitre.org/api/cve/<CVE-ID> \
vetted-op-read --caller <caller> cve-check-published <CVE-ID> \
| jq -r '{state: .cveMetadata.state, datePublished: .cveMetadata.datePublished}'
```

Expand Down
8 changes: 4 additions & 4 deletions tools/osv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ See [`tool.md`](tool.md) for endpoint recipes, payload structures, and confident

## Prerequisites

- **Runtime:** None of its own — this directory documents a read-only adapter; queries are `curl` + `jq` recipes (see `tool.md`).
- **CLIs:** `curl` and `jq`.
- **Credentials / auth:** None — OSV.dev is a public vulnerability database with an open, unauthenticated REST API.
- **Network:** `api.osv.dev` (OSV.dev REST API v1) and `osv.dev` (public web UI).
- **Runtime:** Python 3.11+ via `uv` (through `tools/vetted-ops` dispatcher).
- **CLIs:** `vetted-op-read` (from `tools/vetted-ops`) and `jq`.
- **Credentials / auth:** None — open, unauthenticated REST API.
- **Network:** `api.osv.dev` (REST API v1), routed through `vetted-ops` HTTP read backend.
Comment thread
potiuk marked this conversation as resolved.

## Configuration

Expand Down
23 changes: 6 additions & 17 deletions tools/osv/tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ Fetch the OSV JSON record by its primary ID (or alias):

```bash
# Read-only, unauthenticated. Returns complete OSV schema JSON.
curl -sSf https://api.osv.dev/v1/vulns/<ID>
vetted-op-read --caller <caller> osv-get-vuln <ID>
Comment thread
potiuk marked this conversation as resolved.
```

Extracting alias identifiers (e.g., resolving a GHSA ID to corresponding CVE IDs):

```bash
curl -sSf https://api.osv.dev/v1/vulns/GHSA-7rjr-3q55-vv33 \
vetted-op-read --caller <caller> osv-get-vuln GHSA-7rjr-3q55-vv33 \
| jq -r '{id: .id, aliases: .aliases, summary: .summary}'
```

Expand All @@ -90,7 +90,7 @@ Example JSON response:
Extracting affected version ranges and fixed versions:

```bash
curl -sSf https://api.osv.dev/v1/vulns/<ID> \
vetted-op-read --caller <caller> osv-get-vuln <ID> \
| jq -r '.affected[] | {package: .package.name, ecosystem: .package.ecosystem, fixed: [.ranges[].events[] | select(.fixed != null) | .fixed]}'
```

Expand All @@ -99,9 +99,7 @@ curl -sSf https://api.osv.dev/v1/vulns/<ID> \
Check if a given package release is subject to any known advisories:

```bash
curl -sSf -X POST https://api.osv.dev/v1/query \
-H "Content-Type: application/json" \
-d '{"package": {"name": "jinja2", "ecosystem": "PyPI"}, "version": "2.11.2"}' \
vetted-op-read --caller <caller> osv-query-package jinja2 PyPI 2.11.2 \
| jq -r '.vulns[]? | {id: .id, aliases: .aliases, summary: .summary}'
```

Expand All @@ -112,9 +110,7 @@ Common ecosystems: `PyPI`, `Maven`, `npm`, `crates.io`, `Go`, `Packagist`, `NuGe
Check if a public upstream commit SHA is indexed in OSV as a fix or vulnerability reference:

```bash
curl -sSf -X POST https://api.osv.dev/v1/query \
-H "Content-Type: application/json" \
-d '{"commit": "<COMMIT_HASH>"}' \
vetted-op-read --caller <caller> osv-query-commit <COMMIT_HASH> \
| jq -r '.vulns[]? | {id: .id, aliases: .aliases, summary: .summary}'
```

Expand All @@ -123,14 +119,7 @@ curl -sSf -X POST https://api.osv.dev/v1/query \
Evaluate multiple dependencies in a single round-trip:

```bash
curl -sSf -X POST https://api.osv.dev/v1/querybatch \
-H "Content-Type: application/json" \
-d '{
"queries": [
{"package": {"name": "jinja2", "ecosystem": "PyPI"}, "version": "2.11.2"},
{"package": {"name": "urllib3", "ecosystem": "PyPI"}, "version": "1.26.4"}
]
}' \
vetted-op-read --caller <caller> osv-query-batch /tmp/agent-scratch/batch.json \
| jq -r '.results | to_entries[] | {query: .key, vuln_count: ((.value.vulns // []) | length)}'
```

Expand Down
1 change: 1 addition & 0 deletions tools/sandbox-lint/expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"cve.org",
"www.cve.org",
"cveawg.mitre.org",
"api.osv.dev",
"oauth2.googleapis.com",
"gmail.googleapis.com",
"*.crates.io",
Expand Down
6 changes: 3 additions & 3 deletions tools/spec-loop/specs/vetted-command-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ Replace the wildcard, not the confirmation. Route forge actions through a
dispatcher whose operations are a **closed catalogue** of fixed shapes:

- parameters are typed and validated; none may start with `-`;
- builders return `list[str]` executed without a shell, so no parameter can
become a command or a flag;
- builders return `list[str]` executed without a shell for forge operations, or a request descriptor executed via Python stdlib `urllib.request` for HTTP read operations;
- the repository is policy, never a parameter;
- value-bearing parameters (labels, milestones, assignees, columns, close
reasons) must appear in adopter-declared enums;
Expand Down Expand Up @@ -112,7 +111,8 @@ per [`docs/adapters/registry.md`](../../../docs/adapters/registry.md).
capability the surrounding design withholds. **Widening the catalogue must
not widen the posture**, and that constraint binds every family added next.

3. **Adapter parity.** Operations are `gh`-shaped today. The forge is already an
3. **Adapter parity.** Forge operations are `gh`-shaped today (HTTP read operations
now ship alongside them via a stdlib backend). The forge is already an
adapter axis (`github`, `jira`, `bitbucket`, `sourcehut`, `fossil`), so the
catalogue should eventually resolve its builder per configured forge rather
than assuming one.
Expand Down
46 changes: 33 additions & 13 deletions tools/vetted-ops/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ needs *one* allowlist entry instead of a dozen wildcard `ask` rules.

## Prerequisites

- **Runtime:** Python 3.11+ via `uv`. The package itself is stdlib-only.
- **CLIs:** the `gh` CLI on `PATH`, authenticated for the repositories the policy
names. Every operation shells out to it; the dispatcher runs nothing else.
- **Credentials:** whatever `gh` already uses (`~/.config/gh/`). This tool reads
no credential of its own and stores none.
- **Network:** only what `gh` needs — `github.com` / `api.github.com`.
- **Configuration:** a policy TOML (see *Configuration*). Without one, every
operation refuses.
- **Runtime:** Python 3.11+ via `uv`.
The package itself is stdlib-only.
- **CLIs:** the `gh` CLI on `PATH`, authenticated for the repositories the policy names.
HTTP operations do not use `gh` or `curl`; they use the `urllib.request` standard library module.
- **Credentials:** whatever `gh` already uses (`~/.config/gh/`).
This tool reads no credential of its own and stores none.
- **Network:** `github.com` / `api.github.com` for `gh` operations.
HTTP operations connect to the domains configured in the `[endpoints]` table (e.g., `api.osv.dev`, `cveawg.mitre.org`).
- **Configuration:** a policy TOML (see *Configuration*).
Without one, every operation refuses.

## Why

Expand Down Expand Up @@ -98,9 +100,16 @@ Being precise, because a security tool that overstates itself is worse than none
supplies the text, and `owner`/`name` come from policy. A caller can choose
among the allowlisted queries; it cannot write one, and cannot re-aim one at
another repository.
- **The catalogue is closed.** Widening the surface means editing
[`ops.py`](src/vetted_ops/ops.py) — a reviewed code change, not a runtime
decision.
- **A parameter can never become a URL path traversal.**
For HTTP operations, URLs are built from closed templates and injected parameters are strictly validated to refuse `..` and shell characters.
- **HTTP operations are read-only by construction.**
The backend `"http-read"` implies `writes=False`, enforced by the dispatcher.
Comment thread
potiuk marked this conversation as resolved.
- **No `curl` or `wget` is involved.**
The `urllib.request` implementation automatically obeys `HTTP_PROXY` and `HTTPS_PROXY` environment variables (egress gateway).
- **HTTP responses are streamed to stdout, never to files.**
There is no local filesystem exposure for downloaded data.
- **The catalogue is closed.**
Widening the surface means editing [`ops.py`](src/vetted_ops/ops.py) — a reviewed code change, not a runtime decision.

### The boundary is the entry point, not `--caller`

Expand Down Expand Up @@ -150,13 +159,19 @@ Layers 0–2 are unchanged and still carry the load.

## Configuration

> **Upgrade Note:** Existing adopters upgrading to use `osv-query-package` must add `ecosystems = ["PyPI", "Maven", "npm", ...]` to the `[values]` table in their policy TOML; without it, `vetted-op-read` refuses package queries during parameter validation.

Adopter-owned, at
`.apache-magpie-overrides/tools/vetted-ops/config.toml` by default:

```toml
# Body files must resolve inside this directory.
workspace = "/tmp/agent-scratch"

[endpoints]
osv_api = "https://api.osv.dev/v1"
cve_services_api = "https://cveawg.mitre.org/api"

[repos]
tracker = "acme/tracker" # optional — see below
upstream = "acme/product"
Expand All @@ -169,6 +184,7 @@ assignees = ["alice", "bob"]
issue_states = ["open", "closed", "all"]
pr_states = ["open", "closed", "merged", "all"]
close_reasons = ["completed", "not planned"]
ecosystems = ["PyPI", "Maven", "npm", "Go"]

board_project_id = "PVT_kwDO…" # ProjectV2 node id
board_status_field_id = "PVTSSF_…" # its Status field id
Expand All @@ -179,8 +195,12 @@ board_status_field_id = "PVTSSF_…" # its Status field id

[callers] # caller -> operations it may run
"security-issue-sync" = ["issue-view", "issue-comments", "issue-add-label",
"issue-set-milestone", "issue-comment", "comment-update"]
"security-issue-triage" = ["issue-view", "issue-comments"]
"issue-set-milestone", "issue-comment", "comment-update",
"cve-check-published"]
"security-issue-triage" = ["issue-view", "issue-comments",
"osv-get-vuln", "osv-query-package"]
"security-issue-deduplicate" = ["osv-get-vuln"]
"dependency-audit" = ["osv-query-package", "osv-query-commit", "osv-query-batch"]
"pr-management-triage" = ["pr-list", "pr-view", "pr-checks", "gql-pr-liveness",
"pr-add-label", "pr-remove-label", "pr-draft", "pr-ready",
"pr-comment", "pr-update-branch", "run-rerun-failed",
Expand Down
Loading