@tinyviber/a2h 0.1.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/README.md +229 -0
- package/bin/a2h.js +7 -0
- package/dist/actions/engine.d.ts +40 -0
- package/dist/actions/engine.d.ts.map +1 -0
- package/dist/actions/engine.js +227 -0
- package/dist/actions/engine.js.map +1 -0
- package/dist/actions/policy.d.ts +8 -0
- package/dist/actions/policy.d.ts.map +1 -0
- package/dist/actions/policy.js +55 -0
- package/dist/actions/policy.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +258 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +8 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +69 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/build.d.ts +8 -0
- package/dist/ir/build.d.ts.map +1 -0
- package/dist/ir/build.js +397 -0
- package/dist/ir/build.js.map +1 -0
- package/dist/ir/enrich.d.ts +21 -0
- package/dist/ir/enrich.d.ts.map +1 -0
- package/dist/ir/enrich.js +177 -0
- package/dist/ir/enrich.js.map +1 -0
- package/dist/ir/identity.d.ts +3 -0
- package/dist/ir/identity.d.ts.map +1 -0
- package/dist/ir/identity.js +91 -0
- package/dist/ir/identity.js.map +1 -0
- package/dist/ir/scoring.d.ts +7 -0
- package/dist/ir/scoring.d.ts.map +1 -0
- package/dist/ir/scoring.js +88 -0
- package/dist/ir/scoring.js.map +1 -0
- package/dist/parsers/code.d.ts +3 -0
- package/dist/parsers/code.d.ts.map +1 -0
- package/dist/parsers/code.js +17 -0
- package/dist/parsers/code.js.map +1 -0
- package/dist/parsers/diff.d.ts +11 -0
- package/dist/parsers/diff.d.ts.map +1 -0
- package/dist/parsers/diff.js +125 -0
- package/dist/parsers/diff.js.map +1 -0
- package/dist/parsers/json.d.ts +3 -0
- package/dist/parsers/json.d.ts.map +1 -0
- package/dist/parsers/json.js +16 -0
- package/dist/parsers/json.js.map +1 -0
- package/dist/parsers/log.d.ts +3 -0
- package/dist/parsers/log.d.ts.map +1 -0
- package/dist/parsers/log.js +105 -0
- package/dist/parsers/log.js.map +1 -0
- package/dist/parsers/markdown.d.ts +15 -0
- package/dist/parsers/markdown.d.ts.map +1 -0
- package/dist/parsers/markdown.js +89 -0
- package/dist/parsers/markdown.js.map +1 -0
- package/dist/parsers/text.d.ts +24 -0
- package/dist/parsers/text.d.ts.map +1 -0
- package/dist/parsers/text.js +98 -0
- package/dist/parsers/text.js.map +1 -0
- package/dist/presentation/blocks.d.ts +14 -0
- package/dist/presentation/blocks.d.ts.map +1 -0
- package/dist/presentation/blocks.js +87 -0
- package/dist/presentation/blocks.js.map +1 -0
- package/dist/presentation/present.d.ts +12 -0
- package/dist/presentation/present.d.ts.map +1 -0
- package/dist/presentation/present.js +115 -0
- package/dist/presentation/present.js.map +1 -0
- package/dist/providers/mock.d.ts +3 -0
- package/dist/providers/mock.d.ts.map +1 -0
- package/dist/providers/mock.js +171 -0
- package/dist/providers/mock.js.map +1 -0
- package/dist/providers/registry.d.ts +12 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +20 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +64 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +40 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/renderer/assets/index.html +41 -0
- package/dist/renderer/assets/js/actions.js +205 -0
- package/dist/renderer/assets/js/api.js +75 -0
- package/dist/renderer/assets/js/artifacts.js +422 -0
- package/dist/renderer/assets/js/blocks.js +301 -0
- package/dist/renderer/assets/js/bus.js +21 -0
- package/dist/renderer/assets/js/cards.js +137 -0
- package/dist/renderer/assets/js/dom.js +61 -0
- package/dist/renderer/assets/js/format.js +118 -0
- package/dist/renderer/assets/js/main.js +125 -0
- package/dist/renderer/assets/js/nav.js +80 -0
- package/dist/renderer/assets/js/router.js +64 -0
- package/dist/renderer/assets/js/store.js +69 -0
- package/dist/renderer/assets/js/views.js +356 -0
- package/dist/renderer/assets/styles.css +856 -0
- package/dist/renderer/content.d.ts +11 -0
- package/dist/renderer/content.d.ts.map +1 -0
- package/dist/renderer/content.js +114 -0
- package/dist/renderer/content.js.map +1 -0
- package/dist/scanner/classify.d.ts +17 -0
- package/dist/scanner/classify.d.ts.map +1 -0
- package/dist/scanner/classify.js +117 -0
- package/dist/scanner/classify.js.map +1 -0
- package/dist/scanner/git.d.ts +3 -0
- package/dist/scanner/git.d.ts.map +1 -0
- package/dist/scanner/git.js +43 -0
- package/dist/scanner/git.js.map +1 -0
- package/dist/scanner/ignore.d.ts +22 -0
- package/dist/scanner/ignore.d.ts.map +1 -0
- package/dist/scanner/ignore.js +57 -0
- package/dist/scanner/ignore.js.map +1 -0
- package/dist/scanner/scan.d.ts +8 -0
- package/dist/scanner/scan.d.ts.map +1 -0
- package/dist/scanner/scan.js +108 -0
- package/dist/scanner/scan.js.map +1 -0
- package/dist/security/boundary.d.ts +19 -0
- package/dist/security/boundary.d.ts.map +1 -0
- package/dist/security/boundary.js +59 -0
- package/dist/security/boundary.js.map +1 -0
- package/dist/security/urlPolicy.d.ts +4 -0
- package/dist/security/urlPolicy.d.ts.map +1 -0
- package/dist/security/urlPolicy.js +56 -0
- package/dist/security/urlPolicy.js.map +1 -0
- package/dist/security/workspaceRead.d.ts +25 -0
- package/dist/security/workspaceRead.d.ts.map +1 -0
- package/dist/security/workspaceRead.js +103 -0
- package/dist/security/workspaceRead.js.map +1 -0
- package/dist/semantics/load.d.ts +11 -0
- package/dist/semantics/load.d.ts.map +1 -0
- package/dist/semantics/load.js +537 -0
- package/dist/semantics/load.js.map +1 -0
- package/dist/semantics/resolve.d.ts +37 -0
- package/dist/semantics/resolve.d.ts.map +1 -0
- package/dist/semantics/resolve.js +211 -0
- package/dist/semantics/resolve.js.map +1 -0
- package/dist/semantics/types.d.ts +158 -0
- package/dist/semantics/types.d.ts.map +1 -0
- package/dist/semantics/types.js +3 -0
- package/dist/semantics/types.js.map +1 -0
- package/dist/server/server.d.ts +17 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +510 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/workspace.d.ts +53 -0
- package/dist/server/workspace.d.ts.map +1 -0
- package/dist/server/workspace.js +211 -0
- package/dist/server/workspace.js.map +1 -0
- package/dist/types.d.ts +570 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/util/path.d.ts +11 -0
- package/dist/util/path.d.ts.map +1 -0
- package/dist/util/path.js +35 -0
- package/dist/util/path.js.map +1 -0
- package/dist/util/safeRead.d.ts +38 -0
- package/dist/util/safeRead.d.ts.map +1 -0
- package/dist/util/safeRead.js +117 -0
- package/dist/util/safeRead.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isWithin = isWithin;
|
|
4
|
+
exports.resolveRelPath = resolveRelPath;
|
|
5
|
+
exports.resolveRealPath = resolveRealPath;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const path_1 = require("../util/path");
|
|
9
|
+
// Path containment primitives. These are pure predicates over the filesystem —
|
|
10
|
+
// they read metadata only, never content.
|
|
11
|
+
//
|
|
12
|
+
// They live outside `server/` because containment is not an HTTP concern: a
|
|
13
|
+
// workspace-relative path can arrive from a URL, from a manifest, or from a
|
|
14
|
+
// block, and every one of those routes has to answer the same question.
|
|
15
|
+
function isWithin(root, target) {
|
|
16
|
+
const r = (0, node_path_1.resolve)(root);
|
|
17
|
+
const t = (0, node_path_1.resolve)(target);
|
|
18
|
+
return t === r || t.startsWith(r + node_path_1.sep);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves a workspace-relative POSIX path to an absolute path strictly
|
|
22
|
+
* inside the workspace root. Returns null on traversal attempts.
|
|
23
|
+
*
|
|
24
|
+
* This is a lexical check: it refuses `..`, but says nothing about symlinks.
|
|
25
|
+
* Use `resolveRealPath` when the answer must survive a link in the tree.
|
|
26
|
+
*/
|
|
27
|
+
function resolveRelPath(root, rel) {
|
|
28
|
+
const normalized = (0, path_1.normalizeRel)(rel);
|
|
29
|
+
if (normalized === null)
|
|
30
|
+
return null;
|
|
31
|
+
const abs = (0, node_path_1.resolve)(root, normalized);
|
|
32
|
+
if (!isWithin(root, abs))
|
|
33
|
+
return null;
|
|
34
|
+
return abs;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Like resolveRelPath, but also resolves symlinks and verifies the *real* path
|
|
38
|
+
* stays inside the workspace. This is the check that catches an intermediate
|
|
39
|
+
* directory which is a link out of the tree — `leak -> /etc` makes
|
|
40
|
+
* `leak/passwd` lexical, but never contained.
|
|
41
|
+
*
|
|
42
|
+
* Returns the lexical absolute path (stable, knows no symlinks) on success.
|
|
43
|
+
*/
|
|
44
|
+
function resolveRealPath(root, rel) {
|
|
45
|
+
const abs = resolveRelPath(root, rel);
|
|
46
|
+
if (abs === null)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const realRoot = (0, node_fs_1.realpathSync)(root);
|
|
50
|
+
const real = (0, node_fs_1.realpathSync)(abs);
|
|
51
|
+
if (!isWithin(realRoot, real))
|
|
52
|
+
return null;
|
|
53
|
+
return abs;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary.js","sourceRoot":"","sources":["../../src/security/boundary.ts"],"names":[],"mappings":";;AAWA,4BAIC;AASD,wCAMC;AAUD,0CAWC;AAnDD,qCAAuC;AACvC,yCAAyC;AACzC,uCAA4C;AAE5C,+EAA+E;AAC/E,0CAA0C;AAC1C,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AAExE,SAAgB,QAAQ,CAAC,IAAY,EAAE,MAAc;IACnD,MAAM,CAAC,GAAG,IAAA,mBAAO,EAAC,IAAI,CAAC,CAAC;IACxB,MAAM,CAAC,GAAG,IAAA,mBAAO,EAAC,MAAM,CAAC,CAAC;IAC1B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,eAAG,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,IAAY,EAAE,GAAW;IACtD,MAAM,UAAU,GAAG,IAAA,mBAAY,EAAC,GAAG,CAAC,CAAC;IACrC,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACrC,MAAM,GAAG,GAAG,IAAA,mBAAO,EAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,eAAe,CAAC,IAAY,EAAE,GAAW;IACvD,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACtC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAA,sBAAY,EAAC,IAAI,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,IAAA,sBAAY,EAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urlPolicy.d.ts","sourceRoot":"","sources":["../../src/security/urlPolicy.ts"],"names":[],"mappings":"AAgCA,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAQ5D;AAED,6EAA6E;AAC7E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAI1D"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isSafeLinkHref = isSafeLinkHref;
|
|
4
|
+
exports.isWorkspaceRelative = isWorkspaceRelative;
|
|
5
|
+
/**
|
|
6
|
+
* One answer to "may this href be rendered as a link?", for every layer that
|
|
7
|
+
* writes one.
|
|
8
|
+
*
|
|
9
|
+
* Producer-authored JSON reaches the page as a link target in more than one
|
|
10
|
+
* place (list items today, and whatever block kind is added next), and the
|
|
11
|
+
* renderer is not the only consumer: a link that survives server-side
|
|
12
|
+
* normalisation is also what any future consumer will see. So the policy is a
|
|
13
|
+
* small shared allowlist rather than a check inside the one renderer we
|
|
14
|
+
* happened to write today.
|
|
15
|
+
*
|
|
16
|
+
* Allowed:
|
|
17
|
+
* - `http:` / `https:` / `mailto:` — the same three the markdown renderer
|
|
18
|
+
* validates links against, so a workspace has one URL policy, not two
|
|
19
|
+
* - `#...` — an in-page anchor
|
|
20
|
+
* - a workspace-relative path — routed as an artifact
|
|
21
|
+
*
|
|
22
|
+
* Everything else is refused. `javascript:` and `vbscript:` are script
|
|
23
|
+
* execution; `data:` and `blob:` are document injection; `file:` reaches the
|
|
24
|
+
* local disk. That the page's CSP would also stop some of them is not a reason
|
|
25
|
+
* to pass them through — a second line of defence is not a first one.
|
|
26
|
+
*
|
|
27
|
+
* Protocol-relative (`//host/path`) is refused as well: it is an off-site
|
|
28
|
+
* navigation wearing a path's clothes.
|
|
29
|
+
*/
|
|
30
|
+
const SAFE_LINK_SCHEMES = /^(?:https?|mailto):/i;
|
|
31
|
+
const HAS_SCHEME = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
32
|
+
function hasScheme(value) {
|
|
33
|
+
return HAS_SCHEME.test(value);
|
|
34
|
+
}
|
|
35
|
+
function isSafeLinkHref(href) {
|
|
36
|
+
if (typeof href !== 'string')
|
|
37
|
+
return false;
|
|
38
|
+
const value = href.trim();
|
|
39
|
+
if (value === '')
|
|
40
|
+
return false;
|
|
41
|
+
if (value.startsWith('#'))
|
|
42
|
+
return true;
|
|
43
|
+
if (value.startsWith('//'))
|
|
44
|
+
return false;
|
|
45
|
+
if (hasScheme(value))
|
|
46
|
+
return SAFE_LINK_SCHEMES.test(value);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
/** True when the value is a workspace-relative path (no scheme, no `//`). */
|
|
50
|
+
function isWorkspaceRelative(value) {
|
|
51
|
+
const trimmed = value.trim();
|
|
52
|
+
if (trimmed === '' || trimmed.startsWith('//'))
|
|
53
|
+
return false;
|
|
54
|
+
return !hasScheme(trimmed);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=urlPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urlPolicy.js","sourceRoot":"","sources":["../../src/security/urlPolicy.ts"],"names":[],"mappings":";;AAgCA,wCAQC;AAGD,kDAIC;AA/CD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AACjD,MAAM,UAAU,GAAG,2BAA2B,CAAC;AAE/C,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC;AAED,SAAgB,cAAc,CAAC,IAAa;IAC1C,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1B,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6EAA6E;AAC7E,SAAgB,mBAAmB,CAAC,KAAa;IAC/C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7D,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FileEntry } from '../types';
|
|
2
|
+
/** Default cap for one producer-authored text read. */
|
|
3
|
+
export declare const DEFAULT_MAX_TEXT_BYTES: number;
|
|
4
|
+
/** Hard ceiling. A caller may ask for less, never for more. */
|
|
5
|
+
export declare const MAX_TEXT_BYTES_CEILING: number;
|
|
6
|
+
export interface WorkspaceReaderOptions {
|
|
7
|
+
rootDir: string;
|
|
8
|
+
/** The scanner's index. Its contents are the allowlist. */
|
|
9
|
+
files: readonly FileEntry[];
|
|
10
|
+
}
|
|
11
|
+
export interface WorkspaceReader {
|
|
12
|
+
/** True when `rel` names a file the scanner indexed. */
|
|
13
|
+
has(rel: string): boolean;
|
|
14
|
+
/** True when `rel` is a scanned file that may be read as text. */
|
|
15
|
+
canReadText(rel: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Reads a workspace-relative text file. Returns undefined unless the path
|
|
18
|
+
* passed every check above. An empty file reads as an empty string.
|
|
19
|
+
*/
|
|
20
|
+
readText(rel: string, maxBytes?: number): string | undefined;
|
|
21
|
+
/** True when `rel` is a scanned image that may be served to the browser. */
|
|
22
|
+
canReadImage(rel: string): boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function createWorkspaceReader(options: WorkspaceReaderOptions): WorkspaceReader;
|
|
25
|
+
//# sourceMappingURL=workspaceRead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceRead.d.ts","sourceRoot":"","sources":["../../src/security/workspaceRead.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAmC1C,uDAAuD;AACvD,eAAO,MAAM,sBAAsB,QAAa,CAAC;AAEjD,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,QAAkB,CAAC;AAEtD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,kEAAkE;IAClE,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC7D,4EAA4E;IAC5E,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CAsDtF"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_TEXT_BYTES_CEILING = exports.DEFAULT_MAX_TEXT_BYTES = void 0;
|
|
4
|
+
exports.createWorkspaceReader = createWorkspaceReader;
|
|
5
|
+
const classify_1 = require("../scanner/classify");
|
|
6
|
+
const path_1 = require("../util/path");
|
|
7
|
+
const safeRead_1 = require("../util/safeRead");
|
|
8
|
+
const boundary_1 = require("./boundary");
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// The workspace read capability.
|
|
11
|
+
//
|
|
12
|
+
// Producer-authored content can name any path it likes: a markdown block, an
|
|
13
|
+
// image reference, a relation target, a future protocol extension. This module
|
|
14
|
+
// is the one place such a name is turned into bytes.
|
|
15
|
+
//
|
|
16
|
+
// The point is not that these five checks are individually clever — it is that
|
|
17
|
+
// they live here rather than at each call site. A caller cannot forget them,
|
|
18
|
+
// and a new protocol feature gets them for free by asking for a reader instead
|
|
19
|
+
// of for a path.
|
|
20
|
+
//
|
|
21
|
+
// 1. the path must name a file the scanner indexed (the allowlist — a path
|
|
22
|
+
// the scanner skipped was skipped for a reason);
|
|
23
|
+
// 2. it must not be sensitive (a secret is a secret regardless of who is
|
|
24
|
+
// asking nicely);
|
|
25
|
+
// 3. it must not be a symlink (the scanner already refuses to descend into
|
|
26
|
+
// one, and a link whose name looks like markdown must never become a read
|
|
27
|
+
// primitive);
|
|
28
|
+
// 4. its *real* path must still be inside the workspace — this is the check
|
|
29
|
+
// that catches an intermediate directory that is a link out of the tree,
|
|
30
|
+
// which lexical normalization cannot see;
|
|
31
|
+
// 5. the read is bounded, with a ceiling the caller cannot raise.
|
|
32
|
+
//
|
|
33
|
+
// Checks 1–3 are properties of the scanner's entry. 4 and 5 are re-derived
|
|
34
|
+
// here, per call, because a capability that trusts its caller's earlier
|
|
35
|
+
// checking is not a boundary.
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
/** Default cap for one producer-authored text read. */
|
|
38
|
+
exports.DEFAULT_MAX_TEXT_BYTES = 512 * 1024;
|
|
39
|
+
/** Hard ceiling. A caller may ask for less, never for more. */
|
|
40
|
+
exports.MAX_TEXT_BYTES_CEILING = 4 * 1024 * 1024;
|
|
41
|
+
function createWorkspaceReader(options) {
|
|
42
|
+
const { rootDir } = options;
|
|
43
|
+
const byPath = new Map();
|
|
44
|
+
for (const file of options.files)
|
|
45
|
+
byPath.set(file.path, file);
|
|
46
|
+
function entryFor(rel) {
|
|
47
|
+
const normalized = (0, path_1.normalizeRel)(rel);
|
|
48
|
+
if (normalized === null || normalized === '')
|
|
49
|
+
return undefined;
|
|
50
|
+
return byPath.get(normalized);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The shared gate. `resolveRealPath` runs on every call rather than once at
|
|
54
|
+
* scan time: a directory in the tree can be replaced with a link after the
|
|
55
|
+
* scan, and the containment answer has to be current when the bytes move.
|
|
56
|
+
*
|
|
57
|
+
* Sensitivity is re-derived from the name rather than read off the index's
|
|
58
|
+
* flag alone. The flag is the scanner's answer, and the scanner is good at
|
|
59
|
+
* this — but "the index says it is fine" is precisely the kind of claim a
|
|
60
|
+
* boundary should not have to take on faith.
|
|
61
|
+
*/
|
|
62
|
+
function allows(entry, want) {
|
|
63
|
+
if (!entry)
|
|
64
|
+
return false;
|
|
65
|
+
if (entry.sensitive)
|
|
66
|
+
return false;
|
|
67
|
+
if ((0, classify_1.classifyFile)(basename(entry.path)).sensitive)
|
|
68
|
+
return false;
|
|
69
|
+
if (entry.isSymlink)
|
|
70
|
+
return false;
|
|
71
|
+
if (want === 'image' && (0, classify_1.contentKindOf)(entry.kind) !== 'image')
|
|
72
|
+
return false;
|
|
73
|
+
return (0, boundary_1.resolveRealPath)(rootDir, entry.path) !== null;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
has(rel) {
|
|
77
|
+
return entryFor(rel) !== undefined;
|
|
78
|
+
},
|
|
79
|
+
canReadText(rel) {
|
|
80
|
+
return allows(entryFor(rel), 'text');
|
|
81
|
+
},
|
|
82
|
+
readText(rel, maxBytes = exports.DEFAULT_MAX_TEXT_BYTES) {
|
|
83
|
+
const entry = entryFor(rel);
|
|
84
|
+
if (!allows(entry, 'text'))
|
|
85
|
+
return undefined;
|
|
86
|
+
const abs = (0, boundary_1.resolveRealPath)(rootDir, entry.path);
|
|
87
|
+
if (abs === null)
|
|
88
|
+
return undefined;
|
|
89
|
+
const cap = Math.min(Math.max(0, maxBytes), exports.MAX_TEXT_BYTES_CEILING);
|
|
90
|
+
const read = (0, safeRead_1.readFileNoFollow)(abs, cap);
|
|
91
|
+
return read?.text;
|
|
92
|
+
},
|
|
93
|
+
canReadImage(rel) {
|
|
94
|
+
return allows(entryFor(rel), 'image');
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** The last segment of a workspace-relative path. */
|
|
99
|
+
function basename(rel) {
|
|
100
|
+
const slash = rel.lastIndexOf('/');
|
|
101
|
+
return slash === -1 ? rel : rel.slice(slash + 1);
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=workspaceRead.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceRead.js","sourceRoot":"","sources":["../../src/security/workspaceRead.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FrontmatterSemantics, LoadedSemantics } from './types';
|
|
2
|
+
export declare const MANIFEST_DIR = ".a2h";
|
|
3
|
+
export interface LoadOptions {
|
|
4
|
+
/** Workspace-relative paths of markdown files worth inspecting. */
|
|
5
|
+
markdownPaths?: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function loadSemantics(rootDir: string, options?: LoadOptions): LoadedSemantics;
|
|
8
|
+
export declare function hasExplicitSemantics(loaded: LoadedSemantics): boolean;
|
|
9
|
+
/** Recognised frontmatter keys, all prefixed to avoid clashing with prose. */
|
|
10
|
+
export declare function readFrontmatterSemantics(absPath: string): FrontmatterSemantics | undefined;
|
|
11
|
+
//# sourceMappingURL=load.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/semantics/load.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEV,oBAAoB,EAGpB,eAAe,EAIhB,MAAM,SAAS,CAAC;AAsBjB,eAAO,MAAM,YAAY,SAAS,CAAC;AAUnC,MAAM,WAAW,WAAW;IAC1B,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,eAAe,CAiDzF;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAErE;AAuID,8EAA8E;AAC9E,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAwC1F"}
|