@atlasauth/pca-policy-ci 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +61 -2
- package/dist/cli.d.ts +27 -0
- package/dist/cli.js +166 -0
- package/dist/cli.js.map +1 -0
- package/dist/deps.d.ts +9 -0
- package/dist/deps.js +23 -0
- package/dist/deps.js.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/dist/normalize.d.ts +55 -0
- package/dist/normalize.js +130 -0
- package/dist/normalize.js.map +1 -0
- package/dist/rules.d.ts +17 -0
- package/dist/rules.js +276 -0
- package/dist/rules.js.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.js +32 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +25 -0
- package/dist/util.js +73 -0
- package/dist/util.js.map +1 -0
- package/package.json +58 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Atlas Authorization
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @atlasauth/pca-policy-ci
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A CI gate for Proof-Carrying Authority (PCA) policies. It statically analyzes an envelope, a minted grant, or a delegation chain and reports findings so an unsafe policy fails the build before it ships. It is driven by the `@atlasauth/pca-analyzer` decision procedure.
|
|
4
|
+
|
|
5
|
+
Rules (each can be toggled):
|
|
6
|
+
|
|
7
|
+
- `over-broad-grant`: wildcard verb or resource, a provable always-allow, or a dangerous verb with no narrowing condition
|
|
8
|
+
- `privilege-escalation`: a delegation hop whose authority is not a subset of its parent
|
|
9
|
+
- `redundant-caveat`: a duplicate, subsumed, unsatisfiable, or non-binding caveat or condition
|
|
10
|
+
- `dangerous-reachability`: a dangerous action provably reachable with no budget, rate, or step-up gate
|
|
11
|
+
- `missing-safety-floor`: no expiry, or no blast-radius, rate, or budget gate on a non-trivial grant
|
|
12
|
+
|
|
13
|
+
A hard `error` is only emitted from a proven analyzer verdict; undecidable features (regex resources, cross-field refs, over-large grids) degrade to a `warn` or `info` that says so.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm i -D @atlasauth/pca-policy-ci
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
CLI:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npx pca-policy-ci policies/*.json [--max-severity error|warn|info] [--format text|github] [--quiet]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Each file is a JSON envelope, grant, compiled policy, `{ predicates, caveats }`, or an array (treated as a delegation chain). The exit code is non-zero when any finding reaches `--max-severity` (default `error`). `--format github` emits GitHub Actions annotations.
|
|
30
|
+
|
|
31
|
+
Library:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { lint, lintPolicies } from '@atlasauth/pca-policy-ci';
|
|
35
|
+
|
|
36
|
+
const findings = lint({
|
|
37
|
+
predicates: [{ verb: '*', resource: '*' }],
|
|
38
|
+
caveats: [],
|
|
39
|
+
});
|
|
40
|
+
// [{ rule: 'over-broad-grant', severity: 'error', message: '...', where: 'predicates[0]' }, ...]
|
|
41
|
+
|
|
42
|
+
const summary = lintPolicies(
|
|
43
|
+
[{ label: 'refund-agent', policy: envelope }],
|
|
44
|
+
{ maxSeverity: 'warn' },
|
|
45
|
+
);
|
|
46
|
+
process.exit(summary.exitCode);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
- `lint(input, opts?)`: dispatches to `lintPolicy` (single policy) or `lintChain` (array)
|
|
52
|
+
- `lintPolicies(items, opts?)`: labeled batch, returns a `LintSummary` with `counts`, `ok`, `exitCode`
|
|
53
|
+
- `summarize(results, opts?)`, `normalizePolicyInput`, `DEFAULT_DANGEROUS_VERBS`
|
|
54
|
+
- Options: `rules` to enable/disable or tune each check (dangerous verbs, targets, safety floor)
|
|
55
|
+
|
|
56
|
+
## Status
|
|
57
|
+
|
|
58
|
+
Part of [Proof-Carrying Authority](https://github.com/Atlas-Authorization/pca). A clean run means no rule fired, not that a policy is correct; it complements review, not replaces it.
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT - see LICENSE
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pca-policy-ci — CLI for the PCA policy-safety gate.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* pca-policy-ci <files...> [--max-severity error|warn|info] [--format text|github] [--quiet]
|
|
7
|
+
*
|
|
8
|
+
* Each file is a JSON policy document: an `Envelope`, a minted grant (`Capability`), a compiled
|
|
9
|
+
* policy, a bare `{ predicates, caveats }`, or a JSON ARRAY (treated as a delegation chain). Findings
|
|
10
|
+
* are printed and the process exits non-zero when any finding is at or above `--max-severity`
|
|
11
|
+
* (default `error`). `--format github` emits GitHub-Actions workflow annotations.
|
|
12
|
+
*/
|
|
13
|
+
import { type Severity } from './index';
|
|
14
|
+
interface CliArgs {
|
|
15
|
+
files: string[];
|
|
16
|
+
maxSeverity: Severity;
|
|
17
|
+
format: 'text' | 'github';
|
|
18
|
+
quiet: boolean;
|
|
19
|
+
help: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function parseArgs(argv: readonly string[]): {
|
|
22
|
+
args: CliArgs;
|
|
23
|
+
} | {
|
|
24
|
+
error: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function run(argv: readonly string[], write?: (s: string) => void): number;
|
|
27
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* pca-policy-ci — CLI for the PCA policy-safety gate.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* pca-policy-ci <files...> [--max-severity error|warn|info] [--format text|github] [--quiet]
|
|
8
|
+
*
|
|
9
|
+
* Each file is a JSON policy document: an `Envelope`, a minted grant (`Capability`), a compiled
|
|
10
|
+
* policy, a bare `{ predicates, caveats }`, or a JSON ARRAY (treated as a delegation chain). Findings
|
|
11
|
+
* are printed and the process exits non-zero when any finding is at or above `--max-severity`
|
|
12
|
+
* (default `error`). `--format github` emits GitHub-Actions workflow annotations.
|
|
13
|
+
*/
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.parseArgs = parseArgs;
|
|
19
|
+
exports.run = run;
|
|
20
|
+
const node_fs_1 = require("node:fs");
|
|
21
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
22
|
+
const index_1 = require("./index");
|
|
23
|
+
const SEVERITIES = ['error', 'warn', 'info'];
|
|
24
|
+
function isSeverity(x) {
|
|
25
|
+
return x === 'error' || x === 'warn' || x === 'info';
|
|
26
|
+
}
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
const files = [];
|
|
29
|
+
let maxSeverity = 'error';
|
|
30
|
+
let format = 'text';
|
|
31
|
+
let quiet = false;
|
|
32
|
+
let help = false;
|
|
33
|
+
for (let i = 0; i < argv.length; i++) {
|
|
34
|
+
const a = argv[i];
|
|
35
|
+
if (a === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
if (a === '--help' || a === '-h') {
|
|
38
|
+
help = true;
|
|
39
|
+
}
|
|
40
|
+
else if (a === '--quiet' || a === '-q') {
|
|
41
|
+
quiet = true;
|
|
42
|
+
}
|
|
43
|
+
else if (a === '--max-severity') {
|
|
44
|
+
const v = argv[++i];
|
|
45
|
+
if (v === undefined || !isSeverity(v))
|
|
46
|
+
return { error: `--max-severity expects one of ${SEVERITIES.join('|')}` };
|
|
47
|
+
maxSeverity = v;
|
|
48
|
+
}
|
|
49
|
+
else if (a.startsWith('--max-severity=')) {
|
|
50
|
+
const v = a.slice('--max-severity='.length);
|
|
51
|
+
if (!isSeverity(v))
|
|
52
|
+
return { error: `--max-severity expects one of ${SEVERITIES.join('|')}` };
|
|
53
|
+
maxSeverity = v;
|
|
54
|
+
}
|
|
55
|
+
else if (a === '--format') {
|
|
56
|
+
const v = argv[++i];
|
|
57
|
+
if (v !== 'text' && v !== 'github')
|
|
58
|
+
return { error: `--format expects 'text' or 'github'` };
|
|
59
|
+
format = v;
|
|
60
|
+
}
|
|
61
|
+
else if (a.startsWith('--format=')) {
|
|
62
|
+
const v = a.slice('--format='.length);
|
|
63
|
+
if (v !== 'text' && v !== 'github')
|
|
64
|
+
return { error: `--format expects 'text' or 'github'` };
|
|
65
|
+
format = v;
|
|
66
|
+
}
|
|
67
|
+
else if (a.startsWith('-')) {
|
|
68
|
+
return { error: `unknown option '${a}'` };
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
files.push(a);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { args: { files, maxSeverity, format, quiet, help } };
|
|
75
|
+
}
|
|
76
|
+
const HELP = `pca-policy-ci — statically catch unsafe PCA policies before they ship.
|
|
77
|
+
|
|
78
|
+
Usage:
|
|
79
|
+
pca-policy-ci <files...> [options]
|
|
80
|
+
|
|
81
|
+
Options:
|
|
82
|
+
--max-severity <error|warn|info> Lowest severity that fails the gate (default: error)
|
|
83
|
+
--format <text|github> Output format (default: text; 'github' = workflow annotations)
|
|
84
|
+
--quiet, -q Only print findings and the summary line
|
|
85
|
+
--help, -h Show this help
|
|
86
|
+
|
|
87
|
+
Each file is a JSON policy (Envelope / grant / compiled / { predicates, caveats }) or a JSON array
|
|
88
|
+
treated as a delegation chain. Exits non-zero when any finding is at or above --max-severity.`;
|
|
89
|
+
function lintFile(file) {
|
|
90
|
+
let text;
|
|
91
|
+
try {
|
|
92
|
+
text = (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
return [{ rule: 'malformed-policy', severity: 'error', message: `cannot read file: ${e instanceof Error ? e.message : String(e)}` }];
|
|
96
|
+
}
|
|
97
|
+
let doc;
|
|
98
|
+
try {
|
|
99
|
+
doc = JSON.parse(text);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
return [{ rule: 'malformed-policy', severity: 'error', message: `invalid JSON: ${e instanceof Error ? e.message : String(e)}` }];
|
|
103
|
+
}
|
|
104
|
+
return (0, index_1.lint)(doc);
|
|
105
|
+
}
|
|
106
|
+
const GH_LEVEL = { error: 'error', warn: 'warning', info: 'notice' };
|
|
107
|
+
const TEXT_TAG = { error: 'ERROR', warn: 'WARN', info: 'INFO' };
|
|
108
|
+
function printText(results, quiet, write) {
|
|
109
|
+
for (const r of results) {
|
|
110
|
+
if (!quiet)
|
|
111
|
+
write(`\n${r.label}:`);
|
|
112
|
+
if (r.findings.length === 0) {
|
|
113
|
+
if (!quiet)
|
|
114
|
+
write(' (clean)');
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
for (const f of r.findings) {
|
|
118
|
+
const loc = f.where !== undefined ? ` [${f.where}]` : '';
|
|
119
|
+
write(` ${TEXT_TAG[f.severity].padEnd(5)} ${f.rule}${loc}: ${f.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function escapeGh(s) {
|
|
124
|
+
return s.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
|
|
125
|
+
}
|
|
126
|
+
function printGithub(results, write) {
|
|
127
|
+
for (const r of results) {
|
|
128
|
+
for (const f of r.findings) {
|
|
129
|
+
const props = [`file=${escapeGh(r.label)}`, `title=${escapeGh(f.rule)}`].join(',');
|
|
130
|
+
const loc = f.where !== undefined ? `${f.where}: ` : '';
|
|
131
|
+
write(`::${GH_LEVEL[f.severity]} ${props}::${escapeGh(`${loc}${f.message}`)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function run(argv, write = (s) => node_process_1.default.stdout.write(s + '\n')) {
|
|
136
|
+
const parsed = parseArgs(argv);
|
|
137
|
+
if ('error' in parsed) {
|
|
138
|
+
write(parsed.error);
|
|
139
|
+
write(HELP);
|
|
140
|
+
return 2;
|
|
141
|
+
}
|
|
142
|
+
const { args } = parsed;
|
|
143
|
+
if (args.help) {
|
|
144
|
+
write(HELP);
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
if (args.files.length === 0) {
|
|
148
|
+
write('error: no policy files given');
|
|
149
|
+
write(HELP);
|
|
150
|
+
return 2;
|
|
151
|
+
}
|
|
152
|
+
const results = args.files.map((file) => ({ label: file, findings: lintFile(file) }));
|
|
153
|
+
const summary = (0, index_1.summarize)(results, { maxSeverity: args.maxSeverity });
|
|
154
|
+
if (args.format === 'github')
|
|
155
|
+
printGithub(results, write);
|
|
156
|
+
else
|
|
157
|
+
printText(results, args.quiet, write);
|
|
158
|
+
write(`\n${summary.ok ? 'PASS' : 'FAIL'} — ${summary.counts.error} error(s), ${summary.counts.warn} warning(s), ${summary.counts.info} info across ${results.length} policy/policies (fail threshold: ${summary.maxSeverity})`);
|
|
159
|
+
return summary.exitCode;
|
|
160
|
+
}
|
|
161
|
+
// Entry point (skipped when imported by tests). Guarded so it is inert under a test runner that may
|
|
162
|
+
// not provide the CommonJS `require`/`module` globals.
|
|
163
|
+
if (typeof require !== 'undefined' && typeof module !== 'undefined' && require.main === module) {
|
|
164
|
+
node_process_1.default.exit(run(node_process_1.default.argv.slice(2)));
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/deps.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized re-exports of the two upstream packages this gate is built on. The CI gate does NOT
|
|
3
|
+
* re-implement any static analysis — it drives the real `@atlasauth/pca-analyzer` decision procedure
|
|
4
|
+
* and reads the real `@atlasauth/pca` policy shapes.
|
|
5
|
+
*/
|
|
6
|
+
export { alwaysAllows, alwaysDenies, disjoint, equivalent, reachable, subsumes, predicateNumericSatisfiable, tightenInterval, intervalEmpty, FULL_INTERVAL, } from '@atlasauth/pca-analyzer';
|
|
7
|
+
export type { AnalyzerAction, AnalyzerOptions, PolicyInput, NumInterval, ReachableResult, AlwaysAllowsResult, AlwaysDeniesResult, SubsumesResult, } from '@atlasauth/pca-analyzer';
|
|
8
|
+
export { ENVELOPE_CAVEAT, isLeafCondition } from '@atlasauth/pca';
|
|
9
|
+
export type { Capability, Caveat, Condition, Envelope, LeafCondition, Predicate, RiskPolicy } from '@atlasauth/pca';
|
package/dist/deps.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Centralized re-exports of the two upstream packages this gate is built on. The CI gate does NOT
|
|
4
|
+
* re-implement any static analysis — it drives the real `@atlasauth/pca-analyzer` decision procedure
|
|
5
|
+
* and reads the real `@atlasauth/pca` policy shapes.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.isLeafCondition = exports.ENVELOPE_CAVEAT = exports.FULL_INTERVAL = exports.intervalEmpty = exports.tightenInterval = exports.predicateNumericSatisfiable = exports.subsumes = exports.reachable = exports.equivalent = exports.disjoint = exports.alwaysDenies = exports.alwaysAllows = void 0;
|
|
9
|
+
var pca_analyzer_1 = require("@atlasauth/pca-analyzer");
|
|
10
|
+
Object.defineProperty(exports, "alwaysAllows", { enumerable: true, get: function () { return pca_analyzer_1.alwaysAllows; } });
|
|
11
|
+
Object.defineProperty(exports, "alwaysDenies", { enumerable: true, get: function () { return pca_analyzer_1.alwaysDenies; } });
|
|
12
|
+
Object.defineProperty(exports, "disjoint", { enumerable: true, get: function () { return pca_analyzer_1.disjoint; } });
|
|
13
|
+
Object.defineProperty(exports, "equivalent", { enumerable: true, get: function () { return pca_analyzer_1.equivalent; } });
|
|
14
|
+
Object.defineProperty(exports, "reachable", { enumerable: true, get: function () { return pca_analyzer_1.reachable; } });
|
|
15
|
+
Object.defineProperty(exports, "subsumes", { enumerable: true, get: function () { return pca_analyzer_1.subsumes; } });
|
|
16
|
+
Object.defineProperty(exports, "predicateNumericSatisfiable", { enumerable: true, get: function () { return pca_analyzer_1.predicateNumericSatisfiable; } });
|
|
17
|
+
Object.defineProperty(exports, "tightenInterval", { enumerable: true, get: function () { return pca_analyzer_1.tightenInterval; } });
|
|
18
|
+
Object.defineProperty(exports, "intervalEmpty", { enumerable: true, get: function () { return pca_analyzer_1.intervalEmpty; } });
|
|
19
|
+
Object.defineProperty(exports, "FULL_INTERVAL", { enumerable: true, get: function () { return pca_analyzer_1.FULL_INTERVAL; } });
|
|
20
|
+
var pca_1 = require("@atlasauth/pca");
|
|
21
|
+
Object.defineProperty(exports, "ENVELOPE_CAVEAT", { enumerable: true, get: function () { return pca_1.ENVELOPE_CAVEAT; } });
|
|
22
|
+
Object.defineProperty(exports, "isLeafCondition", { enumerable: true, get: function () { return pca_1.isLeafCondition; } });
|
|
23
|
+
//# sourceMappingURL=deps.js.map
|
package/dist/deps.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps.js","sourceRoot":"","sources":["../src/deps.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,wDAWiC;AAV/B,4GAAA,YAAY,OAAA;AACZ,4GAAA,YAAY,OAAA;AACZ,wGAAA,QAAQ,OAAA;AACR,0GAAA,UAAU,OAAA;AACV,yGAAA,SAAS,OAAA;AACT,wGAAA,QAAQ,OAAA;AACR,2HAAA,2BAA2B,OAAA;AAC3B,+GAAA,eAAe,OAAA;AACf,6GAAA,aAAa,OAAA;AACb,6GAAA,aAAa,OAAA;AAcf,sCAAkE;AAAzD,sGAAA,eAAe,OAAA;AAAE,sGAAA,eAAe,OAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @atlasauth/pca-policy-ci — a CI gate that STATICALLY catches unsafe PCA policies before they ship.
|
|
3
|
+
*
|
|
4
|
+
* This is the "shift-left policy safety" boundary. It drives the real `@atlasauth/pca-analyzer`
|
|
5
|
+
* decision procedure (reachability, totality/always-allow, delegation-safety subsumption, numeric
|
|
6
|
+
* satisfiability) over a PCA `Envelope` / `grant` / chain and reports a list of {@link Finding}s. Five
|
|
7
|
+
* rules, each toggleable:
|
|
8
|
+
*
|
|
9
|
+
* - **over-broad-grant** — a wildcard verb/resource, a semantically-proven always-allow, or a
|
|
10
|
+
* dangerous verb granted with no narrowing `where` or scope-narrowing caveat.
|
|
11
|
+
* - **privilege-escalation** — a delegation hop whose effective authority is NOT a subset of its
|
|
12
|
+
* parent (a hard error if the analyzer proves it; attenuation should make this impossible).
|
|
13
|
+
* - **redundant-caveat** — a caveat (or `where` condition) that can never bite: a duplicate, one
|
|
14
|
+
* subsumed by a stricter one of the same type, an unsatisfiable predicate, a non-binding bound.
|
|
15
|
+
* - **dangerous-reachability** — a dangerous (verb, resource) the analyzer PROVES reachable with no
|
|
16
|
+
* budget / rate / step-up gate.
|
|
17
|
+
* - **missing-safety-floor** — no expiry, or no blast/rate/budget gate on a non-trivial grant.
|
|
18
|
+
*
|
|
19
|
+
* SOUNDNESS: a hard `error` is only ever emitted from a PROVEN analyzer verdict. Any undecidable
|
|
20
|
+
* feature (a `re:` regex resource, a cross-field `ref`, an over-large grid) fails safe to a
|
|
21
|
+
* `warn`/`info` that says it is undecidable — the gate never fails a build on a false positive, and
|
|
22
|
+
* never silently passes a provably-unsafe policy.
|
|
23
|
+
*/
|
|
24
|
+
import type { AnalyzerOptions } from './deps';
|
|
25
|
+
import { type Finding, type LabeledFindings, type LintRules, type LintSummary, type SummaryOptions } from './types';
|
|
26
|
+
export * from './types';
|
|
27
|
+
export { normalizePolicyInput, normalizeChain, isCapability, isEnvelope, type NormalizedPolicy } from './normalize';
|
|
28
|
+
export interface LintOptions extends AnalyzerOptions {
|
|
29
|
+
rules?: LintRules;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Lint a single PCA policy: an `Envelope`, a minted `grant` (`Capability`), a `compilePolicy` result,
|
|
33
|
+
* or a bare `{ predicates, caveats? }`. Returns all findings (empty = clean).
|
|
34
|
+
*/
|
|
35
|
+
export declare function lintPolicy(input: unknown, opts?: LintOptions): Finding[];
|
|
36
|
+
/**
|
|
37
|
+
* Lint a delegation chain (root -> leaf): a `Capability[]` (a real minted chain) or an array of
|
|
38
|
+
* already-effective `{ predicates, caveats }` hops. Runs the privilege-escalation check across every
|
|
39
|
+
* consecutive pair, then runs the single-policy rules on the acting LEAF's effective authority.
|
|
40
|
+
*/
|
|
41
|
+
export declare function lintChain(chain: readonly unknown[], opts?: LintOptions): Finding[];
|
|
42
|
+
/** Dispatch: an array input is a delegation chain ({@link lintChain}); anything else is a single policy. */
|
|
43
|
+
export declare function lint(input: unknown, opts?: LintOptions): Finding[];
|
|
44
|
+
export interface LintItem {
|
|
45
|
+
label?: string;
|
|
46
|
+
policy: unknown;
|
|
47
|
+
}
|
|
48
|
+
/** Lint a list of labeled policies (or chains) and roll the findings into a {@link LintSummary}. */
|
|
49
|
+
export declare function lintPolicies(items: readonly LintItem[], opts?: LintOptions & SummaryOptions): LintSummary;
|
|
50
|
+
/** Roll a set of labeled findings into counts + an exit-code mapping (`error` -> non-zero). */
|
|
51
|
+
export declare function summarize(results: LabeledFindings[], opts?: SummaryOptions): LintSummary;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @atlasauth/pca-policy-ci — a CI gate that STATICALLY catches unsafe PCA policies before they ship.
|
|
4
|
+
*
|
|
5
|
+
* This is the "shift-left policy safety" boundary. It drives the real `@atlasauth/pca-analyzer`
|
|
6
|
+
* decision procedure (reachability, totality/always-allow, delegation-safety subsumption, numeric
|
|
7
|
+
* satisfiability) over a PCA `Envelope` / `grant` / chain and reports a list of {@link Finding}s. Five
|
|
8
|
+
* rules, each toggleable:
|
|
9
|
+
*
|
|
10
|
+
* - **over-broad-grant** — a wildcard verb/resource, a semantically-proven always-allow, or a
|
|
11
|
+
* dangerous verb granted with no narrowing `where` or scope-narrowing caveat.
|
|
12
|
+
* - **privilege-escalation** — a delegation hop whose effective authority is NOT a subset of its
|
|
13
|
+
* parent (a hard error if the analyzer proves it; attenuation should make this impossible).
|
|
14
|
+
* - **redundant-caveat** — a caveat (or `where` condition) that can never bite: a duplicate, one
|
|
15
|
+
* subsumed by a stricter one of the same type, an unsatisfiable predicate, a non-binding bound.
|
|
16
|
+
* - **dangerous-reachability** — a dangerous (verb, resource) the analyzer PROVES reachable with no
|
|
17
|
+
* budget / rate / step-up gate.
|
|
18
|
+
* - **missing-safety-floor** — no expiry, or no blast/rate/budget gate on a non-trivial grant.
|
|
19
|
+
*
|
|
20
|
+
* SOUNDNESS: a hard `error` is only ever emitted from a PROVEN analyzer verdict. Any undecidable
|
|
21
|
+
* feature (a `re:` regex resource, a cross-field `ref`, an over-large grid) fails safe to a
|
|
22
|
+
* `warn`/`info` that says it is undecidable — the gate never fails a build on a false positive, and
|
|
23
|
+
* never silently passes a provably-unsafe policy.
|
|
24
|
+
*/
|
|
25
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
28
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
29
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
30
|
+
}
|
|
31
|
+
Object.defineProperty(o, k2, desc);
|
|
32
|
+
}) : (function(o, m, k, k2) {
|
|
33
|
+
if (k2 === undefined) k2 = k;
|
|
34
|
+
o[k2] = m[k];
|
|
35
|
+
}));
|
|
36
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
37
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.isEnvelope = exports.isCapability = exports.normalizeChain = exports.normalizePolicyInput = void 0;
|
|
41
|
+
exports.lintPolicy = lintPolicy;
|
|
42
|
+
exports.lintChain = lintChain;
|
|
43
|
+
exports.lint = lint;
|
|
44
|
+
exports.lintPolicies = lintPolicies;
|
|
45
|
+
exports.summarize = summarize;
|
|
46
|
+
const normalize_1 = require("./normalize");
|
|
47
|
+
const rules_1 = require("./rules");
|
|
48
|
+
const types_1 = require("./types");
|
|
49
|
+
__exportStar(require("./types"), exports);
|
|
50
|
+
var normalize_2 = require("./normalize");
|
|
51
|
+
Object.defineProperty(exports, "normalizePolicyInput", { enumerable: true, get: function () { return normalize_2.normalizePolicyInput; } });
|
|
52
|
+
Object.defineProperty(exports, "normalizeChain", { enumerable: true, get: function () { return normalize_2.normalizeChain; } });
|
|
53
|
+
Object.defineProperty(exports, "isCapability", { enumerable: true, get: function () { return normalize_2.isCapability; } });
|
|
54
|
+
Object.defineProperty(exports, "isEnvelope", { enumerable: true, get: function () { return normalize_2.isEnvelope; } });
|
|
55
|
+
function resolveRules(rules) {
|
|
56
|
+
const r = rules ?? {};
|
|
57
|
+
const ob = r.overBroadGrant;
|
|
58
|
+
const dr = r.dangerousReachability;
|
|
59
|
+
const sf = r.missingSafetyFloor;
|
|
60
|
+
return {
|
|
61
|
+
overBroadGrant: {
|
|
62
|
+
enabled: ob !== false,
|
|
63
|
+
dangerousVerbs: typeof ob === 'object' && ob.dangerousVerbs ? ob.dangerousVerbs : types_1.DEFAULT_DANGEROUS_VERBS,
|
|
64
|
+
},
|
|
65
|
+
privilegeEscalation: r.privilegeEscalation !== false,
|
|
66
|
+
redundantCaveat: r.redundantCaveat !== false,
|
|
67
|
+
dangerousReachability: {
|
|
68
|
+
enabled: dr !== false,
|
|
69
|
+
dangerousVerbs: typeof dr === 'object' && dr.dangerousVerbs ? dr.dangerousVerbs : types_1.DEFAULT_DANGEROUS_VERBS,
|
|
70
|
+
targets: typeof dr === 'object' && dr.targets ? dr.targets : [],
|
|
71
|
+
},
|
|
72
|
+
missingSafetyFloor: {
|
|
73
|
+
enabled: sf !== false,
|
|
74
|
+
requireExpiry: typeof sf === 'object' ? sf.requireExpiry !== false : true,
|
|
75
|
+
requireScopeGate: typeof sf === 'object' ? sf.requireScopeGate !== false : true,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function analyzerOpts(opts) {
|
|
80
|
+
return opts?.maxCombinations !== undefined ? { maxCombinations: opts.maxCombinations } : {};
|
|
81
|
+
}
|
|
82
|
+
// ---- lintPolicy / lintChain / lint ----------------------------------------------------------------
|
|
83
|
+
/**
|
|
84
|
+
* Lint a single PCA policy: an `Envelope`, a minted `grant` (`Capability`), a `compilePolicy` result,
|
|
85
|
+
* or a bare `{ predicates, caveats? }`. Returns all findings (empty = clean).
|
|
86
|
+
*/
|
|
87
|
+
function lintPolicy(input, opts = {}) {
|
|
88
|
+
const norm = (0, normalize_1.normalizePolicyInput)(input);
|
|
89
|
+
if (!norm.ok)
|
|
90
|
+
return [{ rule: 'malformed-policy', severity: 'error', message: norm.reason }];
|
|
91
|
+
const cfg = resolveRules(opts.rules);
|
|
92
|
+
const ao = analyzerOpts(opts);
|
|
93
|
+
const policy = norm.policy;
|
|
94
|
+
const out = [];
|
|
95
|
+
if (cfg.overBroadGrant.enabled)
|
|
96
|
+
out.push(...(0, rules_1.ruleOverBroadGrant)(policy, cfg.overBroadGrant.dangerousVerbs, ao));
|
|
97
|
+
if (cfg.redundantCaveat)
|
|
98
|
+
out.push(...(0, rules_1.ruleRedundantCaveat)(policy));
|
|
99
|
+
if (cfg.dangerousReachability.enabled) {
|
|
100
|
+
out.push(...(0, rules_1.ruleDangerousReachability)(policy, cfg.dangerousReachability.dangerousVerbs, cfg.dangerousReachability.targets, ao));
|
|
101
|
+
}
|
|
102
|
+
if (cfg.missingSafetyFloor.enabled) {
|
|
103
|
+
out.push(...(0, rules_1.ruleMissingSafetyFloor)(policy, cfg.missingSafetyFloor, cfg.overBroadGrant.dangerousVerbs));
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Lint a delegation chain (root -> leaf): a `Capability[]` (a real minted chain) or an array of
|
|
109
|
+
* already-effective `{ predicates, caveats }` hops. Runs the privilege-escalation check across every
|
|
110
|
+
* consecutive pair, then runs the single-policy rules on the acting LEAF's effective authority.
|
|
111
|
+
*/
|
|
112
|
+
function lintChain(chain, opts = {}) {
|
|
113
|
+
const norm = (0, normalize_1.normalizeChain)(chain);
|
|
114
|
+
if (!norm.ok)
|
|
115
|
+
return [{ rule: 'malformed-policy', severity: 'error', message: norm.reason }];
|
|
116
|
+
const cfg = resolveRules(opts.rules);
|
|
117
|
+
const ao = analyzerOpts(opts);
|
|
118
|
+
const hops = norm.hops;
|
|
119
|
+
const out = [];
|
|
120
|
+
if (cfg.privilegeEscalation)
|
|
121
|
+
out.push(...(0, rules_1.rulePrivilegeEscalation)(hops, ao));
|
|
122
|
+
const leaf = hops[hops.length - 1];
|
|
123
|
+
if (leaf !== undefined) {
|
|
124
|
+
if (cfg.overBroadGrant.enabled)
|
|
125
|
+
out.push(...(0, rules_1.ruleOverBroadGrant)(leaf, cfg.overBroadGrant.dangerousVerbs, ao));
|
|
126
|
+
if (cfg.redundantCaveat)
|
|
127
|
+
out.push(...(0, rules_1.ruleRedundantCaveat)(leaf));
|
|
128
|
+
if (cfg.dangerousReachability.enabled) {
|
|
129
|
+
out.push(...(0, rules_1.ruleDangerousReachability)(leaf, cfg.dangerousReachability.dangerousVerbs, cfg.dangerousReachability.targets, ao));
|
|
130
|
+
}
|
|
131
|
+
if (cfg.missingSafetyFloor.enabled) {
|
|
132
|
+
out.push(...(0, rules_1.ruleMissingSafetyFloor)(leaf, cfg.missingSafetyFloor, cfg.overBroadGrant.dangerousVerbs));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
/** Dispatch: an array input is a delegation chain ({@link lintChain}); anything else is a single policy. */
|
|
138
|
+
function lint(input, opts = {}) {
|
|
139
|
+
return Array.isArray(input) ? lintChain(input, opts) : lintPolicy(input, opts);
|
|
140
|
+
}
|
|
141
|
+
/** Lint a list of labeled policies (or chains) and roll the findings into a {@link LintSummary}. */
|
|
142
|
+
function lintPolicies(items, opts = {}) {
|
|
143
|
+
const results = items.map((item, i) => ({
|
|
144
|
+
label: item.label ?? `policy[${i}]`,
|
|
145
|
+
findings: lint(item.policy, opts),
|
|
146
|
+
}));
|
|
147
|
+
return summarize(results, opts);
|
|
148
|
+
}
|
|
149
|
+
/** Roll a set of labeled findings into counts + an exit-code mapping (`error` -> non-zero). */
|
|
150
|
+
function summarize(results, opts = {}) {
|
|
151
|
+
const maxSeverity = opts.maxSeverity ?? 'error';
|
|
152
|
+
const counts = { error: 0, warn: 0, info: 0 };
|
|
153
|
+
let failing = 0;
|
|
154
|
+
const threshold = types_1.SEVERITY_RANK[maxSeverity];
|
|
155
|
+
for (const r of results) {
|
|
156
|
+
for (const f of r.findings) {
|
|
157
|
+
counts[f.severity] += 1;
|
|
158
|
+
if (types_1.SEVERITY_RANK[f.severity] >= threshold)
|
|
159
|
+
failing += 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const ok = failing === 0;
|
|
163
|
+
return { results, counts, maxSeverity, ok, exitCode: ok ? 0 : 1 };
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input detection + normalization.
|
|
3
|
+
*
|
|
4
|
+
* `lintPolicy` accepts anything a principal might hand a CI gate: a bare predicate/caveat set, a
|
|
5
|
+
* compiled policy, a signed `Envelope`, or a minted `grant` (`Capability`). They all reduce to the
|
|
6
|
+
* same effective authority — a predicate set plus the runtime caveat (gate) set, and (when known) the
|
|
7
|
+
* risk policy. The grant/chain reduction is the subtle part and is documented on {@link fromGrant}.
|
|
8
|
+
*/
|
|
9
|
+
import { type Capability, type Caveat, type Envelope, type Predicate, type RiskPolicy } from '@atlasauth/pca';
|
|
10
|
+
export type PolicyKind = 'bare' | 'compiled' | 'envelope' | 'grant' | 'chain-hop';
|
|
11
|
+
/** The effective authority of one policy (or one chain hop), ready for the static analyzer. */
|
|
12
|
+
export interface NormalizedPolicy {
|
|
13
|
+
predicates: Predicate[];
|
|
14
|
+
/** Runtime gate caveats (expires / rate / max_blast_radius / reversibility_max / ...). */
|
|
15
|
+
caveats: Caveat[];
|
|
16
|
+
/** The risk policy, when the input carried one (envelope / grant / compiled). */
|
|
17
|
+
riskPolicy?: RiskPolicy;
|
|
18
|
+
kind: PolicyKind;
|
|
19
|
+
}
|
|
20
|
+
export type NormalizeResult = {
|
|
21
|
+
ok: true;
|
|
22
|
+
policy: NormalizedPolicy;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
reason: string;
|
|
26
|
+
};
|
|
27
|
+
/** A minted grant / delegation hop: has the content-addressed, signed shape of a `Capability`. */
|
|
28
|
+
export declare function isCapability(x: unknown): x is Capability;
|
|
29
|
+
/** A signed policy envelope (the Root Intent Grant payload). */
|
|
30
|
+
export declare function isEnvelope(x: unknown): x is Envelope;
|
|
31
|
+
/**
|
|
32
|
+
* Reduce a grant to its effective authority. The grant's predicates and baseline gate caveats live
|
|
33
|
+
* INSIDE its first `envelope` caveat (signed into the grant). Delegation then APPENDS further gate
|
|
34
|
+
* caveats to the grant's top-level `caveats` array. So the effective gates are the envelope's own
|
|
35
|
+
* caveats PLUS the appended (non-envelope) top-level caveats, and the predicates come from the
|
|
36
|
+
* envelope. A malformed / unreadable envelope is a hard failure the caller surfaces as a finding.
|
|
37
|
+
*/
|
|
38
|
+
export declare function fromGrant(grant: Capability): NormalizeResult;
|
|
39
|
+
/** Normalize any single-policy input to its effective authority. */
|
|
40
|
+
export declare function normalizePolicyInput(input: unknown): NormalizeResult;
|
|
41
|
+
export type ChainNormalizeResult = {
|
|
42
|
+
ok: true;
|
|
43
|
+
hops: NormalizedPolicy[];
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
reason: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Normalize a delegation chain to one {@link NormalizedPolicy} per hop (root -> leaf). A chain of
|
|
50
|
+
* `Capability`s uses the grant reduction: the predicates are shared (from the root envelope) and each
|
|
51
|
+
* hop's effective gate caveats are the envelope's caveats plus that hop's appended caveats, so a
|
|
52
|
+
* well-formed (append-only) chain yields a monotonically narrowing authority. A chain of already
|
|
53
|
+
* effective `{ predicates, caveats }` hops is taken as-is (useful for testing an escalation directly).
|
|
54
|
+
*/
|
|
55
|
+
export declare function normalizeChain(chain: readonly unknown[]): ChainNormalizeResult;
|