candor-ts 0.7.6 → 0.7.7
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/package.json +1 -1
- package/policy.mjs +5 -3
- package/query.mjs +21 -3
- package/scan.mjs +11 -4
package/package.json
CHANGED
package/policy.mjs
CHANGED
|
@@ -56,10 +56,12 @@ export function parsePolicy(text) {
|
|
|
56
56
|
return { deny, allow, forbid };
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/** §6.2 scope match: by NAME SEGMENT
|
|
59
|
+
/** §6.2 scope match: by NAME SEGMENT, last segment a prefix.
|
|
60
|
+
* Segments split on BOTH "." and "::" — Rust/Java qualify with "::" while TS uses ".", and a shared
|
|
61
|
+
* policy must match across engines (a `Foo::bar` scope authored against Rust was inert in TS before). */
|
|
60
62
|
export function scopeMatches(name, scope) {
|
|
61
|
-
const segs = name.split(
|
|
62
|
-
const parts = scope.split(
|
|
63
|
+
const segs = name.split(/[.:]+/).filter(Boolean);
|
|
64
|
+
const parts = scope.split(/[.:]+/).filter(Boolean);
|
|
63
65
|
if (parts.length === 0 || parts.length > segs.length) return false;
|
|
64
66
|
const last = parts[parts.length - 1], init = parts.slice(0, -1);
|
|
65
67
|
outer: for (let i = 0; i + parts.length <= segs.length; i++) {
|
package/query.mjs
CHANGED
|
@@ -95,7 +95,15 @@ switch (cmd) {
|
|
|
95
95
|
printAgents(); // shared with scan.mjs — one implementation, can't diverge within an install
|
|
96
96
|
break;
|
|
97
97
|
case "parsepolicy": {
|
|
98
|
-
|
|
98
|
+
// An unreadable/missing file is a clean exit-2 error, not an uncaught readFileSync stack trace.
|
|
99
|
+
let text;
|
|
100
|
+
try {
|
|
101
|
+
text = fs.readFileSync(args[0], "utf8");
|
|
102
|
+
} catch {
|
|
103
|
+
console.error(`candor: policy ${args[0] ?? "(no file given)"} could not be read`);
|
|
104
|
+
process.exit(2);
|
|
105
|
+
}
|
|
106
|
+
emit(parsePolicy(text));
|
|
99
107
|
break;
|
|
100
108
|
}
|
|
101
109
|
case "show": {
|
|
@@ -234,8 +242,18 @@ switch (cmd) {
|
|
|
234
242
|
for (const c of rev.get(n) ?? []) if (!affected.has(c)) { affected.add(c); queue.push(c); }
|
|
235
243
|
}
|
|
236
244
|
const violations = [];
|
|
237
|
-
|
|
238
|
-
|
|
245
|
+
// A present policy arg (anything but the 0/1 verbosity sentinels) MUST exist and be readable —
|
|
246
|
+
// a typo'd path must be LOUD, not silently "no policy → ok:true, exit 0" (mirrors scan's --policy,
|
|
247
|
+
// which exits 2 on an unreadable file: a gate that can't read its policy can't certify anything).
|
|
248
|
+
if (maybePolicy && maybePolicy !== "0" && maybePolicy !== "1") {
|
|
249
|
+
let text;
|
|
250
|
+
try {
|
|
251
|
+
text = fs.readFileSync(maybePolicy, "utf8");
|
|
252
|
+
} catch {
|
|
253
|
+
console.error(`candor: policy ${maybePolicy} could not be read; whatif NOT evaluated against it`);
|
|
254
|
+
process.exit(2);
|
|
255
|
+
}
|
|
256
|
+
const pol = parsePolicy(text);
|
|
239
257
|
for (const r of pol.deny) {
|
|
240
258
|
if (r.effects.length && !r.effects.includes(eff)) continue; // pure ([]) forbids ANY effect
|
|
241
259
|
for (const fn of affected)
|
package/scan.mjs
CHANGED
|
@@ -87,7 +87,11 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
87
87
|
if (a === "--out") outPrefix = v; else policyPath = v;
|
|
88
88
|
i++;
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
// Any leading-dash token that isn't a recognized flag is an unknown flag — NOT a positional target
|
|
91
|
+
// (SPEC §6.2/§7). `-h`/`-V`/`--help`/`--version` are print-and-exit modes consumed above, so by here
|
|
92
|
+
// a single-dash token (`-x`, the typo `-policy`) can only be a mistake; treating it as the scan
|
|
93
|
+
// target would silently scan the wrong thing.
|
|
94
|
+
else if (a.startsWith("-")) { console.error(`candor-ts: unknown flag ${a} (${usage})`); process.exit(2); }
|
|
91
95
|
else if (target === null) target = a;
|
|
92
96
|
else if (outPrefix === null) outPrefix = a; // legacy positional prefix
|
|
93
97
|
else { console.error(`candor-ts: unexpected extra argument ${a} (${usage})`); process.exit(2); }
|
|
@@ -193,8 +197,8 @@ if (!wantJson) fs.mkdirSync(path.dirname(path.resolve(outPrefix)), { recursive:
|
|
|
193
197
|
const deps = JSON.parse(fs.readFileSync(pkg, "utf8")).dependencies ?? {};
|
|
194
198
|
if (Object.keys(deps).length > 0)
|
|
195
199
|
console.error("candor-ts: WARNING — the target declares dependencies but has no node_modules; " +
|
|
196
|
-
"imports
|
|
197
|
-
"Run `npm install` in the target first.");
|
|
200
|
+
"imports won't resolve, so calls into those packages read pure/invisible (not Unknown) " +
|
|
201
|
+
"and effects through them are silently dropped. Run `npm install` in the target first.");
|
|
198
202
|
} catch {}
|
|
199
203
|
}
|
|
200
204
|
// Prisma's client types are GENERATED — a project with the prisma dependency but no generated
|
|
@@ -2084,7 +2088,10 @@ if (policyPath) {
|
|
|
2084
2088
|
const incompleteMap = new Map();
|
|
2085
2089
|
for (const [name, rec] of fns) if (rec.incomplete.size) incompleteMap.set(name, rec.incomplete);
|
|
2086
2090
|
const v = evaluatePolicy(parsePolicy(text), functions, cg, incompleteMap);
|
|
2087
|
-
|
|
2091
|
+
// In --json mode stdout is the §2 envelope and must stay pure JSON — route the gate's
|
|
2092
|
+
// [AS-EFF-…] violation lines to stderr so a `candor-ts --json --policy … | jq` pipe never breaks.
|
|
2093
|
+
const emitViolation = wantJson ? (l) => console.error(l) : (l) => console.log(l);
|
|
2094
|
+
for (const line of v) emitViolation(line);
|
|
2088
2095
|
if (v.length) {
|
|
2089
2096
|
console.error(`candor-ts: ${v.length} policy violation(s)`);
|
|
2090
2097
|
process.exit(1);
|