@atlasauth/pca-analyzer 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 +109 -2
- package/dist/caveats.d.ts +22 -0
- package/dist/caveats.js +88 -0
- package/dist/caveats.js.map +1 -0
- package/dist/enumerate.d.ts +68 -0
- package/dist/enumerate.js +375 -0
- package/dist/enumerate.js.map +1 -0
- package/dist/field.d.ts +42 -0
- package/dist/field.js +232 -0
- package/dist/field.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +374 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +109 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +53 -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,110 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @atlasauth/pca-analyzer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Static analyzability of authority for Proof-Carrying Authority (PCA). PCA can verify one action and prove a quantitative trust-budget bound, but on its own it cannot answer "what can this whole delegated authority ever do?". This package decides that over PCA's predicate/caveat policy (reachability, vacuity, totality, delegation-safety subsumption, disjointness, equivalence, and conformance to a declared intent) without an SMT dependency.
|
|
4
|
+
|
|
5
|
+
It is a sound, bounded decision procedure in the spirit of AWS Cedar's symbolic compiler (SymCC, arXiv:2403.04651), which decides policy equivalence, subsumption and always-allow/deny with counterexamples. Instead of discharging to Z3, it exploits a finite-distinguishing-value property of PCA's predicate theory and decides by enumeration against the real `evaluatePredicates` from `@atlasauth/pca`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm i @atlasauth/pca-analyzer @atlasauth/pca
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { compilePolicy } from '@atlasauth/pca';
|
|
17
|
+
import { subsumes, intentConformance, reachable } from '@atlasauth/pca-analyzer';
|
|
18
|
+
|
|
19
|
+
const parent = compilePolicy({ permissions: { stripe: ['refund'] }, limits: { refund: '$500' } });
|
|
20
|
+
const child = compilePolicy({ permissions: { stripe: ['refund'] }, limits: { refund: '$100' } });
|
|
21
|
+
|
|
22
|
+
// Delegation safety: a child must not widen its parent.
|
|
23
|
+
subsumes(parent, child).subsumes; // true
|
|
24
|
+
subsumes(child, parent); // { subsumes: false, counterexample: { verb: 'stripe.refund', params: { amount: 300 }, ... } }
|
|
25
|
+
|
|
26
|
+
// Can any input complete this action into an ALLOW? Returns a verified witness.
|
|
27
|
+
reachable(parent, { verb: 'stripe.refund', resource: 'charge:1', params: { amount: 50 } }); // { reachable: true, witness }
|
|
28
|
+
|
|
29
|
+
// Intent conformance: does the authority stay inside what was meant?
|
|
30
|
+
const policy = compilePolicy({ permissions: { stripe: ['refund'] }, limits: { refund: '$20000' } });
|
|
31
|
+
const r = intentConformance(policy, { verbs: ['stripe.refund'], maxAmount: 10000 });
|
|
32
|
+
// r.conforms === false; r.violations[0].kinds includes 'over-amount'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Queries
|
|
36
|
+
|
|
37
|
+
All take a `CompiledPolicy` (from `compilePolicy` / `agent().policy`) or a bare
|
|
38
|
+
`{ predicates, caveats? }`. Every negative/relational verdict carries a concrete counterexample or
|
|
39
|
+
witness.
|
|
40
|
+
|
|
41
|
+
| Function | Decides |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `reachable(policy, action)` | Can any input complete `(verb, resource, params)` into an ALLOW? Returns a verified `witness`. |
|
|
44
|
+
| `alwaysDenies(policy, verb?)` | Is the policy vacuous (denies everything / everything for `verb`)? |
|
|
45
|
+
| `alwaysAllows(policy, verb)` | Is the policy total for `verb` (admits every action of it)? |
|
|
46
|
+
| `subsumes(a, b)` | Does `a` permit everything `b` does (`admitted(b) ⊆ admitted(a)`)? The delegation-safety check: a child must be subsumed by its parent. |
|
|
47
|
+
| `disjoint(a, b)` | Do the two admit no common action? |
|
|
48
|
+
| `equivalent(a, b)` | Do the two admit exactly the same actions? |
|
|
49
|
+
| `intentConformance(policy, intent)` | Does the policy admit **only** actions inside a declared intent envelope (verbs + resource scopes + amount ceiling)? |
|
|
50
|
+
|
|
51
|
+
`intentConformance` is a safety check distinct from the runtime trust-budget. It catches the
|
|
52
|
+
*"a $14k renewal under a $10k cap on an approved vendor still violates intent"* class: an action that
|
|
53
|
+
passes the policy's predicates and stays under budget, yet falls outside what the principal intended,
|
|
54
|
+
because the policy's own ceiling is higher than (or absent relative to) the intent cap.
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## How it decides (the engine)
|
|
58
|
+
|
|
59
|
+
PCA's `where` conditions are a conjunction of threshold tests (`lt/lte/gt/gte/eq` on numbers),
|
|
60
|
+
membership tests (`eq/ne/in/nin` over finite values) and string `prefix` tests; predicates match on a
|
|
61
|
+
verb allowlist and a resource matcher; the envelope caveats are append-only and conjunctive. The
|
|
62
|
+
admitted region of a policy is therefore a **union of cells** of the arrangement defined by the literals
|
|
63
|
+
that appear in it. The analyzer builds one representative value per cell — every literal and numeric
|
|
64
|
+
threshold, the midpoints between consecutive thresholds, boundary points just outside the range, prefix
|
|
65
|
+
probes, a *fresh* verb / resource / value matching none of the literals, and an *absent* probe for each
|
|
66
|
+
field — then evaluates both policies at every representative with the real evaluator. Because that set
|
|
67
|
+
is distinguishing, agreeing on all representatives implies agreeing on all actions, so the relational
|
|
68
|
+
queries are decided exactly, and any mismatch is a real counterexample. Caveat satisfiability is decided
|
|
69
|
+
separately (`analyzeCaveats`): the context (time window, blast radius, depth, rate) is taken as
|
|
70
|
+
favourably chosen, and the one action-tied caveat, `reversibility_max`, becomes a per-action cap.
|
|
71
|
+
|
|
72
|
+
## Decided exactly vs. sound-over-approximated
|
|
73
|
+
|
|
74
|
+
**Decided exactly** (the fragment):
|
|
75
|
+
|
|
76
|
+
- verb allowlists (`string`, `string[]`, `'*'`);
|
|
77
|
+
- resource matchers that are exact, `'*'`, or a trailing-`*` **prefix**;
|
|
78
|
+
- numeric `where` over `lt/lte/gt/gte/eq` (the interval engine; merges bounds, detects empty
|
|
79
|
+
conjunctions such as `amount >= 100 AND amount <= 10`);
|
|
80
|
+
- finite-domain `eq/ne/in/nin` and string `prefix` (the set engine — the fresh/absent probes make
|
|
81
|
+
unbounded string domains decidable for these operators);
|
|
82
|
+
- `exists` (via the absent probe);
|
|
83
|
+
- the conjunctive envelope caveats: `expires`, `not_before` (empty-window detection), `rate`,
|
|
84
|
+
`max_blast_radius`, `reversibility_max`, `delegation_depth`, `budget_alloc`.
|
|
85
|
+
|
|
86
|
+
**Sound but over-approximated** — the result is reported with `approximate: true` and **fails safe**
|
|
87
|
+
(never claims *subsumed / always-deny / disjoint / equivalent / conforms* without proof; falls back to
|
|
88
|
+
*not-subsumed / reachable / not-disjoint / not-equivalent / non-conforming*):
|
|
89
|
+
|
|
90
|
+
- **`re:` regex resource matchers** — a regex relation (containment/overlap) is not decided; the
|
|
91
|
+
resource dimension cannot be soundly enumerated. (A regex is still evaluated *exactly* against a fully
|
|
92
|
+
concrete resource in `reachable`, where there is nothing to enumerate.)
|
|
93
|
+
- **cross-field `ref` conditions** — a comparison whose operand is another field (`a != env.b`); the two
|
|
94
|
+
fields co-vary and are not resolved statically.
|
|
95
|
+
- **whole-`action.params` conditions** (a condition on the container rather than a `.subfield`).
|
|
96
|
+
- **grid overflow** — a representative grid larger than `maxCombinations` (default 200,000); no
|
|
97
|
+
enumeration is performed.
|
|
98
|
+
|
|
99
|
+
The soundness contract: a proven verdict (`approximate` falsy) is correct for the fragment above; an
|
|
100
|
+
`approximate` verdict is never a false "safe".
|
|
101
|
+
|
|
102
|
+
## Status
|
|
103
|
+
|
|
104
|
+
Experimental. Verdicts without `approximate: true` are exact for the fragment listed above; the cryptography in `@atlasauth/pca` is unaudited, though this package performs no cryptography itself. An external SMT backend for the regex and cross-field fragments is not included.
|
|
105
|
+
|
|
106
|
+
Source and issues: https://github.com/Atlas-Authorization/pca
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT - see LICENSE
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Caveat } from '@atlasauth/pca';
|
|
2
|
+
/**
|
|
3
|
+
* Static satisfiability of a PCA envelope caveat set (the leaf's cumulative, append-only, conjunctive
|
|
4
|
+
* caveats). This answers "is there ANY runtime context under which these caveats can all hold?" — the
|
|
5
|
+
* global gate on whether the authority can ever admit anything.
|
|
6
|
+
*
|
|
7
|
+
* The caveats constrain the runtime CaveatContext (now, blast radius, delegation depth, rate window,
|
|
8
|
+
* reversibility class), not the action's predicate fields. The analyzer treats the context as chosen
|
|
9
|
+
* favourably (the most permissive the authority could ever encounter) — the right stance for "what can
|
|
10
|
+
* this authority EVER do". The one caveat tied to the action itself is `reversibility_max`, whose cap
|
|
11
|
+
* is returned so the per-action check can enforce `class(action) <= cap`.
|
|
12
|
+
*
|
|
13
|
+
* Mirrors `envelopeCaveatEvaluator` in @atlasauth/pca (unknown/ malformed => fail closed).
|
|
14
|
+
*/
|
|
15
|
+
export interface CaveatAnalysis {
|
|
16
|
+
/** Some context satisfies every caveat. */
|
|
17
|
+
sat: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
/** Strictest `reversibility_max` cap as an index into REVERSIBILITY_ORDER, if any caveat sets one. */
|
|
20
|
+
revCap?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function analyzeCaveats(caveats: readonly Caveat[] | undefined): CaveatAnalysis;
|
package/dist/caveats.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeCaveats = analyzeCaveats;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
function finite(v) {
|
|
6
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
7
|
+
}
|
|
8
|
+
function analyzeCaveats(caveats) {
|
|
9
|
+
if (!Array.isArray(caveats))
|
|
10
|
+
return { sat: false, reason: 'caveats are not an array' };
|
|
11
|
+
let notBeforeMax = -Infinity; // the action can only happen at now >= this
|
|
12
|
+
let expiresMin = Infinity; // ...and now < this
|
|
13
|
+
let revCap;
|
|
14
|
+
for (const cv of caveats) {
|
|
15
|
+
if (cv === null || typeof cv !== 'object' || typeof cv.type !== 'string') {
|
|
16
|
+
return { sat: false, reason: 'malformed caveat (not an object with a string type)' };
|
|
17
|
+
}
|
|
18
|
+
switch (cv.type) {
|
|
19
|
+
case 'expires': {
|
|
20
|
+
const at = finite(cv.at);
|
|
21
|
+
if (at === undefined)
|
|
22
|
+
return { sat: false, reason: 'expires.at is not a finite number' };
|
|
23
|
+
expiresMin = Math.min(expiresMin, at);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case 'not_before': {
|
|
27
|
+
const at = finite(cv.at);
|
|
28
|
+
if (at === undefined)
|
|
29
|
+
return { sat: false, reason: 'not_before.at is not a finite number' };
|
|
30
|
+
notBeforeMax = Math.max(notBeforeMax, at);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case 'rate': {
|
|
34
|
+
const max = finite(cv.max);
|
|
35
|
+
const per = finite(cv.per_secs);
|
|
36
|
+
if (max === undefined || per === undefined || per <= 0) {
|
|
37
|
+
return { sat: false, reason: 'rate caveat requires finite max and per_secs > 0' };
|
|
38
|
+
}
|
|
39
|
+
// With zero prior actions the count is 0, so the gate `0 < max` needs max >= 1.
|
|
40
|
+
if (max < 1)
|
|
41
|
+
return { sat: false, reason: `rate.max ${max} < 1 forbids every action` };
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case 'max_blast_radius': {
|
|
45
|
+
const max = finite(cv.max);
|
|
46
|
+
if (max === undefined)
|
|
47
|
+
return { sat: false, reason: 'max_blast_radius.max is not a finite number' };
|
|
48
|
+
// The least blast radius is 0, so this is satisfiable iff max >= 0.
|
|
49
|
+
if (max < 0)
|
|
50
|
+
return { sat: false, reason: 'max_blast_radius.max < 0 forbids every action' };
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case 'reversibility_max': {
|
|
54
|
+
const idx = types_1.REVERSIBILITY_ORDER.indexOf(String(cv.class));
|
|
55
|
+
if (idx < 0)
|
|
56
|
+
return { sat: false, reason: `reversibility_max.class '${String(cv.class)}' is unknown` };
|
|
57
|
+
revCap = revCap === undefined ? idx : Math.min(revCap, idx);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 'delegation_depth': {
|
|
61
|
+
const max = finite(cv.max);
|
|
62
|
+
if (max === undefined)
|
|
63
|
+
return { sat: false, reason: 'delegation_depth.max is not a finite number' };
|
|
64
|
+
// The least depth is 0, so this is satisfiable iff max >= 0.
|
|
65
|
+
if (max < 0)
|
|
66
|
+
return { sat: false, reason: 'delegation_depth.max < 0 forbids every action' };
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case 'budget_alloc': {
|
|
70
|
+
const lim = finite(cv.limit);
|
|
71
|
+
if (lim === undefined || lim < 0) {
|
|
72
|
+
return { sat: false, reason: 'budget_alloc.limit must be a finite number >= 0' };
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
default:
|
|
77
|
+
return { sat: false, reason: `unknown caveat type '${cv.type}' (fail closed)` };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (notBeforeMax !== -Infinity && expiresMin !== Infinity && notBeforeMax >= expiresMin) {
|
|
81
|
+
return {
|
|
82
|
+
sat: false,
|
|
83
|
+
reason: `not_before (${notBeforeMax}) >= expires (${expiresMin}): the validity window is empty`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { sat: true, revCap };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=caveats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caveats.js","sourceRoot":"","sources":["../src/caveats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Caveat, Condition, Predicate } from '@atlasauth/pca';
|
|
2
|
+
import type { AnalyzerAction, PolicyInput } from './types';
|
|
3
|
+
type Cell = unknown;
|
|
4
|
+
/** Default cap on the representative grid size (product of all dimensions). */
|
|
5
|
+
export declare const DEFAULT_MAX_COMBINATIONS = 200000;
|
|
6
|
+
export interface PolicyModel {
|
|
7
|
+
predicates: Predicate[];
|
|
8
|
+
caveats: Caveat[];
|
|
9
|
+
caveatSat: boolean;
|
|
10
|
+
caveatReason?: string;
|
|
11
|
+
/** Strictest `reversibility_max` cap (index into REVERSIBILITY_ORDER), if any. */
|
|
12
|
+
revCap?: number;
|
|
13
|
+
/** Non-empty when the policy uses features the analyzer cannot decide exactly. */
|
|
14
|
+
undecidable: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function normalizePolicy(p: PolicyInput): {
|
|
17
|
+
predicates: Predicate[];
|
|
18
|
+
caveats: Caveat[];
|
|
19
|
+
};
|
|
20
|
+
export declare function toModel(p: PolicyInput): PolicyModel;
|
|
21
|
+
export interface Probes {
|
|
22
|
+
verbs: string[];
|
|
23
|
+
resources: string[];
|
|
24
|
+
/** `undefined` means "no reversibility dimension" (no policy sets a `reversibility_max`). */
|
|
25
|
+
revClasses: (string | undefined)[];
|
|
26
|
+
fields: {
|
|
27
|
+
path: string;
|
|
28
|
+
values: Cell[];
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
export interface ProbeInputs {
|
|
32
|
+
predicateSets: Predicate[][];
|
|
33
|
+
extraVerbs?: string[];
|
|
34
|
+
extraResources?: string[];
|
|
35
|
+
extraFieldConditions?: {
|
|
36
|
+
path: string;
|
|
37
|
+
cond: Condition;
|
|
38
|
+
}[];
|
|
39
|
+
/** Vary the reversibility class over all three classes (needed when any policy caps reversibility). */
|
|
40
|
+
varyReversibility: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function collectProbes(inputs: ProbeInputs): Probes;
|
|
43
|
+
export declare function productSize(pr: Probes): number;
|
|
44
|
+
export interface Rep {
|
|
45
|
+
verb: string;
|
|
46
|
+
resource: string;
|
|
47
|
+
revClass: string | undefined;
|
|
48
|
+
/** Field path -> value, excluding absent fields. */
|
|
49
|
+
assignments: ReadonlyMap<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
export declare function representatives(pr: Probes): Generator<Rep>;
|
|
52
|
+
/** Does the policy admit this representative action? (predicate match AND static caveat gate.) */
|
|
53
|
+
export declare function admits(model: PolicyModel, rep: Rep): boolean;
|
|
54
|
+
/** Concrete admission of a fully-specified action (used by reachable's fast path; exact). */
|
|
55
|
+
export declare function admitsAction(model: PolicyModel, action: AnalyzerAction): boolean;
|
|
56
|
+
export declare function repToAction(rep: Rep): AnalyzerAction;
|
|
57
|
+
export interface SearchOutcome {
|
|
58
|
+
/** A representative for which the predicate returned true, if the search stopped on one. */
|
|
59
|
+
hit?: Rep;
|
|
60
|
+
/** The grid exceeded the configured bound; no enumeration was performed. */
|
|
61
|
+
overflow: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Walk the representative grid, stopping at the first rep for which `predicate` is true. Returns
|
|
65
|
+
* `overflow: true` (without walking) when the grid is larger than `max`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function search(pr: Probes, predicate: (rep: Rep) => boolean, max: number): SearchOutcome;
|
|
68
|
+
export {};
|