Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Security

- **A secret or prompt-injection phrase in a tool description is published as a finding, with the value redacted.** `SHIP-DOC-SECRET-IN-DESCRIPTION` and `SHIP-DOC-INJECTION-RISK` matched a live `sk_live_` key or "ignore all previous instructions" and then dropped the finding, because a regex match has no policy-eligible support: `report.json` had no finding, SARIF no result, and `report.md` said only that policy applicability was inferred, while the same secret in an n8n workflow (`SHIP-N8N-SECRET-IN-WORKFLOW-PARAMETER`) was published. All three now follow one rule and are always visible in `report.json`, `report.md`, SARIF and the packet. The two description findings stay `support.policy_eligible: false` and `agent_action: informational`: they are never a blocker or a named review item and change no exit code, and the `insufficient_evidence` gap beside them is unchanged except that its `why` now leads with the check's catalog description ("Tool description contains a secret-like value."), as every finding-derived gap now does. Whether a lexical match may move a verdict is #1055. Migration: [STABILITY.md](STABILITY.md#secret-observations-1026). (#1026)
- **Redaction covers four more secret shapes, two more credential labels and a finding's extra fields.** `Authorization: Basic <credential>`, a PEM private-key block, a Google `AIza` key and an AWS secret access key written without a label appeared verbatim in `report.json`, `report.md`, SARIF and the packet wherever free text carried them, such as `agent.declared_purpose` or a tool name. The shared rules now redact all four (`basic_auth`, `private_key`, `google_api_key`, `aws_secret_access_key`), an `authorization`, `proxy-authorization` or `auth` labeled value after an optional scheme word (the scheme alternation the host-grant readers use; a `Digest` value to the end of its line), and `Bearer` followed by a tab. A `Basic` token counts only when it decodes to `user:password`, so prose such as `Provides Basic ReadOnly access` is neither redacted nor reported, and a 40-character run that reads as camelCase words or path segments (`getCustomerOrderHistoryForAccountV2Async`, `Sources/PaymentKit/Handlers/RefundTools2`) is not taken for an AWS key, so a SARIF alert keeps its file. The first three also raise the secret findings above and skill `SEC-SECRET-001`; the AWS shape and the labels only redact, because without a prefix or a credential label they are too common to report. `sanitize_model` now walks the extra fields of an `extra="allow"` model, so a value a check plugin attaches to a `Finding` is redacted before any writer sees it and counted in the privacy audit. `privacy_audit.rules_version` is `0.2`. A finding whose evidence held one of these shapes publishes a new fingerprint; re-save a baseline that recorded it. (#1026)
- **`bootstrap` no longer runs code committed in the repository it adopts.** Each step re-entered the CLI as `python -m agents_shipgate` with the workspace as the working directory, and `python -m` puts that directory first on `sys.path`: a committed `yaml/`, `typer/`, `pydantic/` or `agents_shipgate/` package was imported and executed before `detect` read anything. The steps now run as `<sys.executable> -P -m agents_shipgate`, and so does every recovery command a `python -m agents_shipgate` run prints (`next_actions[].executable` is four tokens). `-P` drops only the working directory; `PYTHONPATH` and the environment still reach the child, with relative and empty `PYTHONPATH` entries anchored where `bootstrap` was started rather than resolved inside the workspace. Regression tests plant both packages, and a `yaml/` under a relative `PYTHONPATH` entry, and require bootstrap to finish without importing any of them. (#1020)
- **`capability export` and the MCP `shipgate.capabilities` tool load plugins only on the opt-in.** Both passed an explicit `plugins_enabled=True` unless `--no-plugins` was given, so a read-only tool imported every installed `agents_shipgate.adapters` entry point with `AGENTS_SHIPGATE_ENABLE_PLUGINS` unset, where `scan` and `verify` imported none. They now honour the same opt-in, `--no-plugins` still wins, and the lock's `source.plugins_enabled` records the value that took effect (`false` by default, which moves `source_set_hash`). An adopter whose lock includes tools read by a third-party adapter sets the variable for `capability export`, as `scan` already required. (#1020)
- `docs/trust-model.md` lists every process Shipgate starts — the Git plumbing behind `verify`, `check`, `diff` and the Claude Code hooks, workspace discovery, `bootstrap`'s self-invocation, fixture Git, the `authorization execute` HTTPS push and the native prover — instead of saying it shells out only for Git discovery. `tests/test_adapter_static_only.py` fails when a spawn site is added, removed or moved without that table changing, when a `-m agents_shipgate` argv lacks `-P`, and now also on `asyncio.create_subprocess_*`. Migration: [STABILITY.md](STABILITY.md#exec-trust-opt-in-1020). (#1020)
Expand Down
97 changes: 97 additions & 0 deletions STABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,89 @@ the Action tag) for reproducible CI.

---

<a id="secret-observations-1026"></a>

## Migration Note: Unreleased — secret and injection observations are published, and redaction rules `0.2` (#1026)

**What was wrong.** A live key or "ignore all previous instructions" in a tool
description was detected by `SHIP-DOC-SECRET-IN-DESCRIPTION` or
`SHIP-DOC-INJECTION-RISK` and then dropped: the regex match has no
policy-eligible support, so the scan turned it into an
`inferred_policy_applicability` evidence gap whose text said only that policy
applicability was inferred. `report.json` had no finding, SARIF no result, and
`report.md` never said "secret" or "injection", while the same detection in an
n8n workflow (`SHIP-N8N-SECRET-IN-WORKFLOW-PARAMETER`) was published. The
redaction that publishing relies on missed `Authorization: Basic` credentials,
PEM private-key blocks, Google `AIza` keys and unlabeled AWS secret access keys
in free text, and walked only a model's declared fields, so a value a check
plugin attached to a `Finding` as an extra field reached the in-memory report
unredacted and the privacy audit counted nothing.

**What changes.**

- **Publication.** `SHIP-DOC-SECRET-IN-DESCRIPTION`, `SHIP-DOC-INJECTION-RISK`
and `SHIP-N8N-SECRET-IN-WORKFLOW-PARAMETER` follow one rule: a detected
secret or instruction-like text is a finding in `report.findings`,
`report.md`, SARIF and the packet. The two description checks keep their
`support.policy_eligible: false` support, so they are `agent_action:
informational`, never a blocker or a named review item, and add nothing to
the advisory or strict exit code; their evidence gap is still recorded, and
the verdict it gave (`insufficient_evidence`) is unchanged. The n8n finding
keeps the weight it had. Whether a lexical match may move a verdict is #1055.
- **Gap text.** A gap raised for a finding now leads with the check's catalog
description: `SHIP-DOC-SECRET-IN-DESCRIPTION: Tool description contains a
secret-like value. Policy applicability is supported only by heuristic
evidence.` This applies to every finding-derived gap (for example
`SHIP-SCHEMA-BROAD-FREE-TEXT`); `kind`, `policy_id`, `next_action` and the
decision reason are unchanged.
- **Redaction rules.** `privacy_audit.rules_version` is `0.2`. Every published
string is also redacted for `Basic <token>` where the token, at least eight
characters, decodes to printable UTF-8 `user:password` with a non-empty
user (`basic_auth`, the `Authorization:` label kept; `jürgen:pässwörd1`
counts, `Provides Basic ReadOnly access` is prose and untouched), a PEM
private-key block through its end line, or through the end
of the text when it has none (`private_key`), and `AIza…` keys
(`google_api_key`); these three also raise the secret findings above and
skill `SEC-SECRET-001`. Two more are redacted but raise no finding, because
without a prefix or a credential label the shape is too common to report: a
40-character run holding upper- and lower-case letters and a digit that does
not read as words (`aws_secret_access_key`; a camelCase name such as
`getCustomerOrderHistoryForAccountV2Async` or a path such as
`Sources/PaymentKit/Handlers/RefundTools2`, where every lower-case run is two
letters or more and no `+` appears, is left alone), and an `authorization`,
`proxy-authorization` or `auth` labeled value (`labeled_secret_value`): after
a `Bearer`/`Basic`/`Token`/`Negotiate` scheme word any value of eight or more
characters, after `Digest` the rest of the line, after an `authorization`
label alone a token of twelve or more characters holding a letter and a
digit, and otherwise a value that looks like a secret. `Bearer` followed by a
tab or newline is no longer missed. The extra fields of an `extra="allow"`
model (`Finding`, `Tool`, `SourceReference`) are redacted in the first pass
and counted in `privacy_audit.redacted_paths` (for example
`findings[].debug_context`).
- **Check ids.** None is added, removed or renamed. Per [Check IDs](#check-ids),
the conditions under which `SHIP-DOC-SECRET-IN-DESCRIPTION`,
`SHIP-N8N-SECRET-IN-WORKFLOW-PARAMETER` and `SEC-SECRET-001` fire broaden to
the three shapes above, and this note records it.
- **Fingerprints.** A finding whose evidence holds a newly redacted shape
publishes a different fingerprint, because the public fingerprint is computed
from redacted evidence; a baseline entry for it reads as new once.

**Who must act.** Rotate any credential a published finding names, and remove
it from the description or workflow. A consumer that read `findings: []` as
"nothing detected" now sees informational findings it can filter by
`support.policy_eligible` or `agent_action`. Re-save a baseline holding a
finding whose fingerprint moved.

**What does not change.** No CLI flag, schema, check id, control state or
permission is added, `report_schema_version` stays `1.0`, and
`publish_when_policy_ineligible` stays in-process catalog metadata, absent from
`docs/checks.json`. `verification-input.diff` remains the exact bytes of the
change. The capability lock family (`capabilities.lock.json`, the lock diff
and the capability delta attestation) stays unredacted, as before, pending
#1079.

---

<a id="verify-pr-controlled-inputs-1024"></a>

## Migration Note: Unreleased — `verify` reads PR-controlled inputs from the base (#1024)
Expand Down Expand Up @@ -4884,6 +4967,20 @@ False positives are allowed in favor of privacy; local routing metadata such as
source paths, JSON pointers, and scopes remains structurally present with only
secret-like substrings replaced.

`privacy_audit.rules_version` names the rule set, and moves whenever a rule
starts redacting text an earlier version published. Rules `0.2` (#1026) add
`Basic` credentials, PEM private-key blocks, Google `AIza` keys, unlabeled AWS
secret access keys and `authorization`/`auth` labeled values, and redact the
extra fields of an `extra="allow"` model such as `Finding`. Two of them, the
unlabeled AWS shape and the labeled values, redact only: a rule that also
detects (`SHIP-DOC-SECRET-IN-DESCRIPTION`, `SHIP-N8N-SECRET-IN-WORKFLOW-PARAMETER`,
`SEC-SECRET-001`) needs a prefix or an exact test, such as a `Basic` token
decoding to `user:password`, since a finding is not allowed the false
positives redaction is. Even redaction leaves camelCase names and paths that
read as words alone, so a SARIF location keeps its file. A detected secret or instruction-like
text is always published as a finding with its value redacted, never hidden in
an evidence gap. See [the migration note](#secret-observations-1026).

Changed-file paths are published verbatim, by decision (#742). `check` and
`verify` name each file the change touches exactly as Git names it: in
`changed_files[]`, `trigger.changed_files[]`, `affected_files[].path`,
Expand Down
28 changes: 28 additions & 0 deletions docs/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,31 @@ On `mcp_server_source`, a description written in a form the reader cannot resolv
A tool description contains instruction-override-like language. Rewrite it as neutral metadata.
Purely heuristic matches default to `medium`; multiple matches on write/high-risk tools are `high`.

The match is a regular expression, so it cannot gate a release, but the
observation is the point of the check: it is published in `report.findings`,
`report.md` and SARIF with `support.policy_eligible: false` and
`agent_action: informational`, beside the `inferred_policy_applicability`
evidence gap it also records, whose `why` names it ("Tool description contains
instruction-override-like language."). It is never a blocker or a named review
item, and the advisory exit code does not change (#1026).

### SHIP-DOC-SECRET-IN-DESCRIPTION

A tool description contains a secret-like token or credential value. Remove it and rotate the exposed secret.
Purely heuristic matches default to `medium`; multiple matches on write/high-risk tools are `high`.

The known formats are the shared redaction rules in `core/privacy.py`
(`SECRET_PATTERNS`): provider keys (OpenAI, AWS access key ids, GitHub,
Stripe, Slack, Google `AIza`), JWTs, database URLs with credentials, `Bearer`
credentials, `Basic` tokens that decode to `user:password` (so `Provides Basic
ReadOnly access` is prose, not a match), and PEM private-key blocks, plus a labeled
`api_key`/`password`/`secret`/`token` value. It is published like
`SHIP-DOC-INJECTION-RISK`, with the value redacted everywhere, and follows the
same publication rule as `SHIP-N8N-SECRET-IN-WORKFLOW-PARAMETER` (#1026). An
unlabeled AWS secret access key and an `authorization`/`auth` labeled value are
redacted in every output but do not raise this finding: without a prefix or a
credential label the shape is too common to report.

### SHIP-SCHEMA-BROAD-FREE-TEXT

A write/action-like tool accepts broad free-form input. Constrain the field with structured schema or enums.
Expand Down Expand Up @@ -957,6 +977,14 @@ contains a secret-like value. Evidence includes only the source reference,
stable pointer, and secret kind; it never includes the matched secret value or
a verifier hash for that value.

It matches the same `SECRET_PATTERNS` as `SHIP-DOC-SECRET-IN-DESCRIPTION` and
follows the same publication rule: `docs/checks/security.yaml` marks both
`publish_when_policy_ineligible`, so a detected secret is always a visible
finding in `report.json`, `report.md` and SARIF, never only an evidence gap
(#1026). Whether such a lexical match may also move the release verdict is
#1055; this finding keeps the weight it had, and the description finding gains
none.

### SHIP-CONDUCTOR-DYNAMIC-TOOL-SURFACE-NOT-ENUMERABLE

A Conductor OSS workflow uses a dynamic or missing MCP method/server, dynamic
Expand Down
3 changes: 3 additions & 0 deletions docs/checks/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ checks:
- id: SHIP-DOC-INJECTION-RISK
default_severity: medium
mvp_tier: core
publish_when_policy_ineligible: true
description: Tool description contains instruction-override-like language.
rationale: Tool metadata can be placed into model context and should not contain
prompt-like directives.
Expand All @@ -28,6 +29,7 @@ checks:
- id: SHIP-DOC-SECRET-IN-DESCRIPTION
default_severity: medium
mvp_tier: core
publish_when_policy_ineligible: true
description: Tool description contains a secret-like value.
rationale: Credentials in tool metadata can leak into reports, prompts, or logs.
fires_when: |-
Expand All @@ -38,6 +40,7 @@ checks:
- id: SHIP-N8N-SECRET-IN-WORKFLOW-PARAMETER
default_severity: high
mvp_tier: core
publish_when_policy_ineligible: true
description: n8n workflow JSON contains a secret-like value.
rationale: |-
Workflow JSON, pinned data, static data, and node notes can be committed or reported; hardcoded secret-like values should be moved into credentials or variables.
Expand Down
7 changes: 6 additions & 1 deletion docs/passed-verdict-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ Policy severity, `block: true`, risk overrides, and rule-declared confidence
cannot upgrade underlying evidence. Heuristic-only, mixed, unknown, or
conflicting policy applicability creates a non-waivable evidence gap. The
conservative effect may still increase, but no hard finding is emitted until
the predicate is supported by authoritative static evidence.
the predicate is supported by authoritative static evidence. A check whose
value is the observation itself — a secret or instruction-like text in a tool
description, a contradicted MCP annotation — still publishes that observation
as a finding with `support.policy_eligible: false` beside its gap, and the gap's
`why` names what was detected; such a finding is never a blocker or a named
review item.

Catalog membership never implies binding. `tool_catalog[]` contains every
canonical extracted declaration; `tool_inventory[]`, actions, checks, and
Expand Down
28 changes: 28 additions & 0 deletions llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3616,11 +3616,31 @@ On `mcp_server_source`, a description written in a form the reader cannot resolv
A tool description contains instruction-override-like language. Rewrite it as neutral metadata.
Purely heuristic matches default to `medium`; multiple matches on write/high-risk tools are `high`.

The match is a regular expression, so it cannot gate a release, but the
observation is the point of the check: it is published in `report.findings`,
`report.md` and SARIF with `support.policy_eligible: false` and
`agent_action: informational`, beside the `inferred_policy_applicability`
evidence gap it also records, whose `why` names it ("Tool description contains
instruction-override-like language."). It is never a blocker or a named review
item, and the advisory exit code does not change (#1026).

### SHIP-DOC-SECRET-IN-DESCRIPTION

A tool description contains a secret-like token or credential value. Remove it and rotate the exposed secret.
Purely heuristic matches default to `medium`; multiple matches on write/high-risk tools are `high`.

The known formats are the shared redaction rules in `core/privacy.py`
(`SECRET_PATTERNS`): provider keys (OpenAI, AWS access key ids, GitHub,
Stripe, Slack, Google `AIza`), JWTs, database URLs with credentials, `Bearer`
credentials, `Basic` tokens that decode to `user:password` (so `Provides Basic
ReadOnly access` is prose, not a match), and PEM private-key blocks, plus a labeled
`api_key`/`password`/`secret`/`token` value. It is published like
`SHIP-DOC-INJECTION-RISK`, with the value redacted everywhere, and follows the
same publication rule as `SHIP-N8N-SECRET-IN-WORKFLOW-PARAMETER` (#1026). An
unlabeled AWS secret access key and an `authorization`/`auth` labeled value are
redacted in every output but do not raise this finding: without a prefix or a
credential label the shape is too common to report.

### SHIP-SCHEMA-BROAD-FREE-TEXT

A write/action-like tool accepts broad free-form input. Constrain the field with structured schema or enums.
Expand Down Expand Up @@ -4375,6 +4395,14 @@ contains a secret-like value. Evidence includes only the source reference,
stable pointer, and secret kind; it never includes the matched secret value or
a verifier hash for that value.

It matches the same `SECRET_PATTERNS` as `SHIP-DOC-SECRET-IN-DESCRIPTION` and
follows the same publication rule: `docs/checks/security.yaml` marks both
`publish_when_policy_ineligible`, so a detected secret is always a visible
finding in `report.json`, `report.md` and SARIF, never only an evidence gap
(#1026). Whether such a lexical match may also move the release verdict is
#1055; this finding keeps the weight it had, and the description finding gains
none.

### SHIP-CONDUCTOR-DYNAMIC-TOOL-SURFACE-NOT-ENUMERABLE

A Conductor OSS workflow uses a dynamic or missing MCP method/server, dynamic
Expand Down
Loading
Loading