Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
8fad2c0
feat(security): add deterministic public-surface scrub Claude Code mo…
Kaap10 Oct 16, 2026
8d61be6
fix(security): address maintainer review on deterministic public-surf…
Kaap10 Oct 17, 2026
8615f7b
fix(security): address maintainer review on canonical terms and fail-…
Kaap10 Oct 20, 2026
da4568b
docs: document sync-forbidden-terms.py and update issue-fix token cou…
Kaap10 Oct 20, 2026
10ab29d
fix(security): address maintainer review #3 on Claude Code hook and C…
Kaap10 Oct 21, 2026
cb9a22f
fix(security): address maintainer review round 4 on public-surface sc…
Kaap10 Oct 23, 2026
3a013a4
fix(security): address maintainer review round 5 on public-surface sc…
Kaap10 Oct 23, 2026
c7d7546
fix(security-scrub): invert default to fail-closed on unresolvable sh…
Kaap10 Oct 24, 2026
44f8901
fix(security-scrub): address maintainer review round 7 on public-surf…
Kaap10 Oct 25, 2026
26a2f86
fix(security-scrub): guard against misparsing GitHub API non-repo rou…
Kaap10 Oct 25, 2026
d3f4585
test(security-scrub): add mockup tests for session root and private t…
Kaap10 Oct 25, 2026
1008127
fix(security-scrub): harden public-surface scrub against cd || guards…
Kaap10 Oct 25, 2026
b4c5620
fix(security-scrub): address maintainer review round 8 on public-surf…
Kaap10 Oct 26, 2026
460cb54
Merge remote-tracking branch 'upstream/main' into feat/claude-code-mo…
Kaap10 Oct 26, 2026
74db1f4
build(codex): declare no-op codex-hooks for magpie-security
Kaap10 Oct 26, 2026
6ddb8e5
fix(security-scrub): name --repo <tracker> when the cwd fallback bloc…
potiuk Oct 26, 2026
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
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\.ts)$
pass_filenames: false
- id: check-family-plugins
name: check-family-plugins (marketplace plugins vs frontmatter)
language: system
Expand Down
74 changes: 74 additions & 0 deletions docs/setup/secure-agent-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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 @@ -1850,6 +1854,76 @@ 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`, `git merge`, `gh pr create|edit|review|comment|merge|close`, `gh issue create|edit|comment|close`, `gh release create`, `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. When working directory context falls back to `$.session.root()` (i.e. `cwd`
is omitted in the tool call event), the tracker cwd exemption is disabled to protect
against undetected in-session `cd ../<upstream>` transitions, falling through to the
deterministic term scan.
The Bash tool call carries no `cwd`, so in practice this is always the case:
tracker commands that mention a CVE id need an explicit `--repo <tracker_repo>`, and the
block message says so.
- **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
14 changes: 14 additions & 0 deletions plugins/magpie-security/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "magpie-security",
"description": "Apache Magpie \u2014 security-issue handling lifecycle \u2014 import through CVE publication, triage, sync, dedup, invalidate. Maintainer-only.",
"version": "0.9.0.dev202610251812",
"author": {
"name": "Apache Magpie",
"url": "https://magpie.apache.org/"
},
"homepage": "https://magpie.apache.org/",
"repository": "https://github.com/apache/magpie",
"license": "Apache-2.0",
"skills": "./skills",
"hooks": "./hooks/codex-hooks.json"
}
4 changes: 4 additions & 0 deletions plugins/magpie-security/hooks/codex-hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description": "Apache Magpie \u2014 no Codex hooks. hooks.json holds Claude Code mods only.",
"hooks": {}
}
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.
Resolved
]
}
Loading
Loading