Skip to content
Open
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,12 @@ repos:
# already corrected and staged-ready instead of as an instruction.
- repo: local
hooks:
- id: check-forbidden-terms-sync
name: check-forbidden-terms-sync (canonical scrub terms vs publication.py)
language: system
entry: python3 tools/dev/sync-forbidden-terms.py --check
files: ^(tools/vcs/src/magpie_vcs/publication\.py|tools/dev/sync-forbidden-terms\.py|plugins/magpie-security/hooks/forbidden-terms\.(json|ts))$
pass_filenames: false
- id: check-family-plugins
name: check-family-plugins (marketplace plugins vs frontmatter)
language: system
Expand Down
68 changes: 68 additions & 0 deletions docs/setup/secure-agent-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
- [Extensible — any skill can contribute a guard](#extensible--any-skill-can-contribute-a-guard)
- [Install (user-scope)](#install-user-scope-1)
- [Verify](#verify-1)
- [Public-surface scrub mod (Claude Code plugin / hook)](#public-surface-scrub-mod-claude-code-plugin--hook)
- [Term canonicalization](#term-canonicalization)
- [Scoping and fail-closed gates](#scoping-and-fail-closed-gates)
- [Trade-offs and runtime characteristics](#trade-offs-and-runtime-characteristics)
- [Sandbox-error hint hook](#sandbox-error-hint-hook)
- [Why install it](#why-install-it)
- [Why install it user-scope, not project-scope](#why-install-it-user-scope-not-project-scope-1)
Expand Down Expand Up @@ -1783,6 +1787,70 @@ Expected: a JSON object with `permissionDecision: "deny"` and a
reason mentioning the `mention` guard. A plain command
(`{"tool_input":{"command":"ls"}}`) produces no output and `exit=0`.

## Public-surface scrub mod (Claude Code plugin / hook)

For Claude Code harnesses using plugins (such as `magpie-security`),
a deterministic TypeScript mod hook
([`plugins/magpie-security/hooks/public-surface-scrub.ts`](../../plugins/magpie-security/hooks/public-surface-scrub.ts))
intercepts `Bash` tool calls (`git commit`, `git tag`, `gh pr create|edit|review|comment`, `gh api`)
before execution to prevent accidental early disclosure of security vulnerabilities on public surfaces.

### Term canonicalization

The mod does not maintain a private list of forbidden terms. The forbidden
disclosure terms and regex patterns are derived directly from the canonical
single source of truth: `magpie_vcs.publication.DEFAULT_TERMS`
in `tools/vcs/src/magpie_vcs/publication.py` (via `tools/dev/sync-forbidden-terms.py`
and tracked by the pre-commit sync check).

### Scoping and fail-closed gates

- **Private tracker exemption**: Subcommands explicitly targeting the private security
tracker repository (`tracker_repo` in `project.md`, e.g. `-R airflow-s/airflow-s`)
are exempt.
- **Upstream repository protection**: Subcommands targeting the public codebase
(`upstream_repo` in `project.md`) or operating within an upstream working tree
are scrubbed. Commands targeting unrelated third-party or adopter documentation
repositories pass unchecked.
- **`cd <dir>` transition tracking**: Tracks working directory transitions across
composite subcommands (e.g. `cd ../upstream && git commit -m "..."`).
- **Deterministic git remote verification**: Repository scope is determined by
reading `.git/config` remotes rather than directory-name substring heuristics.
- **Fail-closed on indeterminate scope**: If the target repository scope cannot
be verified deterministically, commands containing embargo terms fail closed.
- **Fail-closed on unreadable files**: Invocations referencing message files
(`-F <file>` / `--file` / `--body-file`) fail closed if the referenced file cannot
be read.
- **Fail-closed on dynamic input**: Commands constructing commit or PR content
dynamically via variables (`$VAR`), command substitution (`$(...)`), stdin pipes
(`-`), or automatic reuse (`git commit --amend` without an explicit message,
`gh pr create --fill`) cannot be verified statically and fail closed. The block
explanation instructs providing text explicitly or via an inspectable message file
(`-F <file>`).

### Trade-offs and runtime characteristics

The public-surface scrub mod is a deterministic guard against **accidental**
public disclosure of embargoed security information, not a boundary against
deliberate circumvention. A command running in a shell can always write to
temporary files, pipe through external helpers, or execute dynamic subshells
that cannot be fully predicted before execution. The hook documents these
boundaries rather than chasing dynamic time-of-check-to-time-of-use (TOCTOU)
scenarios: it fails closed on unresolvable shapes and indeterminate inputs,
while relying on collaborator review for anything outside its deterministic
static scope.

- **Unsandboxed execution**: Claude Code mods and hooks execute directly in the
local Node runtime outside the container sandbox.
- **File inspection**: The hook inspects message files specified by `-F <file>`
via the Claude Code plugin filesystem capability (`$.fs.read()`).
- **Human-only escape hatch**: When legitimate post-coordinated-disclosure
maintenance requires mentioning CVE IDs or vulnerability terms on public surfaces,
a human maintainer sets `MAGPIE_ALLOW_EMBARGO_TERMS=1` in the environment
before starting the session. The escape hatch serves as a zero-token deterministic
guard against accidental leaks; inline environment variables passed to the command
line (e.g. `MAGPIE_ALLOW_EMBARGO_TERMS=1 git commit ...`) are ignored by the hook.

## Sandbox-error hint hook

Companion to the *Sandbox-bypass visibility hook* above — a
Expand Down
49 changes: 49 additions & 0 deletions plugins/magpie-security/hooks/forbidden-terms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

// Generated from tools/vcs/src/magpie_vcs/publication.py:DEFAULT_TERMS. Do not edit.

export interface CanonicalForbiddenTerm {
name: string;
pattern: string;
flags: string;
}

export const CANONICAL_FORBIDDEN_TERMS: ReadonlyArray<CanonicalForbiddenTerm> = [

{ name: "CVE id", pattern: "\\bCVE-\\d{4}-\\d{4,}\\b", flags: "i" },
{ name: "CVE", pattern: "\\bCVEs?\\b", flags: "i" },
{ name: "vulnerability", pattern: "\\bvulnerab(?:le|ility|ilities)\\b", flags: "i" },
{ name: "security fix", pattern: "\\bsecurity[\\s_-]+fix(?:es|ed)?\\b", flags: "i" },
{ name: "advisory", pattern: "\\badvisor(?:y|ies)\\b", flags: "i" },
{ name: "security@", pattern: "\\bsecurity@", flags: "i" },
{ name: "CVE-tool URL", pattern: "cveprocess\\.apache\\.org", flags: "i" },
{ name: "exploit", pattern: "\\bexploit(?:s|able|ed)?\\b", flags: "i" },
{ name: "SSRF", pattern: "\\bSSRF\\b", flags: "i" },
{ name: "XSS", pattern: "\\bXSS\\b", flags: "i" },
{ name: "CSRF", pattern: "\\bCSRF\\b", flags: "i" },
{ name: "RCE", pattern: "\\bRCE\\b", flags: "i" },
{ name: "SQL injection", pattern: "\\bSQL[\\s_-]+injection\\b", flags: "i" },
{ name: "path traversal", pattern: "\\b(?:path|directory)[\\s_-]+traversal\\b", flags: "i" },
{ name: "remote code execution", pattern: "\\b(?:remote|arbitrary)[\\s_-]+code[\\s_-]+execution\\b", flags: "i" },
{ name: "privilege escalation", pattern: "\\bprivilege[\\s_-]+escalation\\b", flags: "i" },
{ name: "auth bypass", pattern: "\\b(?:auth|authentication|authorization)[\\s_-]+bypass\\b", flags: "i" },
{ name: "buffer overflow", pattern: "\\b(?:buffer|heap|stack)[\\s_-]+overflow\\b", flags: "i" },
{ name: "use-after-free", pattern: "\\buse[\\s_-]+after[\\s_-]+free\\b", flags: "i" },
];
7 changes: 7 additions & 0 deletions plugins/magpie-security/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "https://code.claude.com/schemas/hooks.json",
"description": "Apache Magpie — Claude Code mod for deterministic public-surface scrub against unredacted security embargo leaks.",
"modules": [
"./public-surface-scrub.ts"
Comment thread
potiuk marked this conversation as resolved.
]
}
Loading
Loading