@gotgenes/pi-permission-system 5.1.1 → 5.1.2
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.
- package/CHANGELOG.md +9 -0
- package/README.md +5 -6
- package/package.json +1 -1
- package/tests/permission-system.test.ts +183 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [5.1.2](https://github.com/gotgenes/pi-permission-system/compare/v5.1.1...v5.1.2) (2026-05-05)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* fix README per-agent frontmatter example to flat format ([#78](https://github.com/gotgenes/pi-permission-system/issues/78)) ([1295427](https://github.com/gotgenes/pi-permission-system/commit/129542795218a6ada1f8d069a22b5ace5ec6c445))
|
|
14
|
+
* plan fix README frontmatter example and add missing tests ([#78](https://github.com/gotgenes/pi-permission-system/issues/78)) ([3fc99e1](https://github.com/gotgenes/pi-permission-system/commit/3fc99e1b3adf8193c94ac778617ca830488fa621))
|
|
15
|
+
* **retro:** add retro notes for issue [#93](https://github.com/gotgenes/pi-permission-system/issues/93) ([c9e8e89](https://github.com/gotgenes/pi-permission-system/commit/c9e8e89eb4057866198402add374d72a90a2fa2e))
|
|
16
|
+
|
|
8
17
|
## [5.1.1](https://github.com/gotgenes/pi-permission-system/compare/v5.1.0...v5.1.1) (2026-05-05)
|
|
9
18
|
|
|
10
19
|
|
package/README.md
CHANGED
|
@@ -170,22 +170,21 @@ Override global permissions for specific agents via YAML frontmatter in the glob
|
|
|
170
170
|
---
|
|
171
171
|
name: my-agent
|
|
172
172
|
permission:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
mcp: allow
|
|
173
|
+
read: allow
|
|
174
|
+
write: deny
|
|
175
|
+
mcp: allow
|
|
177
176
|
bash:
|
|
178
177
|
git status: allow
|
|
179
178
|
git *: ask
|
|
180
179
|
mcp:
|
|
181
180
|
chrome_devtools_*: deny
|
|
182
181
|
exa_*: allow
|
|
183
|
-
|
|
182
|
+
skill:
|
|
184
183
|
"*": ask
|
|
185
184
|
---
|
|
186
185
|
```
|
|
187
186
|
|
|
188
|
-
**MCP behavior:** `permission.
|
|
187
|
+
**MCP behavior:** `permission.mcp` is the coarse entry/fallback permission for a registered `mcp` tool when one is available. More specific `permission.mcp` target rules override that fallback when they match.
|
|
189
188
|
|
|
190
189
|
**Limitations:** The frontmatter parser is intentionally minimal. Use only `key: value` scalars and nested maps. Avoid arrays, multi-line scalars, and YAML anchors.
|
|
191
190
|
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
rmSync,
|
|
8
8
|
writeFileSync,
|
|
9
9
|
} from "node:fs";
|
|
10
|
-
import { tmpdir } from "node:os";
|
|
10
|
+
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import { dirname, join, resolve } from "node:path";
|
|
12
12
|
import { test } from "vitest";
|
|
13
13
|
|
|
@@ -1831,6 +1831,188 @@ test("external_directory permission is not affected by unrelated surface keys",
|
|
|
1831
1831
|
}
|
|
1832
1832
|
});
|
|
1833
1833
|
|
|
1834
|
+
test("skill pattern map in agent frontmatter overrides global skill policy", () => {
|
|
1835
|
+
const { manager, cleanup } = createManager(
|
|
1836
|
+
{
|
|
1837
|
+
permission: { "*": "deny", skill: "deny" },
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
reviewer: `---
|
|
1841
|
+
name: reviewer
|
|
1842
|
+
permission:
|
|
1843
|
+
skill:
|
|
1844
|
+
"*": ask
|
|
1845
|
+
"pi-*": allow
|
|
1846
|
+
---
|
|
1847
|
+
`,
|
|
1848
|
+
},
|
|
1849
|
+
);
|
|
1850
|
+
|
|
1851
|
+
try {
|
|
1852
|
+
// Matches agent frontmatter pi-* pattern
|
|
1853
|
+
const allowed = manager.checkPermission(
|
|
1854
|
+
"skill",
|
|
1855
|
+
{ name: "pi-code-review" },
|
|
1856
|
+
"reviewer",
|
|
1857
|
+
);
|
|
1858
|
+
assert.equal(allowed.state, "allow");
|
|
1859
|
+
assert.equal(allowed.matchedPattern, "pi-*");
|
|
1860
|
+
assert.equal(allowed.source, "skill");
|
|
1861
|
+
|
|
1862
|
+
// Falls through to agent frontmatter catch-all
|
|
1863
|
+
const asked = manager.checkPermission(
|
|
1864
|
+
"skill",
|
|
1865
|
+
{ name: "other-skill" },
|
|
1866
|
+
"reviewer",
|
|
1867
|
+
);
|
|
1868
|
+
assert.equal(asked.state, "ask");
|
|
1869
|
+
assert.equal(asked.matchedPattern, "*");
|
|
1870
|
+
|
|
1871
|
+
// No agent override — global deny applies
|
|
1872
|
+
const denied = manager.checkPermission("skill", { name: "pi-code-review" });
|
|
1873
|
+
assert.equal(denied.state, "deny");
|
|
1874
|
+
assert.equal(denied.source, "skill");
|
|
1875
|
+
} finally {
|
|
1876
|
+
cleanup();
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
|
|
1880
|
+
test("external_directory pattern map in agent frontmatter overrides global policy", () => {
|
|
1881
|
+
const { manager, cleanup } = createManager(
|
|
1882
|
+
{
|
|
1883
|
+
permission: { "*": "allow", external_directory: "deny" },
|
|
1884
|
+
},
|
|
1885
|
+
{
|
|
1886
|
+
trusted: `---
|
|
1887
|
+
name: trusted
|
|
1888
|
+
permission:
|
|
1889
|
+
external_directory:
|
|
1890
|
+
"*": deny
|
|
1891
|
+
"~/Downloads/*": allow
|
|
1892
|
+
---
|
|
1893
|
+
`,
|
|
1894
|
+
},
|
|
1895
|
+
);
|
|
1896
|
+
|
|
1897
|
+
try {
|
|
1898
|
+
// Matches agent frontmatter ~/Downloads/* pattern
|
|
1899
|
+
const allowed = manager.checkPermission(
|
|
1900
|
+
"external_directory",
|
|
1901
|
+
{ path: `${homedir()}/Downloads/file.txt` },
|
|
1902
|
+
"trusted",
|
|
1903
|
+
);
|
|
1904
|
+
assert.equal(allowed.state, "allow");
|
|
1905
|
+
assert.equal(allowed.matchedPattern, "~/Downloads/*");
|
|
1906
|
+
assert.equal(allowed.source, "special");
|
|
1907
|
+
|
|
1908
|
+
// Falls through to agent frontmatter catch-all deny
|
|
1909
|
+
const denied = manager.checkPermission(
|
|
1910
|
+
"external_directory",
|
|
1911
|
+
{ path: `${homedir()}/Documents/secret.txt` },
|
|
1912
|
+
"trusted",
|
|
1913
|
+
);
|
|
1914
|
+
assert.equal(denied.state, "deny");
|
|
1915
|
+
assert.equal(denied.matchedPattern, "*");
|
|
1916
|
+
|
|
1917
|
+
// No agent override — global deny applies
|
|
1918
|
+
const globalDenied = manager.checkPermission("external_directory", {});
|
|
1919
|
+
assert.equal(globalDenied.state, "deny");
|
|
1920
|
+
assert.equal(globalDenied.source, "special");
|
|
1921
|
+
} finally {
|
|
1922
|
+
cleanup();
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
test("project-agent frontmatter skill rules override global-agent frontmatter skill rules", () => {
|
|
1927
|
+
const { manager, cleanup } = createManagerWithProject(
|
|
1928
|
+
{
|
|
1929
|
+
permission: { "*": "deny" },
|
|
1930
|
+
},
|
|
1931
|
+
{
|
|
1932
|
+
analyst: `---
|
|
1933
|
+
name: analyst
|
|
1934
|
+
permission:
|
|
1935
|
+
skill:
|
|
1936
|
+
"*": ask
|
|
1937
|
+
---
|
|
1938
|
+
`,
|
|
1939
|
+
},
|
|
1940
|
+
{
|
|
1941
|
+
projectAgentFiles: {
|
|
1942
|
+
analyst: `---
|
|
1943
|
+
name: analyst
|
|
1944
|
+
permission:
|
|
1945
|
+
skill:
|
|
1946
|
+
"pi-*": allow
|
|
1947
|
+
"*": deny
|
|
1948
|
+
---
|
|
1949
|
+
`,
|
|
1950
|
+
},
|
|
1951
|
+
},
|
|
1952
|
+
);
|
|
1953
|
+
|
|
1954
|
+
try {
|
|
1955
|
+
// Project-agent pi-* wins over global-agent *: ask
|
|
1956
|
+
const allowed = manager.checkPermission(
|
|
1957
|
+
"skill",
|
|
1958
|
+
{ name: "pi-code-review" },
|
|
1959
|
+
"analyst",
|
|
1960
|
+
);
|
|
1961
|
+
assert.equal(allowed.state, "allow");
|
|
1962
|
+
assert.equal(allowed.matchedPattern, "pi-*");
|
|
1963
|
+
|
|
1964
|
+
// Project-agent *: deny wins over global-agent *: ask
|
|
1965
|
+
const denied = manager.checkPermission(
|
|
1966
|
+
"skill",
|
|
1967
|
+
{ name: "other-skill" },
|
|
1968
|
+
"analyst",
|
|
1969
|
+
);
|
|
1970
|
+
assert.equal(denied.state, "deny");
|
|
1971
|
+
assert.equal(denied.matchedPattern, "*");
|
|
1972
|
+
} finally {
|
|
1973
|
+
cleanup();
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
test("project-agent frontmatter external_directory rules override global-agent frontmatter rules", () => {
|
|
1978
|
+
const { manager, cleanup } = createManagerWithProject(
|
|
1979
|
+
{
|
|
1980
|
+
permission: { "*": "allow", external_directory: "deny" },
|
|
1981
|
+
},
|
|
1982
|
+
{
|
|
1983
|
+
analyst: `---
|
|
1984
|
+
name: analyst
|
|
1985
|
+
permission:
|
|
1986
|
+
external_directory: ask
|
|
1987
|
+
---
|
|
1988
|
+
`,
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
projectAgentFiles: {
|
|
1992
|
+
analyst: `---
|
|
1993
|
+
name: analyst
|
|
1994
|
+
permission:
|
|
1995
|
+
external_directory: allow
|
|
1996
|
+
---
|
|
1997
|
+
`,
|
|
1998
|
+
},
|
|
1999
|
+
},
|
|
2000
|
+
);
|
|
2001
|
+
|
|
2002
|
+
try {
|
|
2003
|
+
// Project-agent allow wins over global-agent ask
|
|
2004
|
+
const result = manager.checkPermission("external_directory", {}, "analyst");
|
|
2005
|
+
assert.equal(result.state, "allow");
|
|
2006
|
+
assert.equal(result.source, "special");
|
|
2007
|
+
|
|
2008
|
+
// Without agent context, global config deny applies
|
|
2009
|
+
const globalResult = manager.checkPermission("external_directory", {});
|
|
2010
|
+
assert.equal(globalResult.state, "deny");
|
|
2011
|
+
} finally {
|
|
2012
|
+
cleanup();
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2015
|
+
|
|
1834
2016
|
test("tool_call blocks path-bearing tools outside cwd when external_directory is denied", async () => {
|
|
1835
2017
|
const rootDir = mkdtempSync(join(tmpdir(), "pi-permission-system-boundary-"));
|
|
1836
2018
|
const cwd = join(rootDir, "repo");
|